Initial commit
Some checks failed
定时更新GitHub源插件 / 自动更新GitHub插件 (push) Has been cancelled

This commit is contained in:
chorblack
2026-03-07 11:19:25 +08:00
commit e75f275ef4
4484 changed files with 645480 additions and 0 deletions

235
AppStore/Action.php Normal file
View File

@@ -0,0 +1,235 @@
<?php ! defined('__TYPECHO_ROOT_DIR__') and exit();
class AppStore_Action extends Typecho_Widget
{
/**
* 是否支持自动下载安装
*
* 如果在云平台上,则处理为手动下载模式。
*
* @var boolean
*/
private $installale = true;
/**
* 应用商店服务器
*
* @var string
*/
private $server = '';
/**
* Http Request 方式
*
* @var string
*/
private $http = '';
/**
* 构造函数
*
* @param mixed $request
* @param mixed $response
* @param null $params
*/
public function __construct($request, $response, $params = NULL)
{
parent::__construct($request, $response, $params);
//检测是否可以自动下载安装
$tempDir = __TYPECHO_ROOT_DIR__.__TYPECHO_PLUGIN_DIR__.'/.app_store/';
if (! @touch($tempDir.'.installable'.time())) {
$this->installale = false;
} else {
unlink($tempDir.'.installable'.time());
}
//如果没有json库加载兼容包
! extension_loaded('json') and include('libs/compat_json.php');
//加载unzip包
include('libs/unzip.php');
//加载助手
include('helpers/helpers.php');
//加载异常类
include('libs/exceptions.php');
//从插件设置中读取应用商店服务器地址
$pluginOptions = Typecho_Widget::widget('Widget_Options')->plugin('AppStore');
$this->server = $pluginOptions->server;
$this->http = 'http_'.$pluginOptions->http;
define('TYPEHO_ADMIN_PATH', __TYPECHO_ROOT_DIR__.__TYPECHO_ADMIN_DIR__.'/');
}
/**
* 应用商店主页
*
*/
public function market()
{
$http = $this->http;
//获取插件列表
$result = json_decode($http($this->server.'packages.json'));
if ($result) {
//导出已激活插件
$activatedPlugins = Typecho_Plugin::export();
foreach ($result->packages as &$_package) {
$pluginPath = __TYPECHO_ROOT_DIR__.__TYPECHO_PLUGIN_DIR__.'/'.$_package->name.'/';
$pluginEntry = $pluginPath.'Plugin.php';
$_package->existed = 0;
if (file_exists($pluginEntry)) {
$_package->existed = 1;
$pluginMeta = Typecho_Plugin::parseInfo($pluginEntry);
foreach ($_package->versions as &$_version) {
$_version->activated = 0;
$_version->description = strip_tags($_version->description);
$_version->author = strip_tags($_version->author);
if ($_version->version == $pluginMeta['version'] and
isset($activatedPlugins['activated'][$_package->name])
) {
$_version->activated = 1;
}
}
} else {
foreach ($_package->versions as &$_version) {
$_version->description = strip_tags($_version->description);
$_version->author = strip_tags($_version->author);
$_version->activated = 0;
}
}
}
}
include 'views/market.php';
}
public function install()
{
$version = $this->request->get('version');
$plugin = $this->request->get('plugin');
$require = $this->request->get('require');
$require === '*' and $require = '';
$pluginPath = __TYPECHO_ROOT_DIR__.__TYPECHO_PLUGIN_DIR__.'/'.$plugin.'/';
$pluginBackupPath = __TYPECHO_ROOT_DIR__.__TYPECHO_PLUGIN_DIR__.'/_'.$plugin.'/';
$activatedPlugins = Typecho_Plugin::export();
$existed = false;
$activated = false;
$tempFile = __TYPECHO_ROOT_DIR__.__TYPECHO_PLUGIN_DIR__.'/.app_store/'.$plugin.'-'.$version.'.zip';
try {
//检查版本
list(, $buildVersion) = explode('/', Typecho_Common::VERSION);
if (! Typecho_Plugin::checkDependence($buildVersion, $require)) {
throw new VersionNotMatchException('版本不匹配,无法安装.');
}
//查看插件是否已经存在
//查看插件是否已经激活
if (file_exists($pluginPath)) {
$existed = true;
if (file_exists($pluginPath.'Plugin.php') and isset($activatedPlugins['activated'][$plugin])) {
$activated = true;
}
}
//插件如果存在,则需要备份下,后面出错可以进行回滚
if ($existed or $activated) {
file_exists($pluginBackupPath) and delete_files($pluginBackupPath) and @rmdir($pluginBackupPath);
@rename($pluginPath, $pluginBackupPath);
}
//下载新插件zip包
$zipUrl = $this->server.'plugins/'.$plugin.'/download/'.str_replace(' ', '%20', $version).'.zip';
$http = $this->http;
$archive = $http($zipUrl);
if (! $archive) {
throw new DownloadErrorException('下载插件包出错!'.$zipUrl);
}
//保存文件
$fp = fopen($tempFile, 'w');
fwrite($fp, $archive);
fclose($fp);
//解压缩文件
$unzip = new Unzip();
//创建文件夹
@mkdir($pluginPath);
$extractedFiles = $unzip->extract($tempFile, $pluginPath);
if ($extractedFiles === false) {
throw new UnzipErrorException('解压缩出错!');
}
//OK,解压缩成功了
//删除备份文件
file_exists($pluginBackupPath) and delete_files($pluginBackupPath) and @rmdir($pluginBackupPath);
//删除临时文件
@unlink($tempFile);
//报告首长, 安装顺利完成
echo json_encode(array(
'status' => true,
'activated' => $activated
));
} catch (VersionNotMatchException $e) {
$e->responseJson();
} catch (DownloadErrorException $e) {
//如果存在备份包,则进行回滚
file_exists($pluginBackupPath) and @rename($pluginBackupPath, $pluginPath);
$e->responseJson();
} catch (UnzipErrorException $e) {
//清理解锁压缩的废弃文件
file_exists($pluginPath) and delete_files($pluginPath) and @rmdir($pluginPath);
//如果存在备份包,则进行回滚
file_exists($pluginBackupPath) and @rename($pluginBackupPath, $pluginPath);
//删除临时文件
@unlink($tempFile);
$e->responseJson();
} catch(Exception $e) {
$error = new JsonableException($e->getMessage());
$error->responseJson();
}
}
}

21
AppStore/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 typecho-app-store
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

107
AppStore/Plugin.php Normal file
View File

