You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
495 lines
14 KiB
495 lines
14 KiB
<?php |
|
/** |
|
* Typecho Blog Platform |
|
* |
|
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org) |
|
* @license GNU General Public License 2.0 |
|
* @version $Id$ |
|
*/ |
|
|
|
/** |
|
* 插件处理类 |
|
* |
|
* @category typecho |
|
* @package Plugin |
|
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org) |
|
* @license GNU General Public License 2.0 |
|
*/ |
|
class Typecho_Plugin |
|
{ |
|
/** |
|
* 所有启用的插件 |
|
* |
|
* @access private |
|
* @var array |
|
*/ |
|
private static $_plugins = array(); |
|
|
|
/** |
|
* 已经加载的文件 |
|
* |
|
* @access private |
|
* @var array |
|
*/ |
|
private static $_required = array(); |
|
|
|
/** |
|
* 实例化的插件对象 |
|
* |
|
* @access private |
|
* @var array |
|
*/ |
|
private static $_instances; |
|
|
|
/** |
|
* 临时存储变量 |
|
* |
|
* @access private |
|
* @var array |
|
*/ |
|
private static $_tmp = array(); |
|
|
|
/** |
|
* 唯一句柄 |
|
* |
|
* @access private |
|
* @var string |
|
*/ |
|
private $_handle; |
|
|
|
/** |
|
* 组件 |
|
* |
|
* @access private |
|
* @var string |
|
*/ |
|
private $_component; |
|
|
|
/** |
|
* 是否触发插件的信号 |
|
* |
|
* @access private |
|
* @var boolean |
|
*/ |
|
private $_signal; |
|
|
|
/** |
|
* 插件初始化 |
|
* |
|
* @access public |
|
* @param string $handle 插件 |
|
*/ |
|
public function __construct($handle) |
|
{ |
|
/** 初始化变量 */ |
|
$this->_handle = $handle; |
|
} |
|
|
|
/** |
|
* 插件handle比对 |
|
* |
|
* @access private |
|
* @param array $pluginHandles |
|
* @param array $otherPluginHandles |
|
* @return array |
|
*/ |
|
private static function pluginHandlesDiff(array $pluginHandles, array $otherPluginHandles) |
|
{ |
|
foreach ($otherPluginHandles as $handle) { |
|
while (false !== ($index = array_search($handle, $pluginHandles))) { |
|
unset($pluginHandles[$index]); |
|
} |
|
} |
|
|
|
return $pluginHandles; |
|
} |
|
|
|
/** |
|
* 插件初始化 |
|
* |
|
* @access public |
|
* @param array $plugins 插件列表 |
|
* @return void |
|
*/ |
|
public static function init(array $plugins) |
|
{ |
|
$plugins['activated'] = array_key_exists('activated', $plugins) ? $plugins['activated'] : array(); |
|
$plugins['handles'] = array_key_exists('handles', $plugins) ? $plugins['handles'] : array(); |
|
|
|
/** 初始化变量 */ |
|
self::$_plugins = $plugins; |
|
} |
|
|
|
/** |
|
* 获取实例化插件对象 |
|
* |
|
* @access public |
|
* @param string $handle 插件 |
|
* @return Typecho_Plugin |
|
*/ |
|
public static function factory($handle) |
|
{ |
|
return isset(self::$_instances[$handle]) ? self::$_instances[$handle] : |
|
(self::$_instances[$handle] = new Typecho_Plugin($handle)); |
|
} |
|
|
|
/** |
|
* 启用插件 |
|
* |
|
* @access public |
|
* @param string $pluginName 插件名称 |
|
* @return void |
|
*/ |
|
public static function activate($pluginName) |
|
{ |
|
self::$_plugins['activated'][$pluginName] = self::$_tmp; |
|
self::$_tmp = array(); |
|
} |
|
|
|
/** |
|
* 禁用插件 |
|
* |
|
* @access public |
|
* @param string $pluginName 插件名称 |
|
* @return void |
|
*/ |
|
public static function deactivate($pluginName) |
|
{ |
|
/** 去掉所有相关回调函数 */ |
|
if (isset(self::$_plugins['activated'][$pluginName]['handles']) && is_array(self::$_plugins['activated'][$pluginName]['handles'])) { |
|
foreach (self::$_plugins['activated'][$pluginName]['handles'] as $handle => $handles) { |
|
self::$_plugins['handles'][$handle] = self::pluginHandlesDiff( |
|
empty(self::$_plugins['handles'][$handle]) ? array() : self::$_plugins['handles'][$handle], |
|
empty($handles) ? array() : $handles); |
|
if (empty(self::$_plugins['handles'][$handle])) { |
|
unset(self::$_plugins['handles'][$handle]); |
|
} |
|
} |
|
} |
|
|
|
/** 禁用当前插件 */ |
|
unset(self::$_plugins['activated'][$pluginName]); |
|
} |
|
|
|
/** |
|
* 导出当前插件设置 |
|
* |
|
* @access public |
|
* @return array |
|
*/ |
|
public static function export() |
|
{ |
|
return self::$_plugins; |
|
} |
|
|
|
/** |
|
* 获取插件文件的头信息 |
|
* |
|
* @access public |
|
* @param string $pluginFile 插件文件路径 |
|
* @return array |
|
*/ |
|
public static function parseInfo($pluginFile) |
|
{ |
|
$tokens = token_get_all(file_get_contents($pluginFile)); |
|
$isDoc = false; |
|
$isFunction = false; |
|
$isClass = false; |
|
$isInClass = false; |
|
$isInFunction = false; |
|
$isDefined = false; |
|
$current = NULL; |
|
|
|
/** 初始信息 */ |
|
$info = array( |
|
'description' => '', |
|
'title' => '', |
|
'author' => '', |
|
'homepage' => '', |
|
'version' => '', |
|
'dependence' => '', |
|
'activate' => false, |
|
'deactivate' => false, |
|
'config' => false, |
|
'personalConfig' => false |
|
); |
|
|
|
$map = array( |
|
'package' => 'title', |
|
'author' => 'author', |
|
'link' => 'homepage', |
|
'dependence'=> 'dependence', |
|
'version' => 'version' |
|
); |
|
|
|
foreach ($tokens as $token) { |
|
/** 获取doc comment */ |
|
if (!$isDoc && is_array($token) && T_DOC_COMMENT == $token[0]) { |
|
|
|
/** 分行读取 */ |
|
$described = false; |
|
$lines = preg_split("(\r|\n)", $token[1]); |
|
foreach ($lines as $line) { |
|
$line = trim($line); |
|
if (!empty($line) && '*' == $line[0]) { |
|
$line = trim(substr($line, 1)); |
|
if (!$described && !empty($line) && '@' == $line[0]) { |
|
$described = true; |
|
} |
|
|
|
if (!$described && !empty($line)) { |
|
$info['description'] .= $line . "\n"; |
|
} else if ($described && !empty($line) && '@' == $line[0]) { |
|
$info['description'] = trim($info['description']); |
|
$line = trim(substr($line, 1)); |
|
$args = explode(' ', $line); |
|
$key = array_shift($args); |
|
|
|
if (isset($map[$key])) { |
|
$info[$map[$key]] = trim(implode(' ', $args)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
$isDoc = true; |
|
} |
|
|
|
if (is_array($token)) { |
|
switch ($token[0]) { |
|
case T_FUNCTION: |
|
$isFunction = true; |
|
break; |
|
case T_IMPLEMENTS: |
|
$isClass = true; |
|
break; |
|
case T_WHITESPACE: |
|
case T_COMMENT: |
|
case T_DOC_COMMENT: |
|
break; |
|
case T_STRING: |
|
$string = strtolower($token[1]); |
|
switch ($string) { |
|
case 'typecho_plugin_interface': |
|
$isInClass = $isClass; |
|
break; |
|
case 'activate': |
|
case 'deactivate': |
|
case 'config': |
|
case 'personalconfig': |
|
if ($isFunction) { |
|
$current = ('personalconfig' == $string ? 'personalConfig' : $string); |
|
} |
|
break; |
|
default: |
|
if (!empty($current) && $isInFunction && $isInClass) { |
|
$info[$current] = true; |
|
} |
|
break; |
|
} |
|
break; |
|
default: |
|
if (!empty($current) && $isInFunction && $isInClass) { |
|
$info[$current] = true; |
|
} |
|
break; |
|
} |
|
} else { |
|
$token = strtolower($token); |
|
switch ($token) { |
|
case '{': |
|
if ($isDefined) { |
|
$isInFunction = true; |
|
} |
|
break; |
|
case '(': |
|
if ($isFunction && !$isDefined) { |
|
$isDefined = true; |
|
} |
|
break; |
|
case '}': |
|
case ';': |
|
$isDefined = false; |
|
$isFunction = false; |
|
$isInFunction = false; |
|
$current = NULL; |
|
break; |
|
default: |
|
if (!empty($current) && $isInFunction && $isInClass) { |
|
$info[$current] = true; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return $info; |
|
} |
|
|
|
/** |
|
* 获取插件路径和类名 |
|
* 返回值为一个数组 |
|
* 第一项为插件路径,第二项为类名 |
|
* |
|
* @access public |
|
* @param string $pluginName 插件名 |
|
* @param string $path 插件目录 |
|
* @return array |
|
* @throws Typecho_Plugin_Exception |
|
*/ |
|
public static function portal($pluginName, $path) |
|
{ |
|
switch (true) { |
|
case file_exists($pluginFileName = $path . '/' . $pluginName . '/Plugin.php'): |
|
$className = $pluginName . '_Plugin'; |
|
break; |
|
case file_exists($pluginFileName = $path . '/' . $pluginName . '.php'): |
|
$className = $pluginName; |
|
break; |
|
default: |
|
throw new Typecho_Plugin_Exception('Missing Plugin ' . $pluginName, 404); |
|
} |
|
|
|
return array($pluginFileName, $className); |
|
} |
|
|
|
/** |
|
* 版本依赖性检测 |
|
* |
|
* @access public |
|
* @param string $version 程序版本 |
|
* @param string $versionRange 依赖的版本规则 |
|
* @return boolean |
|
*/ |
|
public static function checkDependence($version, $versionRange) |
|
{ |
|
//如果没有检测规则,直接掠过 |
|
if (empty($versionRange)) { |
|
return true; |
|
} |
|
|
|
$items = array_map('trim', explode('-', $versionRange)); |
|
if (count($items) < 2) { |
|
$items[1] = $items[0]; |
|
} |
|
|
|
list ($minVersion, $maxVersion) = $items; |
|
|
|
//对*和?的支持,4个9是最大版本 |
|
$minVersion = str_replace(array('*', '?'), array('9999', '9'), $minVersion); |
|
$maxVersion = str_replace(array('*', '?'), array('9999', '9'), $maxVersion); |
|
|
|
if (version_compare($version, $minVersion, '>=') && version_compare($version, $maxVersion, '<=')) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* 插件调用后的触发器 |
|
* |
|
* @access public |
|
* @param boolean $signal 触发器 |
|
* @return Typecho_Plugin |
|
*/ |
|
public function trigger(&$signal) |
|
{ |
|
$signal = false; |
|
$this->_signal = &$signal; |
|
return $this; |
|
} |
|
|
|
/** |
|
* 判断插件是否存在 |
|
* |
|
* @access public |
|
* @param string $pluginName 插件名称 |
|
* @return mixed |
|
*/ |
|
public function exists($pluginName) { |
|
return array_search($pluginName, self::$_plugins['activated']); |
|
} |
|
|
|
/** |
|
* 设置回调函数 |
|
* |
|
* @access public |
|
* @param string $component 当前组件 |
|
* @param mixed $value 回调函数 |
|
* @return void |
|
*/ |
|
public function __set($component, $value) |
|
{ |
|
$weight = 0; |
|
|
|
if (strpos($component, '_') > 0) { |
|
$parts = explode('_', $component, 2); |
|
list($component, $weight) = $parts; |
|
$weight = intval($weight) - 10; |
|
} |
|
|
|
$component = $this->_handle . ':' . $component; |
|
|
|
if (!isset(self::$_plugins['handles'][$component])) { |
|
self::$_plugins['handles'][$component] = array(); |
|
} |
|
|
|
if (!isset(self::$_tmp['handles'][$component])) { |
|
self::$_tmp['handles'][$component] = array(); |
|
} |
|
|
|
foreach (self::$_plugins['handles'][$component] as $key => $val) { |
|
$key = floatval($key); |
|
|
|
if ($weight > $key) { |
|
break; |
|
} else if ($weight == $key) { |
|
$weight += 0.001; |
|
} |
|
} |
|
|
|
self::$_plugins['handles'][$component][strval($weight)] = $value; |
|
self::$_tmp['handles'][$component][] = $value; |
|
|
|
ksort(self::$_plugins['handles'][$component], SORT_NUMERIC); |
|
} |
|
|
|
/** |
|
* 通过魔术函数设置当前组件位置 |
|
* |
|
* @access public |
|
* @param string $component 当前组件 |
|
* @return Typecho_Plugin |
|
*/ |
|
public function __get($component) |
|
{ |
|
$this->_component = $component; |
|
return $this; |
|
} |
|
|
|
/** |
|
* 回调处理函数 |
|
* |
|
* @access public |
|
* @param string $component 当前组件 |
|
* @param string $args 参数 |
|
* @return mixed |
|
*/ |
|
public function __call($component, $args) |
|
{ |
|
$component = $this->_handle . ':' . $component; |
|
$last = count($args); |
|
$args[$last] = $last > 0 ? $args[0] : false; |
|
|
|
if (isset(self::$_plugins['handles'][$component])) { |
|
$args[$last] = NULL; |
|
$this->_signal = true; |
|
foreach (self::$_plugins['handles'][$component] as $callback) { |
|
$args[$last] = call_user_func_array($callback, $args); |
|
} |
|
} |
|
|
|
return $args[$last]; |
|
} |
|
}
|
|
|