diff options
author | Thomas Steur <tsteur@users.noreply.github.com> | 2019-03-15 01:24:36 +0300 |
---|---|---|
committer | diosmosis <diosmosis@users.noreply.github.com> | 2019-03-15 01:24:36 +0300 |
commit | 6395855aa7f3813cc7f413a18d31505ea26ba32a (patch) | |
tree | 2a3157954dd3c29cc024bf7093df53b63be94dd6 /core/Plugin/Manager.php | |
parent | d7c932710e690d87ea62c3ec94ae65fa5bd05b05 (diff) |
Support multiple plugin paths (#14051)
* do not hard code plugins directory
* remove method that is not needed for now
* use plugins directory in more places
* some work on supporting multiple plugin directories
* use more unique name
* couple fixes
* and another fix
* sort plugins
* adjust languagesmanager
* adjust more usages
* Update Manager.php
* adding a plugin to test
* more tests
* make sure plugin resources can be located in custom directory
* adding more tests
* rewrite image paths
* handle more cases
* add tests
* make sure to load plugin
* trying to fix test
* trying it this way
* load plugin
* fix ui test?
* testing if tests succeed this way
* another test
* load custom dir plugin
* load plugin in ui fixture
* change the update statement
* remove update script
* delete column
* fix ui test
* make it work for tests
* fix some tests
* Fix merge.
Diffstat (limited to 'core/Plugin/Manager.php')
-rw-r--r-- | core/Plugin/Manager.php | 180 |
1 files changed, 165 insertions, 15 deletions
diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php index d25234c735..f48f8416fb 100644 --- a/core/Plugin/Manager.php +++ b/core/Plugin/Manager.php @@ -12,11 +12,13 @@ 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\Exception; use Piwik\Exception\PluginDeactivatedException; use Piwik\Filesystem; use Piwik\Log; @@ -26,7 +28,6 @@ use Piwik\Plugin; use Piwik\Plugin\Dimension\ActionDimension; use Piwik\Plugin\Dimension\ConversionDimension; use Piwik\Plugin\Dimension\VisitDimension; -use Piwik\Plugins\Marketplace\Api\Client; use Piwik\Settings\Storage as SettingsStorage; use Piwik\SettingsPiwik; use Piwik\Theme; @@ -52,6 +53,8 @@ class Manager protected $doLoadPlugins = true; + protected static $pluginsToPathCache = array(); + private $pluginsLoadedAndActivated; /** @@ -316,21 +319,164 @@ class Manager */ public function readPluginsDirectory() { - $pluginsName = _glob(self::getPluginsDirectory() . '*', GLOB_ONLYDIR); $result = array(); - if ($pluginsName != false) { - foreach ($pluginsName as $path) { - if (self::pluginStructureLooksValid($path)) { - $result[] = basename($path); + 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; } + 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) { + 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; + } + } + } + }); + } + } + } + + public static function getAlternativeWebRootDirectories() + { + $dirs = array(); + + 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; + } + + /** + * 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($path) + { + 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 $pluginName + * @return mixed|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::getPluginsDirectories() as $dir) { + $path = $dir . $pluginName; + if (is_dir($path)) { + self::$pluginsToPathCache[$pluginName] = self::getPluginRealPath($path); + return $path; + } + } + + // assume default directory when plugin does not exist just yet + return self::getPluginsDirectory() . $pluginName; + } + + /** + * 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. + * @deprecated since Matomo 3.9.0 use {@link (getPluginsDirectories())} or {@link getPluginDirectory($pluginName)} instead + * @return string + */ public static function getPluginsDirectory() { - return PIWIK_INCLUDE_PATH . '/plugins/'; + $path = rtrim(PIWIK_INCLUDE_PATH, '/') . '/plugins/'; + $path = self::getPluginRealPath($path); + return $path; } /** @@ -459,8 +605,11 @@ class Manager public static function deletePluginFromFilesystem($plugin) { - $pluginDir = self::getPluginsDirectory(); - Filesystem::unlinkRecursive($pluginDir . $plugin, $deleteRootToo = true); + $pluginDir = self::getPluginDirectory($plugin); + if (strpos($pluginDir, PIWIK_INCLUDE_PATH) === 0) { + // only delete files for plugins within matomo directory... + Filesystem::unlinkRecursive($pluginDir, $deleteRootToo = true); + } } /** @@ -633,7 +782,7 @@ class Manager 'uninstallable' => true, ); } else { - $translator->addDirectory(self::getPluginsDirectory() . $pluginName . '/lang'); + $translator->addDirectory(self::getPluginDirectory($pluginName) . '/lang'); $this->loadPlugin($pluginName); $info = array( 'activated' => $this->isPluginActivated($pluginName), @@ -694,7 +843,8 @@ class Manager return true; } - $path = self::getPluginsDirectory() . $pluginName; + $path = self::getPluginDirectory($pluginName); + if (!$this->isManifestFileFound($path)) { return true; } @@ -1006,14 +1156,14 @@ class Manager */ protected function makePluginClass($pluginName) { - $pluginFileName = sprintf("%s/%s.php", $pluginName, $pluginName); $pluginClassName = $pluginName; if (!$this->isValidPluginName($pluginName)) { - throw new \Exception(sprintf("The plugin filename '%s' is not a valid plugin name", $pluginFileName)); + throw new \Exception(sprintf("The plugin name '%s' is not a valid plugin name", $pluginName)); } - $path = self::getPluginsDirectory() . $pluginFileName; + $path = self::getPluginDirectory($pluginName); + $path = sprintf('%s/%s.php', $path, $pluginName); if (!file_exists($path)) { // Create the smallest minimal Piwik Plugin @@ -1470,7 +1620,7 @@ class Manager /** @var Translator $translator */ $translator = StaticContainer::get('Piwik\Translation\Translator'); foreach ($this->getAllPluginsNames() as $pluginName) { - $translator->addDirectory(self::getPluginsDirectory() . $pluginName . '/lang'); + $translator->addDirectory(self::getPluginDirectory($pluginName) . '/lang'); } } } |