@@ -0,0 +1,107 @@
<?php ! defined('__TYPECHO_ROOT_DIR__') and exit();
/**
* Typecho 应用商店
*
* @package AppStore
* @author chekun
* @version 2.0.0
* @link https://typecho.chekun.me
*/
class AppStore_Plugin implements Typecho_Plugin_Interface
{
/**
* 插件下载临时目录
*
* @var string
*/
public static $tempPath = '/.app_store/';
/**
* 激活插件方法,如果激活失败,直接抛出异常
*
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function activate()
{
//检查是否有curl扩展
if (! extension_loaded('curl')) {
throw new Typecho_Plugin_Exception('缺少curl扩展支持.');
}
//创建下载临时目录
$tempDir = __TYPECHO_ROOT_DIR__.__TYPECHO_PLUGIN_DIR__.self::$tempPath;
! file_exists($tempDir) and ! @mkdir($tempDir);
//创建菜单和路由
Helper::addPanel(1, 'AppStore/market.php', '应用商店', '应用商店', 'administrator');
Helper::addRoute('app.store.market', __TYPECHO_ADMIN_DIR__.'app-store/market', 'AppStore_Action', 'market');
Helper::addRoute('app.store.install', __TYPECHO_ADMIN_DIR__.'app-store/install', 'AppStore_Action', 'install');
}
/**
* 禁用插件方法,如果禁用失败,直接抛出异常
*
* @static
* @access public
* @return void
* @throws Typecho_Plugin_Exception
*/
public static function deactivate()
{
include 'helpers/helpers.php';
//删除下载临时目录
$tempDir = __TYPECHO_ROOT_DIR__.__TYPECHO_PLUGIN_DIR__.self::$tempPath;
if (file_exists($tempDir) and (! delete_files($tempDir) or !@rmdir($tempDir))) {
throw new Typecho_Plugin_Exception('无法删除插件下载临时目录.');
}
//移除菜单和路由
Helper::removePanel(1, 'AppStore/market.php');
Helper::removeRoute('app.store.market');
Helper::removeRoute('app.store.install');
}
/**
* 获取插件配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form 配置面板
* @return void
*/
public static function config(Typecho_Widget_Helper_Form $form)
{
/** 应用服务器地址 */
$name = new Typecho_Widget_Helper_Form_Element_Text(
'server',
NULL,
'https://typecho.chekun.me/',
_t('应用服务器地址'),
'参与服务端开发的小伙伴可以通过设置此处调试,普通的小伙伴默认就好,😄'
);
$form->addInput($name);
/** 下载插件方法 */
$http = new Typecho_Widget_Helper_Form_Element_Select(
'http',
['curl' => 'curl', 'file_get_contents' => 'file_get_contents'],
'curl',
_t('下载插件方法'),
'不能正常显示插件列表/下载插件的小伙伴可以设置为file_get_contents方式'
);
$form->addInput($http);
}
/**
* 个人用户的配置面板
*
* @access public
* @param Typecho_Widget_Helper_Form $form
* @return void
*/
public static function personalConfig(Typecho_Widget_Helper_Form $form){}
}

36
AppStore/README.md Normal file
View File

