dispatcher = Event_Dispatcher::getInstance(); } /** * Update Plugins config * * @param array $plugins Plugins */ private function updatePluginsConfig($plugins) { $section = Piwik_Config::getInstance()->Plugins; $section['Plugins'] = $plugins; Piwik_Config::getInstance()->Plugins = $section; } /** * Update Plugins_Tracker config * * @param array $plugins Plugins */ private function updatePluginsTrackerConfig($plugins) { $section = Piwik_Config::getInstance()->Plugins_Tracker; $section['Plugins_Tracker'] = $plugins; Piwik_Config::getInstance()->Plugins_Tracker = $section; } /** * Update PluginsInstalled config * * @param array $plugins Plugins */ private function updatePluginsInstalledConfig($plugins) { $section = Piwik_Config::getInstance()->PluginsInstalled; $section['PluginsInstalled'] = $plugins; Piwik_Config::getInstance()->PluginsInstalled = $section; } /** * Returns true if plugin is always activated * * @param string $name Name of plugin * @return bool */ public function isPluginAlwaysActivated($name) { return in_array($name, $this->pluginToAlwaysActivate); } /** * Returns true if plugin has been activated * * @param string $name Name of plugin * @return bool */ public function isPluginActivated($name) { return in_array($name, $this->pluginsToLoad) || $this->isPluginAlwaysActivated($name); } /** * Returns true if plugin is loaded (in memory) * * @param string $name Name of plugin * @return bool */ public function isPluginLoaded($name) { return isset($this->loadedPlugins[$name]); } /** * Reads the directories inside the plugins/ directory and returns their names in an array * * @return array */ public function readPluginsDirectory() { $pluginsName = _glob(PIWIK_INCLUDE_PATH . '/plugins/*', GLOB_ONLYDIR); $result = array(); if ($pluginsName != false) { foreach ($pluginsName as $path) { $name = basename($path); if (file_exists($path . '/' . $name . '.php')) // only add folder if a Plugin/Plugin.php file exists { $result[] = $name; } } } return $result; } /** * Deactivate plugin * * @param string $pluginName Name of plugin */ public function deactivatePlugin($pluginName) { $plugins = $this->pluginsToLoad; $key = array_search($pluginName, $plugins); $plugin = $this->loadPlugin($pluginName); if ($plugin !== null) { $plugin->deactivate(); } if ($key !== false) { unset($plugins[$key]); } $this->updatePluginsConfig($plugins); $pluginsTracker = Piwik_Config::getInstance()->Plugins_Tracker['Plugins_Tracker']; if (!is_null($pluginsTracker)) { $key = array_search($pluginName, $pluginsTracker); if ($key !== false) { unset($pluginsTracker[$key]); $this->updatePluginsTrackerConfig($pluginsTracker); } } // Delete merged js/css files to force regenerations to exclude the deactivated plugin Piwik_Config::getInstance()->forceSave(); Piwik::deleteAllCacheOnUpdate(); } /** * Install loaded plugins */ public function installLoadedPlugins() { foreach ($this->getLoadedPlugins() as $plugin) { try { $this->installPluginIfNecessary($plugin); } catch (Exception $e) { echo $e->getMessage(); } } } /** * Activate the specified plugin and install (if needed) * * @param string $pluginName Name of plugin * @throws Exception */ public function activatePlugin($pluginName) { $plugins = Piwik_Config::getInstance()->Plugins['Plugins']; if (in_array($pluginName, $plugins)) { throw new Exception("Plugin '$pluginName' already activated."); } $existingPlugins = $this->readPluginsDirectory(); if (array_search($pluginName, $existingPlugins) === false) { // ToDo: This fails in tracker-mode. We should log this however. //Piwik::log(sprintf("Unable to find the plugin '%s' in activatePlugin.", $pluginName)); return; } $plugin = $this->loadPlugin($pluginName); if ($plugin === null) { return; } $this->installPluginIfNecessary($plugin); $plugin->activate(); // we add the plugin to the list of activated plugins if (!in_array($pluginName, $plugins)) { $plugins[] = $pluginName; } else { // clean up if we find a dupe $plugins = array_unique($plugins); } // the config file will automatically be saved with the new plugin $this->updatePluginsConfig($plugins); Piwik_Config::getInstance()->forceSave(); // Delete merged js/css files to force regenerations to include the activated plugin Piwik::deleteAllCacheOnUpdate(); } /** * Load the specified plugins * * @param array $pluginsToLoad Array of plugins to load */ public function loadPlugins(array $pluginsToLoad) { // case no plugins to load if (is_null($pluginsToLoad)) { $pluginsToLoad = array(); } $this->pluginsToLoad = $pluginsToLoad; $this->reloadPlugins(); } /** * Disable plugin loading */ public function doNotLoadPlugins() { $this->doLoadPlugins = false; } /** * Disable loading of "always activated" plugins */ public function doNotLoadAlwaysActivatedPlugins() { $this->doLoadAlwaysActivatedPlugins = false; } /** * Load translations for loaded plugins * * @param bool|string $language Optional language code */ public function loadPluginTranslations($language = false) { if (empty($language)) { $language = Piwik_Translate::getInstance()->getLanguageToLoad(); } $plugins = $this->getLoadedPlugins(); foreach ($plugins as $plugin) { $this->loadTranslation($plugin, $language); } } /** * Execute postLoad() hook for loaded plugins * * @see Piwik_Plugin::postLoad() */ public function postLoadPlugins() { $plugins = $this->getLoadedPlugins(); foreach ($plugins as $plugin) { $plugin->postLoad(); } } /** * Returns an array containing the plugins class names (eg. 'Piwik_UserCountry' and NOT 'UserCountry') * * @return array */ public function getLoadedPluginsName() { return array_map('get_class', $this->getLoadedPlugins()); } /** * Returns an array of key,value with the following format: array( * 'UserCountry' => Piwik_Plugin $pluginObject, * 'UserSettings' => Piwik_Plugin $pluginObject, * ); * * @return array */ public function getLoadedPlugins() { return $this->loadedPlugins; } /** * Returns the given Piwik_Plugin object * * @param string $name * @throws Exception * @return array */ public function getLoadedPlugin($name) { if (!isset($this->loadedPlugins[$name])) { throw new Exception("The plugin '$name' has not been loaded."); } return $this->loadedPlugins[$name]; } /** * Load the plugins classes installed. * Register the observers for every plugin. */ private function reloadPlugins() { $this->pluginsToLoad = array_unique($this->pluginsToLoad); if ($this->doLoadAlwaysActivatedPlugins) { $this->pluginsToLoad = array_merge($this->pluginsToLoad, $this->pluginToAlwaysActivate); } foreach ($this->pluginsToLoad as $pluginName) { if (!$this->isPluginLoaded($pluginName)) { $newPlugin = $this->loadPlugin($pluginName); if ($newPlugin === null) { continue; } if ($this->doLoadPlugins && $this->isPluginActivated($pluginName) ) { $this->addPluginObservers($newPlugin); } } } } /** * Loads the plugin filename and instantiates the plugin with the given name, eg. UserCountry * Do NOT give the class name ie. Piwik_UserCountry, but give the plugin name ie. UserCountry * * @param string $pluginName * @throws Exception * @return Piwik_Plugin|null */ public function loadPlugin($pluginName) { if (isset($this->loadedPlugins[$pluginName])) { return $this->loadedPlugins[$pluginName]; } $pluginFileName = sprintf("%s/%s.php", $pluginName, $pluginName); $pluginClassName = sprintf('Piwik_%s', $pluginName); if (!Piwik_Common::isValidFilename($pluginName)) { throw new Exception(sprintf("The plugin filename '%s' is not a valid filename", $pluginFileName)); } $path = PIWIK_INCLUDE_PATH . '/plugins/' . $pluginFileName; if (!file_exists($path)) { // ToDo: We should log this - but this will crash in Tracker mode since core/Piwik is not loaded //Piwik::log(sprintf("Unable to load plugin '%s' because '%s' couldn't be found.", $pluginName, $path)); throw new Exception(sprintf("Unable to load plugin '%s' because '%s' couldn't be found.", $pluginName, $path)); } // Don't remove this. // Our autoloader can't find plugins/PluginName/PluginName.php require_once $path; // prefixed by PIWIK_INCLUDE_PATH if (!class_exists($pluginClassName, false)) { throw new Exception("The class $pluginClassName couldn't be found in the file '$path'"); } $newPlugin = new $pluginClassName(); if (!($newPlugin instanceof Piwik_Plugin)) { throw new Exception("The plugin $pluginClassName in the file $path must inherit from Piwik_Plugin."); } $this->addLoadedPlugin($pluginName, $newPlugin); return $newPlugin; } /** * Unload plugin * * @param Piwik_Plugin $plugin * @throws Exception */ public function unloadPlugin($plugin) { if (!($plugin instanceof Piwik_Plugin)) { $oPlugin = $this->loadPlugin($plugin); if ($oPlugin === null) { unset($this->loadedPlugins[$plugin]); return; } $plugin = $oPlugin; } $hooks = $plugin->getListHooksRegistered(); foreach ($hooks as $hookName => $methodToCall) { $success = $this->dispatcher->removeObserver(array($plugin, $methodToCall), $hookName); if ($success !== true) { throw new Exception("Error unloading plugin = " . $plugin->getPluginName() . ", method = $methodToCall, hook = $hookName "); } } unset($this->loadedPlugins[$plugin->getPluginName()]); } /** * Unload all loaded plugins */ public function unloadPlugins() { $pluginsLoaded = $this->getLoadedPlugins(); foreach ($pluginsLoaded as $plugin) { $this->unloadPlugin($plugin); } } /** * Install loaded plugins */ private function installPlugins() { foreach ($this->getLoadedPlugins() as $plugin) { $this->installPlugin($plugin); } } /** * Install a specific plugin * * @param Piwik_Plugin $plugin * @throws Piwik_PluginsManager_PluginException if installation fails */ private function installPlugin(Piwik_Plugin $plugin) { try { $plugin->install(); } catch (Exception $e) { throw new Piwik_PluginsManager_PluginException($plugin->getPluginName(), $e->getMessage()); } } /** * For the given plugin, add all the observers of this plugin. * * @param Piwik_Plugin $plugin */ private function addPluginObservers(Piwik_Plugin $plugin) { $hooks = $plugin->getListHooksRegistered(); foreach ($hooks as $hookName => $methodToCall) { $this->dispatcher->addObserver(array($plugin, $methodToCall), $hookName); } } /** * Add a plugin in the loaded plugins array * * @param string $pluginName plugin name without prefix (eg. 'UserCountry') * @param Piwik_Plugin $newPlugin */ private function addLoadedPlugin($pluginName, Piwik_Plugin $newPlugin) { $this->loadedPlugins[$pluginName] = $newPlugin; } /** * Load translation * * @param Piwik_Plugin $plugin * @param string $langCode * @throws Exception * @return void */ private function loadTranslation($plugin, $langCode) { // we are in Tracker mode if Piwik_Loader is not (yet) loaded if (!class_exists('Piwik_Loader', false)) { return; } $infos = $plugin->getInformation(); if (!isset($infos['translationAvailable'])) { $infos['translationAvailable'] = false; } $translationAvailable = $infos['translationAvailable']; if (!$translationAvailable) { return; } $pluginName = $plugin->getPluginName(); $path = PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName . '/lang/%s.php'; $defaultLangPath = sprintf($path, $langCode); $defaultEnglishLangPath = sprintf($path, 'en'); $translations = array(); if (file_exists($defaultLangPath)) { require $defaultLangPath; } elseif (file_exists($defaultEnglishLangPath)) { require $defaultEnglishLangPath; } else { throw new Exception("Language file not found for the plugin '$pluginName'."); } Piwik_Translate::getInstance()->mergeTranslationArray($translations); } /** * Return names of installed plugins * * @return array */ public function getInstalledPluginsName() { $pluginNames = Piwik_Config::getInstance()->PluginsInstalled['PluginsInstalled']; return $pluginNames; } /** * Returns names of plugins that should be loaded, but cannot be since their * files cannot be found. * * @return array */ public function getMissingPlugins() { $missingPlugins = array(); if (isset(Piwik_Config::getInstance()->Plugins['Plugins'])) { $plugins = Piwik_Config::getInstance()->Plugins['Plugins']; foreach ($plugins as $pluginName) { // if a plugin is listed in the config, but is not loaded, it does not exist in the folder if (!Piwik_PluginsManager::getInstance()->isPluginLoaded($pluginName)) { $missingPlugins[] = $pluginName; } } } return $missingPlugins; } /** * Install a plugin, if necessary * * @param Piwik_Plugin $plugin */ private function installPluginIfNecessary(Piwik_Plugin $plugin) { $pluginName = $plugin->getPluginName(); $saveConfig = false; // is the plugin already installed or is it the first time we activate it? $pluginsInstalled = $this->getInstalledPluginsName(); if (!in_array($pluginName, $pluginsInstalled)) { $this->installPlugin($plugin); $pluginsInstalled[] = $pluginName; $this->updatePluginsInstalledConfig($pluginsInstalled); $saveConfig = true; } $information = $plugin->getInformation(); // if the plugin is to be loaded during the statistics logging if (isset($information['TrackerPlugin']) && $information['TrackerPlugin'] === true ) { $pluginsTracker = Piwik_Config::getInstance()->Plugins_Tracker['Plugins_Tracker']; if (is_null($pluginsTracker)) { $pluginsTracker = array(); } if (!in_array($pluginName, $pluginsTracker)) { $pluginsTracker[] = $pluginName; $this->updatePluginsTrackerConfig($pluginsTracker); $saveConfig = true; } } if ($saveConfig) { Piwik_Config::getInstance()->forceSave(); } } } /** * @package Piwik * @subpackage Piwik_PluginsManager */ class Piwik_PluginsManager_PluginException extends Exception { function __construct($pluginName, $message) { parent::__construct("There was a problem installing the plugin " . $pluginName . ": " . $message . " If this plugin has already been installed, and if you want to hide this message, you must add the following line under the [PluginsInstalled] entry in your config/config.ini.php file: PluginsInstalled[] = $pluginName"); } } /** * Post an event to the dispatcher which will notice the observers * * @param string $eventName The event name * @param mixed $object Object, array or string that the listeners can read and/or modify. * Listeners can call $object =& $notification->getNotificationObject(); to fetch and then modify this variable. * @param array $info Additional array of data that can be used by the listeners, but not edited * @param bool $pending Should the notification be posted to plugins that register after the notification was sent? * @return void */ function Piwik_PostEvent($eventName, &$object = null, $info = array(), $pending = false) { $notification = new Piwik_Event_Notification($object, $eventName, $info); Piwik_PluginsManager::getInstance()->dispatcher->postNotification($notification, $pending, $bubble = false); } /** * Register an action to execute for a given event * * @param string $hookName Name of event * @param function $function Callback hook */ function Piwik_AddAction($hookName, $function) { Piwik_PluginsManager::getInstance()->dispatcher->addObserver($function, $hookName); } /** * Event notification * * @package Piwik * * @see Event_Notification, libs/Event/Notification.php * @link http://pear.php.net/package/Event_Dispatcher/docs/latest/Event_Dispatcher/Event_Notification.html */ class Piwik_Event_Notification extends Event_Notification { static $showProfiler = false; /** * Use notification counter to profile runtime execution * time and memory usage. */ function increaseNotificationCount( /* array($className|object, $method) */) { parent::increaseNotificationCount(); if (self::$showProfiler && func_num_args() == 1) { $callback = func_get_arg(0); if (is_array($callback)) { $className = is_object($callback[0]) ? get_class($callback[0]) : $callback[0]; $method = $callback[1]; echo "after $className -> $method
"; echo "-"; Piwik::printTimer(); echo "
"; echo "-"; Piwik::printMemoryLeak(); echo "
"; } } } }