* @author Shohei Nakajima * @link http://www.netcommons.org NetCommons Project * @license http://www.netcommons.org/license.txt NetCommons License * @copyright Copyright 2014, NetCommons Project */ App::uses('AppModel', 'Model'); App::uses('Current', 'NetCommons.Utility'); /** * Plugin Model * * @author Shohei Nakajima * @package NetCommons\PluginManager\Model */ class Plugin extends AppModel { /** * PackagistのURL */ const PACKAGIST_URL = 'https://packagist.org/packages/'; /** * GithubのURL */ const GITHUB_URL = 'https://github.com/'; /** * コアプラグイン */ const PLUGIN_TYPE_CORE = '0'; /** * フレームに設置するプラグイン */ const PLUGIN_TYPE_FOR_FRAME = '1'; /** * コントロールパネルプラグイン * ※プラグイン管理でのタブ識別で使用する */ const PLUGIN_TYPE_FOR_CONTROL_PANEL = '2'; /** * サイト管理者が操作できる管理プラグイン */ const PLUGIN_TYPE_FOR_SITE_MANAGER = '2'; /** * システム管理者が操作できる管理プラグイン */ const PLUGIN_TYPE_FOR_SYSTEM_MANGER = '3'; /** * 未インストール */ const PLUGIN_TYPE_FOR_NOT_YET = '4'; /** * テーマ */ const PLUGIN_TYPE_FOR_THEME = '5'; /** * 外部ライブラリ composer */ const PLUGIN_TYPE_FOR_EXT_COMPOSER = '6'; /** * 外部ライブラリ bower */ const PLUGIN_TYPE_FOR_EXT_BOWER = '7'; /** * Behaviors * * @var array */ public $actsAs = array( 'PluginManager.Plugin', 'PluginManager.PluginBower', 'PluginManager.PluginComposer', 'PluginManager.PluginTheme', 'PluginManager.PluginWebroot', 'NetCommons.NetCommonsCache', ); /** * Validation rules * * @var array */ public $validate = array( 'language_id' => array( 'numeric' => array( 'rule' => array('numeric'), //'message' => 'Your custom message here', //'allowEmpty' => false, //'required' => false, //'last' => false, // Stop validation after this rule //'on' => 'create', // Limit validation to 'create' or 'update' operations ), ), 'key' => array( 'notBlank' => array( 'rule' => array('notBlank'), //'message' => 'Your custom message here', //'allowEmpty' => false, //'required' => false, //'last' => false, // Stop validation after this rule //'on' => 'create', // Limit validation to 'create' or 'update' operations ), ), 'name' => array( 'notBlank' => array( 'rule' => array('notBlank'), //'message' => 'Your custom message here', //'allowEmpty' => false, //'required' => false, //'last' => false, // Stop validation after this rule //'on' => 'create', // Limit validation to 'create' or 'update' operations ), ), 'namespace' => array( 'notBlank' => array( 'rule' => array('notBlank'), //'message' => 'Your custom message here', //'allowEmpty' => false, //'required' => false, //'last' => false, // Stop validation after this rule //'on' => 'create', // Limit validation to 'create' or 'update' operations ), ), //'default_action' => array( // 'notBlank' => array( // 'rule' => array('notBlank'), // //'message' => 'Your custom message here', // //'allowEmpty' => false, // //'required' => false, // //'last' => false, // Stop validation after this rule // //'on' => 'create', // Limit validation to 'create' or 'update' operations // ), //), ); //The Associations below have been created with all possible keys, //those that are not needed can be removed /** * belongsTo associations * * @var array */ public $belongsTo = array( 'Language' => array( 'className' => 'M17n.Language', 'foreignKey' => 'language_id', 'conditions' => '', 'fields' => '', 'order' => '' ) ); /** * getMaxWeight * * @param int $type plugins.type * @return int */ public function getMaxWeight($type) { $order = $this->find('first', array( 'recursive' => -1, 'fields' => array('weight'), 'conditions' => array('type' => $type), 'order' => array('weight' => 'DESC') )); if (isset($order[$this->alias]['weight'])) { $weight = (int)$order[$this->alias]['weight']; } else { $weight = 0; } return $weight; } /** * プラグインデータの取得 * * @param int $type プラグインタイプ * @param string $key プラグインキー * @param string|array|null $langId 取得する言語。nullの場合、typeにより共通言語もしくCurrentにセットされているものを使用する * @return array */ public function getPlugins($type, $key = null, $langId = null) { if (! $langId) { $notLangTypes = array( self::PLUGIN_TYPE_CORE, self::PLUGIN_TYPE_FOR_THEME, self::PLUGIN_TYPE_FOR_EXT_COMPOSER, self::PLUGIN_TYPE_FOR_EXT_BOWER ); if (! is_array($type) && in_array($type, $notLangTypes)) { $langId = '0'; } else { $langId = Current::read('Language.id'); } } $conditions = array( 'Plugin.type' => $type, 'Plugin.language_id' => $langId ); if (isset($key)) { $conditions['Plugin.key'] = $key; $order = array(); } else { $order = array( $this->alias . '.weight' => 'asc', $this->alias . '.id' => 'asc' ); } //pluginsテーブルの取得 $plugins = $this->cacheFindQuery('all', array( 'recursive' => -1, 'conditions' => $conditions, 'order' => $order, )); if (! $plugins) { return array(); } $plugins = $this->convertSerializeData($plugins); if (count($plugins) > 1) { return $plugins; } else { return $plugins[0]; } } /** * serialize_dataのデータをコンバートする * * @param array $plugins PluginモデルからfindAllしたもの * @return array */ public function convertSerializeData($plugins) { foreach ($plugins as $i => $plugin) { $plugin['Plugin']['serialize_data'] = unserialize( Hash::get($plugin, 'Plugin.serialize_data', serialize(array())) ); if ($plugin['Plugin']['type'] === self::PLUGIN_TYPE_FOR_EXT_BOWER) { $plugin['Plugin']['package_url'] = Hash::get($plugin, 'Plugin.serialize_data.source'); $plugin['latest'] = $this->getBower($plugin['Plugin']['namespace']); } elseif ($plugin['Plugin']['type'] === self::PLUGIN_TYPE_FOR_THEME) { $plugin['Plugin']['package_url'] = null; $plugin['latest'] = $this->getTheme($plugin['Plugin']['namespace']); } else { $plugin['Plugin']['package_url'] = self::PACKAGIST_URL . $plugin['Plugin']['namespace']; $plugin['latest'] = $this->getComposer($plugin['Plugin']['namespace']); } $plugins[$i] = $plugin; } return $plugins; } /** * 未インストールのプラグイン取得 * * @param int $type プラグインタイプ * @param string $key プラグインキー * @return array */ public function getNewPlugins($type, $key = null) { $conditions = array( 'language_id' => array(Current::read('Language.id'), '0'), ); if ($type === self::PLUGIN_TYPE_FOR_EXT_BOWER) { $packages = $this->getBower(); $latests = Hash::extract($packages, '{s}.key', array()); $conditions['type'] = self::PLUGIN_TYPE_FOR_EXT_BOWER; } elseif ($type === self::PLUGIN_TYPE_FOR_EXT_COMPOSER) { $packages = $this->getComposer(); $notPackages = preg_replace('/-/', '_', preg_replace('/^netcommons\//', '', preg_grep('/^netcommons/', array_keys($packages))) ); $latests = array_diff(Hash::extract($packages, '{s}.key', array()), $notPackages); $conditions['type'] = self::PLUGIN_TYPE_FOR_EXT_COMPOSER; } elseif ($type === self::PLUGIN_TYPE_FOR_THEME) { $packages = $this->getTheme(); $latests = Hash::extract($packages, '{s}.key', array()); $conditions['type'] = self::PLUGIN_TYPE_FOR_THEME; } else { $packages = $this->getComposer(); $latests = preg_replace('/-/', '_', preg_replace('/^netcommons\//', '', preg_grep('/^netcommons/', array_keys($packages))) ); $conditions['type'] = array( self::PLUGIN_TYPE_CORE, self::PLUGIN_TYPE_FOR_FRAME, self::PLUGIN_TYPE_FOR_SITE_MANAGER, self::PLUGIN_TYPE_FOR_SYSTEM_MANGER, ); } $packages = Hash::combine($packages, '{s}.key', '{s}'); $currents = $this->cacheFindQuery('list', array( 'recursive' => -1, 'fields' => array('key', 'commit_version'), 'conditions' => $conditions, )); $currents = array_keys($currents); $inserts = array_diff($latests, $currents); $plugins = array(); if ($key) { if (in_array($key, $inserts, true)) { $plugins[] = $this->__parseNewPlugins($packages[$key]); } } else { $index = 0; foreach ($inserts as $pluginKey) { $plugins[$index] = $this->__parseNewPlugins($packages[$pluginKey]); $index++; } } if ($key) { return Hash::get($plugins, '0'); } else { return $plugins; } } /** * 未インストールのプラグインのパース処理 * * @param array $package パッケージ * @return array */ private function __parseNewPlugins($package) { $plugin = array(); $plugin['Plugin'] = $package; $plugin['Plugin']['serialize_data'] = $package; $plugin['latest'] = $package; if (Hash::get($plugin['Plugin'], 'type') === self::PLUGIN_TYPE_FOR_EXT_BOWER) { $plugin['Plugin']['package_url'] = Hash::get($package, 'source'); } elseif (Hash::get($plugin['Plugin'], 'type') === self::PLUGIN_TYPE_FOR_THEME) { $plugin['Plugin']['package_url'] = null; } else { $plugin['Plugin']['package_url'] = self::PACKAGIST_URL . $plugin['Plugin']['namespace']; } return $plugin; } /** * プラグインの表示順序更新 * * @param array $data Request data * @return bool True on success * @throws InternalErrorException */ public function saveWeight($data) { $this->loadModels([ 'Plugin' => 'PluginManager.Plugin', ]); //トランザクションBegin $this->begin(); try { //Pluginテーブルの登録 $fieldList = array('weight'); foreach ($data['Plugins'] as $req) { $plugins = $this->find('all', array( 'recursive' => -1, 'conditions' => array('key' => $req['Plugin']['key']), )); foreach ($plugins as $plugin) { if ($plugin['Plugin']['weight'] === $req['Plugin']['weight']) { continue; } unset($plugin['Plugin']['modified_user'], $plugin['Plugin']['modified']); $plugin['Plugin']['weight'] = (int)$req['Plugin']['weight']; if (! $this->save($plugin, array('fieldList' => $fieldList))) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } } } //トランザクションCommit $this->commit(); } catch (Exception $ex) { //トランザクションRollback $this->rollback($ex); } return true; } /** * 多言語化するプラグインの選択 * * 呼び出しもとでbeginを実行する * * @param array $data リクエストデータ * @return bool * @throws InternalErrorException */ public function saveEnableM17n($data) { if (! $data['Plugin']['key']) { return true; } $update = array( 'Plugin.is_m17n' => true, ); $conditions = array( 'Plugin.key' => $data['Plugin']['key'], 'Plugin.type' => self::PLUGIN_TYPE_FOR_FRAME ); if (! $this->updateAll($update, $conditions)) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } $update = array( 'Plugin.is_m17n' => false, ); $conditions = array( 'Plugin.key NOT IN' => $data['Plugin']['key'], 'Plugin.type' => self::PLUGIN_TYPE_FOR_FRAME ); if (! $this->updateAll($update, $conditions)) { throw new InternalErrorException(__d('net_commons', 'Internal Server Error')); } //キャッシュのクリア $this->cacheClear(); return true; } /** * 更新があるかどうか * * @return bool */ public function hasUpdate() { $composers = $this->getComposer(); $bowers = $this->getBower(); $themes = $this->getTheme(); $latests = array_merge( Hash::combine($bowers, '{s}.key', '{s}.commit_version'), Hash::combine($composers, '{s}.key', '{s}.commit_version'), Hash::combine($themes, '{s}.key', '{s}.commit_version') ); $currents = $this->find('list', array( 'recursive' => -1, 'fields' => array('key', 'commit_version'), 'conditions' => array( 'language_id' => array(Current::read('Language.id'), '0'), ), )); $diffLatest = array_diff($latests, $currents); $diffCurrent = array_diff($currents, $latests); $return = !empty($diffLatest) || !empty($diffCurrent); return $return; } /** * プラグインの全タイプを取得 * * 一括アップデートで使用する * * @return array */ public function getTypes() { //pluginsテーブルの取得 $plugins = $this->find('all', array( 'recursive' => -1, 'fields' => ['type'], 'group' => 'type', 'order' => ['type' => 'asc'], 'callbacks' => false, )); if (! $plugins) { return array(); } $result = []; foreach ($plugins as $plugin) { $result[] = $plugin[$this->alias]['type']; } return $result; } }