| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| 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; |
|
|
| |
| |
| |
| class Manager |
| { |
| public const LAST_PLUGIN_ACTIVATION_TIME_OPTION_PREFIX = 'LastPluginActivation.'; |
| public const LAST_PLUGIN_DEACTIVATION_TIME_OPTION_PREFIX = 'LastPluginDeactivation.'; |
|
|
| |
| |
| |
| public static function getInstance() |
| { |
| return StaticContainer::get('Piwik\Plugin\Manager'); |
| } |
|
|
| |
| |
| |
| protected $pluginsToLoad = []; |
|
|
| |
| |
| |
| protected $doLoadPlugins = true; |
|
|
| |
| |
| |
| protected static $pluginsToPathCache = []; |
|
|
| |
| |
| |
| protected static $pluginsToWebRootDirCache = []; |
|
|
| |
| |
| |
| private $pluginsLoadedAndActivated; |
|
|
| |
| |
| |
| protected $loadedPlugins = []; |
| |
| |
| |
| public const DEFAULT_THEME = "Morpheus"; |
|
|
| |
| |
| |
| protected $doLoadAlwaysActivatedPlugins = true; |
|
|
| |
| |
| |
| |
| protected static $pluginToAlwaysActivate = array( |
| 'FeatureFlags', |
| 'BulkTracking', |
| 'CoreVue', |
| 'CoreHome', |
| 'CoreUpdater', |
| 'CoreAdminHome', |
| 'CoreConsole', |
| 'CorePluginsAdmin', |
| 'CoreVisualizations', |
| 'Installation', |
| 'SitesManager', |
| 'UsersManager', |
| 'Intl', |
| 'API', |
| 'Proxy', |
| 'LanguagesManager', |
| 'WebsiteMeasurable', |
|
|
| |
| self::DEFAULT_THEME, |
| ); |
|
|
| |
| |
| |
| private $trackerPluginsNotToLoad = []; |
|
|
| |
| |
| |
| private $pluginList; |
|
|
| public function __construct(PluginList $pluginList) |
| { |
| $this->pluginList = $pluginList; |
| } |
|
|
| |
| |
| |
| |
| 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); |
| } |
|
|
| |
| |
| |
| |
| public function loadCorePluginsDuringTracker() |
| { |
| $pluginsToLoad = $this->pluginList->getActivatedPlugins(); |
| $pluginsToLoad = array_diff($pluginsToLoad, $this->getTrackerPluginsNotToLoad()); |
| $this->loadPlugins($pluginsToLoad); |
| } |
|
|
| |
| |
| |
| public function loadTrackerPlugins() |
| { |
| $cacheId = 'PluginsTracker'; |
| $cache = Cache::getEagerCache(); |
|
|
| if ($cache->contains($cacheId)) { |
| |
| $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); |
|
|
| |
| |
| |
| $this->makeSureOnlyActivatedPluginsAreLoaded(); |
|
|
| return $pluginsTracker; |
| } |
|
|
| |
| |
| |
| |
| |
| public function setTrackerPluginsNotToLoad($plugins) |
| { |
| $this->trackerPluginsNotToLoad = $plugins; |
| } |
|
|
| |
| |
| |
| |
| |
| public function getTrackerPluginsNotToLoad() |
| { |
| return $this->trackerPluginsNotToLoad; |
| } |
|
|
| |
| public const TRACKER_EVENT_PREFIX = 'Tracker.'; |
|
|
| |
| |
| |
| |
| public function isPluginOfficialAndNotBundledWithCore($pluginName) |
| { |
| static $gitModules; |
| if (empty($gitModules)) { |
| $gitModules = file_get_contents(PIWIK_INCLUDE_PATH . '/.gitmodules'); |
| } |
| |
| return false !== strpos($gitModules, "plugins/" . $pluginName . "\n"); |
| } |
|
|
| |
| |
| |
| |
| |
| private function updatePluginsConfig($pluginsToLoad): void |
| { |
| $pluginsToLoad = $this->pluginList->sortPluginsAndRespectDependencies($pluginsToLoad); |
| $section = PiwikConfig::getInstance()->Plugins; |
| $section['Plugins'] = $pluginsToLoad; |
| PiwikConfig::getInstance()->Plugins = $section; |
| } |
|
|
| |
| |
| |
| |
| |
| private function updatePluginsInstalledConfig($plugins) |
| { |
| $section = PiwikConfig::getInstance()->PluginsInstalled; |
| $section['PluginsInstalled'] = $plugins; |
| PiwikConfig::getInstance()->PluginsInstalled = $section; |
| } |
|
|
| |
| |
| |
| public function clearPluginsInstalledConfig() |
| { |
| $this->updatePluginsInstalledConfig([]); |
| PiwikConfig::getInstance()->forceSave(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function isPluginAlwaysActivated($name) |
| { |
| return in_array($name, self::$pluginToAlwaysActivate); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function isPluginUninstallable($name) |
| { |
| return !$this->isPluginBundledWithCore($name); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function isPluginActivated($name) |
| { |
| return in_array($name, $this->pluginsToLoad) |
| || ($this->doLoadAlwaysActivatedPlugins && $this->isPluginAlwaysActivated($name)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function doesPluginRequireInternetConnection($name) |
| { |
| $plugin = $this->makePluginClass($name); |
| return $plugin->requiresInternetConnection(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function checkIsPluginActivated($pluginName): void |
| { |
| if (!$this->isPluginInFilesystem($pluginName)) { |
| throw new PluginNotFoundException($pluginName); |
| } |
| if (!$this->isPluginActivated($pluginName)) { |
| throw new PluginDeactivatedException($pluginName); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function isPluginLoaded($name) |
| { |
| return isset($this->loadedPlugins[$name]); |
| } |
|
|
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| public static function initPluginDirectories() |
| { |
| $envDirs = getenv('MATOMO_PLUGIN_DIRS'); |
| if (!empty($envDirs)) { |
| |
| 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'); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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; |
| } |
| } |
| } |
| }); |
| } |
|
|
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| public function getWebRootDirectoriesForCustomPluginDirs() |
| { |
| return array_intersect_key(self::$pluginsToWebRootDirCache, array_flip($this->pluginsToLoad)); |
| } |
|
|
| |
| |
| |
| public function getPluginUmdsToLoadOnDemand() |
| { |
| $pluginsToLoadOnDemand = []; |
| foreach ($this->getPluginsLoadedAndActivated() as $plugin) { |
| if (method_exists($plugin, 'shouldLoadUmdOnDemand') && $plugin->shouldLoadUmdOnDemand()) { |
| $pluginsToLoadOnDemand[] = $plugin->getPluginName(); |
| } |
| } |
| return $pluginsToLoadOnDemand; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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) { |
| |
| $real = realpath($path); |
| if ($real && Common::stringEndsWith($path, '/')) { |
| return rtrim($real, '/') . '/'; |
| } |
| if ($real) { |
| return $real; |
| } |
| } |
| return $path; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| public static function getPluginDirectory($pluginName) |
| { |
| if (isset(self::$pluginsToPathCache[$pluginName])) { |
| return self::$pluginsToPathCache[$pluginName]; |
| } |
|
|
| $corePluginsDir = PIWIK_INCLUDE_PATH . '/plugins/' . $pluginName; |
| if (is_dir($corePluginsDir)) { |
| |
| 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; |
| } |
| } |
|
|
| |
| return self::getPluginsDirectory() . $pluginName; |
| } |
|
|
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public static function getPluginsDirectory() |
| { |
| $path = rtrim(PIWIK_INCLUDE_PATH, '/') . '/plugins/'; |
| $path = self::getPluginRealPath($path); |
| return $path; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function deactivatePlugin($pluginName) |
| { |
| $plugins = $this->pluginList->getActivatedPlugins(); |
| if (!in_array($pluginName, $plugins)) { |
| |
| return; |
| } |
|
|
| $this->clearCache($pluginName); |
|
|
| |
| $this->executePluginDeactivate($pluginName); |
|
|
| $this->savePluginTime(self::LAST_PLUGIN_DEACTIVATION_TIME_OPTION_PREFIX, $pluginName); |
|
|
| $this->unloadPluginFromMemory($pluginName); |
|
|
| $this->removePluginFromConfig($pluginName); |
|
|
| |
| |
| |
| |
| |
| Piwik::postEvent('PluginManager.pluginDeactivated', array($pluginName)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| Piwik::postEvent('PluginManager.pluginUninstalled', array($pluginName)); |
|
|
| return true; |
| } |
|
|
| private function clearCache(string $pluginName): void |
| { |
| $this->resetTransientCache(); |
| Filesystem::deleteAllCacheOnUpdate($pluginName); |
| } |
|
|
| |
| |
| |
| |
| public static function deletePluginFromFilesystem($plugin) |
| { |
| $pluginDir = self::getPluginDirectory($plugin); |
| if (strpos($pluginDir, PIWIK_INCLUDE_PATH) === 0) { |
| |
| Filesystem::unlinkRecursive($pluginDir, $deleteRootToo = true); |
| } |
| } |
|
|
| |
| |
| |
| 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 ($this->isPluginAlwaysActivated($pluginName) && !in_array($pluginName, $pluginsActivated)) { |
| $this->activatePlugin($pluginName); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function activatePlugin($pluginName) |
| { |
| $plugins = $this->pluginList->getActivatedPlugins(); |
| if (in_array($pluginName, $plugins)) { |
| |
| return; |
| } |
|
|
| if (!$this->isPluginInFilesystem($pluginName)) { |
| throw new PluginNotFoundException($pluginName); |
| } |
| $this->deactivateThemeIfTheme($pluginName); |
|
|
| |
| $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); |
|
|
| |
| |
| |
| |
| |
| Piwik::postEvent('PluginManager.pluginActivated', array($pluginName)); |
| } |
|
|
| |
| |
| |
| |
| public function isPluginInFilesystem($pluginName) |
| { |
| $existingPlugins = $this->readPluginsDirectory(); |
| $isPluginInFilesystem = array_search($pluginName, $existingPlugins) !== false; |
| return $this->isValidPluginName($pluginName) |
| && $isPluginInFilesystem; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function getThemeEnabled(): ?Plugin |
| { |
| $plugins = $this->getLoadedPlugins(); |
|
|
| $theme = null; |
| foreach ($plugins as $plugin) { |
| |
| if ( |
| $plugin->isTheme() |
| && $this->isPluginActivated($plugin->getPluginName()) |
| ) { |
| if ($plugin->getPluginName() != self::DEFAULT_THEME) { |
| return $plugin; |
| } |
| $theme = $plugin; |
| } |
| } |
| 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); |
| } |
|
|
| |
| |
| |
| public function getNumberOfActivatedPluginsExcludingAlwaysActivated() |
| { |
| $counter = 0; |
|
|
| $pluginNames = $this->getLoadedPluginsName(); |
| foreach ($pluginNames as $pluginName) { |
| if ( |
| $this->isPluginActivated($pluginName) |
| && !$this->isPluginAlwaysActivated($pluginName) |
| ) { |
| $counter++; |
| } |
| } |
|
|
| return $counter; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function loadAllPluginsAndGetTheirInfo() |
| { |
| |
| $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) { |
| |
| if ($this->isPluginBogus($pluginName)) { |
| continue; |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function isPluginBundledWithCore($name) |
| { |
| return $this->isPluginEnabledByDefault($name) |
| || in_array($name, $this->pluginList->getCorePluginsDisabledByDefault()) |
| || $name == self::DEFAULT_THEME; |
| } |
|
|
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function loadPlugins(array $pluginsToLoad) |
| { |
| $this->resetTransientCache(); |
| $this->pluginsToLoad = $this->makePluginsToLoad($pluginsToLoad); |
| $this->reloadActivatedPlugins(); |
| } |
|
|
| |
| |
| |
| |
| public function doNotLoadPlugins() |
| { |
| $this->doLoadPlugins = false; |
| } |
|
|
| |
| |
| |
| |
| public function doNotLoadAlwaysActivatedPlugins() |
| { |
| $this->doLoadAlwaysActivatedPlugins = false; |
| } |
|
|
| |
| |
| |
| |
| public function postLoadPlugins() |
| { |
| $plugins = $this->getLoadedPlugins(); |
| foreach ($plugins as $plugin) { |
| $plugin->postLoad(); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function getLoadedPluginsName() |
| { |
| return array_keys($this->getLoadedPlugins()); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function getLoadedPlugins() |
| { |
| return $this->loadedPlugins; |
| } |
|
|
| |
| |
| |
| |
| public function getIncompatiblePlugins($piwikVersion) |
| { |
| $plugins = $this->getLoadedPlugins(); |
|
|
| $incompatible = []; |
| foreach ($plugins as $plugin) { |
| if ($plugin->hasMissingDependencies($piwikVersion)) { |
| $incompatible[] = $plugin; |
| } |
| } |
|
|
| return $incompatible; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function getActivatedPlugins() |
| { |
| return $this->pluginsToLoad; |
| } |
|
|
| |
| |
| |
| public function getActivatedPluginsFromConfig() |
| { |
| $plugins = $this->pluginList->getActivatedPlugins(); |
|
|
| return $this->makePluginsToLoad($plugins); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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]; |
| } |
|
|
| |
| |
| |
| |
| private function reloadActivatedPlugins(): void |
| { |
| $pluginsToPostPendingEventsTo = []; |
| foreach ($this->pluginsToLoad as $pluginName) { |
| $pluginsToPostPendingEventsTo = $this->reloadActivatedPlugin($pluginName, $pluginsToPostPendingEventsTo); |
| } |
|
|
| |
| foreach ($pluginsToPostPendingEventsTo as $plugin) { |
| EventDispatcher::getInstance()->postPendingEventsTo($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); |
|
|
| |
| |
| $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()) { |
| |
| |
| 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 { |
| |
| $pluginLicenseInfo = ['missing' => false]; |
| } |
|
|
| if (!empty($pluginLicenseInfo['missing']) && (!defined('PIWIK_TEST_MODE') || !PIWIK_TEST_MODE)) { |
| $this->unloadPluginFromMemory($pluginName); |
| return $pluginsToPostPendingEventsTo; |
| } |
| } |
|
|
| $pluginsToPostPendingEventsTo[] = $newPlugin; |
|
|
| return $pluginsToPostPendingEventsTo; |
| } |
|
|
| |
| |
| |
| public static function getLicenseCache() |
| { |
| return Cache::getLazyCache(); |
| } |
|
|
| |
| |
| |
| public function getIgnoredBogusPlugins() |
| { |
| $ignored = array(); |
| foreach ($this->pluginsToLoad as $pluginName) { |
| if ($this->isPluginThirdPartyAndBogus($pluginName)) { |
| $ignored[] = $pluginName; |
| } |
| } |
| return $ignored; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| public static function getAlwaysActivatedPlugins(): array |
| { |
| return self::$pluginToAlwaysActivate; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function loadPlugin($pluginName) |
| { |
| if (isset($this->loadedPlugins[$pluginName])) { |
| return $this->loadedPlugins[$pluginName]; |
| } |
|
|
| $newPlugin = $this->makePluginClass($pluginName); |
|
|
| $this->addLoadedPlugin($pluginName, $newPlugin); |
| return $newPlugin; |
| } |
|
|
| |
| |
| |
| |
| public function isValidPluginName($pluginName) |
| { |
| return (bool) preg_match('/^[a-zA-Z]([a-zA-Z0-9_]){0,59}$/D', $pluginName); |
| } |
|
|
| |
| |
| |
| |
| 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)) { |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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()]); |
| } |
|
|
| |
| |
| |
| |
| public function unloadPlugins() |
| { |
| $this->resetTransientCache(); |
|
|
| $pluginsLoaded = $this->getLoadedPlugins(); |
| foreach ($pluginsLoaded as $plugin) { |
| $this->unloadPlugin($plugin); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function executePluginInstall(Plugin $plugin) |
| { |
| try { |
| $plugin->install(); |
| } catch (\Exception $e) { |
| throw new \Piwik\Plugin\PluginException($plugin->getPluginName(), $e->getMessage()); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function addLoadedPlugin($pluginName, Plugin $newPlugin) |
| { |
| $this->resetTransientCache(); |
|
|
| $this->loadedPlugins[$pluginName] = $newPlugin; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function getInstalledPluginsName() |
| { |
| return Config::getInstance()->PluginsInstalled['PluginsInstalled']; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function getMissingPlugins() |
| { |
| $missingPlugins = []; |
|
|
| $plugins = $this->pluginList->getActivatedPlugins(); |
|
|
| foreach ($plugins as $pluginName) { |
| |
| if ( |
| !$this->isPluginLoaded($pluginName) && !$this->isPluginBogus($pluginName) && |
| !($this->doesPluginRequireInternetConnection($pluginName) && !SettingsPiwik::isInternetEnabled()) |
| ) { |
| $missingPlugins[] = $pluginName; |
| } |
| } |
|
|
| return $missingPlugins; |
| } |
|
|
| |
| |
| |
| private function installPluginIfNecessary(Plugin $plugin): void |
| { |
| $pluginName = $plugin->getPluginName(); |
| $saveConfig = false; |
|
|
| |
| $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; |
|
|
| |
| |
| |
| |
| |
| |
| |
| Piwik::postEvent('PluginManager.pluginInstalled', array($pluginName)); |
| } |
|
|
| if ($saveConfig) { |
| PiwikConfig::getInstance()->forceSave(); |
| $this->clearCache($pluginName); |
| } |
| } |
|
|
| |
| |
| |
| 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', |
| 'DoNotTrack', |
| 'AnonymizeIP', |
| 'wp-optimize', |
| ); |
| return in_array($pluginName, $bogusPlugins); |
| } |
|
|
| private function deactivateThemeIfTheme(string $pluginName): void |
| { |
| |
| $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) { |
| } |
| } |
|
|
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| private function uninstallDimension(string $prefix, Dimension $dimension, $allDimensions): void |
| { |
| if (!$this->doesAnotherPluginDefineSameColumnWithDbEntry($dimension, $allDimensions)) { |
| $dimension->uninstall(); |
|
|
| $this->removeInstalledVersionFromOptionTable($prefix . $dimension->getColumnName()); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| 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); |
| } |
|
|
| |
| |
| |
| |
| 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); |
| } |
|
|
| |
| |
| |
| public function loadPluginTranslations() |
| { |
| |
| $translator = StaticContainer::get('Piwik\Translation\Translator'); |
| foreach ($this->getAllPluginsNames() as $pluginName) { |
| $translator->addDirectory(self::getPluginDirectory($pluginName) . '/lang'); |
| } |
| } |
|
|
| |
| |
| |
| 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; |
| } |
| |
| } |
| } |
|
|
| 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; |
| } |
| } |
| } |
| } |
|
|