Download core/Plugin/Manager.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 58.4 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/Plugin/Manager.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/core/Plugin/Manager.php
-
curl -L -o Manager.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/Plugin/Manager.php
58.4 kB
| /** | |
| * Matomo - free/libre analytics platform | |
| * | |
| * @link https://matomo.org | |
| * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later | |
| */ | |
| namespace Piwik\Plugin; | |
| use Piwik\Application\Kernel\PluginList; | |
| use Piwik\Cache; | |
| use Piwik\Columns\Dimension; | |
| use Piwik\Common; | |
| use Piwik\Config; | |
| use Piwik\Config as PiwikConfig; | |
| use Piwik\Container\StaticContainer; | |
| use Piwik\Development; | |
| use Piwik\EventDispatcher; | |
| use Piwik\Exception\PluginDeactivatedException; | |
| use Piwik\Exception\PluginNotFoundException; | |
| use Piwik\Filesystem; | |
| use Piwik\Log; | |
| use Piwik\Notification; | |
| use Piwik\Option; | |
| use Piwik\Piwik; | |
| use Piwik\Plugin; | |
| use Piwik\Plugin\Dimension\ActionDimension; | |
| use Piwik\Plugin\Dimension\ConversionDimension; | |
| use Piwik\Plugin\Dimension\VisitDimension; | |
| use Piwik\Settings\Storage as SettingsStorage; | |
| use Piwik\SettingsPiwik; | |
| use Piwik\SettingsServer; | |
| use Piwik\Theme; | |
| use Piwik\Translation\Translator; | |
| use Piwik\Updater; | |
| /** | |
| * The singleton that manages plugin loading/unloading and installation/uninstallation. | |
| */ | |
| class Manager | |
| { | |
| public const LAST_PLUGIN_ACTIVATION_TIME_OPTION_PREFIX = 'LastPluginActivation.'; | |
| public const LAST_PLUGIN_DEACTIVATION_TIME_OPTION_PREFIX = 'LastPluginDeactivation.'; | |
| /** | |
| * @return self | |
| */ | |
| public static function getInstance() | |
| { | |
| return StaticContainer::get('Piwik\Plugin\Manager'); | |
| } | |
| /** | |
| * @var list<string> | |
| */ | |
| protected $pluginsToLoad = []; | |
| /** | |
| * @var bool | |
| */ | |
| protected $doLoadPlugins = true; | |
| /** | |
| * @var array<string, string> | |
| */ | |
| protected static $pluginsToPathCache = []; | |
| /** | |
| * @var array<string, string> | |
| */ | |
| protected static $pluginsToWebRootDirCache = []; | |
| /** | |
| * @var Plugin[]|null | |
| */ | |
| private $pluginsLoadedAndActivated; | |
| /** | |
| * @var Plugin[] | |
| */ | |
| protected $loadedPlugins = []; | |
| /** | |
| * Default theme used in Matomo. | |
| */ | |
| public const DEFAULT_THEME = "Morpheus"; | |
| /** | |
| * @var bool | |
| */ | |
| protected $doLoadAlwaysActivatedPlugins = true; | |
| /** | |
| * These are always activated and cannot be deactivated | |
| * @var string[] | |
| */ | |
| protected static $pluginToAlwaysActivate = array( | |
| 'FeatureFlags', | |
| 'BulkTracking', | |
| 'CoreVue', | |
| 'CoreHome', | |
| 'CoreUpdater', | |
| 'CoreAdminHome', | |
| 'CoreConsole', | |
| 'CorePluginsAdmin', | |
| 'CoreVisualizations', | |
| 'Installation', | |
| 'SitesManager', | |
| 'UsersManager', | |
| 'Intl', | |
| 'API', | |
| 'Proxy', | |
| 'LanguagesManager', | |
| 'WebsiteMeasurable', | |
| // default Piwik theme, always enabled | |
| self::DEFAULT_THEME, | |
| ); | |
| /** | |
| * @var list<string> | |
| */ | |
| private $trackerPluginsNotToLoad = []; | |
| /** | |
| * @var PluginList | |
| */ | |
| private $pluginList; | |
| public function __construct(PluginList $pluginList) | |
| { | |
| $this->pluginList = $pluginList; | |
| } | |
| /** | |
| * Loads plugin that are enabled | |
| * @return void | |
| */ | |
| public function loadActivatedPlugins() | |
| { | |
| $pluginsToLoad = $this->getActivatedPluginsFromConfig(); | |
| if (!SettingsPiwik::isInternetEnabled()) { | |
| $pluginsToLoad = array_filter($pluginsToLoad, function ($name) { | |
| $plugin = Manager::makePluginClass($name); | |
| return !$plugin->requiresInternetConnection(); | |
| }); | |
| } | |
| $this->loadPlugins($pluginsToLoad); | |
| } | |
| /** | |
| * Called during Tracker | |
| * @return void | |
| */ | |
| public function loadCorePluginsDuringTracker() | |
| { | |
| $pluginsToLoad = $this->pluginList->getActivatedPlugins(); | |
| $pluginsToLoad = array_diff($pluginsToLoad, $this->getTrackerPluginsNotToLoad()); | |
| $this->loadPlugins($pluginsToLoad); | |
| } | |
| /** | |
| * @return list<string> names of plugins that have been loaded | |
| */ | |
| public function loadTrackerPlugins() | |
| { | |
| $cacheId = 'PluginsTracker'; | |
| $cache = Cache::getEagerCache(); | |
| if ($cache->contains($cacheId)) { | |
| /** @var list<string> $pluginsTracker */ | |
| $pluginsTracker = $cache->fetch($cacheId); | |
| } else { | |
| $this->unloadPlugins(); | |
| $this->loadActivatedPlugins(); | |
| $pluginsTracker = []; | |
| foreach ($this->loadedPlugins as $pluginName => $plugin) { | |
| if ($this->isTrackerPlugin($plugin)) { | |
| $pluginsTracker[] = $pluginName; | |
| } | |
| } | |
| if (!empty($pluginsTracker)) { | |
| $cache->save($cacheId, $pluginsTracker); | |
| } | |
| } | |
| if (empty($pluginsTracker)) { | |
| $this->unloadPlugins(); | |
| return []; | |
| } | |
| $pluginsTracker = array_diff($pluginsTracker, $this->getTrackerPluginsNotToLoad()); | |
| $this->doNotLoadAlwaysActivatedPlugins(); | |
| $this->loadPlugins($pluginsTracker); | |
| // we could simply unload all plugins first before loading plugins but this way it is much faster | |
| // since we won't have to create each plugin again and we won't have to parse each plugin metadata file | |
| // again etc | |
| $this->makeSureOnlyActivatedPluginsAreLoaded(); | |
| return $pluginsTracker; | |
| } | |
| /** | |
| * Do not load the specified plugins (used during testing, to disable Provider plugin) | |
| * @param list<string> $plugins | |
| * @return void | |
| */ | |
| public function setTrackerPluginsNotToLoad($plugins) | |
| { | |
| $this->trackerPluginsNotToLoad = $plugins; | |
| } | |
| /** | |
| * Get list of plugins to not load | |
| * | |
| * @return list<string> | |
| */ | |
| public function getTrackerPluginsNotToLoad() | |
| { | |
| return $this->trackerPluginsNotToLoad; | |
| } | |
| // If a plugin hooks onto at least an event starting with "Tracker.", we load the plugin during tracker | |
| public const TRACKER_EVENT_PREFIX = 'Tracker.'; | |
| /** | |
| * @param string $pluginName | |
| * @return bool | |
| */ | |
| public function isPluginOfficialAndNotBundledWithCore($pluginName) | |
| { | |
| static $gitModules; | |
| if (empty($gitModules)) { | |
| $gitModules = file_get_contents(PIWIK_INCLUDE_PATH . '/.gitmodules'); | |
| } | |
| // All submodules are officially maintained plugins | |
| return false !== strpos($gitModules, "plugins/" . $pluginName . "\n"); | |
| } | |
| /** | |
| * Update Plugins config | |
| * | |
| * @param string[] $pluginsToLoad Plugins | |
| */ | |
| private function updatePluginsConfig($pluginsToLoad): void | |
| { | |
| $pluginsToLoad = $this->pluginList->sortPluginsAndRespectDependencies($pluginsToLoad); | |
| $section = PiwikConfig::getInstance()->Plugins; | |
| $section['Plugins'] = $pluginsToLoad; | |
| PiwikConfig::getInstance()->Plugins = $section; | |
| } | |
| /** | |
| * Update PluginsInstalled config | |
| * | |
| * @param string[] $plugins Plugins | |
| */ | |
| private function updatePluginsInstalledConfig($plugins) | |
| { | |
| $section = PiwikConfig::getInstance()->PluginsInstalled; | |
| $section['PluginsInstalled'] = $plugins; | |
| PiwikConfig::getInstance()->PluginsInstalled = $section; | |
| } | |
| /** | |
| * @return void | |
| */ | |
| public function clearPluginsInstalledConfig() | |
| { | |
| $this->updatePluginsInstalledConfig([]); | |
| PiwikConfig::getInstance()->forceSave(); | |
| } | |
| /** | |
| * Returns true if plugin is always activated | |
| * | |
| * @param string $name Name of plugin | |
| * @return bool | |
| */ | |
| private function isPluginAlwaysActivated($name) | |
| { | |
| return in_array($name, self::$pluginToAlwaysActivate); | |
| } | |
| /** | |
| * Returns true if the plugin can be uninstalled. Any non-core plugin can be uninstalled. | |
| * | |
| * @param string $name | |
| * @return bool | |
| */ | |
| private function isPluginUninstallable($name) | |
| { | |
| return !$this->isPluginBundledWithCore($name); | |
| } | |
| /** | |
| * Returns `true` if a plugin has been activated. | |
| * | |
| * @param string $name Name of plugin, eg, `'Actions'`. | |
| * @return bool | |
| * @api | |
| */ | |
| public function isPluginActivated($name) | |
| { | |
| return in_array($name, $this->pluginsToLoad) | |
| || ($this->doLoadAlwaysActivatedPlugins && $this->isPluginAlwaysActivated($name)); | |
| } | |
| /** | |
| * Returns `true` if a plugin requires an working internet connection | |
| * | |
| * @param string $name Name of plugin, eg, `'Actions'`. | |
| * @return bool | |
| */ | |
| public function doesPluginRequireInternetConnection($name) | |
| { | |
| $plugin = $this->makePluginClass($name); | |
| return $plugin->requiresInternetConnection(); | |
| } | |
| /** | |
| * Checks whether the given plugin is activated, if not triggers an exception. | |
| * | |
| * @param string $pluginName | |
| * @throws PluginDeactivatedException|PluginNotFoundException | |
| */ | |
| public function checkIsPluginActivated($pluginName): void | |
| { | |
| if (!$this->isPluginInFilesystem($pluginName)) { | |
| throw new PluginNotFoundException($pluginName); | |
| } | |
| if (!$this->isPluginActivated($pluginName)) { | |
| throw new PluginDeactivatedException($pluginName); | |
| } | |
| } | |
| /** | |
| * Returns `true` if plugin is loaded (in memory). | |
| * | |
| * @param string $name Name of plugin, eg, `'Actions'`. | |
| * @return bool | |
| * @api | |
| */ | |
| public function isPluginLoaded($name) | |
| { | |
| return isset($this->loadedPlugins[$name]); | |
| } | |
| /** | |
| * Reads the directories inside the plugins/ directory and returns their names in an array | |
| * | |
| * @return list<string> | |
| */ | |
| public function readPluginsDirectory() | |
| { | |
| $result = array(); | |
| foreach (self::getPluginsDirectories() as $pluginsDir) { | |
| $pluginsName = _glob($pluginsDir . '*', GLOB_ONLYDIR); | |
| if ($pluginsName != false) { | |
| foreach ($pluginsName as $path) { | |
| if (self::pluginStructureLooksValid($path)) { | |
| $result[] = basename($path); | |
| } | |
| } | |
| } | |
| } | |
| sort($result); | |
| return $result; | |
| } | |
| /** | |
| * @return void | |
| */ | |
| public static function initPluginDirectories() | |
| { | |
| $envDirs = getenv('MATOMO_PLUGIN_DIRS'); | |
| if (!empty($envDirs)) { | |
| // we expect it in the format `absoluteStorageDir1;webrootPathRelative1:absoluteStorageDir2;webrootPathRelative1` | |
| if (empty($GLOBALS['MATOMO_PLUGIN_DIRS'])) { | |
| $GLOBALS['MATOMO_PLUGIN_DIRS'] = array(); | |
| } | |
| $envDirs = explode(':', $envDirs); | |
| foreach ($envDirs as $envDir) { | |
| $envDir = explode(';', $envDir); | |
| $absoluteDir = rtrim($envDir[0], '/') . '/'; | |
| $GLOBALS['MATOMO_PLUGIN_DIRS'][] = array( | |
| 'pluginsPathAbsolute' => $absoluteDir, | |
| 'webrootDirRelativeToMatomo' => isset($envDir[1]) ? $envDir[1] : null, | |
| ); | |
| } | |
| } | |
| if (!empty($GLOBALS['MATOMO_PLUGIN_DIRS'])) { | |
| foreach ($GLOBALS['MATOMO_PLUGIN_DIRS'] as $pluginDir => &$settings) { | |
| if (!isset($settings['pluginsPathAbsolute'])) { | |
| throw new \Exception('Missing "pluginsPathAbsolute" configuration for plugin dir'); | |
| } | |
| if (!isset($settings['webrootDirRelativeToMatomo'])) { | |
| throw new \Exception('Missing "webrootDirRelativeToMatomo" configuration for plugin dir'); | |
| } | |
| } | |
| $pluginDirs = self::getPluginsDirectories(); | |
| if (count($pluginDirs) > 1) { | |
| self::registerPluginDirAutoload($pluginDirs); | |
| } | |
| } | |
| $envCopyDir = getenv('MATOMO_PLUGIN_COPY_DIR'); | |
| if (!empty($envCopyDir)) { | |
| $GLOBALS['MATOMO_PLUGIN_COPY_DIR'] = $envCopyDir; | |
| } | |
| if ( | |
| !empty($GLOBALS['MATOMO_PLUGIN_COPY_DIR']) | |
| && !in_array($GLOBALS['MATOMO_PLUGIN_COPY_DIR'], self::getPluginsDirectories()) | |
| ) { | |
| throw new \Exception('"MATOMO_PLUGIN_COPY_DIR" dir must be one of "MATOMO_PLUGIN_DIRS" directories'); | |
| } | |
| } | |
| /** | |
| * Registers a new autoloader to support the loading of Matomo plugin classes when the plugins are installed | |
| * outside the Matomo plugins folder. | |
| * @param string[] $pluginDirs | |
| * @return void | |
| */ | |
| public static function registerPluginDirAutoload($pluginDirs) | |
| { | |
| spl_autoload_register(function ($className) use ($pluginDirs) { | |
| if (strpos($className, 'Piwik\Plugins\\') === 0) { | |
| $withoutPrefix = str_replace('Piwik\Plugins\\', '', $className); | |
| $path = str_replace('\\', DIRECTORY_SEPARATOR, $withoutPrefix) . '.php'; | |
| foreach ($pluginDirs as $pluginsDirectory) { | |
| if (file_exists($pluginsDirectory . $path)) { | |
| require_once $pluginsDirectory . $path; | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| /** | |
| * @return array<string, string> | |
| */ | |
| public static function getAlternativeWebRootDirectories() | |
| { | |
| $dirs = []; | |
| if (!empty($GLOBALS['MATOMO_PLUGIN_DIRS'])) { | |
| foreach ($GLOBALS['MATOMO_PLUGIN_DIRS'] as $pluginDir) { | |
| $absolute = rtrim($pluginDir['pluginsPathAbsolute'], '/') . '/'; | |
| $relative = rtrim($pluginDir['webrootDirRelativeToMatomo'], '/') . '/'; | |
| $dirs[$absolute] = $relative; | |
| } | |
| } | |
| return $dirs; | |
| } | |
| /** | |
| * @return string[] | |
| */ | |
| public function getWebRootDirectoriesForCustomPluginDirs() | |
| { | |
| return array_intersect_key(self::$pluginsToWebRootDirCache, array_flip($this->pluginsToLoad)); | |
| } | |
| /** | |
| * @return list<string> | |
| */ | |
| public function getPluginUmdsToLoadOnDemand() | |
| { | |
| $pluginsToLoadOnDemand = []; | |
| foreach ($this->getPluginsLoadedAndActivated() as $plugin) { | |
| if (method_exists($plugin, 'shouldLoadUmdOnDemand') && $plugin->shouldLoadUmdOnDemand()) { | |
| $pluginsToLoadOnDemand[] = $plugin->getPluginName(); | |
| } | |
| } | |
| return $pluginsToLoadOnDemand; | |
| } | |
| /** | |
| * Returns the path to all plugins directories. Each plugins directory may contain several plugins. | |
| * All paths have a trailing slash '/'. | |
| * @return string[] | |
| * @api | |
| */ | |
| public static function getPluginsDirectories() | |
| { | |
| $dirs = array(self::getPluginsDirectory()); | |
| if (!empty($GLOBALS['MATOMO_PLUGIN_DIRS'])) { | |
| $extraDirs = array_map(function ($dir) { | |
| return rtrim($dir['pluginsPathAbsolute'], '/') . '/'; | |
| }, $GLOBALS['MATOMO_PLUGIN_DIRS']); | |
| $dirs = array_merge($dirs, $extraDirs); | |
| } | |
| return $dirs; | |
| } | |
| private static function getPluginRealPath(string $path): string | |
| { | |
| if (strpos($path, '../') !== false) { | |
| // for tests, only do it when needed re performance etc | |
| $real = realpath($path); | |
| if ($real && Common::stringEndsWith($path, '/')) { | |
| return rtrim($real, '/') . '/'; | |
| } | |
| if ($real) { | |
| return $real; | |
| } | |
| } | |
| return $path; | |
| } | |
| /** | |
| * Gets the path to a specific plugin. If the plugin does not exist in any plugins folder, the default plugins | |
| * folder will be assumed. | |
| * | |
| * @param string $pluginName | |
| * @return string | |
| * @api | |
| */ | |
| public static function getPluginDirectory($pluginName) | |
| { | |
| if (isset(self::$pluginsToPathCache[$pluginName])) { | |
| return self::$pluginsToPathCache[$pluginName]; | |
| } | |
| $corePluginsDir = PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName; | |
| if (is_dir($corePluginsDir)) { | |
| // for faster performance | |
| self::$pluginsToPathCache[$pluginName] = self::getPluginRealPath($corePluginsDir); | |
| return self::$pluginsToPathCache[$pluginName]; | |
| } | |
| foreach (self::getAlternativeWebRootDirectories() as $dir => $relative) { | |
| $path = $dir . $pluginName; | |
| if (is_dir($path)) { | |
| self::$pluginsToPathCache[$pluginName] = self::getPluginRealPath($path); | |
| self::$pluginsToWebRootDirCache[$pluginName] = rtrim($relative, '/'); | |
| return $path; | |
| } | |
| } | |
| // assume default directory when plugin does not exist just yet | |
| return self::getPluginsDirectory() . $pluginName; | |
| } | |
| /** | |
| * Returns the plugin directory path relative to Matomo's root directory. | |
| */ | |
| public static function getRelativePluginDirectory(string $pluginName): string | |
| { | |
| $result = self::getPluginDirectory($pluginName); | |
| $matomoPath = rtrim(PIWIK_INCLUDE_PATH, '/') . '/'; | |
| $webroots = array_merge( | |
| Manager::getAlternativeWebRootDirectories(), | |
| [$matomoPath => '/'] | |
| ); | |
| foreach ($webroots as $webrootAbsolute => $webrootRelative) { | |
| if (strpos($result, $webrootAbsolute) === 0) { | |
| $result = str_replace($webrootAbsolute, $webrootRelative, $result); | |
| break; | |
| } | |
| } | |
| $result = ltrim($result, '/'); | |
| return $result; | |
| } | |
| /** | |
| * Returns the path to the directory where core plugins are located. Please note since Matomo 3.9 | |
| * plugins may also be located in other directories and therefore this method has been deprecated. | |
| * @internal since Matomo 3.9.0 use {@link (getPluginsDirectories())} or {@link getPluginDirectory($pluginName)} instead | |
| * @return string | |
| */ | |
| public static function getPluginsDirectory() | |
| { | |
| $path = rtrim(PIWIK_INCLUDE_PATH, '/') . '/plugins/'; | |
| $path = self::getPluginRealPath($path); | |
| return $path; | |
| } | |
| /** | |
| * Deactivate plugin | |
| * | |
| * @param string $pluginName Name of plugin | |
| * @return void | |
| */ | |
| public function deactivatePlugin($pluginName) | |
| { | |
| $plugins = $this->pluginList->getActivatedPlugins(); | |
| if (!in_array($pluginName, $plugins)) { | |
| // plugin is already deactivated | |
| return; | |
| } | |
| $this->clearCache($pluginName); | |
| // execute deactivate() to let the plugin do cleanups | |
| $this->executePluginDeactivate($pluginName); | |
| $this->savePluginTime(self::LAST_PLUGIN_DEACTIVATION_TIME_OPTION_PREFIX, $pluginName); | |
| $this->unloadPluginFromMemory($pluginName); | |
| $this->removePluginFromConfig($pluginName); | |
| /** | |
| * Event triggered after a plugin has been deactivated. | |
| * | |
| * @param string $pluginName The plugin that has been deactivated. | |
| */ | |
| Piwik::postEvent('PluginManager.pluginDeactivated', array($pluginName)); | |
| } | |
| /** | |
| * Tries to find the given components such as a Menu or Tasks implemented by plugins. | |
| * This method won't cache the found components. If you need to find the same component multiple times you might | |
| * want to cache the result to save a tiny bit of time. | |
| * | |
| * @param string $componentName The name of the component you want to look for. In case you request a | |
| * component named 'Menu' it'll look for a file named 'Menu.php' within the | |
| * root of all plugin folders that implement a class named | |
| * Piwik\Plugins\$PluginName\Menu. | |
| * @param string $expectedSubclass If not empty, a check will be performed whether a found file extends the | |
| * given subclass. If the requested file exists but does not extend this class | |
| * a warning will be shown to advice a developer to extend this certain class. | |
| * | |
| * @template T of object | |
| * @phpstan-param class-string<T>|''|false|null $expectedSubclass | |
| * | |
| * @return array<class-string<T>> | |
| */ | |
| public function findComponents($componentName, $expectedSubclass) | |
| { | |
| $plugins = $this->getPluginsLoadedAndActivated(); | |
| $components = array(); | |
| foreach ($plugins as $plugin) { | |
| $component = $plugin->findComponent($componentName, $expectedSubclass); | |
| if (!empty($component)) { | |
| $components[] = $component; | |
| } | |
| } | |
| return $components; | |
| } | |
| /** | |
| * @template T of object | |
| * @param string $directoryWithinPlugin | |
| * @param class-string<T>|''|false|null $expectedSubclass | |
| * @return array<class-string<T>> | |
| */ | |
| public function findMultipleComponents($directoryWithinPlugin, $expectedSubclass) | |
| { | |
| $plugins = $this->getPluginsLoadedAndActivated(); | |
| $found = array(); | |
| foreach ($plugins as $plugin) { | |
| $components = $plugin->findMultipleComponents($directoryWithinPlugin, $expectedSubclass); | |
| if (!empty($components)) { | |
| $found = array_merge($found, $components); | |
| } | |
| } | |
| return $found; | |
| } | |
| /** | |
| * Uninstalls a Plugin (deletes plugin files from the disk) | |
| * Only deactivated plugins can be uninstalled | |
| * | |
| * @param string $pluginName | |
| * @return bool | |
| */ | |
| public function uninstallPlugin($pluginName) | |
| { | |
| if ($this->isPluginLoaded($pluginName)) { | |
| throw new \Exception("To uninstall the plugin $pluginName, first disable it in Matomo > Settings > Plugins"); | |
| } | |
| $this->loadAllPluginsAndGetTheirInfo(); | |
| SettingsStorage\Backend\PluginSettingsTable::removeAllSettingsForPlugin($pluginName); | |
| SettingsStorage\Backend\MeasurableSettingsTable::removeAllSettingsForPlugin($pluginName); | |
| $this->executePluginDeactivate($pluginName); | |
| $this->executePluginUninstall($pluginName); | |
| $this->removePluginFromPluginsInstalledConfig($pluginName); | |
| $this->unloadPluginFromMemory($pluginName); | |
| $this->removePluginFromConfig($pluginName); | |
| $this->removeInstalledVersionFromOptionTable($pluginName); | |
| $this->clearCache($pluginName); | |
| self::deletePluginFromFilesystem($pluginName); | |
| if ($this->isPluginInFilesystem($pluginName)) { | |
| return false; | |
| } | |
| /** | |
| * Event triggered after a plugin has been uninstalled. | |
| * | |
| * @param string $pluginName The plugin that has been uninstalled. | |
| */ | |
| Piwik::postEvent('PluginManager.pluginUninstalled', array($pluginName)); | |
| return true; | |
| } | |
| private function clearCache(string $pluginName): void | |
| { | |
| $this->resetTransientCache(); | |
| Filesystem::deleteAllCacheOnUpdate($pluginName); | |
| } | |
| /** | |
| * @param string $plugin | |
| * @return void | |
| */ | |
| public static function deletePluginFromFilesystem($plugin) | |
| { | |
| $pluginDir = self::getPluginDirectory($plugin); | |
| if (strpos($pluginDir, PIWIK_INCLUDE_PATH) === 0) { | |
| // only delete files for plugins within matomo directory... | |
| Filesystem::unlinkRecursive($pluginDir, $deleteRootToo = true); | |
| } | |
| } | |
| /** | |
| * Install loaded plugins | |
| */ | |
| public function installLoadedPlugins(): void | |
| { | |
| Log::debug("Loaded plugins: " . implode(", ", array_keys($this->getLoadedPlugins()))); | |
| $pluginsActivated = $this->pluginList->getActivatedPlugins(); | |
| foreach ($this->getLoadedPlugins() as $plugin) { | |
| $this->installPluginIfNecessary($plugin); | |
| $pluginName = $plugin->getPluginName(); | |
| // if a new plugin was added that is always activated, ensure activate is called once nevertheless | |
| // otherwise the `activate` method of the plugin might never get called. | |
| if ($this->isPluginAlwaysActivated($pluginName) && !in_array($pluginName, $pluginsActivated)) { | |
| $this->activatePlugin($pluginName); | |
| } | |
| } | |
| } | |
| /** | |
| * Activate the specified plugin and install (if needed) | |
| * | |
| * @param string $pluginName Name of plugin | |
| * @return void | |
| */ | |
| public function activatePlugin($pluginName) | |
| { | |
| $plugins = $this->pluginList->getActivatedPlugins(); | |
| if (in_array($pluginName, $plugins)) { | |
| // plugin is already activated | |
| return; | |
| } | |
| if (!$this->isPluginInFilesystem($pluginName)) { | |
| throw new PluginNotFoundException($pluginName); | |
| } | |
| $this->deactivateThemeIfTheme($pluginName); | |
| // Load plugin | |
| $plugin = $this->loadPlugin($pluginName); | |
| if ($plugin === null) { | |
| throw new \Exception("The plugin '$pluginName' was found in the filesystem, but could not be loaded.'"); | |
| } | |
| $this->installPluginIfNecessary($plugin); | |
| $plugin->activate(); | |
| $this->savePluginTime(self::LAST_PLUGIN_ACTIVATION_TIME_OPTION_PREFIX, $pluginName); | |
| EventDispatcher::getInstance()->postPendingEventsTo($plugin); | |
| $this->pluginsToLoad[] = $pluginName; | |
| $this->updatePluginsConfig($this->pluginsToLoad); | |
| PiwikConfig::getInstance()->forceSave(); | |
| $this->clearCache($pluginName); | |
| /** | |
| * Event triggered after a plugin has been activated. | |
| * | |
| * @param string $pluginName The plugin that has been activated. | |
| */ | |
| Piwik::postEvent('PluginManager.pluginActivated', array($pluginName)); | |
| } | |
| /** | |
| * @param string $pluginName | |
| * @return bool | |
| */ | |
| public function isPluginInFilesystem($pluginName) | |
| { | |
| $existingPlugins = $this->readPluginsDirectory(); | |
| $isPluginInFilesystem = array_search($pluginName, $existingPlugins) !== false; | |
| return $this->isValidPluginName($pluginName) | |
| && $isPluginInFilesystem; | |
| } | |
| /** | |
| * Returns the currently enabled theme. | |
| * | |
| * If no theme is enabled, the **Morpheus** plugin is returned (this is the base and default theme). | |
| * | |
| * @api | |
| */ | |
| public function getThemeEnabled(): ?Plugin | |
| { | |
| $plugins = $this->getLoadedPlugins(); | |
| $theme = null; | |
| foreach ($plugins as $plugin) { | |
| /* @var $plugin Plugin */ | |
| if ( | |
| $plugin->isTheme() | |
| && $this->isPluginActivated($plugin->getPluginName()) | |
| ) { | |
| if ($plugin->getPluginName() != self::DEFAULT_THEME) { | |
| return $plugin; // enabled theme (not default) | |
| } | |
| $theme = $plugin; // default theme | |
| } | |
| } | |
| return $theme; | |
| } | |
| /** | |
| * @param string $themeName | |
| * @throws \Exception | |
| * @return Theme | |
| */ | |
| public function getTheme($themeName) | |
| { | |
| $plugins = $this->getLoadedPlugins(); | |
| foreach ($plugins as $plugin) { | |
| if ($plugin->isTheme() && $plugin->getPluginName() == $themeName) { | |
| return new Theme($plugin); | |
| } | |
| } | |
| throw new \Exception('Theme not found : ' . $themeName); | |
| } | |
| /** | |
| * @return int | |
| */ | |
| public function getNumberOfActivatedPluginsExcludingAlwaysActivated() | |
| { | |
| $counter = 0; | |
| $pluginNames = $this->getLoadedPluginsName(); | |
| foreach ($pluginNames as $pluginName) { | |
| if ( | |
| $this->isPluginActivated($pluginName) | |
| && !$this->isPluginAlwaysActivated($pluginName) | |
| ) { | |
| $counter++; | |
| } | |
| } | |
| return $counter; | |
| } | |
| /** | |
| * Returns info regarding all plugins. Loads plugins that can be loaded. | |
| * | |
| * @return array An array that maps plugin names with arrays of plugin information. Plugin | |
| * information consists of the following entries: | |
| * | |
| * - **activated**: Whether the plugin is activated. | |
| * - **alwaysActivated**: Whether the plugin should always be activated, | |
| * or not. | |
| * - **uninstallable**: Whether the plugin is uninstallable or not. | |
| * - **invalid**: If the plugin is invalid, this property will be set to true. | |
| * If the plugin is not invalid, this property will not exist. | |
| * - **info**: If the plugin was loaded, will hold the plugin information. | |
| * See {@link Piwik\Plugin::getInformation()}. | |
| * @phpstan-return array<string, array{info?: array<string, mixed>, activated: bool, alwaysActivated: bool, uninstallable: bool, invalid?: bool, missingRequirements?: string}> | |
| * @api | |
| */ | |
| public function loadAllPluginsAndGetTheirInfo() | |
| { | |
| /** @var Translator $translator */ | |
| $translator = StaticContainer::get('Piwik\Translation\Translator'); | |
| $plugins = array(); | |
| $listPlugins = array_merge( | |
| $this->readPluginsDirectory(), | |
| $this->pluginList->getActivatedPlugins() | |
| ); | |
| $listPlugins = array_unique($listPlugins); | |
| $internetFeaturesEnabled = SettingsPiwik::isInternetEnabled(); | |
| foreach ($listPlugins as $pluginName) { | |
| // Hide plugins that are never going to be used | |
| if ($this->isPluginBogus($pluginName)) { | |
| continue; | |
| } | |
| // If the plugin is not core and looks bogus, do not load | |
| if ($this->isPluginThirdPartyAndBogus($pluginName)) { | |
| $info = array( | |
| 'invalid' => true, | |
| 'activated' => false, | |
| 'alwaysActivated' => false, | |
| 'uninstallable' => true, | |
| ); | |
| } else { | |
| $translator->addDirectory(self::getPluginDirectory($pluginName) . '/lang'); | |
| $this->loadPlugin($pluginName); | |
| $info = array( | |
| 'activated' => $this->isPluginActivated($pluginName), | |
| 'alwaysActivated' => $this->isPluginAlwaysActivated($pluginName), | |
| 'uninstallable' => $this->isPluginUninstallable($pluginName), | |
| ); | |
| } | |
| $plugins[$pluginName] = $info; | |
| } | |
| $loadedPlugins = $this->getLoadedPlugins(); | |
| foreach ($loadedPlugins as $oPlugin) { | |
| $pluginName = $oPlugin->getPluginName(); | |
| $info = array( | |
| 'info' => $oPlugin->getInformation(), | |
| 'activated' => $this->isPluginActivated($pluginName), | |
| 'alwaysActivated' => $this->isPluginAlwaysActivated($pluginName), | |
| 'missingRequirements' => $oPlugin->getMissingDependenciesAsString(), | |
| 'uninstallable' => $this->isPluginUninstallable($pluginName), | |
| ); | |
| $plugins[$pluginName] = $info; | |
| } | |
| return $plugins; | |
| } | |
| protected static function isManifestFileFound(string $path): bool | |
| { | |
| return file_exists($path . "/" . MetadataLoader::PLUGIN_JSON_FILENAME); | |
| } | |
| /** | |
| * Returns `true` if the plugin is bundled with core or `false` if it is third party. | |
| * | |
| * @param string $name The name of the plugin, eg, `'Actions'`. | |
| * @return bool | |
| */ | |
| public function isPluginBundledWithCore($name) | |
| { | |
| return $this->isPluginEnabledByDefault($name) | |
| || in_array($name, $this->pluginList->getCorePluginsDisabledByDefault()) | |
| || $name == self::DEFAULT_THEME; | |
| } | |
| /** | |
| * @param string $pluginName | |
| * @return bool | |
| * @ignore | |
| */ | |
| public function isPluginThirdPartyAndBogus($pluginName) | |
| { | |
| if ($this->isPluginBundledWithCore($pluginName)) { | |
| return false; | |
| } | |
| if ($this->isPluginBogus($pluginName)) { | |
| return true; | |
| } | |
| $path = self::getPluginDirectory($pluginName); | |
| if (!$this->isManifestFileFound($path)) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Load AND activates the specified plugins. It will also overwrite all previously loaded plugins, so it acts | |
| * as a setter. | |
| * | |
| * @param string[] $pluginsToLoad Array of plugins to load. | |
| * @return void | |
| */ | |
| public function loadPlugins(array $pluginsToLoad) | |
| { | |
| $this->resetTransientCache(); | |
| $this->pluginsToLoad = $this->makePluginsToLoad($pluginsToLoad); | |
| $this->reloadActivatedPlugins(); | |
| } | |
| /** | |
| * Disable plugin loading. | |
| * @return void | |
| */ | |
| public function doNotLoadPlugins() | |
| { | |
| $this->doLoadPlugins = false; | |
| } | |
| /** | |
| * Disable loading of "always activated" plugins. | |
| * @return void | |
| */ | |
| public function doNotLoadAlwaysActivatedPlugins() | |
| { | |
| $this->doLoadAlwaysActivatedPlugins = false; | |
| } | |
| /** | |
| * Execute postLoad() hook for loaded plugins | |
| * @return void | |
| */ | |
| public function postLoadPlugins() | |
| { | |
| $plugins = $this->getLoadedPlugins(); | |
| foreach ($plugins as $plugin) { | |
| $plugin->postLoad(); | |
| } | |
| } | |
| /** | |
| * Returns an array containing the names of all loaded plugins (eg. 'UserCountry' and NOT the | |
| * fully qualified class name '\Piwik\Plugins\UserCountry\UserCountry'). | |
| * | |
| * @return list<string> | |
| */ | |
| public function getLoadedPluginsName() | |
| { | |
| return array_keys($this->getLoadedPlugins()); | |
| } | |
| /** | |
| * Returns an array mapping loaded plugin names with their plugin objects, eg, | |
| * | |
| * array( | |
| * 'UserCountry' => Plugin $pluginObject, | |
| * 'UserLanguage' => Plugin $pluginObject, | |
| * ); | |
| * | |
| * @return Plugin[] | |
| */ | |
| public function getLoadedPlugins() | |
| { | |
| return $this->loadedPlugins; | |
| } | |
| /** | |
| * @param string $piwikVersion | |
| * @return Plugin[] | |
| */ | |
| public function getIncompatiblePlugins($piwikVersion) | |
| { | |
| $plugins = $this->getLoadedPlugins(); | |
| $incompatible = []; | |
| foreach ($plugins as $plugin) { | |
| if ($plugin->hasMissingDependencies($piwikVersion)) { | |
| $incompatible[] = $plugin; | |
| } | |
| } | |
| return $incompatible; | |
| } | |
| /** | |
| * Returns an array of plugins that are currently loaded and activated, | |
| * mapping loaded plugin names with their plugin objects, eg, | |
| * | |
| * array( | |
| * 'UserCountry' => Plugin $pluginObject, | |
| * 'UserLanguage' => Plugin $pluginObject, | |
| * ); | |
| * | |
| * @return Plugin[] | |
| */ | |
| public function getPluginsLoadedAndActivated() | |
| { | |
| if (is_null($this->pluginsLoadedAndActivated)) { | |
| $enabled = $this->getActivatedPlugins(); | |
| if (empty($enabled)) { | |
| return array(); | |
| } | |
| $plugins = $this->getLoadedPlugins(); | |
| $enabled = array_combine($enabled, $enabled); | |
| $plugins = array_intersect_key($plugins, $enabled); | |
| $this->pluginsLoadedAndActivated = $plugins; | |
| } | |
| return $this->pluginsLoadedAndActivated; | |
| } | |
| /** | |
| * Returns a list of all names of currently activated plugin eg, | |
| * | |
| * array( | |
| * 'UserCountry' | |
| * 'UserLanguage' | |
| * ); | |
| * | |
| * @return string[] | |
| */ | |
| public function getActivatedPlugins() | |
| { | |
| return $this->pluginsToLoad; | |
| } | |
| /** | |
| * @return string[] | |
| */ | |
| public function getActivatedPluginsFromConfig() | |
| { | |
| $plugins = $this->pluginList->getActivatedPlugins(); | |
| return $this->makePluginsToLoad($plugins); | |
| } | |
| /** | |
| * Returns a Plugin object by name. | |
| * | |
| * @param string $name The name of the plugin, eg, `'Actions'`. | |
| * @return Plugin | |
| */ | |
| public function getLoadedPlugin($name) | |
| { | |
| if (!isset($this->loadedPlugins[$name]) || is_null($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 reloadActivatedPlugins(): void | |
| { | |
| $pluginsToPostPendingEventsTo = []; | |
| foreach ($this->pluginsToLoad as $pluginName) { | |
| $pluginsToPostPendingEventsTo = $this->reloadActivatedPlugin($pluginName, $pluginsToPostPendingEventsTo); | |
| } | |
| // post pending events after all plugins are successfully loaded | |
| foreach ($pluginsToPostPendingEventsTo as $plugin) { | |
| EventDispatcher::getInstance()->postPendingEventsTo($plugin); | |
| } | |
| } | |
| /** | |
| * @param list<Plugin> $pluginsToPostPendingEventsTo | |
| * @return list<Plugin> | |
| */ | |
| private function reloadActivatedPlugin(string $pluginName, array $pluginsToPostPendingEventsTo): array | |
| { | |
| if ($this->isPluginLoaded($pluginName) || $this->isPluginThirdPartyAndBogus($pluginName)) { | |
| return $pluginsToPostPendingEventsTo; | |
| } | |
| $newPlugin = $this->loadPlugin($pluginName); | |
| if ($newPlugin === null) { | |
| return $pluginsToPostPendingEventsTo; | |
| } | |
| $requirements = $newPlugin->getMissingDependencies(); | |
| if (!empty($requirements)) { | |
| foreach ($requirements as $requirement) { | |
| $possiblePluginName = $requirement['requirement']; | |
| if (in_array($possiblePluginName, $this->pluginsToLoad, $strict = true)) { | |
| $pluginsToPostPendingEventsTo = $this->reloadActivatedPlugin($possiblePluginName, $pluginsToPostPendingEventsTo); | |
| } | |
| } | |
| } | |
| if ($newPlugin->hasMissingDependencies()) { | |
| $this->unloadPluginFromMemory($pluginName); | |
| // at this state we do not know yet whether current user has super user access. We do not even know | |
| // if someone is actually logged in. | |
| $message = Piwik::translate('CorePluginsAdmin_WeCouldNotLoadThePluginAsItHasMissingDependencies', array($pluginName, $newPlugin->getMissingDependenciesAsString())); | |
| $message .= ' '; | |
| $message .= Piwik::translate('General_PleaseContactYourPiwikAdministrator'); | |
| $notification = new Notification($message); | |
| $notification->context = Notification::CONTEXT_ERROR; | |
| Notification\Manager::notify('PluginManager_PluginUnloaded', $notification); | |
| return $pluginsToPostPendingEventsTo; | |
| } | |
| if ( | |
| $newPlugin->isPremiumFeature() | |
| && SettingsPiwik::isInternetEnabled() | |
| && !Development::isEnabled() | |
| && $this->isPluginActivated('Marketplace') | |
| && $this->isPluginActivated($pluginName) | |
| ) { | |
| $cacheKey = 'MarketplacePluginMissingLicense' . $pluginName; | |
| $cache = self::getLicenseCache(); | |
| if ($cache->contains($cacheKey)) { | |
| $pluginLicenseInfo = $cache->fetch($cacheKey); | |
| } elseif (!SettingsServer::isTrackerApiRequest()) { | |
| // prevent requesting license info during a tracker request see https://github.com/matomo-org/matomo/issues/14401 | |
| // as possibly many instances would try to do this at the same time | |
| try { | |
| $plugins = StaticContainer::get('Piwik\Plugins\Marketplace\Plugins'); | |
| $licenseInfo = $plugins->getLicenseValidInfo($pluginName); | |
| } catch (\Exception $e) { | |
| $licenseInfo = []; | |
| } | |
| $pluginLicenseInfo = ['missing' => !empty($licenseInfo['isMissingLicense'])]; | |
| $sixHours = 3600 * 6; | |
| $cache->save($cacheKey, $pluginLicenseInfo, $sixHours); | |
| } else { | |
| // tracker mode, we assume it is not missing until cache is written | |
| $pluginLicenseInfo = ['missing' => false]; | |
| } | |
| if (!empty($pluginLicenseInfo['missing']) && (!defined('PIWIK_TEST_MODE') || !PIWIK_TEST_MODE)) { | |
| $this->unloadPluginFromMemory($pluginName); | |
| return $pluginsToPostPendingEventsTo; | |
| } | |
| } | |
| $pluginsToPostPendingEventsTo[] = $newPlugin; | |
| return $pluginsToPostPendingEventsTo; | |
| } | |
| /** | |
| * @return \Matomo\Cache\Lazy | |
| */ | |
| public static function getLicenseCache() | |
| { | |
| return Cache::getLazyCache(); | |
| } | |
| /** | |
| * @return list<string> | |
| */ | |
| public function getIgnoredBogusPlugins() | |
| { | |
| $ignored = array(); | |
| foreach ($this->pluginsToLoad as $pluginName) { | |
| if ($this->isPluginThirdPartyAndBogus($pluginName)) { | |
| $ignored[] = $pluginName; | |
| } | |
| } | |
| return $ignored; | |
| } | |
| /** | |
| * Returns the name of all plugins found in this Piwik instance | |
| * (including those not enabled and themes) | |
| * | |
| * @return list<string> | |
| */ | |
| public static function getAllPluginsNames() | |
| { | |
| $pluginList = StaticContainer::get('Piwik\Application\Kernel\PluginList'); | |
| $pluginsToLoad = array_merge( | |
| self::getInstance()->readPluginsDirectory(), | |
| $pluginList->getCorePluginsDisabledByDefault() | |
| ); | |
| $pluginsToLoad = array_values(array_unique($pluginsToLoad)); | |
| return $pluginsToLoad; | |
| } | |
| /** | |
| * Return the list of plugins that are always activated | |
| * | |
| * @return list<string> | |
| */ | |
| public static function getAlwaysActivatedPlugins(): array | |
| { | |
| return self::$pluginToAlwaysActivate; | |
| } | |
| /** | |
| * Loads the plugin filename and instantiates the plugin with the given name, eg. UserCountry. | |
| * Contrary to loadPlugins() it does not activate the plugin, it only loads it. | |
| * | |
| * @param string $pluginName | |
| * @return Plugin|null | |
| */ | |
| public function loadPlugin($pluginName) | |
| { | |
| if (isset($this->loadedPlugins[$pluginName])) { | |
| return $this->loadedPlugins[$pluginName]; | |
| } | |
| $newPlugin = $this->makePluginClass($pluginName); | |
| $this->addLoadedPlugin($pluginName, $newPlugin); | |
| return $newPlugin; | |
| } | |
| /** | |
| * @param string $pluginName | |
| * @return bool | |
| */ | |
| public function isValidPluginName($pluginName) | |
| { | |
| return (bool) preg_match('/^[a-zA-Z]([a-zA-Z0-9_]){0,59}$/D', $pluginName); | |
| } | |
| /** | |
| * @param string $pluginName | |
| * @return Plugin | |
| */ | |
| protected function makePluginClass($pluginName) | |
| { | |
| $pluginClassName = $pluginName; | |
| if (!$this->isValidPluginName($pluginName)) { | |
| throw new \Exception(sprintf("The plugin name '%s' is not a valid plugin name", $pluginName)); | |
| } | |
| $path = self::getPluginDirectory($pluginName); | |
| $path = sprintf('%s/%s.php', $path, $pluginName); | |
| if (!file_exists($path)) { | |
| // Create the smallest minimal Piwik Plugin | |
| // Eg. Used for Morpheus default theme which does not have a Morpheus.php file | |
| return new Plugin($pluginName); | |
| } | |
| require_once $path; | |
| $namespacedClass = $this->getClassNamePlugin($pluginName); | |
| if (!class_exists($namespacedClass, false)) { | |
| throw new \Exception("The class $pluginClassName couldn't be found in the file '$path'"); | |
| } | |
| $newPlugin = new $namespacedClass(); | |
| if (!($newPlugin instanceof Plugin)) { | |
| throw new \Exception("The plugin $pluginClassName in the file $path must inherit from Plugin."); | |
| } | |
| return $newPlugin; | |
| } | |
| protected function getClassNamePlugin(string $pluginName): string | |
| { | |
| $className = $pluginName; | |
| if ($pluginName === 'API') { | |
| $className = 'Plugin'; | |
| } | |
| return "\\Piwik\\Plugins\\$pluginName\\$className"; | |
| } | |
| private function resetTransientCache(): void | |
| { | |
| $this->pluginsLoadedAndActivated = null; | |
| } | |
| /** | |
| * Unload plugin | |
| * | |
| * @param Plugin|string $plugin | |
| * @return void | |
| */ | |
| public function unloadPlugin($plugin) | |
| { | |
| $this->resetTransientCache(); | |
| if (!($plugin instanceof Plugin)) { | |
| $oPlugin = $this->loadPlugin($plugin); | |
| if ($oPlugin === null) { | |
| unset($this->loadedPlugins[$plugin]); | |
| return; | |
| } | |
| $plugin = $oPlugin; | |
| } | |
| unset($this->loadedPlugins[$plugin->getPluginName()]); | |
| } | |
| /** | |
| * Unload all loaded plugins | |
| * @return void | |
| */ | |
| public function unloadPlugins() | |
| { | |
| $this->resetTransientCache(); | |
| $pluginsLoaded = $this->getLoadedPlugins(); | |
| foreach ($pluginsLoaded as $plugin) { | |
| $this->unloadPlugin($plugin); | |
| } | |
| } | |
| /** | |
| * Install a specific plugin | |
| * | |
| * @return void | |
| * @throws \Piwik\Plugin\PluginException if installation fails | |
| */ | |
| private function executePluginInstall(Plugin $plugin) | |
| { | |
| try { | |
| $plugin->install(); | |
| } catch (\Exception $e) { | |
| throw new \Piwik\Plugin\PluginException($plugin->getPluginName(), $e->getMessage()); | |
| } | |
| } | |
| /** | |
| * Add a plugin in the loaded plugins array | |
| * | |
| * @param string $pluginName plugin name without prefix (eg. 'UserCountry') | |
| * @return void | |
| * @internal | |
| */ | |
| public function addLoadedPlugin($pluginName, Plugin $newPlugin) | |
| { | |
| $this->resetTransientCache(); | |
| $this->loadedPlugins[$pluginName] = $newPlugin; | |
| } | |
| /** | |
| * Return names of all installed plugins. | |
| * | |
| * @return list<string> | |
| * @api | |
| */ | |
| public function getInstalledPluginsName() | |
| { | |
| return Config::getInstance()->PluginsInstalled['PluginsInstalled']; | |
| } | |
| /** | |
| * Returns names of plugins that should be loaded, but cannot be since their | |
| * files cannot be found. | |
| * | |
| * @return list<string> | |
| * @api | |
| */ | |
| public function getMissingPlugins() | |
| { | |
| $missingPlugins = []; | |
| $plugins = $this->pluginList->getActivatedPlugins(); | |
| foreach ($plugins as $pluginName) { | |
| // if a plugin is listed in the config, but is not loaded, it does not exist in the folder | |
| if ( | |
| !$this->isPluginLoaded($pluginName) && !$this->isPluginBogus($pluginName) && | |
| !($this->doesPluginRequireInternetConnection($pluginName) && !SettingsPiwik::isInternetEnabled()) | |
| ) { | |
| $missingPlugins[] = $pluginName; | |
| } | |
| } | |
| return $missingPlugins; | |
| } | |
| /** | |
| * Install a plugin, if necessary | |
| */ | |
| private function installPluginIfNecessary(Plugin $plugin): void | |
| { | |
| $pluginName = $plugin->getPluginName(); | |
| $saveConfig = false; | |
| // is the plugin already installed or is it the first time we activate it? | |
| $pluginsInstalled = $this->getInstalledPluginsName(); | |
| if (!$this->isPluginInstalled($pluginName)) { | |
| $this->executePluginInstall($plugin); | |
| $pluginsInstalled[] = $pluginName; | |
| $this->updatePluginsInstalledConfig($pluginsInstalled); | |
| $updater = new Updater(); | |
| $updater->markComponentSuccessfullyUpdated($plugin->getPluginName(), $plugin->getVersion(), $isNew = true); | |
| $saveConfig = true; | |
| /** | |
| * Event triggered after a new plugin has been installed. | |
| * | |
| * Note: Might be triggered more than once if the config file is not writable | |
| * | |
| * @param string $pluginName The plugin that has been installed. | |
| */ | |
| Piwik::postEvent('PluginManager.pluginInstalled', array($pluginName)); | |
| } | |
| if ($saveConfig) { | |
| PiwikConfig::getInstance()->forceSave(); | |
| $this->clearCache($pluginName); | |
| } | |
| } | |
| /** | |
| * @return bool | |
| */ | |
| public function isTrackerPlugin(Plugin $plugin) | |
| { | |
| if (!$this->isPluginInstalled($plugin->getPluginName())) { | |
| return false; | |
| } | |
| if ($plugin->isTrackerPlugin()) { | |
| return true; | |
| } | |
| $dimensions = VisitDimension::getDimensions($plugin); | |
| if (!empty($dimensions)) { | |
| return true; | |
| } | |
| $dimensions = ActionDimension::getDimensions($plugin); | |
| if (!empty($dimensions)) { | |
| return true; | |
| } | |
| $hooks = $plugin->registerEvents(); | |
| $hookNames = array_keys($hooks); | |
| foreach ($hookNames as $name) { | |
| if (strpos($name, self::TRACKER_EVENT_PREFIX) === 0) { | |
| return true; | |
| } | |
| if ($name === 'Request.initAuthenticationObject') { | |
| return true; | |
| } | |
| } | |
| $dimensions = ConversionDimension::getDimensions($plugin); | |
| if (!empty($dimensions)) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| private static function pluginStructureLooksValid(string $path): bool | |
| { | |
| $name = basename($path); | |
| return file_exists($path . "/" . $name . ".php") | |
| || self::isManifestFileFound($path); | |
| } | |
| private function removePluginFromPluginsInstalledConfig(string $pluginName): void | |
| { | |
| $pluginsInstalled = Config::getInstance()->PluginsInstalled['PluginsInstalled']; | |
| $key = array_search($pluginName, $pluginsInstalled); | |
| if ($key !== false) { | |
| unset($pluginsInstalled[$key]); | |
| } | |
| $this->updatePluginsInstalledConfig($pluginsInstalled); | |
| } | |
| private function removePluginFromPluginsConfig(string $pluginName): void | |
| { | |
| $pluginsEnabled = $this->pluginList->getActivatedPlugins(); | |
| $key = array_search($pluginName, $pluginsEnabled); | |
| if ($key !== false) { | |
| unset($pluginsEnabled[$key]); | |
| } | |
| $this->updatePluginsConfig($pluginsEnabled); | |
| } | |
| private function isPluginBogus(string $pluginName): bool | |
| { | |
| $bogusPlugins = array( | |
| 'PluginMarketplace', //defines a plugin.json but 1.x Piwik plugin | |
| 'DoNotTrack', // Removed in 2.0.3 | |
| 'AnonymizeIP', // Removed in 2.0.3 | |
| 'wp-optimize', // a WP plugin that has a plugin.json file but is not a matomo plugin | |
| ); | |
| return in_array($pluginName, $bogusPlugins); | |
| } | |
| private function deactivateThemeIfTheme(string $pluginName): void | |
| { | |
| // Only one theme enabled at a time | |
| $themeEnabled = $this->getThemeEnabled(); | |
| if ( | |
| $themeEnabled | |
| && $themeEnabled->getPluginName() != self::DEFAULT_THEME | |
| ) { | |
| $themeAlreadyEnabled = $themeEnabled->getPluginName(); | |
| $plugin = $this->loadPlugin($pluginName); | |
| if ($plugin && $plugin->isTheme()) { | |
| $this->deactivatePlugin($themeAlreadyEnabled); | |
| } | |
| } | |
| } | |
| private function executePluginDeactivate(string $pluginName): void | |
| { | |
| if (!$this->isPluginBogus($pluginName)) { | |
| $plugin = $this->loadPlugin($pluginName); | |
| if ($plugin !== null) { | |
| $plugin->deactivate(); | |
| } | |
| } | |
| } | |
| private function unloadPluginFromMemory(string $pluginName): void | |
| { | |
| $this->unloadPlugin($pluginName); | |
| $key = array_search($pluginName, $this->pluginsToLoad); | |
| if ($key !== false) { | |
| unset($this->pluginsToLoad[$key]); | |
| } | |
| } | |
| private function removePluginFromConfig(string $pluginName): void | |
| { | |
| $this->removePluginFromPluginsConfig($pluginName); | |
| PiwikConfig::getInstance()->forceSave(); | |
| } | |
| private function executePluginUninstall(string $pluginName): void | |
| { | |
| try { | |
| $plugin = $this->getLoadedPlugin($pluginName); | |
| $plugin->uninstall(); | |
| } catch (\Exception $e) { | |
| } | |
| if (empty($plugin)) { | |
| return; | |
| } | |
| try { | |
| $visitDimensions = VisitDimension::getAllDimensions(); | |
| foreach (VisitDimension::getDimensions($plugin) as $dimension) { | |
| $this->uninstallDimension(VisitDimension::INSTALLER_PREFIX, $dimension, $visitDimensions); | |
| } | |
| } catch (\Exception $e) { | |
| } | |
| try { | |
| $actionDimensions = ActionDimension::getAllDimensions(); | |
| foreach (ActionDimension::getDimensions($plugin) as $dimension) { | |
| $this->uninstallDimension(ActionDimension::INSTALLER_PREFIX, $dimension, $actionDimensions); | |
| } | |
| } catch (\Exception $e) { | |
| } | |
| try { | |
| $conversionDimensions = ConversionDimension::getAllDimensions(); | |
| foreach (ConversionDimension::getDimensions($plugin) as $dimension) { | |
| $this->uninstallDimension(ConversionDimension::INSTALLER_PREFIX, $dimension, $conversionDimensions); | |
| } | |
| } catch (\Exception $e) { | |
| } | |
| } | |
| /** | |
| * @param VisitDimension|ActionDimension|ConversionDimension $dimension | |
| * @param VisitDimension[]|ActionDimension[]|ConversionDimension[] $allDimensions | |
| */ | |
| private function doesAnotherPluginDefineSameColumnWithDbEntry($dimension, $allDimensions): bool | |
| { | |
| $module = $dimension->getModule(); | |
| $columnName = $dimension->getColumnName(); | |
| foreach ($allDimensions as $dim) { | |
| if ( | |
| $dim->getColumnName() === $columnName && | |
| $dim->hasColumnType() && | |
| $dim->getModule() !== $module | |
| ) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * @param string $prefix column installer prefix | |
| * @param ConversionDimension|VisitDimension|ActionDimension $dimension | |
| * @param VisitDimension[]|ActionDimension[]|ConversionDimension[] $allDimensions | |
| */ | |
| private function uninstallDimension(string $prefix, Dimension $dimension, $allDimensions): void | |
| { | |
| if (!$this->doesAnotherPluginDefineSameColumnWithDbEntry($dimension, $allDimensions)) { | |
| $dimension->uninstall(); | |
| $this->removeInstalledVersionFromOptionTable($prefix . $dimension->getColumnName()); | |
| } | |
| } | |
| /** | |
| * @param string $pluginName | |
| * @param bool $checkPluginExistsInFilesystem if enabled, it won't rely on the information in the config file only | |
| * but also check the filesystem if the plugin really is installed. | |
| * For performance reasons this is not the case by default. | |
| * @return bool | |
| */ | |
| public function isPluginInstalled($pluginName, $checkPluginExistsInFilesystem = false) | |
| { | |
| $pluginsInstalled = $this->getInstalledPluginsName(); | |
| $isInstalledInConfig = in_array($pluginName, $pluginsInstalled); | |
| if ($isInstalledInConfig && $checkPluginExistsInFilesystem) { | |
| return $this->isPluginInFilesystem($pluginName); | |
| } | |
| return $isInstalledInConfig; | |
| } | |
| private function removeInstalledVersionFromOptionTable(string $name): void | |
| { | |
| $updater = new Updater(); | |
| $updater->markComponentSuccessfullyUninstalled($name); | |
| } | |
| private function makeSureOnlyActivatedPluginsAreLoaded(): void | |
| { | |
| foreach ($this->getLoadedPlugins() as $pluginName => $plugin) { | |
| if (!in_array($pluginName, $this->pluginsToLoad)) { | |
| $this->unloadPlugin($plugin); | |
| } | |
| } | |
| } | |
| /** | |
| * Reading the plugins from the global.ini.php config file | |
| * | |
| * @return array | |
| */ | |
| protected function getPluginsFromGlobalIniConfigFile() | |
| { | |
| return $this->pluginList->getPluginsBundledWithPiwik(); | |
| } | |
| protected function isPluginEnabledByDefault(string $name): bool | |
| { | |
| $pluginsBundledWithPiwik = $this->getPluginsFromGlobalIniConfigFile(); | |
| if (empty($pluginsBundledWithPiwik)) { | |
| return false; | |
| } | |
| return in_array($name, $pluginsBundledWithPiwik); | |
| } | |
| /** | |
| * @param list<string> $pluginsToLoad | |
| * @return list<string> | |
| */ | |
| private function makePluginsToLoad(array $pluginsToLoad) | |
| { | |
| $pluginsToLoad = array_unique($pluginsToLoad); | |
| if ($this->doLoadAlwaysActivatedPlugins) { | |
| $pluginsToLoad = array_merge($pluginsToLoad, self::$pluginToAlwaysActivate); | |
| } | |
| $pluginsToLoad = array_unique($pluginsToLoad); | |
| return $this->pluginList->sortPlugins($pluginsToLoad); | |
| } | |
| /** | |
| * @return void | |
| */ | |
| public function loadPluginTranslations() | |
| { | |
| /** @var Translator $translator */ | |
| $translator = StaticContainer::get('Piwik\Translation\Translator'); | |
| foreach ($this->getAllPluginsNames() as $pluginName) { | |
| $translator->addDirectory(self::getPluginDirectory($pluginName) . '/lang'); | |
| } | |
| } | |
| /** | |
| * @return bool | |
| */ | |
| public function hasPremiumFeatures() | |
| { | |
| foreach ($this->getPluginsLoadedAndActivated() as $activatedPlugin) { | |
| if ($activatedPlugin->isPremiumFeature()) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| private function savePluginTime(string $timingName, string $pluginName): void | |
| { | |
| $optionName = $timingName . $pluginName; | |
| try { | |
| Option::set($optionName, (string)time()); | |
| } catch (\Exception $e) { | |
| if (SettingsPiwik::isMatomoInstalled()) { | |
| throw $e; | |
| } | |
| // we ignore any error while Matomo is not installed yet. refs #16741 | |
| } | |
| } | |
| public function getVersion(string $pluginName): ?string | |
| { | |
| if ($this->isPluginLoaded($pluginName)) { | |
| return (string) $this->getLoadedPlugin($pluginName)->getVersion(); | |
| } elseif (!$this->isPluginInFilesystem($pluginName)) { | |
| return null; | |
| } else { | |
| try { | |
| $plugin = new Plugin($pluginName); | |
| return (string) $plugin->getVersion(); | |
| } catch (\Exception $e) { | |
| return null; | |
| } | |
| } | |
| } | |
| } | |