├── market.php
├── views
├── na.svg
├── sp.svg
├── gh.svg
├── tf.svg
└── market.php
├── data
└── LICENSE
├── LICENSE
├── README.md
├── Plugin.php
└── Action.php
/market.php:
--------------------------------------------------------------------------------
1 | redirect(Typecho_Common::url(__TYPECHO_ADMIN_DIR__.'te-store/market',$options->index));
5 |
--------------------------------------------------------------------------------
/views/na.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/views/sp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/data/LICENSE:
--------------------------------------------------------------------------------
1 | MPL-2.0 License
2 |
3 | Copyright (c) 1998–2020 Mozilla Foundation
4 |
5 | The file cacert.pem in this directory is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
--------------------------------------------------------------------------------
/views/gh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/views/tf.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018-2020 Yuzhong Zheng (jzwalk)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 新GitHub表格解析版插件仓库TeStore v1.1.5
2 |
3 | 以免服务端思路复活这款插件,通过读取GitHub上的专用表格实现插件仓库的搜索、下载和安装删除等功能。
4 |
5 | - v1.1.5(20-6-14):
6 |
7 | 1. 修正直接读取GitHub源无法正确解析列表的bug,作者信息全取html代码以兼容各种文档写法;
8 |
9 | 2. 获取本地插件meta信息时过滤html标签增强准确性,列表页搜索功能现已支持按作者名称筛选。
10 |
11 | - v1.1.4(20-6-10):
12 |
13 | 1. 列表页面顶部增加点击标签,已安装插件可分页显示,列表附注图标更新为更清晰的SVG格式;
14 |
15 | 2. 操作按钮样式和反馈提示效果沿用系统风格,提交方式由ajax改为同步post,移除原sticky效果;
16 |
17 | 3. 安装过程增加目录检测和升级清理,移除pclzip使用PHP原生解压缩方法,data改为常规目录等。
18 |
19 | - v1.1.3(20-6-7):
20 |
21 | 1. 修正冗余代码增强可读性,修复非管理员用户可查看列表bug,修复同名插件只安装第一个bug;
22 |
23 | 2. 支持jsDelivr/GitCDN镜像替换raw文件地址加速,表格或zip包链接为raw文件地址时自动加速;
24 |
25 | 3. API检测ZIP_CDN目录,同步按期限缓存,镜像开启时优先下载ZIP_CDN内的raw文件加速地址。
26 |
27 | - v1.1.2(18-8-20):
28 |
29 | 新增curl下载方式(支持pem证书识别),修正http源的主页链接问题,改善DOM编码的兼容性表现。
30 |
31 | - v1.1.1(18-8-7):
32 |
33 | 修正PHP7兼容、DOM编码和多作者链接解析问题,优化父文件夹识别、ajax报错及缺省筛选效果。
34 |
35 | - v1.1.0(18-8-5):([@羽中](https://github.com/jzwalk))
36 |
37 | 1. 使用PHP DOM解析Github社区目录下的md表格做默认源,更新缓存功能逻辑,减少读写操作;
38 |
39 | 2. 安装实现文件递归扫描和智能文件夹路径判断,更换zip库,优化ajax交互效果,增加action校验;
40 |
41 | 3. 列表加载异步化,字母排序已安装自动提前,支持分页和按简介筛选,支持图标及版本升级提醒。
42 |
43 | - v1.0.0(14-11-13):([@zhulin3141](https://github.com/zhulin3141))
44 |
45 | 正则匹配读取新浪云服务端页面,ajax方式实现下载、安装及卸载功能,列表支持缓存和名称筛选。
46 |
47 | ###### 更多详见作者博客:http://www.yzmb.me/archives/net/testore-for-typecho
48 | ###### 或可查看论坛原帖:http://forum.typecho.org/viewtopic.php?f=6&t=11097
--------------------------------------------------------------------------------
/Plugin.php:
--------------------------------------------------------------------------------
1 | navBar = array('TeStore_Plugin','render');
41 | Helper::addPanel(1,'TeStore/market.php',_t('TE插件仓库'),_t('TE插件仓库'),'administrator');
42 | Helper::addRoute('te-store_market',__TYPECHO_ADMIN_DIR__.'te-store/market','TeStore_Action','market');
43 | Helper::addRoute('te-store_install',__TYPECHO_ADMIN_DIR__.'te-store/install','TeStore_Action','install');
44 | Helper::addRoute('te-store_uninstall',__TYPECHO_ADMIN_DIR__.'te-store/uninstall','TeStore_Action','uninstall');
45 | }
46 |
47 | /**
48 | * 禁用插件方法,如果禁用失败,直接抛出异常
49 | *
50 | * @static
51 | * @access public
52 | * @return void
53 | * @throws Typecho_Plugin_Exception
54 | */
55 | public static function deactivate()
56 | {
57 | Helper::removePanel(1,'TeStore/market.php');
58 | Helper::removeRoute('te-store_market');
59 | Helper::removeRoute('te-store_install');
60 | Helper::removeRoute('te-store_uninstall');
61 | }
62 |
63 | /**
64 | * 获取插件配置面板
65 | *
66 | * @access public
67 | * @param Typecho_Widget_Helper_Form $form 配置面板
68 | * @return void
69 | */
70 | public static function config(Typecho_Widget_Helper_Form $form)
71 | {
72 | $source = new Typecho_Widget_Helper_Form_Element_Textarea('source',
73 | NULL,'https://github.com/typecho-fans/plugins/blob/master/TESTORE.md'.PHP_EOL.'https://github.com/typecho-fans/plugins/blob/master/README.md',_t('插件信息来源'),
74 | _t('应为可公开访问且包含符合本插件规定表格内容的页面地址, 每行一个, 默认: ').'
75 | https://github.com/typecho-fans/plugins/blob/master/README.md - '._t('Typecho-Fans内部插件索引(社区维护版列表)').'
76 | https://github.com/typecho-fans/plugins/blob/master/TESTORE.md - '._t('Typecho-Fans外部插件登记表(TeStore专用)').'
77 | '._t('以上Markdown语法文件在Github上由多人共同维护, 参与方式详见文件说明'));
78 | $source->addRule('required',_t('文件地址不能为空'));
79 | $form->addInput($source);
80 |
81 | $cache = new Typecho_Widget_Helper_Form_Element_Select('cache_time',
82 | array(
83 | '0'=>_t('不缓存'),
84 | '6'=>_t('6小时'),
85 | '12'=>_t('12小时'),
86 | '24'=>_t('1天'),
87 | '72'=>_t('3天'),
88 | '168'=>_t('1周')
89 | ),
90 | '24',_t('数据缓存时限'),_t('设置本地缓存数据时间'));
91 | $form->addInput($cache);
92 |
93 | $proxy = new Typecho_Widget_Helper_Form_Element_Radio('proxy',
94 | array(''=>_t('否'),'cdn.jsdelivr.net/gh'=>_t('jsDelivr镜像'),'gitcdn.xyz/repo'=>_t('GitCDN镜像1'),'gitcdn.link/repo'=>_t('GitCDN镜像2')),'',_t('使用代理加速'),_t('GitHub连接不畅时可选'));
95 | $form->addInput($proxy);
96 |
97 | $curl = new Typecho_Widget_Helper_Form_Element_Checkbox('curl',
98 | array(1=>'是'),0, _t('cURL方式下载'),_t('默认方式无效时可尝试'));
99 | $form->addInput($curl);
100 |
101 | $showNavMenu = new Typecho_Widget_Helper_Form_Element_Radio('showNavMenu',
102 | array(1=>_t('显示'),0=>_t('关闭')),1,_t('导航快捷按钮'));
103 | $form->addInput($showNavMenu);
104 | }
105 |
106 | /**
107 | * 检查cURL支持
108 | *
109 | * @param array $settings
110 | * @return string
111 | */
112 | public static function configCheck(array $settings)
113 | {
114 | if (!class_exists('ZipArchive')) {
115 | return _t('主机未安装ZipArchive扩展, 无法安装插件');
116 | }
117 | if ($settings['curl'] && !extension_loaded('curl')) {
118 | return _t('主机未安装cURL扩展, 无法使用此方式下载');
119 | }
120 | }
121 |
122 | /**
123 | * 个人用户的配置面板
124 | *
125 | * @access public
126 | * @param Typecho_Widget_Helper_Form $form
127 | * @return void
128 | */
129 | public static function personalConfig(Typecho_Widget_Helper_Form $form){}
130 |
131 | /**
132 | * 输出导航按钮
133 | *
134 | * @access public
135 | * @return void
136 | */
137 | public static function render()
138 | {
139 | $options = Helper::options();
140 | if ($options->plugin('TeStore')->showNavMenu && Typecho_Widget::widget('Widget_User')->pass('administrator',true)){
141 | echo ''._t('TE插件仓库').'';
142 | }
143 | }
144 |
145 | /**
146 | * 判断目录可写
147 | *
148 | * @access public
149 | * @return boolean
150 | */
151 | public static function testWrite($dir)
152 | {
153 | $testFile = "_test.txt";
154 | $fp = @fopen($dir."/".$testFile,"w");
155 | if (!$fp) {
156 | return false;
157 | }
158 | fclose($fp);
159 | $rs = @unlink($dir."/".$testFile);
160 | if ($rs) {
161 | return true;
162 | }
163 | return false;
164 | }
165 |
166 | }
--------------------------------------------------------------------------------
/views/market.php:
--------------------------------------------------------------------------------
1 | options->index);
5 | $keywords = htmlspecialchars($this->request->keywords);
6 | $group = htmlspecialchars($this->request->group);
7 | $page = htmlspecialchars($this->request->page);
8 | define('TYPEHO_ADMIN_PATH',__TYPECHO_ROOT_DIR__.__TYPECHO_ADMIN_DIR__);
9 |
10 | //异步加载插件列表
11 | if ($this->request->is('action=loadlist')) {
12 | $this->security->protect();
13 |
14 | $pluginData = $this->getPluginData();
15 | if ($pluginData) {
16 | $name = '';
17 | $pluginDatas = array();
18 | //筛选关键词
19 | foreach ($pluginData as $plugin) {
20 | if (!$keywords || false!==stripos($plugin['pluginName'],$keywords) || false!==stripos($plugin['desc'],$keywords) || false!==stripos(htmlspecialchars_decode(strip_tags($plugin['authorHtml'])),$keywords)) {
21 | $pluginDatas[] = $plugin;
22 | }
23 | }
24 |
25 | $installed = $this->getLocalPlugins();
26 | $infos = array();
27 | $pluginIns = array();
28 | //检测已安装
29 | foreach ($pluginDatas as $key=>$plugin) {
30 | if ($infos = $this->getLocalInfos($plugin['pluginName'])) {
31 | if ($infos[0]==htmlspecialchars_decode(strip_tags($plugin['authorHtml']))) {
32 | $pluginIns[] = $plugin;
33 | unset($pluginDatas[$key]);
34 | }
35 | }
36 | }
37 | $pluginDatas = $group || $group=='installed' ? $pluginIns : $pluginDatas;
38 |
39 | //处理分页
40 | $pluginData = array_chunk($pluginDatas,20);
41 | $page = $page && isset($pluginData[$page-1]) ? $page-1 : 0;
42 | $nav = new Typecho_Widget_Helper_PageNavigator_Box(count($pluginDatas),$page+1,20,
43 | $storeUrl.'market?'.($keywords ? 'keywords='.$keywords.'&' : '').($group ? 'group='.$group.'&' : '').'page={page}');
44 |
45 | //准备加速用API数据
46 | if ($this->settings->proxy) {
47 | $this->ZIP_CDN();
48 | }
49 | }
50 | ?>
51 |
52 |
53 |
54 | - class="current">
55 | - class="current">
56 |
57 |
58 |
59 |
60 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | |
85 | |
86 | |
87 | |
88 | |
89 |
90 |
91 |
92 |
110 |
111 | >
112 |
113 |
115 |
116 |
117 |
118 | |
119 | |
120 | getLocalInfos($name);
125 | //已安装判断升级
126 | $update = $infos && $infos[1]<$version;
127 | $version = stripos($version,'v')===0 ? substr($version,1) : $version;
128 | if ($update && $infos[0]==$author) : ?>
129 | ⇠ |
130 |
131 |
132 |
133 | |
134 |
135 | $val) :
138 | $authors[$key] = trim($val);
139 | endforeach; ?>
140 |
143 | |
144 |
145 |
147 |
148 | |
149 |
150 | |
151 |
152 |
153 |
154 | |
155 | options->adminUrl .'options-plugin.php?config=TeStore">',''); ?>
156 | |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
171 |
172 | title = _t('TE插件仓库');
177 | include TYPEHO_ADMIN_PATH.'header.php';
178 | include TYPEHO_ADMIN_PATH.'menu.php';
179 | ?>
180 |
181 |
182 |
183 |
184 |
title; ?>
185 |
186 |
187 |
192 |
193 |
194 |
199 |
216 |
252 | options = $this->widget('Widget_Options');
26 | $this->settings = $this->options->plugin('TeStore');
27 | $this->security = $this->widget('Widget_Security');
28 | $this->user = $this->widget('Widget_User');
29 | $this->useCurl = $this->settings->curl;
30 | $this->pluginRoot = __TYPECHO_ROOT_DIR__.__TYPECHO_PLUGIN_DIR__;
31 | }
32 |
33 | /**
34 | * 获取已启用插件名称
35 | *
36 | * @access private
37 | * @return array
38 | */
39 | private function getActivePlugins()
40 | {
41 | $activatedPlugins = Typecho_Plugin::export();
42 | return array_keys($activatedPlugins['activated']);
43 | }
44 |
45 | /**
46 | * 获取已安装插件信息
47 | *
48 | * @access private
49 | * @param string $name 插件名称
50 | * @return array
51 | */
52 | private function getLocalInfos($name)
53 | {
54 | $infos = array();
55 | $pluginDir = $this->pluginRoot.'/'.$name;
56 | $pluginFile = is_dir($pluginDir) ? $pluginDir.'/Plugin.php' : $pluginDir.'.php';
57 | if (is_file($pluginFile)) {
58 | $parse = Typecho_Plugin::parseInfo($pluginFile);
59 | $infos = array(strip_tags($parse['author']),strip_tags($parse['version'])); //兼容html混写
60 | }
61 | return $infos;
62 | }
63 |
64 | /**
65 | * 读取并整理插件信息
66 | *
67 | * @access public
68 | * @return array
69 | */
70 | public function getPluginData()
71 | {
72 | $pluginInfo = array();
73 | $cacheDir = $this->pluginRoot.'/TeStore/data/';
74 | $cacheFile = $cacheDir.'list.json';
75 | $cacheTime = $this->settings->cache_time;
76 |
77 | //读取缓存文件
78 | if ($cacheTime && is_file($cacheFile) && (time()-filemtime($cacheFile))<=$cacheTime*3600) {
79 | $data = file_get_contents($cacheFile);
80 | $pluginInfo = Json::decode($data,true);
81 | //读取表格地址
82 | } else {
83 | $html = '';
84 | $isRaw = false;
85 | $pages = array_filter(preg_split('/(\r|\n|\r\n)/',strip_tags($this->settings->source)));
86 | foreach ($pages as $page) {
87 | $page = trim($page);
88 | if ($page) {
89 | $proxy = $this->settings->proxy;
90 | $isRaw = strpos($page,'raw.githubusercontent.com') || strpos($page,'raw/master');
91 | //替换加速地址
92 | if ($proxy || $isRaw) {
93 | $page = str_replace(array('github.com','raw.githubusercontent.com'),$proxy,$page);
94 | $page = $proxy=='cdn.jsdelivr.net/gh' ? str_replace(array('blob/','raw/','master/'),'',$page) : str_replace(array('blob/','raw/'),'',$page);
95 | }
96 | $html .= $this->useCurl ? $this->curlGet($page) : @file_get_contents($page,0,
97 | stream_context_create(array('http'=>array('timeout'=>20)))); //设20秒超时
98 | //转码MD格式
99 | if ($proxy || $isRaw) {
100 | $html = htmlspecialchars_decode(Markdown::convert($html)); //fix 17.10.30 Markdown
101 | }
102 | }
103 | }
104 |
105 | //解析表格内容
106 | if ($html) {
107 | $dom = new DOMDocument('1.0','utf-8');
108 | $html = function_exists('mb_convert_encoding') ? mb_convert_encoding($html,'HTML-ENTITIES','UTF-8') : $html;
109 | @$dom->loadHTML($html);
110 | $trs = $dom->getElementsByTagName('tr');
111 |
112 | $tdVal = '';
113 | $texts = array();
114 | $tds = array();
115 | $a = (object)array();
116 | $href = '';
117 | $urls = array();
118 | foreach ($trs as $trKey=>$trVal) {
119 | if ($trVal->parentNode->tagName=='tbody') {
120 | //获取td纯文本
121 | foreach ($trVal->childNodes as $tdKey=>$td) {
122 | $tdVal = $td->nodeValue;
123 | if ($tdVal) {
124 | $texts[$trKey][] = htmlspecialchars(trim($tdVal));
125 | }
126 | }
127 | $tds = $trs->item($trKey)->getElementsByTagName('td');
128 | //获取td元数据
129 | foreach ($tds as $tdKey=>$tdVal) {
130 | if ($tdKey!==1 && $tdKey!==2) {
131 | $a = $tds->item($tdKey)->getElementsByTagName('a');
132 | $href = $a->item(0) ? $a->item(0)->getAttribute('href') : '';
133 | if ($tdKey==3) {
134 | $href = str_replace(array('',' | '),'',$dom->saveXML($tds->item($tdKey))); //全取作者栏html
135 | }
136 | $urls[] = trim($href);
137 | }
138 | }
139 | }
140 | }
141 | $texts = array_values($texts);
142 | $urls = array_chunk($urls,3);
143 |
144 | //合并关联键名
145 | $keys = array('pluginName','desc','version','mark','pluginUrl','authorHtml','zipFile');
146 | $names = array();
147 | $vals = array();
148 | $datas = array();
149 | foreach ($texts as $key=>$val) {
150 | $names[] = isset($val[0]) ? $val[0] : $val[1]; //fix for PHP 7.0+
151 | $vals = array_values(array_filter($val));
152 | unset($vals[3]); //去除作者栏text
153 | $datas[] = array_combine($keys,array_merge($vals,$urls[$key]));
154 | }
155 | //按插件名排序
156 | array_multisort($names,SORT_ASC,$datas);
157 |
158 | $pluginInfo = $datas;
159 | }
160 |
161 | //生成缓存文件
162 | if ($pluginInfo && $cacheTime) {
163 | if (!is_dir($cacheDir)) {
164 | $this->makedir($cacheDir);
165 | }
166 | file_put_contents($cacheFile,Json::encode($pluginInfo));
167 | }
168 | }
169 |
170 | return $pluginInfo;
171 | }
172 |
173 | /**
174 | * 输出插件列表页面
175 | *
176 | * @access private
177 | * @return void
178 | */
179 | public function market()
180 | {
181 | //禁止非管理员访问
182 | $this->user->pass('administrator');
183 |
184 | include_once 'views/market.php';
185 | }
186 |
187 | /**
188 | * 执行安装插件步骤
189 | *
190 | * @access public
191 | * @return void
192 | */
193 | public function install()
194 | {
195 | $this->security->protect();
196 | //禁止非管理员访问
197 | $this->user->pass('administrator');
198 |
199 | $plugin = $this->request->plugin;
200 | $author = $this->request->author;
201 | $zip = $this->request->zip;
202 | $result = array(
203 | 'status'=>false,
204 | 'error'=>_t('没有找到插件文件')
205 | );
206 |
207 | if ($zip) {
208 | //检测是否已启用
209 | $activated = $this->getActivePlugins();
210 | if (!empty($activated) && in_array($plugin,$activated)) {
211 | $result['error'] = _t('请先禁用此插件');
212 | } else {
213 | $tempDir = $this->pluginRoot.'/TeStore/.tmp';
214 | $tempFile = $tempDir.'/'.$plugin.'.zip';
215 | if (is_dir($tempDir)) {
216 | @$this->delTree($tempDir,true); //清理临时目录
217 | } else {
218 | $this->makedir($tempDir); //创建临时目录
219 | }
220 | $proxy = $this->settings->proxy;
221 | //替换为加速地址
222 | if ($proxy || strpos($zip,'raw.githubusercontent.com') || strpos($zip,'raw/master')) {
223 | $cdn = $this->ZIP_CDN($plugin,$author);
224 | $zip = $cdn ? $cdn : $zip;
225 | $proxy = $proxy ? $proxy : 'cdn.jsdelivr.net/gh';
226 | $zip = str_replace(array('github.com','raw.githubusercontent.com'),$proxy,$zip);
227 | $zip = $proxy=='cdn.jsdelivr.net/gh' ? str_replace(array('blob/','raw/','master/'),'',$zip) : str_replace(array('blob/','raw/'),'',$zip);
228 | }
229 | //下载至临时目录
230 | $zipFile = $this->useCurl ? $this->curlGet($zip) : @file_get_contents($zip,0,
231 | stream_context_create(array('http'=>array('timeout'=>20)))); //设20秒超时
232 | if (!$zipFile) {
233 | $result['error'] = _t('下载压缩包出错');
234 | } else {
235 | if (strpos($zipFile,'404')===0 || strpos($zipFile,'Couldn\'t find')===0) {
236 | $result['error'] = _t('未找到下载文件');
237 | @unlink($tempFile);
238 | } else {
239 | file_put_contents($tempFile,$zipFile);
240 | $phpZip = new ZipArchive();
241 | $open = $phpZip->open($tempFile,ZipArchive::CHECKCONS);
242 | if ($open!==true) {
243 | $result['error'] = _t('压缩包校验错误');
244 | @unlink($tempFile);
245 | } else {
246 | //解压至临时目录
247 | if (!$phpZip->extractTo($tempDir)) {
248 | $error = error_get_last();
249 | $result['error'] = $error['message'];
250 | } else {
251 | $phpZip->close();
252 | @unlink($tempFile); //删除已解压包
253 |
254 | //遍历各文件层级
255 | foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tempDir)) as $fileName) {
256 | if (!is_dir($fileName)) {
257 | $tmpRoutes[] = $fileName;
258 | }
259 | }
260 |
261 | //定位Plugin.php
262 | $trueDir = '';
263 | $parentDir = '';
264 | foreach ($tmpRoutes as $tmpRoute) {
265 | if (!strcasecmp(basename($tmpRoute),'Plugin.php')) {
266 | $trueDir = dirname($tmpRoute);
267 | $parentDir = dirname($trueDir);
268 | }
269 | }
270 |
271 | //处理目录型插件
272 | if ($trueDir) {
273 | $pluginDir = $this->pluginRoot.'/'.$plugin;
274 | if (is_dir($pluginDir)) {
275 | @$this->delTree($pluginDir,true); //清理旧版残留
276 | }
277 | foreach ($tmpRoutes as $tmpRoute) {
278 | //按文件路径创建目录
279 | $fileDir = $parentDir==$tempDir ? $tempDir : $parentDir;
280 | $tarRoute = str_replace((strpos($tmpRoute,$trueDir)===0 ? $trueDir : $fileDir),
281 | $pluginDir,$tmpRoute);
282 | $tarDir = dirname($tarRoute);
283 | if (!is_dir($tarDir)) {
284 | $this->makedir($tarDir);
285 | }
286 | //移动文件到各层目录
287 | if (!rename($tmpRoute,$tarRoute)) {
288 | $error = error_get_last();
289 | $result['error'] = $error['message'];
290 | }
291 | }
292 | $result['status'] = true;
293 |
294 | //处理单文件型插件
295 | } elseif (count($tmpRoutes)<=2) {
296 | foreach ($tmpRoutes as $tmpRoute) {
297 | $name = basename($tmpRoute);
298 | if ($name==$plugin.'.php') {
299 | //移动文件到根目录
300 | if (!rename($tmpRoute,$this->pluginRoot.'/'.$name)) {
301 | $result['error'] = _t('移动文件出错');
302 | } else {
303 | $result['status'] = true;
304 | }
305 | }
306 | }
307 | }
308 |
309 | //清空临时目录
310 | @$this->delTree($tempDir,true);
311 | }
312 | }
313 | }
314 | }
315 | }
316 | }
317 |
318 | //返回提示信息
319 | if ($result['status']) {
320 | $this->widget('Widget_Notice')->highlight('plugin-'.$plugin);
321 | $this->widget('Widget_Notice')->set(_t('安装插件 %s 成功, 可以在下方启用',$plugin),'success');
322 | $this->response->redirect($this->options->adminUrl.'plugins.php#plugin-'.end($activated));
323 | } else {
324 | $this->widget('Widget_Notice')->set(_t('安装插件 %s 失败: %s',$plugin,$result['error']),'error');
325 | $this->response->goBack();
326 | }
327 | }
328 |
329 | /**
330 | * 执行卸载插件步骤
331 | *
332 | * @access public
333 | * @return void
334 | */
335 | public function uninstall()
336 | {
337 | $this->security->protect();
338 | //禁止非管理员访问
339 | $this->user->pass('administrator');
340 |
341 | $plugin = $this->request->plugin;
342 | $result = array(
343 | 'status'=>false,
344 | 'error'=>_t('移除文件出错')
345 | );
346 |
347 | if ($this->getLocalInfos($plugin)) {
348 | $activated = $this->getActivePlugins();
349 | //已启用则自动禁用
350 | if (!empty($activated) && in_array($plugin,$activated)) {
351 | Helper::removePlugin($plugin);
352 | }
353 |
354 | $pluginDir = $this->pluginRoot.'/'.$plugin;
355 | //清空目录型插件
356 | if (is_dir($pluginDir)) {
357 | if (!@$this->delTree($pluginDir)) {
358 | $error = error_get_last();
359 | $result['error'] = $error['message'];
360 | } else {
361 | $result['status'] = true;
362 | }
363 | //删除单文件插件
364 | } else {
365 | @unlink($pluginDir.'.php');
366 | $result['status'] = true;
367 | }
368 | }
369 |
370 | //返回提示信息
371 | if ($result['status']) {
372 | $this->widget('Widget_Notice')->set(_t('删除插件 %s 成功',$plugin),'success');
373 | } else {
374 | $this->widget('Widget_Notice')->set(_t('删除插件 %s 失败: %s',$plugin,$result['error']),'error');
375 | }
376 | $this->response->goBack();
377 | }
378 |
379 | /**
380 | * 检测可加速zip地址
381 | *
382 | * @access public
383 | * @param string $name 插件名称
384 | * @param string $author 作者名称
385 | * @return string
386 | */
387 | public function ZIP_CDN($name='',$author='')
388 | {
389 | $datas = array();
390 | $cacheDir = $this->pluginRoot.'/TeStore/data/';
391 | $cacheFile = $cacheDir.'zip_cdn.json';
392 | $cacheTime = $this->settings->cache_time;
393 |
394 | //读取缓存文件
395 | if ($cacheTime && is_file($cacheFile) && (time()-filemtime($cacheFile))<=$cacheTime*3600) {
396 | $data = file_get_contents($cacheFile);
397 | $datas = Json::decode($data,true);
398 | //读取API数据
399 | } else {
400 | $api = 'https://api.github.com/repositories/14101953/contents/ZIP_CDN';
401 | $data = $this->useCurl ? $this->curlGet($api) : @file_get_contents($api,0,
402 | stream_context_create(array('http'=>array('header'=>array('User-Agent: PHP'),'timeout'=>20)))); //API要求header
403 | if ($data) {
404 | $datas = Json::decode($data,true);
405 | //生成缓存文件
406 | if ($cacheTime) {
407 | if (!is_dir($cacheDir)) {
408 | $this->makedir($cacheDir);
409 | }
410 | file_put_contents($cacheFile,$data);
411 | }
412 | }
413 | }
414 |
415 | $zip = '';
416 | if ($name && $author) {
417 | foreach ($datas as $data) {
418 | if ($data['name']==$name.'_'.$author.'.zip') { //带作者名优先
419 | $zip = $data['download_url'];
420 | } elseif ($data['name']==$name.'.zip') {
421 | $zip = $data['download_url'];
422 | }
423 | }
424 | }
425 |
426 | return $zip;
427 | }
428 |
429 | /**
430 | * 递归创建本地目录
431 | *
432 | * @access private
433 | * @param string $path 目录路径
434 | * @return boolean
435 | */
436 | private function makedir($path)
437 | {
438 | $path = preg_replace('/\\\+/','/',$path);
439 | $current = rtrim($path,'/');
440 | $last = $current;
441 |
442 | while (!is_dir($current) && false!==strpos($path,'/')) {
443 | $last = $current;
444 | $current = dirname($current);
445 | }
446 | if ($last==$current) {
447 | return true;
448 | }
449 | if (!@mkdir($last)) {
450 | return false;
451 | }
452 |
453 | $stat = @stat($last);
454 | $perms = $stat['mode'] & 0007777;
455 | @chmod($last,$perms);
456 |
457 | return $this->makedir($path);
458 | }
459 |
460 | /**
461 | * 清空目录内文件
462 | *
463 | * @access private
464 | * @param string $folder 目录路径
465 | * @param boolean $keep 保留目录
466 | * @return boolean
467 | */
468 | private function delTree($folder,$keep=false) {
469 | $files = array_diff(scandir($folder),array('.','..'));
470 | foreach ($files as $file) {
471 | $path = $folder.'/'.$file;
472 | is_dir($path) ? $this->delTree($path) : unlink($path);
473 | }
474 | return $keep ? true : rmdir($folder);
475 | }
476 |
477 | /**
478 | * 使用cURL方法下载
479 | *
480 | * @access private
481 | * @return string
482 | */
483 | private function curlGet($url) {
484 | $curl = curl_init();
485 |
486 | curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
487 | curl_setopt($curl,CURLOPT_HEADER,0);
488 | curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,1);
489 | curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,2);
490 | curl_setopt($curl,CURLOPT_CAINFO,'usr/plugins/TeStore/data/cacert.pem'); //证书识别库
491 | curl_setopt($curl,CURLOPT_TIMEOUT,30); //设30秒超时
492 | curl_setopt($curl,CURLOPT_URL,$url);
493 |
494 | $result = curl_exec($curl);
495 | curl_close($curl);
496 |
497 | return $result;
498 | }
499 |
500 | }
501 |
--------------------------------------------------------------------------------