PageRenderTime 47ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/core/PluginsManager.php

https://github.com/ntulip/piwik
PHP | 541 lines | 436 code | 37 blank | 68 comment | 18 complexity | cd5891efe8e4c8824c4f01961c93dd2a MD5 | raw file
  1. <?php
  2. /**
  3. * Piwik - Open source web analytics
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
  7. * @version $Id: PluginsManager.php 1492 2009-10-11 20:49:29Z vipsoft $
  8. *
  9. * @category Piwik
  10. * @package Piwik
  11. */
  12. // no direct access
  13. defined('PIWIK_INCLUDE_PATH') or die;
  14. /**
  15. * @see core/PluginsFunctions/Menu.php
  16. * @see core/PluginsFunctions/AdminMenu.php
  17. * @see core/PluginsFunctions/WidgetsList.php
  18. * @see core/PluginsFunctions/Sql.php
  19. */
  20. require_once PIWIK_INCLUDE_PATH . '/core/PluginsFunctions/Menu.php';
  21. require_once PIWIK_INCLUDE_PATH . '/core/PluginsFunctions/AdminMenu.php';
  22. require_once PIWIK_INCLUDE_PATH . '/core/PluginsFunctions/WidgetsList.php';
  23. require_once PIWIK_INCLUDE_PATH . '/core/PluginsFunctions/Sql.php';
  24. /**
  25. * @package Piwik
  26. * @subpackage Piwik_PluginsManager
  27. */
  28. class Piwik_PluginsManager
  29. {
  30. /**
  31. * @var Event_Dispatcher
  32. */
  33. public $dispatcher;
  34. protected $pluginsToLoad = array();
  35. protected $languageToLoad = null;
  36. protected $doLoadPlugins = true;
  37. protected $loadedPlugins = array();
  38. protected $doLoadAlwaysActivatedPlugins = true;
  39. protected $pluginToAlwaysActivate = array( 'CoreHome', 'CoreUpdater', 'CoreAdminHome', 'CorePluginsAdmin' );
  40. static private $instance = null;
  41. /**
  42. * Returns the singleton Piwik_PluginsManager
  43. *
  44. * @return Piwik_PluginsManager
  45. */
  46. static public function getInstance()
  47. {
  48. if (self::$instance == null)
  49. {
  50. $c = __CLASS__;
  51. self::$instance = new $c();
  52. }
  53. return self::$instance;
  54. }
  55. private function __construct()
  56. {
  57. $this->dispatcher = Event_Dispatcher::getInstance();
  58. }
  59. public function isPluginAlwaysActivated( $name )
  60. {
  61. return in_array( $name, $this->pluginToAlwaysActivate);
  62. }
  63. public function isPluginActivated( $name )
  64. {
  65. return in_array( $name, $this->pluginsToLoad)
  66. || $this->isPluginAlwaysActivated( $name );
  67. }
  68. public function isPluginLoaded( $name )
  69. {
  70. return isset($this->loadedPlugins[$name]);
  71. }
  72. /**
  73. * Reads the directories inside the plugins/ directory and returns their names in an array
  74. *
  75. * @return array
  76. */
  77. public function readPluginsDirectory()
  78. {
  79. $pluginsName = glob( PIWIK_INCLUDE_PATH . '/plugins/*', GLOB_ONLYDIR);
  80. $pluginsName = $pluginsName === false ? array() : array_map('basename', $pluginsName);
  81. return $pluginsName;
  82. }
  83. public function deactivatePlugin($pluginName)
  84. {
  85. $plugins = $this->pluginsToLoad;
  86. $key = array_search($pluginName,$plugins);
  87. if($key !== false)
  88. {
  89. unset($plugins[$key]);
  90. Zend_Registry::get('config')->Plugins = $plugins;
  91. }
  92. $pluginsTracker = Zend_Registry::get('config')->Plugins_Tracker->Plugins_Tracker;
  93. if(!is_null($pluginsTracker))
  94. {
  95. $pluginsTracker = $pluginsTracker->toArray();
  96. $key = array_search($pluginName,$pluginsTracker);
  97. if($key !== false)
  98. {
  99. unset($pluginsTracker[$key]);
  100. Zend_Registry::get('config')->Plugins_Tracker = $pluginsTracker;
  101. }
  102. }
  103. }
  104. public function installLoadedPlugins()
  105. {
  106. foreach($this->getLoadedPlugins() as $plugin)
  107. {
  108. try {
  109. $this->installPluginIfNecessary( $plugin );
  110. }catch(Exception $e){
  111. echo $e->getMessage();
  112. }
  113. }
  114. }
  115. public function activatePlugin($pluginName)
  116. {
  117. $plugins = Zend_Registry::get('config')->Plugins->Plugins->toArray();
  118. if(in_array($pluginName,$plugins))
  119. {
  120. throw new Exception("Plugin '$pluginName' already activated.");
  121. }
  122. $existingPlugins = $this->readPluginsDirectory();
  123. if( array_search($pluginName,$existingPlugins) === false)
  124. {
  125. throw new Exception("Unable to find the plugin '$pluginName'.");
  126. }
  127. $plugin = $this->loadPlugin($pluginName);
  128. $this->installPluginIfNecessary($plugin);
  129. // we add the plugin to the list of activated plugins
  130. $plugins[] = $pluginName;
  131. // the config file will automatically be saved with the new plugin
  132. Zend_Registry::get('config')->Plugins = $plugins;
  133. }
  134. public function setPluginsToLoad( array $pluginsToLoad )
  135. {
  136. // case no plugins to load
  137. if(is_null($pluginsToLoad))
  138. {
  139. $pluginsToLoad = array();
  140. }
  141. $this->pluginsToLoad = $pluginsToLoad;
  142. $this->loadPlugins();
  143. }
  144. public function doNotLoadPlugins()
  145. {
  146. $this->doLoadPlugins = false;
  147. }
  148. public function doNotLoadAlwaysActivatedPlugins()
  149. {
  150. $this->doLoadAlwaysActivatedPlugins = false;
  151. }
  152. public function postLoadPlugins()
  153. {
  154. $plugins = $this->getLoadedPlugins();
  155. foreach($plugins as $plugin)
  156. {
  157. $this->loadTranslation( $plugin, $this->languageToLoad );
  158. $plugin->postLoad();
  159. }
  160. }
  161. /**
  162. * Returns an array containing the plugins class names (eg. 'Piwik_UserCountry' and NOT 'UserCountry')
  163. *
  164. * @return array
  165. */
  166. public function getLoadedPluginsName()
  167. {
  168. $oPlugins = $this->getLoadedPlugins();
  169. $pluginNames = array_map('get_class',$oPlugins);
  170. return $pluginNames;
  171. }
  172. /**
  173. * Returns an array of key,value with the following format: array(
  174. * 'UserCountry' => Piwik_Plugin $pluginObject,
  175. * 'UserSettings' => Piwik_Plugin $pluginObject,
  176. * );
  177. *
  178. * @return array
  179. */
  180. public function getLoadedPlugins()
  181. {
  182. return $this->loadedPlugins;
  183. }
  184. /**
  185. * Returns the given Piwik_Plugin object
  186. *
  187. * @param string $name
  188. * @return Piwik_Piwik
  189. */
  190. public function getLoadedPlugin($name)
  191. {
  192. if(!isset($this->loadedPlugins[$name]))
  193. {
  194. throw new Exception("The plugin '$name' has not been loaded.");
  195. }
  196. return $this->loadedPlugins[$name];
  197. }
  198. /**
  199. * Load the plugins classes installed.
  200. * Register the observers for every plugin.
  201. *
  202. */
  203. public function loadPlugins()
  204. {
  205. $this->pluginsToLoad = array_unique($this->pluginsToLoad);
  206. if($this->doLoadAlwaysActivatedPlugins)
  207. {
  208. $this->pluginsToLoad = array_merge($this->pluginsToLoad, $this->pluginToAlwaysActivate);
  209. }
  210. foreach($this->pluginsToLoad as $pluginName)
  211. {
  212. if(!$this->isPluginLoaded($pluginName))
  213. {
  214. $newPlugin = $this->loadPlugin($pluginName);
  215. if($this->doLoadPlugins
  216. && $this->isPluginActivated($pluginName))
  217. {
  218. $this->addPluginObservers( $newPlugin );
  219. $this->addLoadedPlugin( $pluginName, $newPlugin);
  220. }
  221. }
  222. }
  223. }
  224. /**
  225. * Loads the plugin filename and instanciates the plugin with the given name, eg. UserCountry
  226. * Do NOT give the class name ie. Piwik_UserCountry, but give the plugin name ie. UserCountry
  227. *
  228. * @param Piwik_Plugin $pluginName
  229. */
  230. public function loadPlugin( $pluginName )
  231. {
  232. if(isset($this->loadedPlugins[$pluginName]))
  233. {
  234. return $this->loadedPlugins[$pluginName];
  235. }
  236. $pluginFileName = $pluginName . '/' . $pluginName . '.php';
  237. $pluginClassName = 'Piwik_'.$pluginName;
  238. if( !Piwik_Common::isValidFilename($pluginName))
  239. {
  240. throw new Exception("The plugin filename '$pluginFileName' is not a valid filename");
  241. }
  242. $path = PIWIK_INCLUDE_PATH . '/plugins/' . $pluginFileName;
  243. if(!file_exists($path))
  244. {
  245. throw new Exception("Unable to load plugin '$pluginName' because '$path' couldn't be found.");
  246. }
  247. // Don't remove this.
  248. // Our autoloader can't find plugins/PluginName/PluginName.php
  249. require_once $path; // prefixed by PIWIK_INCLUDE_PATH
  250. if(!class_exists($pluginClassName, false))
  251. {
  252. throw new Exception("The class $pluginClassName couldn't be found in the file '$path'");
  253. }
  254. $newPlugin = new $pluginClassName();
  255. if(!($newPlugin instanceof Piwik_Plugin))
  256. {
  257. throw new Exception("The plugin $pluginClassName in the file $path must inherit from Piwik_Plugin.");
  258. }
  259. return $newPlugin;
  260. }
  261. public function setLanguageToLoad( $code )
  262. {
  263. $this->languageToLoad = $code;
  264. }
  265. /**
  266. * @param Piwik_Plugin $plugin
  267. */
  268. public function unloadPlugin( $plugin )
  269. {
  270. if(!($plugin instanceof Piwik_Plugin ))
  271. {
  272. $plugin = $this->loadPlugin( $plugin );
  273. }
  274. $hooks = $plugin->getListHooksRegistered();
  275. foreach($hooks as $hookName => $methodToCall)
  276. {
  277. $success = $this->dispatcher->removeObserver( array( $plugin, $methodToCall), $hookName );
  278. if($success !== true)
  279. {
  280. throw new Exception("Error unloading plugin = ".$plugin->getClassName() . ", method = $methodToCall, hook = $hookName ");
  281. }
  282. }
  283. unset($this->loadedPlugins[$plugin->getClassName()]);
  284. }
  285. public function unloadPlugins()
  286. {
  287. $pluginsLoaded = $this->getLoadedPlugins();
  288. foreach($pluginsLoaded as $plugin)
  289. {
  290. $this->unloadPlugin($plugin);
  291. }
  292. }
  293. private function installPlugins()
  294. {
  295. foreach($this->getLoadedPlugins() as $plugin)
  296. {
  297. $this->installPlugin($plugin);
  298. }
  299. }
  300. private function installPlugin( Piwik_Plugin $plugin )
  301. {
  302. try{
  303. $plugin->install();
  304. } catch(Exception $e) {
  305. throw new Piwik_PluginsManager_PluginException($plugin->getName(), $plugin->getClassName(), $e->getMessage()); }
  306. }
  307. /**
  308. * For the given plugin, add all the observers of this plugin.
  309. */
  310. private function addPluginObservers( Piwik_Plugin $plugin )
  311. {
  312. $hooks = $plugin->getListHooksRegistered();
  313. foreach($hooks as $hookName => $methodToCall)
  314. {
  315. $this->dispatcher->addObserver( array( $plugin, $methodToCall), $hookName );
  316. }
  317. }
  318. /**
  319. * Add a plugin in the loaded plugins array
  320. *
  321. * @param string plugin name without prefix (eg. 'UserCountry')
  322. * @param Piwik_Plugin $newPlugin
  323. */
  324. private function addLoadedPlugin( $pluginName, Piwik_Plugin $newPlugin )
  325. {
  326. $this->loadedPlugins[$pluginName] = $newPlugin;
  327. }
  328. /**
  329. * @param Piwik_Plugin $plugin
  330. * @param string $langCode
  331. */
  332. private function loadTranslation( $plugin, $langCode )
  333. {
  334. // we are certainly in Tracker mode, Zend is not loaded
  335. if(!class_exists('Zend_Loader', false))
  336. {
  337. return ;
  338. }
  339. $infos = $plugin->getInformation();
  340. if(!isset($infos['translationAvailable']))
  341. {
  342. $infos['translationAvailable'] = false;
  343. }
  344. $translationAvailable = $infos['translationAvailable'];
  345. if(!$translationAvailable)
  346. {
  347. return;
  348. }
  349. $pluginName = $plugin->getClassName();
  350. $path = PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName .'/lang/%s.php';
  351. $defaultLangPath = sprintf($path, $langCode);
  352. $defaultEnglishLangPath = sprintf($path, 'en');
  353. $translations = array();
  354. if(file_exists($defaultLangPath))
  355. {
  356. require $defaultLangPath;
  357. }
  358. elseif(file_exists($defaultEnglishLangPath))
  359. {
  360. require $defaultEnglishLangPath;
  361. }
  362. else
  363. {
  364. throw new Exception("Language file not found for the plugin '$pluginName'.");
  365. }
  366. Piwik_Translate::getInstance()->mergeTranslationArray($translations);
  367. }
  368. /**
  369. * @return array
  370. */
  371. public function getInstalledPluginsName()
  372. {
  373. if(!class_exists('Zend_Registry', false))
  374. {
  375. throw new Exception("Not possible to list installed plugins (case Tracker module)");
  376. }
  377. $pluginNames = Zend_Registry::get('config')->PluginsInstalled->PluginsInstalled->toArray();
  378. return $pluginNames;
  379. }
  380. public function getInstalledPlugins()
  381. {
  382. $plugins = $this->getLoadedPlugins();
  383. $installed = $this->getInstalledPluginsName();
  384. return array_intersect_key($plugins, array_combine($installed, array_fill(0, count($installed), 1)));
  385. }
  386. private function installPluginIfNecessary( Piwik_Plugin $plugin )
  387. {
  388. $pluginName = $plugin->getClassName();
  389. // is the plugin already installed or is it the first time we activate it?
  390. $pluginsInstalled = $this->getInstalledPluginsName();
  391. if(!in_array($pluginName,$pluginsInstalled))
  392. {
  393. $this->installPlugin($plugin);
  394. $pluginsInstalled[] = $pluginName;
  395. Zend_Registry::get('config')->PluginsInstalled = array('PluginsInstalled' => $pluginsInstalled);
  396. }
  397. $information = $plugin->getInformation();
  398. // if the plugin is to be loaded during the statistics logging
  399. if(isset($information['TrackerPlugin'])
  400. && $information['TrackerPlugin'] === true)
  401. {
  402. $pluginsTracker = Zend_Registry::get('config')->Plugins_Tracker->Plugins_Tracker;
  403. if(is_null($pluginsTracker))
  404. {
  405. $pluginsTracker = array();
  406. }
  407. else
  408. {
  409. $pluginsTracker = $pluginsTracker->toArray();
  410. }
  411. if(!in_array($pluginName, $pluginsTracker))
  412. {
  413. $pluginsTracker[] = $pluginName;
  414. Zend_Registry::get('config')->Plugins_Tracker = array('Plugins_Tracker' => $pluginsTracker);
  415. }
  416. }
  417. }
  418. }
  419. /**
  420. * @package Piwik
  421. * @subpackage Piwik_PluginsManager
  422. */
  423. class Piwik_PluginsManager_PluginException extends Exception
  424. {
  425. function __construct($pluginName, $className, $message)
  426. {
  427. parent::__construct("There was a problem installing the plugin ". $pluginName . ": " . $message. "
  428. If this plugin has already been installed, and if you want to hide this message</b>, you must add the following line under the
  429. [PluginsInstalled]
  430. entry in your config/config.ini.php file:
  431. PluginsInstalled[] = $className" );
  432. }
  433. }
  434. /**
  435. * Post an event to the dispatcher which will notice the observers
  436. */
  437. function Piwik_PostEvent( $eventName, &$object = null, $info = array() )
  438. {
  439. $notification = new Piwik_Event_Notification($object, $eventName, $info);
  440. Piwik_PluginsManager::getInstance()->dispatcher->postNotification( $notification, true, false );
  441. }
  442. /**
  443. * Register an action to execute for a given event
  444. */
  445. function Piwik_AddAction( $hookName, $function )
  446. {
  447. Piwik_PluginsManager::getInstance()->dispatcher->addObserver( $function, $hookName );
  448. }
  449. /**
  450. * @package Piwik
  451. * @see Event_Notification, libs/Event/Notification.php
  452. * @link http://pear.php.net/package/Event_Dispatcher/docs/latest/Event_Dispatcher/Event_Notification.html
  453. */
  454. class Piwik_Event_Notification extends Event_Notification
  455. {
  456. static $showProfiler = false;
  457. function increaseNotificationCount(/* array($className|object, $method) */) {
  458. parent::increaseNotificationCount();
  459. if(self::$showProfiler && func_num_args() == 1)
  460. {
  461. $callback = func_get_arg(0);
  462. if(is_array($callback)) {
  463. $className = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
  464. $method = $callback[1];
  465. echo "after $className -> $method <br>";
  466. echo "-"; Piwik::printTimer();
  467. echo "<br>";
  468. echo "-"; Piwik::printMemoryLeak();
  469. echo "<br>";
  470. }
  471. }
  472. }
  473. }