@@ -0,0 +1,36 @@
> :no_entry:不可用需要抓取和储存zip包的服务端配合作者已关闭官网。
> 原Go脚本服务端源码(已废弃)https://github.com/chekun/typecho-app-store
> 新Go脚本服务端源码(已停更)https://github.com/chekun/echoed
Typecho 应用商店 插件
========================
The missing plugins store for Typecho!
## 如何使用
- 方法一
进入 [官网](https://typecho.chekun.me/) 搜索 AppStore, 下载对应的版本
- 方法二
进入 [Github Releases](https://github.com/typecho-app-store/AppStore/releases) 下载对应的版本
- 方法三
```
git clone git@github.com:chekun/AppStore.git AppStore
```
> 不管使用哪种方法,最终请将得到的文件夹命名为 *AppStore* 放到 *Typecho* 目录的 *usr/plugins* 目录下
> 进入插件面板,启用本插件即可
## 代码贡献者
- [@chekun](https://github.com/chekun)
- [@jzwalk](https://github.com/jzwalk)
- [@fengyunljp](https://github.com/fengyunljp)
- [@aiwb](https://github.com/aiwb)
## 欢迎参与开发,共同完善

View File

@@ -0,0 +1,84 @@
<?php
/**
* Delete Files
*
* Deletes all files contained in the supplied directory path.
* Files must be writable or owned by the system in order to be deleted.
* If the second parameter is set to TRUE, any directories contained
* within the supplied base directory will be nuked as well.
*
* @param string $path File path
* @param bool $del_dir Whether to delete any directories found in the path
* @param bool $htdocs Whether to skip deleting .htaccess and index page files
* @param int $_level Current directory depth level (default: 0; internal use only)
* @return bool
*/
function delete_files($path, $del_dir = true, $htdocs = false, $_level = 0)
{
// Trim the trailing slash
$path = rtrim($path, '/\\');
if ( ! $current_dir = @opendir($path))
{
return FALSE;
}
while (FALSE !== ($filename = @readdir($current_dir)))
{
if ($filename !== '.' && $filename !== '..')
{
if (is_dir($path.DIRECTORY_SEPARATOR.$filename) && $filename[0] !== '.')
{
delete_files($path.DIRECTORY_SEPARATOR.$filename, $del_dir, $htdocs, $_level + 1);
}
elseif ($htdocs !== TRUE OR ! preg_match('/^(\.htaccess|index\.(html|htm|php)|web\.config)$/i', $filename))
{
@unlink($path.DIRECTORY_SEPARATOR.$filename);
}
}
}
closedir($current_dir);
return ($del_dir === true && $_level > 0)
? @rmdir($path)
: true;
}
/**
* Http Get Request using file_get_contents
*
* @param $url
* @return string
*/
function http_file_get_contents($url)
{
$out = file_get_contents($url);
return $out;
}
/**
* Http Get Request using curl
*
* @param $url
* @return string
*/
function http_curl($url)
{
$ssl = substr($url, 0, 8) == "https://" ? true : false;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
if ($ssl) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
}
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Referer: '.$_SERVER['SERVER_NAME']
));
$out = curl_exec($ch);
curl_close($ch);
return $out;
}

View File

@@ -0,0 +1,899 @@
<?php
if ( !class_exists( 'Services_JSON' ) ) :
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Converts to and from JSON format.
*
* JSON (JavaScript Object Notation) is a lightweight data-interchange
* format. It is easy for humans to read and write. It is easy for machines
* to parse and generate. It is based on a subset of the JavaScript
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
* This feature can also be found in Python. JSON is a text format that is
* completely language independent but uses conventions that are familiar
* to programmers of the C-family of languages, including C, C++, C#, Java,
* JavaScript, Perl, TCL, and many others. These properties make JSON an
* ideal data-interchange language.
*
* This package provides a simple encoder and decoder for JSON notation. It
* is intended for use with client-side Javascript applications that make
* use of HTTPRequest to perform server communication functions - data can
* be encoded into JSON notation for use in a client-side javascript, or
* decoded from incoming Javascript requests. JSON format is native to
* Javascript, and can be directly eval()'ed with no further parsing
* overhead
*
* All strings should be in ASCII or UTF-8 format!
*
* LICENSE: Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met: Redistributions of source code must retain the
* above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* @category
* @package Services_JSON
* @author Michal Migurski <mike-json@teczno.com>
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @copyright 2005 Michal Migurski
* @version CVS: $Id: JSON.php 288200 2009-09-09 15:41:29Z alan_k $
* @license http://www.opensource.org/licenses/bsd-license.php
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_SLICE', 1);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_STR', 2);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_ARR', 3);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_OBJ', 4);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_CMT', 5);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_LOOSE_TYPE', 16);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
/**
* Converts to and from JSON format.
*
* Brief example of use:
*
* <code>
* // create a new instance of Services_JSON
* $json = new Services_JSON();
*
* // convert a complexe value to JSON notation, and send it to the browser
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
* $output = $json->encode($value);
*
* print($output);
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
*
* // accept incoming POST data, assumed to be in JSON notation
* $input = file_get_contents('php://input', 1000000);
* $value = $json->decode($input);
* </code>
*/
class Services_JSON
{
/**
* constructs a new JSON instance
*
* @param int $use object behavior flags; combine with boolean-OR
*
* possible values:
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
* "{...}" syntax creates associative arrays
* instead of objects in decode().
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
* Values which can't be encoded (e.g. resources)
* appear as NULL instead of throwing errors.
* By default, a deeply-nested resource will
* bubble up with an error, so all return values
* from encode() should be checked with isError()
*/
function Services_JSON($use = 0)
{
$this->use = $use;
}
/**
* convert a string from one UTF-16 char to one UTF-8 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf16 UTF-16 character
* @return string UTF-8 character
* @access private
*/
function utf162utf8($utf16)
{
// oh please oh please oh please oh please oh please
if(function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
$bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
switch(true) {
case ((0x7F & $bytes) == $bytes):
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x7F & $bytes);
case (0x07FF & $bytes) == $bytes:
// return a 2-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xC0 | (($bytes >> 6) & 0x1F))
. chr(0x80 | ($bytes & 0x3F));
case (0xFFFF & $bytes) == $bytes:
// return a 3-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xE0 | (($bytes >> 12) & 0x0F))
. chr(0x80 | (($bytes >> 6) & 0x3F))
. chr(0x80 | ($bytes & 0x3F));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* convert a string from one UTF-8 char to one UTF-16 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf8 UTF-8 character
* @return string UTF-16 character
* @access private
*/
function utf82utf16($utf8)
{
// oh please oh please oh please oh please oh please
if(function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
}
switch(strlen($utf8)) {
case 1:
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return $utf8;
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8[0]) >> 2))
. chr((0xC0 & (ord($utf8[0]) << 6))
| (0x3F & ord($utf8[1])));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8[0]) << 4))
| (0x0F & (ord($utf8[1]) >> 2)))
. chr((0xC0 & (ord($utf8[1]) << 6))
| (0x7F & ord($utf8[2])));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* encodes an arbitrary variable into JSON format (and sends JSON Header)
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function encode($var)
{
header('Content-type: application/json');
return $this->_encode($var);
}
/**
* encodes an arbitrary variable into JSON format without JSON Header - warning - may allow CSS!!!!)
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function encodeUnsafe($var)
{
return $this->_encode($var);
}
/**
* PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function _encode($var)
{
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false';
case 'NULL':
return 'null';
case 'integer':
return (int) $var;
case 'double':
case 'float':
return (float) $var;
case 'string':
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
$ascii = '';
$strlen_var = strlen($var);
/*
* Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary
*/
for ($c = 0; $c < $strlen_var; ++$c) {
$ord_var_c = ord($var[$c]);
switch (true) {
case $ord_var_c == 0x08:
$ascii .= '\b';
break;
case $ord_var_c == 0x09:
$ascii .= '\t';
break;
case $ord_var_c == 0x0A:
$ascii .= '\n';
break;
case $ord_var_c == 0x0C:
$ascii .= '\f';
break;
case $ord_var_c == 0x0D:
$ascii .= '\r';
break;
case $ord_var_c == 0x22:
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
$ascii .= '\\'.$var[$c];
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $var[$c];
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
if ($c+1 >= $strlen_var) {
$c += 1;
$ascii .= '?';
break;
}
$char = pack('C*', $ord_var_c, ord($var[$c + 1]));
$c += 1;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF0) == 0xE0):
if ($c+2 >= $strlen_var) {
$c += 2;
$ascii .= '?';
break;
}
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
@ord($var[$c + 1]),
@ord($var[$c + 2]));
$c += 2;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF8) == 0xF0):
if ($c+3 >= $strlen_var) {
$c += 3;
$ascii .= '?';
break;
}
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]));
$c += 3;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
if ($c+4 >= $strlen_var) {
$c += 4;
$ascii .= '?';
break;
}
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]));
$c += 4;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFE) == 0xFC):
if ($c+5 >= $strlen_var) {
$c += 5;
$ascii .= '?';
break;
}
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var[$c + 1]),
ord($var[$c + 2]),
ord($var[$c + 3]),
ord($var[$c + 4]),
ord($var[$c + 5]));
$c += 5;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
}
}
return '"'.$ascii.'"';
case 'array':
/*
* As per JSON spec if any array key is not an integer
* we must treat the the whole array as an object. We
* also try to catch a sparsely populated associative
* array with numeric keys here because some JS engines
* will create an array with empty indexes up to
* max_index which can cause memory issues and because
* the keys, which may be relevant, will be remapped
* otherwise.
*
* As per the ECMA and JSON specification an object may
* have any string as a property. Unfortunately due to
* a hole in the ECMA specification if the key is a
* ECMA reserved word or starts with a digit the
* parameter is only accessible using ECMAScript's
* bracket notation.
*/
// treat as a JSON object
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
$properties = array_map(array($this, 'name_value'),
array_keys($var),
array_values($var));
foreach($properties as $property) {
if(Services_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
}
// treat it like a regular array
$elements = array_map(array($this, '_encode'), $var);
foreach($elements as $element) {
if(Services_JSON::isError($element)) {
return $element;
}
}
return '[' . join(',', $elements) . ']';
case 'object':
$vars = get_object_vars($var);
$properties = array_map(array($this, 'name_value'),
array_keys($vars),
array_values($vars));
foreach($properties as $property) {
if(Services_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
default:
return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
? 'null'
: new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
}
}
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
* @param string $name name of key to use
* @param mixed $value reference to an array element to be encoded
*
* @return string JSON-formatted name-value pair, like '"name":value'
* @access private
*/
function name_value($name, $value)
{
$encoded_value = $this->_encode($value);
if(Services_JSON::isError($encoded_value)) {
return $encoded_value;
}
return $this->_encode(strval($name)) . ':' . $encoded_value;
}
/**
* reduce a string by removing leading and trailing comments and whitespace
*
* @param $str string string value to strip of comments and whitespace
*
* @return string string value stripped of comments and whitespace
* @access private
*/
function reduce_string($str)
{
$str = preg_replace(array(
// eliminate single line comments in '// ...' form
'#^\s*//(.+)$#m',
// eliminate multi-line comments in '/* ... */' form, at start of string
'#^\s*/\*(.+)\*/#Us',
// eliminate multi-line comments in '/* ... */' form, at end of string
'#/\*(.+)\*/\s*$#Us'
), '', $str);
// eliminate extraneous space
return trim($str);
}
/**
* decodes a JSON string into appropriate variable
*
* @param string $str JSON-formatted string
*
* @return mixed number, boolean, string, array, or object
* corresponding to given JSON input string.
* See argument 1 to Services_JSON() above for object-output behavior.
* Note that decode() always returns strings
* in ASCII or UTF-8 format!
* @access public
*/
function decode($str)
{
$str = $this->reduce_string($str);
switch (strtolower($str)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
$m = array();
if (is_numeric($str)) {
// Lookie-loo, it's a number
// This would work on its own, but I'm trying to be
// good about returning integers where appropriate:
// return (float)$str;
// Return float or int, as appropriate
return ((float)$str == (integer)$str)
? (integer)$str
: (float)$str;
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
// STRINGS RETURNED IN UTF-8 FORMAT
$delim = substr($str, 0, 1);
$chrs = substr($str, 1, -1);
$utf8 = '';
$strlen_chrs = strlen($chrs);
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = substr($chrs, $c, 2);
$ord_chrs_c = ord($chrs[$c]);
switch (true) {
case $substr_chrs_c_2 == '\b':
$utf8 .= chr(0x08);
++$c;
break;
case $substr_chrs_c_2 == '\t':
$utf8 .= chr(0x09);
++$c;
break;
case $substr_chrs_c_2 == '\n':
$utf8 .= chr(0x0A);
++$c;
break;
case $substr_chrs_c_2 == '\f':
$utf8 .= chr(0x0C);
++$c;
break;
case $substr_chrs_c_2 == '\r':
$utf8 .= chr(0x0D);
++$c;
break;
case $substr_chrs_c_2 == '\\"':
case $substr_chrs_c_2 == '\\\'':
case $substr_chrs_c_2 == '\\\\':
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
$utf8 .= $chrs[++$c];
}
break;
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
// single, escaped unicode character
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
. chr(hexdec(substr($chrs, ($c + 4), 2)));
$utf8 .= $this->utf162utf8($utf16);
$c += 5;
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
$utf8 .= $chrs[$c];
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 2);
++$c;
break;
case ($ord_chrs_c & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 3);
$c += 2;
break;
case ($ord_chrs_c & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 4);
$c += 3;
break;
case ($ord_chrs_c & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 5);
$c += 4;
break;
case ($ord_chrs_c & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 6);
$c += 5;
break;
}
}
return $utf8;
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
if ($str[0] == '[') {
$stk = array(SERVICES_JSON_IN_ARR);
$arr = array();
} else {
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = array();
} else {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = new stdClass();
}
}
array_push($stk, array('what' => SERVICES_JSON_SLICE,
'where' => 0,
'delim' => false));
$chrs = substr($str, 1, -1);
$chrs = $this->reduce_string($chrs);
if ($chrs == '') {
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} else {
return $obj;
}
}
//print("\nparsing {$chrs}\n");
$strlen_chrs = strlen($chrs);
for ($c = 0; $c <= $strlen_chrs; ++$c) {
$top = end($stk);
$substr_chrs_c_2 = substr($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = substr($chrs, $top['where'], ($c - $top['where']));
array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
if (reset($stk) == SERVICES_JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack
array_push($arr, $this->decode($slice));
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
// we are in an object, so figure
// out the property name and set an
// element in an associative array,
// for now
$parts = array();
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// "name":value pair
$key = $this->decode($parts[1]);
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// name:value pair, where name is unquoted
$key = $parts[1];
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
}
}
} elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
// found a quote, and we are not inside a string
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
//print("Found start of string at {$c}\n");
} elseif (($chrs[$c] == $top['delim']) &&
($top['what'] == SERVICES_JSON_IN_STR) &&
((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
// found a quote, we're in a string, and it's not escaped
// we know that it's not escaped becase there is _not_ an
// odd number of backslashes at the end of the string so far
array_pop($stk);
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
} elseif (($chrs[$c] == '[') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-bracket, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
//print("Found start of array at {$c}\n");
} elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($chrs[$c] == '{') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
//print("Found start of object at {$c}\n");
} elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($substr_chrs_c_2 == '/*') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a comment start, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
$c++;
//print("Found start of comment at {$c}\n");
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
// found a comment end, and we're in one now
array_pop($stk);
$c++;
for ($i = $top['where']; $i <= $c; ++$i)
$chrs = substr_replace($chrs, ' ', $i, 1);
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
}
}
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
return $obj;
}
}
}
}
/**
* @todo Ultimately, this should just call PEAR::isError()
*/
function isError($data, $code = null)
{
if (class_exists('pear')) {
return PEAR::isError($data, $code);
} elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
is_subclass_of($data, 'services_json_error'))) {
return true;
}
return false;
}
}
if (class_exists('PEAR_Error')) {
class Services_JSON_Error extends PEAR_Error
{
function Services_JSON_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
}
}
} else {
/**
* @todo Ultimately, this class shall be descended from PEAR_Error
*/
class Services_JSON_Error
{
function Services_JSON_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
}
}
}
endif;
/**
* 为了兼容低版本的php而增加的函数集
* json从5.2.0开始支持wordpress 2.9开始提供json函数的兼容性代码
*/
if ( !function_exists('json_encode') ) {
function json_encode( $string ) {
global $wp_json;
if ( !is_a($wp_json, 'Services_JSON') ) {
$wp_json = new Services_JSON();
}
return $wp_json->encodeUnsafe( $string );
}
}
if ( !function_exists('json_decode') ) {
function json_decode( $string, $assoc_array = false ) {
global $wp_json;
if ( !is_a($wp_json, 'Services_JSON') ) {
$wp_json = new Services_JSON();
}
$res = $wp_json->decode( $string );
if ( $assoc_array )
$res = _json_decode_object_helper( $res );
return $res;
}
function _json_decode_object_helper($data) {
if ( is_object($data) )
$data = get_object_vars($data);
return is_array($data) ? array_map(__FUNCTION__, $data) : $data;
}
}

