├── 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 | 60 |
61 | 69 |
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 | 119 | 120 | 130 | 131 | 134 | 144 | 145 | 147 | 148 | 151 | 152 | 153 | 154 | 157 | 158 | 159 | 160 |
> 112 | 113 | typecho-fans 115 | n/a 116 | 117 | special 118 | 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 | ⇠ 132 | 133 | 135 | $val) : 138 | $authors[$key] = trim($val); 139 | endforeach; ?> 140 |
141 | 142 |
143 |
149 | 150 |
155 | options->adminUrl .'options-plugin.php?config=TeStore">',''); ?> 156 |
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 |
188 |
189 | 190 |
191 |
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 | --------------------------------------------------------------------------------