55use fast \Http ;
66use GuzzleHttp \Client ;
77use GuzzleHttp \Exception \TransferException ;
8+ use GuzzleHttp \TransferStats ;
89use PhpZip \Exception \ZipException ;
910use PhpZip \ZipFile ;
1011use RecursiveDirectoryIterator ;
1314use think \Cache ;
1415use think \Db ;
1516use think \Exception ;
17+ use think \Log ;
1618
1719/**
1820 * 插件服务
1921 * @package think\addons
2022 */
2123class Service
2224{
25+ /**
26+ * 插件列表
27+ */
28+ public static function addons ($ params = [])
29+ {
30+ $ params ['domain ' ] = request ()->host (true );
31+ return self ::sendRequest ('/addon/index ' , $ params , 'GET ' );
32+ }
33+
34+ /**
35+ * 登录
36+ */
37+ public static function login ($ params = [])
38+ {
39+ return self ::sendRequest ('/user/login ' , $ params , 'POST ' );
40+ }
41+
42+ /**
43+ * 会员信息
44+ */
45+ public static function userinfo ($ params = [])
46+ {
47+ return self ::sendRequest ('/user/index ' , $ params , 'POST ' );
48+ }
49+
50+ /**
51+ * 退出
52+ */
53+ public static function logout ($ params = [])
54+ {
55+ return self ::sendRequest ('/user/logout ' , $ params , 'POST ' );
56+ }
57+
58+ /**
59+ * 检测插件是否购买授权
60+ */
61+ public static function isBuy ($ name , $ extend = [])
62+ {
63+ $ params = array_merge (['name ' => $ name , 'domain ' => request ()->host (true )], $ extend );
64+ return self ::sendRequest ('/addon/isbuy ' , $ params , 'POST ' );
65+ }
66+
67+ /**
68+ * 检测插件是否授权
69+ *
70+ * @param string $name 插件名称
71+ * @param string $domain 验证域名
72+ */
73+ public static function isAuthorization ($ name , $ domain = '' )
74+ {
75+ $ config = self ::config ($ name );
76+ $ request = request ();
77+ $ domain = self ::getRootDomain ($ domain ? $ domain : $ request ->host (true ));
78+ if (isset ($ config ['domains ' ]) && isset ($ config ['domains ' ]) && isset ($ config ['validations ' ]) && isset ($ config ['licensecodes ' ])) {
79+ $ index = array_search ($ domain , $ config ['domains ' ]);
80+ if ((in_array ($ domain , $ config ['domains ' ]) && in_array (md5 (md5 ($ domain ) . ($ config ['licensecodes ' ][$ index ] ?? '' )), $ config ['validations ' ])) || $ request ->isCli ()) {
81+ return true ;
82+ }
83+ }
84+ return false ;
85+ }
2386
2487 /**
2588 * 远程下载插件
@@ -39,27 +102,28 @@ public static function download($name, $extend = [])
39102 $ content = $ body ->getContents ();
40103 if (substr ($ content , 0 , 1 ) === '{ ' ) {
41104 $ json = (array )json_decode ($ content , true );
42-
43105 //如果传回的是一个下载链接,则再次下载
44106 if ($ json ['data ' ] && isset ($ json ['data ' ]['url ' ])) {
45107 $ response = $ client ->get ($ json ['data ' ]['url ' ]);
46108 $ body = $ response ->getBody ();
47109 $ content = $ body ->getContents ();
48110 } else {
111+ Log::write ("[addon] " . $ content );
49112 //下载返回错误,抛出异常
50113 throw new AddonException ($ json ['msg ' ], $ json ['code ' ], $ json ['data ' ]);
51114 }
52115 }
53116 } catch (TransferException $ e ) {
54- throw new Exception ("Addon package download failed " );
117+ Log::write ("[addon] " . $ e ->getMessage ());
118+ throw new Exception (config ('app_debug ' ) ? $ e ->getMessage () : "Addon package download failed " );
55119 }
56120
57121 if ($ write = fopen ($ tmpFile , 'w ' )) {
58122 fwrite ($ write , $ content );
59123 fclose ($ write );
60124 return $ tmpFile ;
61125 }
62- throw new Exception ("No permission to write temporary files " );
126+ throw new Exception (config ( ' app_debug ' ) && isset ( $ content ) ? $ content : "No permission to write temporary files " );
63127 }
64128
65129 /**
@@ -83,7 +147,7 @@ public static function unzip($name)
83147 $ zip ->openFile ($ file );
84148 } catch (ZipException $ e ) {
85149 $ zip ->close ();
86- throw new Exception ('Unable to open the zip file ' );
150+ throw new Exception (config ( ' app_debug ' ) ? $ e -> getMessage () : 'Unable to open the zip file ' );
87151 }
88152
89153 $ dir = self ::getAddonDir ($ name );
@@ -95,7 +159,7 @@ public static function unzip($name)
95159 try {
96160 $ zip ->extractTo ($ dir );
97161 } catch (ZipException $ e ) {
98- throw new Exception ('Unable to extract the file ' );
162+ throw new Exception (config ( ' app_debug ' ) ? $ e -> getMessage () : 'Unable to extract the file ' );
99163 } finally {
100164 $ zip ->close ();
101165 }
@@ -209,6 +273,7 @@ public static function local($file, $extend = [])
209273
210274 $ info ['config ' ] = get_addon_config ($ name ) ? 1 : 0 ;
211275 $ info ['bootstrap ' ] = is_file (Service::getBootstrapFile ($ name ));
276+ $ info ['testdata ' ] = is_file (Service::getTestdataFile ($ name ));
212277 return $ info ;
213278 }
214279
@@ -220,18 +285,7 @@ public static function local($file, $extend = [])
220285 */
221286 public static function valid ($ params = [])
222287 {
223- $ client = self ::getClient ();
224- $ multipart = [];
225- foreach ($ params as $ name => $ value ) {
226- $ multipart [] = ['name ' => $ name , 'contents ' => $ value ];
227- }
228- try {
229- $ response = $ client ->post ('/addon/valid ' , ['multipart ' => $ multipart ]);
230- $ content = $ response ->getBody ()->getContents ();
231- } catch (TransferException $ e ) {
232- throw new Exception ("Network error " );
233- }
234- $ json = (array )json_decode ($ content , true );
288+ $ json = self ::sendRequest ('/addon/valid ' , $ params , 'POST ' );
235289 if ($ json && isset ($ json ['code ' ])) {
236290 if ($ json ['code ' ]) {
237291 return true ;
@@ -265,7 +319,6 @@ public static function backup($name)
265319 $ zipFile ->close ();
266320 }
267321
268-
269322 return true ;
270323 }
271324
@@ -313,12 +366,14 @@ public static function noconflict($name)
313366 /**
314367 * 导入SQL
315368 *
316- * @param string $name 插件名称
369+ * @param string $name 插件名称
370+ * @param string $fileName SQL文件名称
317371 * @return boolean
318372 */
319- public static function importsql ($ name )
373+ public static function importsql ($ name, $ fileName = null )
320374 {
321- $ sqlFile = self ::getAddonDir ($ name ) . 'install.sql ' ;
375+ $ fileName = is_null ($ fileName ) ? 'install.sql ' : $ fileName ;
376+ $ sqlFile = self ::getAddonDir ($ name ) . $ fileName ;
322377 if (is_file ($ sqlFile )) {
323378 $ lines = file ($ sqlFile );
324379 $ templine = '' ;
@@ -356,7 +411,7 @@ public static function refresh()
356411 $ bootstrapArr = [];
357412 foreach ($ addons as $ name => $ addon ) {
358413 $ bootstrapFile = self ::getBootstrapFile ($ name );
359- if ($ addon ['state ' ] && is_file ($ bootstrapFile )) {
414+ if ($ addon ['state ' ] && is_file ($ bootstrapFile ) && self :: isAuthorization ( $ name ) ) {
360415 $ bootstrapArr [] = file_get_contents ($ bootstrapFile );
361416 }
362417 }
@@ -407,6 +462,8 @@ public static function install($name, $force = false, $extend = [])
407462 throw new Exception ('Addon already exists ' );
408463 }
409464
465+ $ extend ['domain ' ] = request ()->host (true );
466+
410467 // 远程下载插件
411468 $ tmpFile = Service::download ($ name , $ extend );
412469
@@ -464,6 +521,7 @@ public static function install($name, $force = false, $extend = [])
464521
465522 $ info ['config ' ] = get_addon_config ($ name ) ? 1 : 0 ;
466523 $ info ['bootstrap ' ] = is_file (Service::getBootstrapFile ($ name ));
524+ $ info ['testdata ' ] = is_file (Service::getTestdataFile ($ name ));
467525 return $ info ;
468526 }
469527
@@ -879,6 +937,84 @@ public static function getGlobalFiles($name, $onlyconflict = false)
879937 return $ list ;
880938 }
881939
940+ /**
941+ * 更新本地应用插件授权
942+ */
943+ public static function authorization ($ params = [])
944+ {
945+ $ addonList = get_addon_list ();
946+ $ result = [];
947+ $ domain = request ()->host (true );
948+ $ addons = [];
949+ foreach ($ addonList as $ name => $ item ) {
950+ $ config = self ::config ($ name );
951+ $ addons [] = ['name ' => $ name , 'domains ' => $ config ['domains ' ] ?? [], 'licensecodes ' => $ config ['licensecodes ' ] ?? [], 'validations ' => $ config ['validations ' ] ?? []];
952+ }
953+ $ params = array_merge ($ params , [
954+ 'faversion ' => config ('fastadmin.version ' ),
955+ 'domain ' => $ domain ,
956+ 'addons ' => $ addons
957+ ]);
958+ $ result = self ::sendRequest ('/addon/authorization ' , $ params , 'POST ' );
959+ if (isset ($ result ['code ' ]) && $ result ['code ' ] == 1 ) {
960+ $ json = $ result ['data ' ]['addons ' ] ?? [];
961+ foreach ($ addonList as $ name => $ item ) {
962+ self ::config ($ name , ['domains ' => $ json [$ name ]['domains ' ] ?? [], 'licensecodes ' => $ json [$ name ]['licensecodes ' ] ?? [], 'validations ' => $ json [$ name ]['validations ' ] ?? []]);
963+ }
964+ return true ;
965+ } else {
966+ throw new Exception ($ result ['msg ' ] ?? __ ('Network error ' ));
967+ }
968+ }
969+
970+ /**
971+ * 验证插件授权,应用插件需要授权使用,移除或绕过授权验证,保留追究法律责任的权利
972+ * @param $name
973+ * @return bool
974+ */
975+ public static function checkAddonAuthorization ($ name )
976+ {
977+ $ request = request ();
978+ $ config = self ::config ($ name );
979+ $ domain = self ::getRootDomain ($ request ->host (true ));
980+ //应用插件需要授权使用,移除或绕过授权验证,保留追究法律责任的权利
981+ if (isset ($ config ['domains ' ]) && isset ($ config ['domains ' ]) && isset ($ config ['validations ' ]) && isset ($ config ['licensecodes ' ])) {
982+ $ index = array_search ($ domain , $ config ['domains ' ]);
983+ if ((in_array ($ domain , $ config ['domains ' ]) && in_array (md5 (md5 ($ domain ) . ($ config ['licensecodes ' ][$ index ] ?? '' )), $ config ['validations ' ])) || $ request ->isCli ()) {
984+ $ request ->bind ('authorized ' , $ domain ?: 'cli ' );
985+ return true ;
986+ } elseif ($ config ['domains ' ]) {
987+ foreach ($ config ['domains ' ] as $ index => $ item ) {
988+ if (substr_compare ($ domain , ". " . $ item , -strlen (". " . $ item )) === 0 && in_array (md5 (md5 ($ item ) . ($ config ['licensecodes ' ][$ index ] ?? '' )), $ config ['validations ' ])) {
989+ $ request ->bind ('authorized ' , $ domain );
990+ return true ;
991+ }
992+ }
993+ }
994+ }
995+ return false ;
996+ }
997+
998+ /**
999+ * 获取顶级域名
1000+ * @param $domain
1001+ * @return string
1002+ */
1003+ public static function getRootDomain ($ domain )
1004+ {
1005+ $ host = strtolower (trim ($ domain ));
1006+ $ hostArr = explode ('. ' , $ host );
1007+ $ hostCount = count ($ hostArr );
1008+ $ cnRegex = '/\w+\.(gov|org|ac|mil|net|edu|com|bj|tj|sh|cq|he|sx|nm|ln|jl|hl|js|zj|ah|fj|jx|sd|ha|hb|hn|gd|gx|hi|sc|gz|yn|xz|sn|gs|qh|nx|xj|tw|hk|mo)\.cn$/i ' ;
1009+ $ countryRegex = '/\w+\.(\w{2}|com|net)\.\w{2}$/i ' ;
1010+ if ($ hostCount > 2 && (preg_match ($ cnRegex , $ host ) || preg_match ($ countryRegex , $ host ))) {
1011+ $ host = implode ('. ' , array_slice ($ hostArr , -3 , 3 , true ));
1012+ } else {
1013+ $ host = implode ('. ' , array_slice ($ hostArr , -2 , 2 , true ));
1014+ }
1015+ return $ host ;
1016+ }
1017+
8821018 /**
8831019 * 获取插件行为、路由配置文件
8841020 * @return string
@@ -897,6 +1033,15 @@ public static function getBootstrapFile($name)
8971033 return ADDON_PATH . $ name . DS . 'bootstrap.js ' ;
8981034 }
8991035
1036+ /**
1037+ * 获取testdata.sql路径
1038+ * @return string
1039+ */
1040+ public static function getTestdataFile ($ name )
1041+ {
1042+ return ADDON_PATH . $ name . DS . 'testdata.sql ' ;
1043+ }
1044+
9001045 /**
9011046 * 获取指定插件的目录
9021047 */
@@ -964,7 +1109,7 @@ protected static function getCheckDirs()
9641109 * 获取请求对象
9651110 * @return Client
9661111 */
967- protected static function getClient ()
1112+ public static function getClient ()
9681113 {
9691114 $ options = [
9701115 'base_uri ' => self ::getServerUrl (),
@@ -985,6 +1130,32 @@ protected static function getClient()
9851130 return $ client ;
9861131 }
9871132
1133+ /**
1134+ * 发送请求
1135+ * @return array
1136+ * @throws Exception
1137+ * @throws \GuzzleHttp\Exception\GuzzleException
1138+ */
1139+ public static function sendRequest ($ url , $ params = [], $ method = 'POST ' )
1140+ {
1141+ $ json = [];
1142+ try {
1143+ $ client = self ::getClient ();
1144+ $ options = strtoupper ($ method ) == 'POST ' ? ['form_params ' => $ params ] : ['query ' => $ params ];
1145+ $ response = $ client ->request ($ method , $ url , $ options );
1146+ $ body = $ response ->getBody ();
1147+ $ content = $ body ->getContents ();
1148+ $ json = (array )json_decode ($ content , true );
1149+ } catch (TransferException $ e ) {
1150+ Log::write ("[addon] " . $ e ->getMessage ());
1151+ throw new Exception (config ('app_debug ' ) ? $ e ->getMessage () : __ ('Network error ' ));
1152+ } catch (\Exception $ e ) {
1153+ Log::write ("[addon] " . $ e ->getMessage ());
1154+ throw new Exception (config ('app_debug ' ) ? $ e ->getMessage () : __ ('Unknown data format ' ));
1155+ }
1156+ return $ json ;
1157+ }
1158+
9881159 /**
9891160 * 匹配配置文件中info信息
9901161 * @param ZipFile $zip
0 commit comments