View File

@@ -0,0 +1,21 @@
<?php
class JsonableException extends Exception {
public function responseJson()
{
header('Content-Type: application/json');
echo json_encode(array(
'status' => false,
'error' => $this->getMessage(),
));
}
}
class VersionNotMatchException extends JsonableException{}
class DownloadErrorException extends JsonableException{}
class UnzipErrorException extends JsonableException{}

656
AppStore/libs/unzip.php Normal file
View File

@@ -0,0 +1,656 @@
<?php
/**
* UnZip Class
*
* This class is based on a library I found at PHPClasses:
* http://phpclasses.org/package/2495-PHP-Pack-and-unpack-files-packed-in-ZIP-archives.html
*
* The original library is a little rough around the edges so I
* refactored it and added several additional methods -- Phil Sturgeon
*
* This class requires extension ZLib Enabled.
*
* @package CodeIgniter
* @subpackage Libraries
* @category Encryption
* @author Alexandre Tedeschi
* @author Phil Sturgeon
* @author Don Myers
* @link http://bitbucket.org/philsturgeon/codeigniter-unzip
* @license
* @version 1.0.0
*/
class Unzip {
private $compressed_list = array();
// List of files in the ZIP
private $central_dir_list = array();
// Central dir list... It's a kind of 'extra attributes' for a set of files
private $end_of_central = array();
// End of central dir, contains ZIP Comments
private $info = array();
private $error = array();
private $_zip_file = '';
private $_target_dir = FALSE;
private $apply_chmod = 0777;
private $fh;
private $zip_signature = "\x50\x4b\x03\x04";
// local file header signature
private $dir_signature = "\x50\x4b\x01\x02";
// central dir header signature
private $central_signature_end = "\x50\x4b\x05\x06";
// ignore these directories (useless meta data)
private $_skip_dirs = array('__MACOSX');
private $_allow_extensions = NULL; // What is allowed out of the zip
// --------------------------------------------------------------------
/**
* Constructor
*
* @access Public
* @internal param $string
* @return \Unzip
*/
function __construct()
{
define('FILE_READ_MODE', 0644);
define('FILE_WRITE_MODE', 0666);
define('DIR_READ_MODE', 0755);
define('DIR_WRITE_MODE', 0777);
}
// --------------------------------------------------------------------
/**
* re inizilize all variables
* @access Private
* @param none
* @return none
*/
private function _reinit()
{
$this->compressed_list = array();
$this->central_dir_list = array();
$this->end_of_central = array();
$this->info = array();
$this->error = array();
}
/**
* Unzip all files in archive.
*
* @access Public
* @param $zip_file
* @param null $target_dir
* @param bool $preserve_filepath
* @internal param $none
* @return none
*/
public function extract($zip_file, $target_dir = NULL, $preserve_filepath = TRUE)
{
$this->_reinit();
$this->_zip_file = $zip_file;
$this->_target_dir = $target_dir ? $target_dir : dirname($this->_zip_file);
if ( ! $files = $this->_list_files())
{
$this->set_error('ZIP folder was empty.');
return FALSE;
}
$file_locations = array();
foreach ($files as $file => $trash)
{
$dirname = pathinfo($file, PATHINFO_DIRNAME);
$extension = pathinfo($file, PATHINFO_EXTENSION);
$folders = explode('/', $dirname);
$out_dn = $this->_target_dir . '/' . $dirname;
// Skip stuff in stupid folders
if (in_array(current($folders), $this->_skip_dirs))
{
continue;
}
// Skip any files that are not allowed
if (is_array($this->_allow_extensions) AND $extension AND ! in_array($extension, $this->_allow_extensions))
{
continue;
}
if ( ! is_dir($out_dn) AND $preserve_filepath)
{
$str = "";
foreach ($folders as $folder)
{
$str = $str ? $str . '/' . $folder : $folder;
if ( ! is_dir($this->_target_dir . '/' . $str))
{
$this->set_debug('Creating folder: ' . $this->_target_dir . '/' . $str);
if ( ! @mkdir($this->_target_dir . '/' . $str))
{
$this->set_error('Desitnation path is not writable.');
return FALSE;
}
// Apply chmod if configured to do so
$this->apply_chmod AND chmod($this->_target_dir . '/' . $str, $this->apply_chmod);
}
}
}
if (substr($file, -1, 1) == '/') continue;
$file_locations[] = $file_location = $this->_target_dir . '/' . ($preserve_filepath ? $file : basename($file));
$this->_extract_file($file, $file_location);
}
$this->compressed_list = array();
return $file_locations;
}
// --------------------------------------------------------------------
/**
* What extensions do we want out of this ZIP
*
* @access Public
* @param none
* @return none
*/
public function allow($ext = NULL)
{
$this->_allow_extensions = $ext;
}
// --------------------------------------------------------------------
/**
* Show error messages
*
* @access public
* @param string $open
* @param string $close
* @internal param $string
* @return string
*/
public function error_string($open = '<p>', $close = '</p>')
{
return $open . implode($close . $open, $this->error) . $close;
}
// --------------------------------------------------------------------
/**
* Show debug messages
*
* @access public
* @param string $open
* @param string $close
* @internal param $string
* @return string
*/
public function debug_string($open = '<p>', $close = '</p>')
{
return $open . implode($close . $open, $this->info) . $close;
}
// --------------------------------------------------------------------
/**
* Save errors
*
* @access Private
* @param string
* @return none
*/
function set_error($string)
{
$this->error[] = $string;
}
// --------------------------------------------------------------------
/**
* Save debug data
*
* @access Private
* @param string
* @return none
*/
function set_debug($string)
{
$this->info[] = $string;
}
// --------------------------------------------------------------------
/**
* List all files in archive.
*
* @access Public
* @param boolean
* @return mixed
*/
private function _list_files($stop_on_file = FALSE)
{
if (sizeof($this->compressed_list))
{
$this->set_debug('Returning already loaded file list.');
return $this->compressed_list;
}
// Open file, and set file handler
$fh = fopen($this->_zip_file, 'r');
$this->fh = &$fh;
if ( ! $fh)
{
$this->set_error('Failed to load file: ' . $this->_zip_file);
return FALSE;
}
$this->set_debug('Loading list from "End of Central Dir" index list...');
if ( ! $this->_load_file_list_by_eof($fh, $stop_on_file))
{
$this->set_debug('Failed! Trying to load list looking for signatures...');
if ( ! $this->_load_files_by_signatures($fh, $stop_on_file))
{
$this->set_debug('Failed! Could not find any valid header.');
$this->set_error('ZIP File is corrupted or empty');
return FALSE;
}
}
return $this->compressed_list;
}
// --------------------------------------------------------------------
/**
* Unzip file in archive.
*
* @access Public
* @param string , boolean
* @param bool $target_file_name
* @return Unziped file.
*/
private function _extract_file($compressed_file_name, $target_file_name = FALSE)
{
if ( ! sizeof($this->compressed_list))
{
$this->set_debug('Trying to unzip before loading file list... Loading it!');
$this->_list_files(FALSE, $compressed_file_name);
}
$fdetails = &$this->compressed_list[$compressed_file_name];
if ( ! isset($this->compressed_list[$compressed_file_name]))
{
$this->set_error('File "<strong>$compressed_file_name</strong>" is not compressed in the zip.');
return FALSE;
}
if (substr($compressed_file_name, -1) == '/')
{
$this->set_error('Trying to unzip a folder name "<strong>$compressed_file_name</strong>".');
return FALSE;
}
if ( ! $fdetails['uncompressed_size'])
{
$this->set_debug('File "<strong>$compressed_file_name</strong>" is empty.');
return $target_file_name ? file_put_contents($target_file_name, '') : '';
}
fseek($this->fh, $fdetails['contents_start_offset']);
$ret = $this->_uncompress(
fread($this->fh, $fdetails['compressed_size']),
$fdetails['compression_method'],
$fdetails['uncompressed_size'],
$target_file_name
);
if ($this->apply_chmod AND $target_file_name)
{
chmod($target_file_name, FILE_READ_MODE);
}
return $ret;
}
// --------------------------------------------------------------------
/**
* Free the file resource.
*
* @access Public
* @param none
* @return none
*/
public function close()
{
// Free the file resource
if ($this->fh)
{
fclose($this->fh);
}
}
// --------------------------------------------------------------------
/**
* Free the file resource Automatic destroy.
*
* @access Public
* @param none
* @return none
*/
public function __destroy()
{
$this->close();
}
// --------------------------------------------------------------------
/**
* Uncompress file. And save it to the targetFile.
*
* @access Private
* @param $content
* @param $mode
* @param $uncompressed_size
* @param bool $target_file_name
* @internal param $Filecontent , int, int, boolean
* @return none
*/
private function _uncompress($content, $mode, $uncompressed_size, $target_file_name = FALSE)
{
switch ($mode)
{
case 0:
return $target_file_name ? file_put_contents($target_file_name, $content) : $content;
case 1:
$this->set_error('Shrunk mode is not supported... yet?');
return FALSE;
case 2:
case 3:
case 4:
case 5:
$this->set_error('Compression factor ' . ($mode - 1) . ' is not supported... yet?');
return FALSE;
case 6:
$this->set_error('Implode is not supported... yet?');
return FALSE;
case 7:
$this->set_error('Tokenizing compression algorithm is not supported... yet?');
return FALSE;
case 8:
// Deflate
return $target_file_name ?
file_put_contents($target_file_name, gzinflate($content, $uncompressed_size)) :
gzinflate($content, $uncompressed_size);
case 9:
$this->set_error('Enhanced Deflating is not supported... yet?');
return FALSE;
case 10:
$this->set_error('PKWARE Date Compression Library Impoloding is not supported... yet?');
return FALSE;
case 12:
// Bzip2
return $target_file_name ?
file_put_contents($target_file_name, bzdecompress($content)) :
bzdecompress($content);
case 18:
$this->set_error('IBM TERSE is not supported... yet?');
return FALSE;
default:
$this->set_error('Unknown uncompress method: $mode');
return FALSE;
}
}
private function _load_file_list_by_eof(&$fh, $stop_on_file = FALSE)
{
// Check if there's a valid Central Dir signature.
// Let's consider a file comment smaller than 1024 characters...
// Actually, it length can be 65536.. But we're not going to support it.
for ($x = 0; $x < 1024; $x++)
{
fseek($fh, -22 - $x, SEEK_END);
$signature = fread($fh, 4);
if ($signature == $this->central_signature_end)
{
// If found EOF Central Dir
$eodir['disk_number_this'] = unpack("v", fread($fh, 2)); // number of this disk
$eodir['disk_number'] = unpack("v", fread($fh, 2)); // number of the disk with the start of the central directory
$eodir['total_entries_this'] = unpack("v", fread($fh, 2)); // total number of entries in the central dir on this disk
$eodir['total_entries'] = unpack("v", fread($fh, 2)); // total number of entries in
$eodir['size_of_cd'] = unpack("V", fread($fh, 4)); // size of the central directory
$eodir['offset_start_cd'] = unpack("V", fread($fh, 4)); // offset of start of central directory with respect to the starting disk number
$zip_comment_lenght = unpack("v", fread($fh, 2)); // zipfile comment length
$eodir['zipfile_comment'] = $zip_comment_lenght[1] ? fread($fh, $zip_comment_lenght[1]) : ''; // zipfile comment
$this->end_of_central = array(
'disk_number_this' => $eodir['disk_number_this'][1],
'disk_number' => $eodir['disk_number'][1],
'total_entries_this' => $eodir['total_entries_this'][1],
'total_entries' => $eodir['total_entries'][1],
'size_of_cd' => $eodir['size_of_cd'][1],
'offset_start_cd' => $eodir['offset_start_cd'][1],
'zipfile_comment' => $eodir['zipfile_comment'],
);
// Then, load file list
fseek($fh, $this->end_of_central['offset_start_cd']);
$signature = fread($fh, 4);
while ($signature == $this->dir_signature)
{
$dir['version_madeby'] = unpack("v", fread($fh, 2)); // version made by
$dir['version_needed'] = unpack("v", fread($fh, 2)); // version needed to extract
$dir['general_bit_flag'] = unpack("v", fread($fh, 2)); // general purpose bit flag
$dir['compression_method'] = unpack("v", fread($fh, 2)); // compression method
$dir['lastmod_time'] = unpack("v", fread($fh, 2)); // last mod file time
$dir['lastmod_date'] = unpack("v", fread($fh, 2)); // last mod file date
$dir['crc-32'] = fread($fh, 4); // crc-32
$dir['compressed_size'] = unpack("V", fread($fh, 4)); // compressed size
$dir['uncompressed_size'] = unpack("V", fread($fh, 4)); // uncompressed size
$zip_file_length = unpack("v", fread($fh, 2)); // filename length
$extra_field_length = unpack("v", fread($fh, 2)); // extra field length
$fileCommentLength = unpack("v", fread($fh, 2)); // file comment length
$dir['disk_number_start'] = unpack("v", fread($fh, 2)); // disk number start
$dir['internal_attributes'] = unpack("v", fread($fh, 2)); // internal file attributes-byte1
$dir['external_attributes1'] = unpack("v", fread($fh, 2)); // external file attributes-byte2
$dir['external_attributes2'] = unpack("v", fread($fh, 2)); // external file attributes
$dir['relative_offset'] = unpack("V", fread($fh, 4)); // relative offset of local header
$dir['file_name'] = fread($fh, $zip_file_length[1]); // filename
$dir['extra_field'] = $extra_field_length[1] ? fread($fh, $extra_field_length[1]) : ''; // extra field
$dir['file_comment'] = $fileCommentLength[1] ? fread($fh, $fileCommentLength[1]) : ''; // file comment
// Convert the date and time, from MS-DOS format to UNIX Timestamp
$binary_mod_date = str_pad(decbin($dir['lastmod_date'][1]), 16, '0', STR_PAD_LEFT);
$binary_mod_time = str_pad(decbin($dir['lastmod_time'][1]), 16, '0', STR_PAD_LEFT);
$last_mod_year = bindec(substr($binary_mod_date, 0, 7)) + 1980;
$last_mod_month = bindec(substr($binary_mod_date, 7, 4));
$last_mod_day = bindec(substr($binary_mod_date, 11, 5));
$last_mod_hour = bindec(substr($binary_mod_time, 0, 5));
$last_mod_minute = bindec(substr($binary_mod_time, 5, 6));
$last_mod_second = bindec(substr($binary_mod_time, 11, 5));
$this->central_dir_list[$dir['file_name']] = array(
'version_madeby' => $dir['version_madeby'][1],
'version_needed' => $dir['version_needed'][1],
'general_bit_flag' => str_pad(decbin($dir['general_bit_flag'][1]), 8, '0', STR_PAD_LEFT),
'compression_method' => $dir['compression_method'][1],
'lastmod_datetime' => mktime($last_mod_hour, $last_mod_minute, $last_mod_second, $last_mod_month, $last_mod_day, $last_mod_year),
'crc-32' => str_pad(dechex(ord($dir['crc-32'][3])), 2, '0', STR_PAD_LEFT) .
str_pad(dechex(ord($dir['crc-32'][2])), 2, '0', STR_PAD_LEFT) .
str_pad(dechex(ord($dir['crc-32'][1])), 2, '0', STR_PAD_LEFT) .
str_pad(dechex(ord($dir['crc-32'][0])), 2, '0', STR_PAD_LEFT),
'compressed_size' => $dir['compressed_size'][1],
'uncompressed_size' => $dir['uncompressed_size'][1],
'disk_number_start' => $dir['disk_number_start'][1],
'internal_attributes' => $dir['internal_attributes'][1],
'external_attributes1' => $dir['external_attributes1'][1],
'external_attributes2' => $dir['external_attributes2'][1],
'relative_offset' => $dir['relative_offset'][1],
'file_name' => $dir['file_name'],
'extra_field' => $dir['extra_field'],
'file_comment' => $dir['file_comment'],
);
$signature = fread($fh, 4);
}
// If loaded centralDirs, then try to identify the offsetPosition of the compressed data.
if ($this->central_dir_list)
{
foreach ($this->central_dir_list as $filename => $details)
{
$i = $this->_get_file_header($fh, $details['relative_offset']);
$this->compressed_list[$filename]['file_name'] = $filename;
$this->compressed_list[$filename]['compression_method'] = $details['compression_method'];
$this->compressed_list[$filename]['version_needed'] = $details['version_needed'];
$this->compressed_list[$filename]['lastmod_datetime'] = $details['lastmod_datetime'];
$this->compressed_list[$filename]['crc-32'] = $details['crc-32'];
$this->compressed_list[$filename]['compressed_size'] = $details['compressed_size'];
$this->compressed_list[$filename]['uncompressed_size'] = $details['uncompressed_size'];
$this->compressed_list[$filename]['lastmod_datetime'] = $details['lastmod_datetime'];
$this->compressed_list[$filename]['extra_field'] = $i['extra_field'];
$this->compressed_list[$filename]['contents_start_offset'] = $i['contents_start_offset'];
if (strtolower($stop_on_file) == strtolower($filename))
{
break;
}
}
}
return TRUE;
}
}
return FALSE;
}
private function _load_files_by_signatures(&$fh, $stop_on_file = FALSE)
{
fseek($fh, 0);
$return = FALSE;
for (;;)
{
$details = $this->_get_file_header($fh);
if ( ! $details)
{
$this->set_debug('Invalid signature. Trying to verify if is old style Data Descriptor...');
fseek($fh, 12 - 4, SEEK_CUR); // 12: Data descriptor - 4: Signature (that will be read again)
$details = $this->_get_file_header($fh);
}
if ( ! $details)
{
$this->set_debug('Still invalid signature. Probably reached the end of the file.');
break;
}
$filename = $details['file_name'];
$this->compressed_list[$filename] = $details;
$return = true;
if (strtolower($stop_on_file) == strtolower($filename))
{
break;
}
}
return $return;
}
private function _get_file_header(&$fh, $start_offset = FALSE)
{
if ($start_offset !== FALSE)
{
fseek($fh, $start_offset);
}
$signature = fread($fh, 4);
if ($signature == $this->zip_signature)
{
// Get information about the zipped file
$file['version_needed'] = unpack("v", fread($fh, 2)); // version needed to extract
$file['general_bit_flag'] = unpack("v", fread($fh, 2)); // general purpose bit flag
$file['compression_method'] = unpack("v", fread($fh, 2)); // compression method
$file['lastmod_time'] = unpack("v", fread($fh, 2)); // last mod file time
$file['lastmod_date'] = unpack("v", fread($fh, 2)); // last mod file date
$file['crc-32'] = fread($fh, 4); // crc-32
$file['compressed_size'] = unpack("V", fread($fh, 4)); // compressed size
$file['uncompressed_size'] = unpack("V", fread($fh, 4)); // uncompressed size
$zip_file_length = unpack("v", fread($fh, 2)); // filename length
$extra_field_length = unpack("v", fread($fh, 2)); // extra field length
$file['file_name'] = fread($fh, $zip_file_length[1]); // filename
$file['extra_field'] = $extra_field_length[1] ? fread($fh, $extra_field_length[1]) : ''; // extra field
$file['contents_start_offset'] = ftell($fh);
// Bypass the whole compressed contents, and look for the next file
fseek($fh, $file['compressed_size'][1], SEEK_CUR);
// Convert the date and time, from MS-DOS format to UNIX Timestamp
$binary_mod_date = str_pad(decbin($file['lastmod_date'][1]), 16, '0', STR_PAD_LEFT);
$binary_mod_time = str_pad(decbin($file['lastmod_time'][1]), 16, '0', STR_PAD_LEFT);
$last_mod_year = bindec(substr($binary_mod_date, 0, 7)) + 1980;
$last_mod_month = bindec(substr($binary_mod_date, 7, 4));
$last_mod_day = bindec(substr($binary_mod_date, 11, 5));
$last_mod_hour = bindec(substr($binary_mod_time, 0, 5));
$last_mod_minute = bindec(substr($binary_mod_time, 5, 6));
$last_mod_second = bindec(substr($binary_mod_time, 11, 5));
// Mount file table
$i = array(
'file_name' => $file['file_name'],
'compression_method' => $file['compression_method'][1],
'version_needed' => $file['version_needed'][1],
'lastmod_datetime' => mktime($last_mod_hour, $last_mod_minute, $last_mod_second, $last_mod_month, $last_mod_day, $last_mod_year),
'crc-32' => str_pad(dechex(ord($file['crc-32'][3])), 2, '0', STR_PAD_LEFT) .
str_pad(dechex(ord($file['crc-32'][2])), 2, '0', STR_PAD_LEFT) .
str_pad(dechex(ord($file['crc-32'][1])), 2, '0', STR_PAD_LEFT) .
str_pad(dechex(ord($file['crc-32'][0])), 2, '0', STR_PAD_LEFT),
'compressed_size' => $file['compressed_size'][1],
'uncompressed_size' => $file['uncompressed_size'][1],
'extra_field' => $file['extra_field'],
'general_bit_flag' => str_pad(decbin($file['general_bit_flag'][1]), 8, '0', STR_PAD_LEFT),
'contents_start_offset' => $file['contents_start_offset']
);
return $i;
}
return FALSE;
}
}
/* End of file Unzip.php */
/* Location: ./system/libraries/Unzip.php */

