Файл: mg-core/lib/pm.php
Строк: 377
<?php
/**
* Класс PM - плагин менеджер, управляет плагинами и
* регистрирует их. Устанавливает взаимодействие пользовательских функций с системой.
*
* @author Авдеев Марк <mark-avdeev@mail.ru>
* @package moguta.cms
* @subpackage Libraries
*/
class PM implements PluginManager {
static private $_instance = null;
// Заристрированные обработчики хуков.
static private $_eventHook;
// Список зарегистрированных шорткодов.
static public $listShortCode = array();
static private $_pluginsPath = 'mg-plugins/';
public function __construct() {
self::$_eventHook = array();
}
/**
* Добавляет локаль плагина, если она есть в папке /locales текущего плагина $pluginName.
* @param $pluginName - название плагина
*/
static public function plugLocales($pluginName) {
$return = array();
$filename = self::$_pluginsPath.$pluginName.'/locales/'.MG::getOption('languageLocale').'.php';
if (file_exists($filename)) {
include($filename);
$return = $lang;
}
return $return;
}
private function __clone() {
}
private function __wakeup() {
}
/**
* Возвращет единственный экземпляр данного класса.
* @return object - объект класса PM.
*/
public static function getInstance() {
if (is_null(self::$_instance)) {
self::$_instance = new self;
}
return self::$_instance;
}
/**
* Возвращает массив названий шорткодов.
* Все хуки для шорткодов начинаются с префикса "shortcode_"
* @return array - массив названий шорткодов
*/
public static function getListShortCode() {
// if (empty(self::$listShortCode)) {
$result = array();
if (sizeof(self::$_eventHook)) {
foreach (self::$_eventHook as $eventHook) {
$nameHook = $eventHook->getHookName();
if (strpos($nameHook, 'shortcode_') === 0) {
$result[] = str_replace('shortcode_', '', $nameHook);
}
}
}
self::$listShortCode = $result;
// }
return self::$listShortCode;
}
/**
* Возвращает массив названий зарегистрированных хуков.
* @return array - массив названий зарегистрированных хуков.
*/
public static function getListNameHooks() {
$result = array();
if (sizeof(self::$_eventHook)) {
foreach (self::$_eventHook as $eventHook) {
$result[] = strtolower($eventHook->getHookName());
}
}
return $result;
}
/**
* Проверяет зарегистрирован ли хук.
* @param $hookname - имя хука, который надо проверить на регистрацию.
* @return bool.
*/
public static function isHookInReg($hookname) {
return in_array(strtolower($hookname), self::getListNameHooks());
}
/**
* Инициализирует объект данного класса.
*/
public static function init() {
self::getInstance();
}
/**
* Регистрирует обработчик для действия, занося его в реестр обработчиков.
* @param Hook $eventHook - объект содержащий информацию об обработчике и событии.
*/
public static function registration(Hook $eventHook) {
self::$_eventHook[] = $eventHook;
}
/**
* Удаляет из рееестра данные об обработчике.
* @param Hook $eventHook - объект содержащий информацию об обработчике и событии.
*/
public static function delete(Hook $eventHook) {
if ($id = array_search($eventHook, self::$_eventHook, TRUE)) {
unset(self::$_eventHook[$id]);
}
}
/**
* Вычисляет приоритетность пользовательских функций, назначеных на обработку одного и тогоже события.
* Используется для сравнения приоритетов в функции.
*
* @param $a - приоритет текущей функции.
* @param $a - приоритет предыдущей функции usort в методе 'PM::createHook'.
*/
public static function prioritet($a, $b) {
return $a['priority'] - $b['priority'];
}
/**
* Инициализирует то или иное событие в коде программы,
* сообщая об этом всем зарегистрированных обработчикам.
* Если существуют обработчики назначенные на данное событие,
* то запускает их пользовательские функции, в порядке очереди
* определенной приоритетами.
*
* @param string $hookName - название события.
* @param type $arg - массив арументов.
* @param type $result - флаг, определяющий, должена ли пользовательская
* функция вернуть результат для дальнейшей работы в месте инициализации события.
* @return array
*/
public static function createHook($hookName, $arg, $result = false) {
$hookName = strtolower($hookName);
if ($result) {
if (sizeof(self::$_eventHook)) {
foreach (self::$_eventHook as $eventHook) {
// Если нашлись пользовательские функции которые хотя обработать событие.
if ($eventHook->getHookName() == $hookName && $eventHook->getCountArg() == 1) {
// В массив найденых обработчиков записываем все обработчики и их порядок выполнения.
$handleEventHooks[] = array(
'eventHook' => $eventHook,
'priority' => $eventHook->getPriority()
);
}
}
// Запускает функции всех подходящих обработчиков.
if (!empty($handleEventHooks)) {
// Сортировка в порядке приоритетов.
usort($handleEventHooks, array(__CLASS__, "prioritet"));
foreach ($handleEventHooks as $handle) {
$arg['result'] = $handle['eventHook']->run($arg);
}
return $arg['result'];
}
}
return $arg['result'];
} else {
$countArg = count($arg);
if (sizeof(self::$_eventHook)) {
foreach (self::$_eventHook as $eventHook) {
if ($eventHook->getHookName() == $hookName && $eventHook->getCountArg() == $countArg) {
$eventHook->run($arg);
}
}
}
}
}
/**
* Подключает все плагины соответствующии требованиям.
* - Все плагины должны содержаться в каталоге mg-plugins/;
* - Название папки содержащей плагин, может быть любым;
* - Если в папке плагина есть файл index.php и в первом блоковом комментариии
* он содержит хотябы один из доступных параметров PluginName ,
* то плагин будет подключен.
* Пример мета информации в index.php
* <code>
* Plugin Name: Hello World
* Plugin URI: http://moguta.ru/plugins/HelloWorld/
* Description: Плагин для демонстрации функционала
* Author: mogutaTeam
* Version: 1.0
* </code>
* @return void
*/
public static function includePlugins() {
$pluginsInfo = self::getPluginsInfo();
foreach ($pluginsInfo as $plugin) {
// Подключает только активные плагины.
if ("1" == $plugin['Active']&&!empty($plugin['folderName'])) {
require_once PLUGIN_DIR.$plugin['folderName'].'/index.php';
}
}
}
/**
* Подключает один конкретный плагин хранящийся в выбраной директории.
* @param string $folderName - наименование папки плагина.
* @return void
*/
public static function includePluginInFolder($folderName) {
require_once PLUGIN_DIR.$folderName.'/index.php';
}
/**
* Считывает информацию обо всех плагинах в дирректории PLUGIN_DIR.
* @return void
*/
public static function getPluginsInfo() {
$result = array();
$plugins = scandir(PLUGIN_DIR);
unset($plugins[1]);
foreach ($plugins as $folderName) {
if (!is_dir($folderName)) {
$plug = self::readInfo($folderName);
if($plug){
$result[] = $plug;
}
}
}
// Считываем активность плагинов из БД.
$res = DB::query("SELECT * FROM ".PREFIX."plugins");
while ($row = DB::fetchArray($res)) {
$pluginsActivity[$row['folderName']] = $row['active'];
}
// Дополняем массив найденых плагинов информацие их активности.
foreach ($result as $id => $plugin) {
$result[$id]['Active'] = isset($pluginsActivity[$plugin['folderName']]) ? $pluginsActivity[$plugin['folderName']] : 0;
}
// Сортировка в порядке активности.
usort($result, array(__CLASS__, "sortByActivity"));
return $result;
}
/**
* Функция для сортировки плагинов по активности.
*
* @param $a - активность текущего плагина.
* @param $a - активность предыдущего плагина (usort в методе 'PM::getPluginsInfo').
*/
public static function sortByActivity($a, $b) {
return $b['Active'] - $a['Active'];
}
/**
* Считывает информацию о конкретном плагине плагине.
* @param string $folderName - путь к файлу index.php плагина
* @return если соответствует стандартам то array иначе null.
*/
public static function readInfo($folderName) {
$pluginDirectory = PLUGIN_DIR.$folderName.'/index.php';
// Считываем содержание index.php.
if (file_exists($pluginDirectory)) {
$contentIndex = file_get_contents($pluginDirectory);
// Удаляем все переносы строк.
$contentIndex = str_replace(array("rn", "n", "r"), 'infoParam', $contentIndex);
// Ищем все блочные комментарии.
preg_match_all('~/*(.*?)*/~i', $contentIndex, $pluginInfo);
$result = array();
// Определяем доступные информационные параметры.
$parametr = array(
'PluginName' => 'Plugin Name',
'PluginURI' => 'Plugin URI',
'Description' => 'Description',
'Author' => 'Author',
'Version' => 'Version'
);
// Ищем в первом блочном комментарии информацию, по доступным параметрам $parametr.
foreach ($parametr as $key => $value) {
preg_match('~'.$value.'s?:s?(.*?)infoParam~i', $pluginInfo[1][0], $pluginData);
$tmp = '';
if (!empty($pluginData[1])) {
//замена фигурной скобки на мнемонику, чтобы не производить обработку шорткода
$tmp = str_replace('[', '[', $pluginData[1]);
}
$result[$key] = $tmp;
}
// Если не существует параметра PluginName, то файл не корректно задает плагин.
if (!empty($result['PluginName'])) {
$result['folderName'] = $folderName;
return $result;
}
}
return null;
}
/**
* Получает название папки в которой хранится плагин.
* @param type $dir - директория в коотрой хранится плагин.
*/
public static function getFolderPlugin($dir) {
$section = explode(DIRECTORY_SEPARATOR, dirname($dir));
$folderName = count($section) > 1 ? end($section) : $dir;
return strtolower($folderName);
}
/**
* Ищет в контенте шорткоды и запускает их обработчики.
* Если шотркод не определен или его плагин отключен, он будет возвращен без обработки.
*
* @param string $content - строка для поиска в ней шорткодов.
* @return string исходную строку с результатами выполнения хуков для шорткодов.
*/
public static function doShortcode($content) {
$shortCodes = self::getListShortCode();
if (empty($shortCodes)) {
return $content;
}
// Получает шаблон для поиска шорткодов.
$pattern = self::getShortcodeRegex();
return preg_replace_callback("/$pattern/s", array(__CLASS__, 'doShortcodeTag'), $content);
}
/**
* Возвращает шаблон для поиска по регулярному выражению.
* Регулярное выражение содержит 6 различных частей,
* для обеспечения разбора контента, которые ищут:
* 1 - Открывающую скобку [ исключая вложения их друг в друга [[]];
* 2 - Название шорткода;
* 3 - Список аргументов;
* 4 - Закрывающий слешь /;
* 5 - Содержимое шорткода, между тегами;
* 6 - Закрывающую скобку [ исключая вложения их друг в друга [[]];
*
* @return string регулярное выражение для поиска шорткода.
*/
public static function getShortcodeRegex() {
$tagnames = self::getListShortCode();
$tagregexp = join('|', array_map('preg_quote', $tagnames));
// ВНИМАНИЕ! Не используйте это
// выражение без методов do_shortcode_tag() и strip_shortcode_tag()
return
'\[' // Открывающая скобка
.'(\[?)' // 1: Дополнительная проверка на вложенность: [[tag]]
."($tagregexp)" // 2: Имя тега
.'\b' // Слово - граница
.'(' // 3: Проверка внутри открыторого тега
.'[^\]\/]*' // - не закрывающий слеш или скобка
.'(?:'
.'\/(?!\])' // нет последовательности - /]
.'[^\]\/]*' // нет закрывающей скобки либо слеша
.')*?'
.')'
.'(?:'
.'(\/)' // 4: Текущий тег - закрывающий ...
.'\]' // ... и закрывающая скобка
.'|'
.'\]' // Закрывающая скобка
.'(?:'
.'(' // 5: Содержимое между тегами [shotcode]$content[shotcode/]
.'[^\[]*+' // Нет открывающей скобки
.'(?:'
.'\[(?!\/\2\])' // нет последовательности закрывающий слеш со скобкой
.'[^\[]*+' // нет открывающейся скобки
.')*+'
.')'
.'\[\/\2\]' // Закрывающий тег кода
.')?'
.')'
.'(\]?)'; // 6: исключает вложение[[tag]]
}
/**
* Проверка разобраных частей для передачи в обработчик хука
* @param array $m массив полученный регулярным выражением
*/
public static function doShortcodeTag($m) {
if ($m[1] == '[' && $m[6] == ']') {
return substr($m[0], 1, -1);
}
$tag = $m[2];
$attr = self::shortcodeParseAtts($m[3]);
// если между тегами есть содержимое, до записываем его в аргументы с ключем content
if (!empty($m[5])) {
$attr['content'] = $m[5];
}
return self::createHook('shortcode_'.$tag, $attr, true);
}
/**
* Возвращает список атрибутов внутри шорткода.
* в виде массива пар - {ключ:значение}
* @param string строка
* @return array массив атрибутов со значениями.
*/
public static function shortcodeParseAtts($text) {
$atts = array();
$pattern = '/(w+)s*=s*"([^"]*)"(?:s|$)|(w+)s*=s*'([^']*)'(?:s|$)|(w+)s*=s*([^s'"]+)(?:s|$)|"([^"]*)"(?:s|$)|(S+)(?:s|$)/';
$text = preg_replace("/[x{00a0}x{200b}]+/u", " ", $text);
if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
foreach ($match as $m) {
if (!empty($m[1]))
$atts[strtolower($m[1])] = stripcslashes($m[2]);
elseif (!empty($m[3]))
$atts[strtolower($m[3])] = stripcslashes($m[4]);
elseif (!empty($m[5]))
$atts[strtolower($m[5])] = stripcslashes($m[6]);
elseif (isset($m[7]) and strlen($m[7]))
$atts[] = stripcslashes($m[7]);
elseif (isset($m[8]))
$atts[] = stripcslashes($m[8]);
}
} else {
$atts = ltrim($text);
}
return $atts;
}
/**
* Очищает контент от всех шорткодов.
*
* @param string $content строка с шорткодами.
* @return string исходная строка уже без шорткодов.
*/
public static function stripShortcodes($content) {
//$shortCodes = self::getListShortCode();
//$shortCodes = self::$listShortCode;
// if (empty($shortCodes)) {
// return $content;
// }
// Получает шаблон для поиска шорткодов.
$pattern = self::getShortcodeRegex();
// $content = preg_replace_callback("/$pattern/s", array(__CLASS__, 'stripShortcodeTag'), $content);
//$content = 'iiiiii';
return $content;
}
public static function stripShortcodeTag($m) {
if ($m[1] == '[' && $m[6] == ']') {
return substr($m[0], 1, -1);
}
return $m[1].$m[6];
}
/**
* Метод валидации загружаемого плагина.
*
* @param array $file_array - массив данных загружаемого плагина
* @return array данные о загрузке,
* 'data' => если имеется имя загруженного плагина
'msg' => статус(сообщение) загрузки
*/
public static function downloadPlugin($file_array) {
//имя плагина
$name = $file_array['name'];
//его размер
$size = $file_array['size'];
//временная папка архива плагина
$path = self::$_pluginsPath;
//поддерживаемые форматы
$validFormats = array('zip');
$_lang = MG::get('lang');
if (strlen($name)) {
$fullName = explode('.', $name);
$ext = array_pop($fullName);
$name = implode('.', $fullName);
if (in_array($ext, $validFormats)) {
if ($size < (1024 * 1024 )) {
$actualName = $name.'.'.$ext;
$tmp = $file_array['tmp_name'];
if (move_uploaded_file($tmp, $path.$actualName)) {
$data = $path.$actualName;
$msg = $_lang['PLUG_UPLOAD'];
} else {
$msg = $_lang['PLUG_UPLOAD_ERR'];
}
} else {
$msg = $_lang['PLUG_UPLOAD_ERR2'];
}
} else {
$msg = $_lang['PLUG_UPLOAD_ERR3'];
}
} else {
$msg = $_lang['PLUG_UPLOAD_ERR4'];
}
return array(
'data' => $data,
'msg' => $msg
);
}
/**
* Распаковка плагина.
*
* @param string $archiveFile - путь до файла с плагином
* @return boolean
*/
public static function extractPluginZip($archiveFile) {
if (file_exists($archiveFile)) {
$zip = new ZipArchive;
$res = $zip->open($archiveFile, ZIPARCHIVE::CREATE);
if ($res === TRUE) {
$zip->extractTo(self::$_pluginsPath);
$zip->close();
unlink($archiveFile);
return true;
}
}
return false;
}
/**
* Удаление информации о плагине из БД.
*
* @param string $pluginFolder имя плагина
* @return boolean
*/
public static function deletePlagin($pluginFolder) {
if (DB::query('
DELETE FROM `'.PREFIX.'plugins`
WHERE folderName = "%s"
', $pluginFolder)) {
return true;
}
}
}