8
AppStore/market.php Normal file
View File

@@ -0,0 +1,8 @@
<?php !defined('__TYPECHO_ROOT_DIR__') and exit();
$options = Helper::options();
$siteUrl = $options->siteUrl;
$isRewrite = $options->rewrite;
$absUrl = ($isRewrite ? rtrim($siteUrl, '/') : $siteUrl."index.php");
Typecho_Response::getInstance()->redirect($absUrl.__TYPECHO_ADMIN_DIR__.'app-store/market');

25
AppStore/static/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

83
AppStore/views/js.php Normal file
View File

@@ -0,0 +1,83 @@
<?php ! defined('__TYPECHO_ROOT_DIR__') and exit();?>
<?php if ($typechoVersion <= 0.8): ?>
<script src="<?php echo $options->pluginUrl('AppStore/static/js/jquery.js'); ?>"></script>
<script>
var $jQuery = jQuery.noConflict(true);
</script>
<?php else: ?>
<script>
var $jQuery = $;
</script>
<?php endif; ?>
<script>
(function($) {
"use strict"
function init()
{
$('.as-card .as-version-selector').change(function() {
var $version = $(this).children(':selected');
var $card = $(this).parent().parent();
$card.children('.as-require').html($version.data('require'));
$card.children('.as-description').html($version.data('description'));
$card.children('.as-author').html($version.data('author'));
});
$('.as-card .as-install').click(function() {
var $this = $(this);
var $card = $this.parent().parent();
var $version = $card.children('.as-versions').children('.as-version-selector').children(':selected');
if ($card.data('existed')) {
if ($version.data('activated')) {
if (! confirm('<?php echo _t('该插件该版本已经激活使用了!\n确定继续安装吗'); ?>')) {
return false;
}
} else {
if (! confirm('<?php echo _t('该插件该版本已经存在了!\n确定继续安装吗'); ?>')) {
return false;
}
}
}
$.ajax({
url: '<?php echo str_replace('/market', '/install', Typecho_Request::getInstance()->makeUriByRequest()); ?>',
dataType: 'json',
data: {
version: $version.val(),
require: $version.data('require'),
plugin: $card.data('name')
},
beforeSend: function() {
$this.attr('disabled', true).text('安装中');
}
}).always(function() {
$this.attr('disabled', false);
}).fail(function() {
alert('安装失败');
if ($card.data('existed')) {
$this.text('重装');
} else {
$this.text('安装');
}
}).done(function(result) {
if (result.status) {
$card.data('existed', 1);
$version.data('activated', result.activated);
$this.next().children('i').addClass('as-existed active');
alert('安装成功');
window.location.reload();
} else {
alert(result.error);
}
});
});
}
init();
})($jQuery);
</script>

44
AppStore/views/list.php Normal file
View File

@@ -0,0 +1,44 @@
<div class="col-mb-12 typecho-list">
<h4 class="typecho-list-table-title">已安装的插件</h4>
<div class="typecho-table-wrap">
<?php if ($result): ?>
<table class="typecho-list-table">
<thead>
<?php include 'row-title.php'; ?>
</thead>
<tbody>
<?php foreach ($result->packages as $plugin):
if ($plugin->existed) {
include 'row.php';
}
endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="message" style="width:20em;text-align: center;margin:0 auto">
<h3 style="font-size: 2em">没有找到任何插件</h3>
</div>
<?php endif; ?>
</div>
<h4 class="typecho-list-table-title">未安装的插件</h4>
<div class="typecho-table-wrap">
<?php if ($result): ?>
<table class="typecho-list-table">
<thead>
<?php include 'row-title.php'; ?>
</thead>
<tbody>
<?php foreach ($result->packages as $plugin):
if (!$plugin->existed) {
include 'row.php';
}
endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="message" style="width:20em;text-align: center;margin:0 auto">
<h3 style="font-size: 2em">没有找到任何插件</h3>
</div>
<?php endif; ?>
</div>
</div>

55
AppStore/views/market.php Normal file
View File

@@ -0,0 +1,55 @@
<?php ! defined('__TYPECHO_ROOT_DIR__') and exit();
include TYPEHO_ADMIN_PATH.'common.php';
$menu->title = _t('应用商店');
include TYPEHO_ADMIN_PATH.'header.php';
include TYPEHO_ADMIN_PATH.'menu.php';
list($version, $buildVersion) = explode('/', Typecho_Common::VERSION);
$typechoVersion = floatval($version);
?>
<style>
.as-name,
.as-require,
.as-author,
.as-operations {
white-space: nowrap;
}
</style>
<?php if ($typechoVersion <= 0.8): ?>
<div class="main">
<div class="body container">
<div class="typecho-page-title">
<div class="column-24">
<h2><?php echo $menu->title; ?> <small><cite>The missing plugins' store for Typecho</cite></small></h2>
<div>
<a href="https://github.com/chekun/AppStore/issues" target="_blank"><?php echo _t('提建议/吐槽专用'); ?></a>
</div>
</div>
</div>
<div class="row typecho-page-main" role="main">
<?php include 'list.php'; ?>
</div>
</div>
</div>
<?php else: ?>
<div class="main">
<div class="body container">
<div class="typecho-page-title">
<h2>
<?php echo $menu->title; ?> <small><cite>The missing plugins' store for Typecho</cite></small>
<div style="float:right">
<a href="https://github.com/chekun/AppStore/issues" target="_blank"><?php echo _t('提建议/吐槽专用'); ?></a>
</div>
</h2>
</div>
<div class="row typecho-page-main" role="main">
<?php include 'list.php'; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php
include TYPEHO_ADMIN_PATH.'copyright.php';
include TYPEHO_ADMIN_PATH.'common-js.php';
include 'js.php';
include TYPEHO_ADMIN_PATH.'footer.php';
?>

View File

@@ -0,0 +1,8 @@
<tr>
<th><?php echo _t('名称'); ?></th>
<th><?php echo _t('描述'); ?></th>
<th><?php echo _t('版本'); ?></th>
<th><?php echo _t('版本要求'); ?></th>
<th><?php echo _t('作者'); ?></th>
<th><?php echo _t('操作'); ?></th>
</tr>

43
AppStore/views/row.php Normal file
View File

@@ -0,0 +1,43 @@
<?php if ($plugin->type === 'plugin'): ?>
<tr class="as-card" data-name="<?php echo $plugin->name; ?>" data-existed="<?php echo $plugin->existed ?>">
<td class="as-name"><?php echo $plugin->name; ?></td>
<td class="as-description"><?php echo $plugin->versions[0]->description; ?></td>
<td class="as-versions">
<select class="as-version-selector">
<?php foreach ($plugin->versions as $version): ?>
<option value="<?php echo $version->version; ?>" data-activated="<?php echo $version->activated; ?>" data-author="<?php echo $version->author; ?>" data-require="<?php echo $version->require; ?>" data-description="<?php echo $version->description; ?>"><?php echo $version->version; ?></option>
<?php endforeach; ?>
</select>
</td>
<td class="as-require" ><?php echo $plugin->versions[0]->require; ?></td>
<td class="as-author"><a href="<?php echo $plugin->versions[0]->link; ?>" target="_blank"><?php echo $plugin->versions[0]->author; ?></a></td>
<td class="as-operations">
<?php if ($this->installale): ?>
<?php if ($plugin->existed): ?>
<a class="as-install" href="javascript:;"><?php echo _t("重装"); ?></a>
<?php else: ?>
<a class="as-install" href="javascript:;"><?php echo _t("安装"); ?></a>
<?php endif; ?>
<?php else: ?>
<a onclick="return confirm('没有写入权限或者运行在云平台中\n点击确认后将进行下载请手动传到服务器上!');" href="<?php echo $this->server.'archive/'.$plugin->name.'/'.str_replace(' ', '%20', $version->version);?>"><?php echo _t('下载'); ?></a>
<?php endif; ?>
</td>
</tr>
<?php else: ?>
<tr class="as-card" data-name="<?php echo $plugin->name; ?>">
<td class="as-name">「主题」<?php echo $plugin->name; ?></td>
<td class="as-description"><?php echo $plugin->versions[0]->description; ?></td>
<td class="as-versions">
<select class="as-version-selector">
<?php foreach ($plugin->versions as $version): ?>
<option value="<?php echo $version->version; ?>" data-activated="<?php echo $version->activated; ?>" data-author="<?php echo $version->author; ?>" data-require="<?php echo $version->require; ?>" data-description="<?php echo $version->description; ?>"><?php echo $version->version; ?></option>
<?php endforeach; ?>
</select>
</td>
<td class="as-require" ><?php echo $plugin->versions[0]->require; ?></td>
<td class="as-author"><a href="<?php echo $plugin->versions[0]->link; ?>" target="_blank"><?php echo $plugin->versions[0]->author; ?></a></td>
<td class="as-operations">
<a onclick="return confirm('暂不支持主题下载,确定后会定向到官网下载!');" href="https://typecho.chekun.me/"><?php echo _t('下载'); ?></a>
</td>
</tr>
<?php endif; ?>