Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--core/API/Proxy.php2
-rw-r--r--core/AssetManager/UIAsset/OnDiskUIAsset.php19
-rw-r--r--core/AssetManager/UIAssetFetcher.php33
-rw-r--r--core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php36
-rw-r--r--core/Columns/Updater.php10
-rw-r--r--core/Container/ContainerFactory.php4
-rw-r--r--core/Plugin.php9
-rw-r--r--core/Plugin/ArchivedMetric.php5
-rw-r--r--core/Plugin/Manager.php180
-rw-r--r--core/Plugin/MetadataLoader.php2
-rw-r--r--core/Theme.php12
-rw-r--r--core/Twig.php28
-rw-r--r--core/Updater.php14
-rw-r--r--core/bootstrap.php2
-rw-r--r--plugins/CoreConsole/Commands/GenerateAngularComponent.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateAngularDirective.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateApi.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateArchiver.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateCommand.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateController.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateDimension.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateMenu.php3
-rw-r--r--plugins/CoreConsole/Commands/GeneratePlugin.php4
-rw-r--r--plugins/CoreConsole/Commands/GeneratePluginBase.php34
-rw-r--r--plugins/CoreConsole/Commands/GenerateReport.php2
-rw-r--r--plugins/CoreConsole/Commands/GenerateScheduledTask.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateSettings.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateTest.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateUpdate.php2
-rw-r--r--plugins/CoreConsole/Commands/GenerateVisualizationPlugin.php3
-rw-r--r--plugins/CoreConsole/Commands/GenerateWidget.php3
-rw-r--r--plugins/CorePluginsAdmin/Controller.php3
-rw-r--r--plugins/CorePluginsAdmin/PluginInstaller.php7
-rw-r--r--plugins/ExampleTheme/ExampleTheme.php5
-rw-r--r--plugins/LanguagesManager/API.php22
-rw-r--r--plugins/LanguagesManager/Commands/PluginsWithTranslations.php9
-rw-r--r--plugins/LanguagesManager/Commands/Update.php19
-rw-r--r--plugins/LanguagesManager/TranslationWriter/Writer.php2
-rw-r--r--tests/PHPUnit/Fixtures/UITestFixture.php4
-rw-r--r--tests/PHPUnit/bootstrap.php7
-rw-r--r--tests/PHPUnit/phpunit.xml.dist8
-rw-r--r--tests/PHPUnit/proxy/includes.php7
-rw-r--r--tests/UI/expected-screenshots/UIIntegrationTest_admin_settings_general.png4
-rw-r--r--tests/UI/expected-screenshots/UIIntegrationTest_customdirplugin.png3
-rw-r--r--tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png4
-rw-r--r--tests/UI/specs/UIIntegration_spec.js8
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/.gitignore1
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/API.php22
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/Controller.php23
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/CustomClass.php15
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/CustomDirPlugin.php47
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/SystemSettings.php34
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/config/config.php4
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/images/ok.pngbin0 -> 626 bytes
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/lang/en.json5
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/plugin.json29
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/stylesheets/import.less3
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/stylesheets/test.less15
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/templates/index.twig27
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/tests/Fixtures/SimpleFixtureTrackFewVisits.php35
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/tests/Integration/SystemSettingsTest.php49
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/tests/System/APITest.php44
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/tests/System/expected/test___API.get_day.xml44
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/tests/System/expected/test___Goals.getItemsSku_day.xml12
-rw-r--r--tests/resources/custompluginsdir/CustomDirPlugin/tests/Unit/CustomClassTest.php35
-rw-r--r--tests/resources/custompluginsdir/javascripts/test.js1
67 files changed, 887 insertions, 110 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e6def5b37a..aae2491c48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,14 +7,15 @@ The Product Changelog at **[matomo.org/changelog](https://matomo.org/changelog)*
## Matomo 3.9.0
### Breaking Changes
-
* `Referrers.getKeywordsForPageUrl` and `Referrers.getKeywordsForPageTitle` APIs have been deprecated and will be removed in Matomo 4.0.0
* By default, Matomo [application logs](https://matomo.org/faq/troubleshooting/faq_115/) will now be logged in `tmp/logs/matomo.log` instead of `tmp/logs/piwik.log`. This log file path can be edited in your config/config.ini.php in the INI setting `logger_file_path`.
### New Features
+* It is now possible to locate plugins in a custom directory by setting an environment variable `MATOMO_PLUGIN_DIRS` or a `$GLOBALS['MATOMO_PLUGIN_DIRS']` variable in `$MATOMO_ROOT/bootstrap.php`.
* It is now possible to use monolog's FingersCrossedHandler which buffers all logs and logs all of them in case of warning or error.
### New APIs
+* New API methods `Piwik\Plugin\Manager::getPluginsDirectories()` and `Piwik\Plugin\Manager::getPluginDirectory($pluginName)` have been added as it is now possible to locate Matomo plugins in different directories and it should be no longer assumed a plugin is located in the "/plugins" directory.
* A new tracker method `disableQueueRequest` has been added to disable queued requests which may be useful when logs are imported.
* The event `LanguageManager.getAvailableLanguages` has been deprecated. Use `LanguagesManager.getAvailableLanguages` instead.
diff --git a/core/API/Proxy.php b/core/API/Proxy.php
index 1f2ca5a59d..833e905372 100644
--- a/core/API/Proxy.php
+++ b/core/API/Proxy.php
@@ -442,7 +442,7 @@ class Proxy
private function includeApiFile($fileName)
{
$module = self::getModuleNameFromClassName($fileName);
- $path = Manager::getPluginsDirectory() . $module . '/API.php';
+ $path = Manager::getPluginDirectory($module) . '/API.php';
if (is_readable($path)) {
require_once $path; // prefixed by PIWIK_INCLUDE_PATH
diff --git a/core/AssetManager/UIAsset/OnDiskUIAsset.php b/core/AssetManager/UIAsset/OnDiskUIAsset.php
index cbd702a691..48557480e0 100644
--- a/core/AssetManager/UIAsset/OnDiskUIAsset.php
+++ b/core/AssetManager/UIAsset/OnDiskUIAsset.php
@@ -10,6 +10,7 @@ namespace Piwik\AssetManager\UIAsset;
use Exception;
use Piwik\AssetManager\UIAsset;
+use Piwik\Common;
use Piwik\Filesystem;
class OnDiskUIAsset extends UIAsset
@@ -25,13 +26,26 @@ class OnDiskUIAsset extends UIAsset
private $relativeLocation;
/**
+ * @var string
+ */
+ private $relativeRootDir;
+
+ /**
* @param string $baseDirectory
* @param string $fileLocation
*/
- public function __construct($baseDirectory, $fileLocation)
+ public function __construct($baseDirectory, $fileLocation, $relativeRootDir = '')
{
$this->baseDirectory = $baseDirectory;
$this->relativeLocation = $fileLocation;
+
+ if (!empty($relativeRootDir)
+ && is_string($relativeRootDir)
+ && !Common::stringEndsWith($relativeRootDir, '/')) {
+ $relativeRootDir .= '/';
+ }
+
+ $this->relativeRootDir = $relativeRootDir;
}
public function getAbsoluteLocation()
@@ -41,6 +55,9 @@ class OnDiskUIAsset extends UIAsset
public function getRelativeLocation()
{
+ if (isset($this->relativeRootDir)) {
+ return $this->relativeRootDir . $this->relativeLocation;
+ }
return $this->relativeLocation;
}
diff --git a/core/AssetManager/UIAssetFetcher.php b/core/AssetManager/UIAssetFetcher.php
index 0b8e9b3a55..c9086b0e71 100644
--- a/core/AssetManager/UIAssetFetcher.php
+++ b/core/AssetManager/UIAssetFetcher.php
@@ -9,6 +9,7 @@
namespace Piwik\AssetManager;
use Piwik\AssetManager\UIAsset\OnDiskUIAsset;
+use Piwik\Plugin\Manager;
use Piwik\Theme;
abstract class UIAssetFetcher
@@ -89,9 +90,39 @@ abstract class UIAssetFetcher
private function populateCatalog()
{
+ $pluginBaseDir = Manager::getPluginsDirectory();
+ $pluginWebDirectories = Manager::getAlternativeWebRootDirectories();
+ $matomoRootDir = $this->getBaseDirectory();
+
foreach ($this->fileLocations as $fileLocation) {
+ $fileAbsolute = $matomoRootDir . '/' . $fileLocation;
+
$newUIAsset = new OnDiskUIAsset($this->getBaseDirectory(), $fileLocation);
- $this->catalog->addUIAsset($newUIAsset);
+ if ($newUIAsset->exists()) {
+ $this->catalog->addUIAsset($newUIAsset);
+ continue;
+ }
+
+ $found = false;
+
+ if (strpos($fileAbsolute, $pluginBaseDir) === 0) {
+ // we iterate over all custom plugin directories only for plugin files, not libs files (not needed there)
+ foreach ($pluginWebDirectories as $pluginDirectory => $relative) {
+ $fileTest = str_replace($pluginBaseDir, $pluginDirectory, $fileAbsolute);
+ $newFileRelative = str_replace($pluginDirectory, '', $fileTest);
+ $testAsset = new OnDiskUIAsset($pluginDirectory, $newFileRelative, $relative);
+ if ($testAsset->exists()) {
+ $this->catalog->addUIAsset($testAsset);
+ $found = true;
+ break;
+ }
+ }
+ }
+
+ if (!$found) {
+ // we add it anyway so it'll trigger an error about the missing file
+ $this->catalog->addUIAsset($newUIAsset);
+ }
}
}
diff --git a/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php b/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php
index a485a513c9..b3cebc8841 100644
--- a/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php
+++ b/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php
@@ -15,6 +15,7 @@ use Piwik\AssetManager\UIAssetMerger;
use Piwik\Common;
use Piwik\Exception\StylesheetLessCompileException;
use Piwik\Piwik;
+use Piwik\Plugin\Manager;
class StylesheetUIAssetMerger extends UIAssetMerger
{
@@ -191,8 +192,9 @@ class StylesheetUIAssetMerger extends UIAssetMerger
private function getCssPathsRewriter($uiAsset)
{
$baseDirectory = dirname($uiAsset->getRelativeLocation());
+ $webDirs = Manager::getAlternativeWebRootDirectories();
- return function ($matches) use ($baseDirectory) {
+ return function ($matches) use ($baseDirectory, $webDirs) {
$absolutePath = PIWIK_DOCUMENT_ROOT . "/$baseDirectory/" . $matches[2];
// Allow to import extension less file
@@ -201,13 +203,39 @@ class StylesheetUIAssetMerger extends UIAssetMerger
}
// Prevent from rewriting full path
- $absolutePath = realpath($absolutePath);
- if ($absolutePath) {
+ $absolutePathReal = realpath($absolutePath);
+ if ($absolutePathReal) {
$relativePath = $baseDirectory . "/" . $matches[2];
$relativePath = str_replace('\\', '/', $relativePath);
$publicPath = $matches[1] . $relativePath;
} else {
- $publicPath = $matches[1] . $matches[2];
+ foreach ($webDirs as $absPath => $relativePath) {
+ if (strpos($baseDirectory, $relativePath) === 0) {
+ if (strpos($matches[2], '.') === 0) {
+ // eg ../images/ok.png
+ $fileRelative = $baseDirectory . '/' . $matches[2];
+ $fileAbsolute = $absPath . str_replace($relativePath, '', $fileRelative);
+ if (file_exists($fileAbsolute)) {
+ return $matches[1] . $fileRelative;
+ }
+ } elseif (strpos($matches[2], 'plugins/') === 0) {
+ // eg plugins/Foo/images/ok.png
+ $fileRelative = substr($matches[2], strlen('plugins/'));
+ $fileAbsolute = $absPath . $fileRelative;
+ if (file_exists($fileAbsolute)) {
+ return $matches[1] . $relativePath . $fileRelative;
+ }
+ } elseif ($matches[1] === '@import "') {
+ $fileRelative = $baseDirectory . '/' . $matches[2];
+ $fileAbsolute = $absPath . str_replace($relativePath, '', $fileRelative);
+ if (file_exists($fileAbsolute)) {
+ return $matches[1] . $baseDirectory . '/' . $matches[2];
+ }
+ }
+ }
+ }
+
+ $publicPath = $matches[1] . $matches[2];
}
return $publicPath;
diff --git a/core/Columns/Updater.php b/core/Columns/Updater.php
index 1c750753cc..fa41a19d9e 100644
--- a/core/Columns/Updater.php
+++ b/core/Columns/Updater.php
@@ -342,11 +342,13 @@ class Updater extends \Piwik\Updates
private static function getCurrentDimensionFileChanges()
{
- $files = Filesystem::globr(Manager::getPluginsDirectory() . '*/Columns', '*.php');
-
$times = array();
- foreach ($files as $file) {
- $times[$file] = filemtime($file);
+ foreach (Manager::getPluginsDirectories() as $pluginsDir) {
+ $files = Filesystem::globr($pluginsDir . '*/Columns', '*.php');
+
+ foreach ($files as $file) {
+ $times[$file] = filemtime($file);
+ }
}
return $times;
diff --git a/core/Container/ContainerFactory.php b/core/Container/ContainerFactory.php
index e1696db194..39d398782d 100644
--- a/core/Container/ContainerFactory.php
+++ b/core/Container/ContainerFactory.php
@@ -121,7 +121,7 @@ class ContainerFactory
// add plugin environment configs
$plugins = $this->pluginList->getActivatedPlugins();
foreach ($plugins as $plugin) {
- $baseDir = Manager::getPluginsDirectory() . $plugin;
+ $baseDir = Manager::getPluginDirectory($plugin);
$environmentFile = $baseDir . '/config/' . $environment . '.php';
if (file_exists($environmentFile)) {
@@ -135,7 +135,7 @@ class ContainerFactory
$plugins = $this->pluginList->getActivatedPlugins();
foreach ($plugins as $plugin) {
- $baseDir = Manager::getPluginsDirectory() . $plugin;
+ $baseDir = Manager::getPluginDirectory($plugin);
$file = $baseDir . '/config/config.php';
if (file_exists($file)) {
diff --git a/core/Plugin.php b/core/Plugin.php
index fcfdc612a7..c0ac9ecb0a 100644
--- a/core/Plugin.php
+++ b/core/Plugin.php
@@ -355,9 +355,9 @@ class Plugin
$cacheId = 'Plugin' . $this->pluginName . $componentName . $expectedSubclass;
- $pluginsDir = Manager::getPluginsDirectory();
+ $pluginsDir = Manager::getPluginDirectory($this->pluginName);
- $componentFile = sprintf('%s%s/%s.php', $pluginsDir, $this->pluginName, $componentName);
+ $componentFile = sprintf('%s/%s.php', $pluginsDir, $componentName);
if ($this->cache->contains($cacheId)) {
$classname = $this->cache->fetch($cacheId);
@@ -537,8 +537,9 @@ class Plugin
{
$components = array();
- $pluginsDir = Manager::getPluginsDirectory();
- $baseDir = $pluginsDir . $this->pluginName . '/' . $directoryWithinPlugin;
+ $pluginsDir = Manager::getPluginDirectory($this->pluginName);
+ $baseDir = $pluginsDir . '/' . $directoryWithinPlugin;
+
$files = Filesystem::globr($baseDir, '*.php');
foreach ($files as $file) {
diff --git a/core/Plugin/ArchivedMetric.php b/core/Plugin/ArchivedMetric.php
index 05fa3d8665..f68a1f2c10 100644
--- a/core/Plugin/ArchivedMetric.php
+++ b/core/Plugin/ArchivedMetric.php
@@ -64,6 +64,11 @@ class ArchivedMetric extends Metric
$this->aggregation = $aggregation;
}
+ public function getAggregation()
+ {
+ return $this->aggregation;
+ }
+
public function setDimension($dimension)
{
$this->dimension = $dimension;
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');
}
}
}
diff --git a/core/Plugin/MetadataLoader.php b/core/Plugin/MetadataLoader.php
index 80eae18a80..a28f4cfc43 100644
--- a/core/Plugin/MetadataLoader.php
+++ b/core/Plugin/MetadataLoader.php
@@ -133,7 +133,7 @@ class MetadataLoader
*/
private function getPathToPluginFolder()
{
- return \Piwik\Plugin\Manager::getPluginsDirectory() . $this->pluginName;
+ return \Piwik\Plugin\Manager::getPluginDirectory($this->pluginName);
}
/**
diff --git a/core/Theme.php b/core/Theme.php
index ed6261334c..e6eb8756c9 100644
--- a/core/Theme.php
+++ b/core/Theme.php
@@ -75,7 +75,8 @@ class Theme
public function rewriteAssetsPathToTheme($output)
{
- if ($this->themeName == \Piwik\Plugin\Manager::DEFAULT_THEME) {
+ if ($this->themeName == \Piwik\Plugin\Manager::DEFAULT_THEME
+ && !Manager::getAlternativeWebRootDirectories()) {
return $output;
}
@@ -140,6 +141,15 @@ class Theme
if (file_exists($fileToCheck)) {
return str_replace($pathAsset, $overridingAsset, $source);
}
+
+ // not rewritten by theme, but may be located in custom webroot directory
+ foreach (Manager::getAlternativeWebRootDirectories() as $absDir => $webRootDirectory) {
+ $withoutPlugins = str_replace('plugins/', '', $pathAsset);
+ if (file_exists($absDir . '/' . $withoutPlugins)) {
+ return $webRootDirectory . $withoutPlugins;
+ }
+ }
+
return $source;
}
diff --git a/core/Twig.php b/core/Twig.php
index 4b5f1e51ea..cac8624201 100644
--- a/core/Twig.php
+++ b/core/Twig.php
@@ -313,9 +313,8 @@ class Twig
*/
private function getDefaultThemeLoader()
{
- $themeLoader = new Twig_Loader_Filesystem(array(
- sprintf("%s%s/templates/", Manager::getPluginsDirectory(), \Piwik\Plugin\Manager::DEFAULT_THEME)
- ), PIWIK_DOCUMENT_ROOT.DIRECTORY_SEPARATOR);
+ $themeDir = Manager::getPluginDirectory(\Piwik\Plugin\Manager::DEFAULT_THEME) . '/templates/';
+ $themeLoader = new Twig_Loader_Filesystem(array($themeDir), PIWIK_DOCUMENT_ROOT.DIRECTORY_SEPARATOR);
return $themeLoader;
}
@@ -327,13 +326,13 @@ class Twig
*/
protected function getCustomThemeLoader(Plugin $theme)
{
- $pluginsDir = Manager::getPluginsDirectory();
- if (!file_exists(sprintf("%s%s/templates/", $pluginsDir, $theme->getPluginName()))) {
+ $pluginsDir = Manager::getPluginDirectory($theme->getPluginName());
+ $themeDir = $pluginsDir . '/templates/';
+
+ if (!file_exists($themeDir)) {
return false;
}
- $themeLoader = new Twig_Loader_Filesystem(array(
- sprintf("%s%s/templates/", $pluginsDir, $theme->getPluginName())
- ), PIWIK_DOCUMENT_ROOT.DIRECTORY_SEPARATOR);
+ $themeLoader = new Twig_Loader_Filesystem(array($themeDir), PIWIK_DOCUMENT_ROOT.DIRECTORY_SEPARATOR);
return $themeLoader;
}
@@ -522,11 +521,11 @@ class Twig
$pluginManager = \Piwik\Plugin\Manager::getInstance();
$plugins = $pluginManager->getAllPluginsNames();
- $pluginsDir = Manager::getPluginsDirectory();
foreach ($plugins as $name) {
- $path = sprintf("%s%s/templates/", $pluginsDir, $name);
+ $pluginsDir = Manager::getPluginDirectory($name);
+ $path = sprintf("%s/templates/", $pluginsDir);
if (is_dir($path)) {
- $loader->addPath($pluginsDir . $name . '/templates', $name);
+ $loader->addPath(rtrim($path, '/'), $name);
}
}
}
@@ -541,12 +540,11 @@ class Twig
$pluginManager = \Piwik\Plugin\Manager::getInstance();
$plugins = $pluginManager->getAllPluginsNames();
- $pluginsDir = Manager::getPluginsDirectory();
-
foreach ($plugins as $name) {
- $path = sprintf("%s%s/templates/plugins/%s/", $pluginsDir, $pluginName, $name);
+ $pluginsDir = Manager::getPluginDirectory($name);
+ $path = sprintf("%s/templates/plugins/%s/", $pluginsDir, $name);
if (is_dir($path)) {
- $loader->addPath($pluginsDir . $pluginName . '/templates/plugins/'. $name, $name);
+ $loader->addPath(rtrim($path, '/'), $name);
}
}
}
diff --git a/core/Updater.php b/core/Updater.php
index 9af07459b2..85c52e309b 100644
--- a/core/Updater.php
+++ b/core/Updater.php
@@ -63,7 +63,13 @@ class Updater
public function __construct($pathUpdateFileCore = null, $pathUpdateFilePlugins = null, Columns\Updater $columnsUpdater = null)
{
$this->pathUpdateFileCore = $pathUpdateFileCore ?: PIWIK_INCLUDE_PATH . '/core/Updates/';
- $this->pathUpdateFilePlugins = $pathUpdateFilePlugins ?: Manager::getPluginsDirectory() . '%s/Updates/';
+
+ if ($pathUpdateFilePlugins) {
+ $this->pathUpdateFilePlugins = $pathUpdateFilePlugins;
+ } else {
+ $this->pathUpdateFilePlugins = null;
+ }
+
$this->columnsUpdater = $columnsUpdater ?: new Columns\Updater();
self::$activeInstance = $this;
@@ -345,7 +351,11 @@ class Updater
} elseif (ColumnUpdater::isDimensionComponent($name)) {
$componentsWithUpdateFile[$name][PIWIK_INCLUDE_PATH . '/core/Columns/Updater.php'] = $newVersion;
} else {
- $pathToUpdates = sprintf($this->pathUpdateFilePlugins, $name) . '*.php';
+ if ($this->pathUpdateFilePlugins) {
+ $pathToUpdates = sprintf($this->pathUpdateFilePlugins, $name) . '*.php';
+ } else {
+ $pathToUpdates = Manager::getPluginDirectory($name) . '/Updates/*.php';
+ }
}
if (!empty($pathToUpdates)) {
diff --git a/core/bootstrap.php b/core/bootstrap.php
index f0cae7495e..004ce59327 100644
--- a/core/bootstrap.php
+++ b/core/bootstrap.php
@@ -41,6 +41,8 @@ require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/upgrade.php';
// Composer autoloader
require_once PIWIK_VENDOR_PATH . '/autoload.php';
+\Piwik\Plugin\Manager::initPluginDirectories();
+
/**
* Eaccelerator does not support closures and is known to be not comptabile with Piwik. Therefore we are disabling
* it automatically. At this point it looks like Eaccelerator is no longer under development and the bug has not
diff --git a/plugins/CoreConsole/Commands/GenerateAngularComponent.php b/plugins/CoreConsole/Commands/GenerateAngularComponent.php
index 081da16b00..c5a7de2ef5 100644
--- a/plugins/CoreConsole/Commands/GenerateAngularComponent.php
+++ b/plugins/CoreConsole/Commands/GenerateAngularComponent.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -38,7 +39,7 @@ class GenerateAngularComponent extends GenerateAngularConstructBase
. $pluginName);
}
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array(
'ExamplePlugin' => $pluginName,
'example-component' => $componentLower,
diff --git a/plugins/CoreConsole/Commands/GenerateAngularDirective.php b/plugins/CoreConsole/Commands/GenerateAngularDirective.php
index c5c0a767c7..605f417dfd 100644
--- a/plugins/CoreConsole/Commands/GenerateAngularDirective.php
+++ b/plugins/CoreConsole/Commands/GenerateAngularDirective.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -39,7 +40,7 @@ class GenerateAngularDirective extends GenerateAngularConstructBase
throw new \Exception('The AngularJS directive ' . $directiveLower . ' already exists in plugin ' . $pluginName);
}
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array(
'ExamplePlugin' => $pluginName,
'directive-component' => $directiveLower,
diff --git a/plugins/CoreConsole/Commands/GenerateApi.php b/plugins/CoreConsole/Commands/GenerateApi.php
index 9fc3c5b7ae..cbaaa9cdc9 100644
--- a/plugins/CoreConsole/Commands/GenerateApi.php
+++ b/plugins/CoreConsole/Commands/GenerateApi.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -29,7 +30,7 @@ class GenerateApi extends GeneratePluginBase
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array('ExamplePlugin' => $pluginName);
$whitelistFiles = array('/API.php');
diff --git a/plugins/CoreConsole/Commands/GenerateArchiver.php b/plugins/CoreConsole/Commands/GenerateArchiver.php
index 29bcee7bb6..18e43c11e8 100644
--- a/plugins/CoreConsole/Commands/GenerateArchiver.php
+++ b/plugins/CoreConsole/Commands/GenerateArchiver.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -29,7 +30,7 @@ class GenerateArchiver extends GeneratePluginBase
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array('ExamplePlugin' => $pluginName, 'EXAMPLEPLUGIN' => strtoupper($pluginName));
$whitelistFiles = array('/Archiver.php');
diff --git a/plugins/CoreConsole/Commands/GenerateCommand.php b/plugins/CoreConsole/Commands/GenerateCommand.php
index 9fe95c06d3..243a61dc75 100644
--- a/plugins/CoreConsole/Commands/GenerateCommand.php
+++ b/plugins/CoreConsole/Commands/GenerateCommand.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -32,7 +33,7 @@ class GenerateCommand extends GeneratePluginBase
$commandName = $this->getCommandName($input, $output);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleCommand';
+ $exampleFolder = Manager::getPluginDirectory('ExampleCommand');
$replace = array(
'ExampleCommandDescription' => $commandName,
'ExampleCommand' => $pluginName,
diff --git a/plugins/CoreConsole/Commands/GenerateController.php b/plugins/CoreConsole/Commands/GenerateController.php
index d5c3b84078..863c3fe867 100644
--- a/plugins/CoreConsole/Commands/GenerateController.php
+++ b/plugins/CoreConsole/Commands/GenerateController.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -29,7 +30,7 @@ class GenerateController extends GeneratePluginBase
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array('ExamplePlugin' => $pluginName);
$whitelistFiles = array('/Controller.php', '/templates', '/templates/index.twig');
diff --git a/plugins/CoreConsole/Commands/GenerateDimension.php b/plugins/CoreConsole/Commands/GenerateDimension.php
index 900b6c0533..bc10670cde 100644
--- a/plugins/CoreConsole/Commands/GenerateDimension.php
+++ b/plugins/CoreConsole/Commands/GenerateDimension.php
@@ -11,6 +11,7 @@ namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Common;
use Piwik\DbHelper;
+use Piwik\Plugin\Manager;
use Piwik\Plugin\Report;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -53,7 +54,7 @@ class GenerateDimension extends GeneratePluginBase
$dimensionClassName = $this->getDimensionClassName($dimensionName);
$translatedDimensionName = $this->makeTranslationIfPossible($pluginName, ucfirst($dimensionName));
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleTracker';
+ $exampleFolder = Manager::getPluginDirectory('ExampleTracker');
$replace = array('example_action_dimension' => strtolower($columnName),
'example_visit_dimension' => strtolower($columnName),
'example_conversion_dimension' => strtolower($columnName),
diff --git a/plugins/CoreConsole/Commands/GenerateMenu.php b/plugins/CoreConsole/Commands/GenerateMenu.php
index 868a47b8f3..e6539d7c9f 100644
--- a/plugins/CoreConsole/Commands/GenerateMenu.php
+++ b/plugins/CoreConsole/Commands/GenerateMenu.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -29,7 +30,7 @@ class GenerateMenu extends GeneratePluginBase
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array('ExamplePlugin' => $pluginName);
$whitelistFiles = array('/Menu.php');
diff --git a/plugins/CoreConsole/Commands/GeneratePlugin.php b/plugins/CoreConsole/Commands/GeneratePlugin.php
index a214a40a5f..ad29a39a94 100644
--- a/plugins/CoreConsole/Commands/GeneratePlugin.php
+++ b/plugins/CoreConsole/Commands/GeneratePlugin.php
@@ -47,7 +47,7 @@ class GeneratePlugin extends GeneratePluginBase
$exampleDescription = $info['description'];
if ($isTheme) {
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleTheme';
+ $exampleFolder = Plugin\Manager::getPluginDirectory('ExampleTheme');
$replace = array(
'ExampleTheme' => $pluginName,
$exampleDescription => $description,
@@ -58,7 +58,7 @@ class GeneratePlugin extends GeneratePluginBase
} else {
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Plugin\Manager::getPluginDirectory('ExamplePlugin');
$replace = array(
'ExamplePlugin' => $pluginName,
$exampleDescription => $description,
diff --git a/plugins/CoreConsole/Commands/GeneratePluginBase.php b/plugins/CoreConsole/Commands/GeneratePluginBase.php
index 211dc94db3..6d99e13a7b 100644
--- a/plugins/CoreConsole/Commands/GeneratePluginBase.php
+++ b/plugins/CoreConsole/Commands/GeneratePluginBase.php
@@ -14,6 +14,7 @@ use Piwik\Development;
use Piwik\Filesystem;
use Piwik\Plugin\ConsoleCommand;
use Piwik\Plugin\Dependency;
+use Piwik\Plugin\Manager;
use Piwik\Version;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -27,12 +28,7 @@ abstract class GeneratePluginBase extends ConsoleCommand
public function getPluginPath($pluginName)
{
- return PIWIK_INCLUDE_PATH . $this->getRelativePluginPath($pluginName);
- }
-
- private function getRelativePluginPath($pluginName)
- {
- return '/plugins/' . $pluginName;
+ return Manager::getPluginDirectory($pluginName);
}
private function createFolderWithinPluginIfNotExists($pluginNameOrCore, $folder)
@@ -111,7 +107,7 @@ abstract class GeneratePluginBase extends ConsoleCommand
protected function checkAndUpdateRequiredPiwikVersion($pluginName, OutputInterface $output)
{
$pluginJsonPath = $this->getPluginPath($pluginName) . '/plugin.json';
- $relativePluginJson = $this->getRelativePluginPath($pluginName) . '/plugin.json';
+ $relativePluginJson = Manager::getPluginDirectory($pluginName) . '/plugin.json';
if (!file_exists($pluginJsonPath) || !is_writable($pluginJsonPath)) {
return;
@@ -326,11 +322,13 @@ abstract class GeneratePluginBase extends ConsoleCommand
protected function getPluginNames()
{
- $pluginDirs = \_glob(PIWIK_INCLUDE_PATH . '/plugins/*', GLOB_ONLYDIR);
-
$pluginNames = array();
- foreach ($pluginDirs as $pluginDir) {
- $pluginNames[] = basename($pluginDir);
+ foreach (Manager::getPluginsDirectories() as $pluginsDir) {
+ $pluginDirs = \_glob($pluginsDir . '*', GLOB_ONLYDIR);
+
+ foreach ($pluginDirs as $pluginDir) {
+ $pluginNames[] = basename($pluginDir);
+ }
}
return $pluginNames;
@@ -338,15 +336,17 @@ abstract class GeneratePluginBase extends ConsoleCommand
protected function getPluginNamesHavingNotSpecificFile($filename)
{
- $pluginDirs = \_glob(PIWIK_INCLUDE_PATH . '/plugins/*', GLOB_ONLYDIR);
-
$pluginNames = array();
- foreach ($pluginDirs as $pluginDir) {
- if (!file_exists($pluginDir . '/' . $filename)) {
- $pluginNames[] = basename($pluginDir);
+ foreach (Manager::getPluginsDirectories() as $pluginsDir) {
+ $pluginDirs = \_glob($pluginsDir . '*', GLOB_ONLYDIR);
+
+ foreach ($pluginDirs as $pluginDir) {
+ if (!file_exists($pluginDir . '/' . $filename)) {
+ $pluginNames[] = basename($pluginDir);
+ }
}
- }
+ }
return $pluginNames;
}
diff --git a/plugins/CoreConsole/Commands/GenerateReport.php b/plugins/CoreConsole/Commands/GenerateReport.php
index 1faa1c9dc6..b30ec7d61e 100644
--- a/plugins/CoreConsole/Commands/GenerateReport.php
+++ b/plugins/CoreConsole/Commands/GenerateReport.php
@@ -45,7 +45,7 @@ class GenerateReport extends GeneratePluginBase
$order = $this->getOrder($category);
$apiName = $this->getApiName($reportName);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleReport';
+ $exampleFolder = Manager::getPluginDirectory('ExampleReport');
$replace = array('GetExampleReport' => ucfirst($apiName),
'getExampleReport' => lcfirst($apiName),
'getApiReport' => lcfirst($apiName),
diff --git a/plugins/CoreConsole/Commands/GenerateScheduledTask.php b/plugins/CoreConsole/Commands/GenerateScheduledTask.php
index 8337e7146f..2317805aec 100644
--- a/plugins/CoreConsole/Commands/GenerateScheduledTask.php
+++ b/plugins/CoreConsole/Commands/GenerateScheduledTask.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -29,7 +30,7 @@ class GenerateScheduledTask extends GeneratePluginBase
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array('ExamplePlugin' => $pluginName);
$whitelistFiles = array('/Tasks.php');
diff --git a/plugins/CoreConsole/Commands/GenerateSettings.php b/plugins/CoreConsole/Commands/GenerateSettings.php
index a5b0c4e077..245446fa6a 100644
--- a/plugins/CoreConsole/Commands/GenerateSettings.php
+++ b/plugins/CoreConsole/Commands/GenerateSettings.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -33,7 +34,7 @@ class GenerateSettings extends GeneratePluginBase
$pluginName = $this->getPluginName($input, $output, $settingsType, $settingsFilename);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleSettingsPlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExampleSettingsPlugin');
$replace = array('ExampleSettingsPlugin' => $pluginName);
$whitelistFiles = array('/' . $settingsFilename);
diff --git a/plugins/CoreConsole/Commands/GenerateTest.php b/plugins/CoreConsole/Commands/GenerateTest.php
index 95dac4cfbd..b489c74d1d 100644
--- a/plugins/CoreConsole/Commands/GenerateTest.php
+++ b/plugins/CoreConsole/Commands/GenerateTest.php
@@ -10,6 +10,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Common;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -33,7 +34,7 @@ class GenerateTest extends GeneratePluginBase
$testType = $this->getTestType($input, $output);
$testName = $this->getTestName($input, $output, $testType);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array(
'ExamplePlugin' => $pluginName,
'SimpleTest' => $testName,
diff --git a/plugins/CoreConsole/Commands/GenerateUpdate.php b/plugins/CoreConsole/Commands/GenerateUpdate.php
index 826448de38..9dffe019f2 100644
--- a/plugins/CoreConsole/Commands/GenerateUpdate.php
+++ b/plugins/CoreConsole/Commands/GenerateUpdate.php
@@ -33,7 +33,7 @@ class GenerateUpdate extends GeneratePluginBase
$namespace = $this->getNamespace($component);
$className = $this->getUpdateClassName($component, $version);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Plugin\Manager::getPluginDirectory('ExamplePlugin');
$replace = array('Piwik\Plugins\ExamplePlugin\Updates' => $namespace,
'ExamplePlugin' => $component,
'Updates_0_0_2' => $className,
diff --git a/plugins/CoreConsole/Commands/GenerateVisualizationPlugin.php b/plugins/CoreConsole/Commands/GenerateVisualizationPlugin.php
index 92a5027716..807646b2c2 100644
--- a/plugins/CoreConsole/Commands/GenerateVisualizationPlugin.php
+++ b/plugins/CoreConsole/Commands/GenerateVisualizationPlugin.php
@@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
+use Piwik\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@@ -39,7 +40,7 @@ class GenerateVisualizationPlugin extends GeneratePlugin
$this->generatePluginFolder($pluginName);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleVisualization';
+ $exampleFolder = Manager::getPluginDirectory('ExampleVisualization');
$replace = array(
'SimpleTable' => $visualizationName,
'simpleTable' => lcfirst($visualizationName),
diff --git a/plugins/CoreConsole/Commands/GenerateWidget.php b/plugins/CoreConsole/Commands/GenerateWidget.php
index 2b603aa1fa..a66efba3c6 100644
--- a/plugins/CoreConsole/Commands/GenerateWidget.php
+++ b/plugins/CoreConsole/Commands/GenerateWidget.php
@@ -10,6 +10,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Piwik;
+use Piwik\Plugin\Manager;
use Piwik\Translate;
use Piwik\Widget\WidgetsList;
use Symfony\Component\Console\Input\InputInterface;
@@ -45,7 +46,7 @@ class GenerateWidget extends GeneratePluginBase
$widgetMethod = $this->getWidgetMethodName($widgetName);
$widgetClass = ucfirst($widgetMethod);
- $exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
+ $exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array('ExamplePlugin' => $pluginName,
'MyExampleWidget' => $widgetClass,
'Example Widget Name' => $this->makeTranslationIfPossible($pluginName, $widgetName),
diff --git a/plugins/CorePluginsAdmin/Controller.php b/plugins/CorePluginsAdmin/Controller.php
index cefb7e4080..bbcd04cb9f 100644
--- a/plugins/CorePluginsAdmin/Controller.php
+++ b/plugins/CorePluginsAdmin/Controller.php
@@ -480,7 +480,8 @@ class Controller extends Plugin\ControllerAdmin
$uninstalled = $this->pluginManager->uninstallPlugin($pluginName);
if (!$uninstalled) {
- $path = Plugin\Manager::getPluginsDirectory() . $pluginName . '/';
+ $path = Plugin\Manager::getPluginDirectory($pluginName) . '/';
+
$messagePermissions = Filechecks::getErrorMessageMissingPermissions($path);
$messageIntro = $this->translator->translate("Warning: \"%s\" could not be uninstalled. Piwik did not have enough permission to delete the files in $path. ",
diff --git a/plugins/CorePluginsAdmin/PluginInstaller.php b/plugins/CorePluginsAdmin/PluginInstaller.php
index 09ec89de59..78bccb88c4 100644
--- a/plugins/CorePluginsAdmin/PluginInstaller.php
+++ b/plugins/CorePluginsAdmin/PluginInstaller.php
@@ -129,10 +129,13 @@ class PluginInstaller
private function makeSureFoldersAreWritable()
{
- Filechecks::dieIfDirectoriesNotWritable(array(
+ $dirs = array(
StaticContainer::get('path.tmp') . self::PATH_TO_DOWNLOAD,
Manager::getPluginsDirectory()
- ));
+ );
+ // we do not require additional plugin directories to be writeable ({@link Manager::getPluginsDirectories()})
+ // as we only upload to core plugins directory anyway
+ Filechecks::dieIfDirectoriesNotWritable($dirs);
}
/**
diff --git a/plugins/ExampleTheme/ExampleTheme.php b/plugins/ExampleTheme/ExampleTheme.php
index 0ca5b8fcf7..dd9711c64c 100644
--- a/plugins/ExampleTheme/ExampleTheme.php
+++ b/plugins/ExampleTheme/ExampleTheme.php
@@ -22,9 +22,6 @@ class ExampleTheme extends Plugin
public function configureThemeVariables(Plugin\ThemeStyles $vars)
{
- $vars->fontFamilyBase = 'Arial, Verdana, sans-serif';
- $vars->colorBrand = '#5793d4';
- $vars->colorHeaderBackground = '#0091ea';
- $vars->colorHeaderText = '#0d0d0d';
+ $vars->fontFamilyBase = 'Verdana, sans-serif';
}
}
diff --git a/plugins/LanguagesManager/API.php b/plugins/LanguagesManager/API.php
index 9946da3b88..b41ba9c68b 100644
--- a/plugins/LanguagesManager/API.php
+++ b/plugins/LanguagesManager/API.php
@@ -105,11 +105,18 @@ class API extends \Piwik\Plugin\API
$data = file_get_contents(PIWIK_INCLUDE_PATH . '/lang/en.json');
$englishTranslation = json_decode($data, true);
+ $pluginDirectories = Manager::getPluginsDirectories();
// merge with plugin translations if any
- $pluginFiles = glob(sprintf('%s*/lang/en.json', Manager::getPluginsDirectory()));
+
+ $pluginFiles = array();
+ foreach ($pluginDirectories as $pluginsDir) {
+ $pluginFiles = array_merge($pluginFiles, glob(sprintf('%s*/lang/en.json', $pluginsDir)));
+ }
+
foreach ($pluginFiles as $file) {
+ $fileWithoutPluginDir = str_replace($pluginDirectories, '', $file);
- preg_match('/\/plugins\/([^\/]+)\/lang/i', $file, $matches);
+ preg_match('/([^\/]+)\/lang/i', $fileWithoutPluginDir, $matches);
$plugin = $matches[1];
if (!$excludeNonCorePlugins || Manager::getInstance()->isPluginBundledWithCore($plugin)) {
@@ -126,10 +133,15 @@ class API extends \Piwik\Plugin\API
$translations = json_decode($data, true);
// merge with plugin translations if any
- $pluginFiles = glob(sprintf('%s*/lang/%s.json', Manager::getPluginsDirectory(), $filename));
+ $pluginFiles = array();
+ foreach ($pluginDirectories as $pluginsDir) {
+ $pluginFiles = array_merge($pluginFiles, glob(sprintf('%s*/lang/%s.json', $pluginsDir, $filename)));
+ }
+
foreach ($pluginFiles as $file) {
+ $fileWithoutPluginDir = str_replace($pluginDirectories, '', $file);
- preg_match('/\/plugins\/([^\/]+)\/lang/i', $file, $matches);
+ preg_match('/([^\/]+)\/lang/i', $fileWithoutPluginDir, $matches);
$plugin = $matches[1];
if (!$excludeNonCorePlugins || Manager::getInstance()->isPluginBundledWithCore($plugin)) {
@@ -232,7 +244,7 @@ class API extends \Piwik\Plugin\API
return false;
}
- $languageFile = Manager::getPluginsDirectory() . "$pluginName/lang/$languageCode.json";
+ $languageFile = Manager::getPluginDirectory($pluginName) . "/lang/$languageCode.json";
if (!file_exists($languageFile)) {
return false;
diff --git a/plugins/LanguagesManager/Commands/PluginsWithTranslations.php b/plugins/LanguagesManager/Commands/PluginsWithTranslations.php
index 62d13cda70..2b24b7cacd 100644
--- a/plugins/LanguagesManager/Commands/PluginsWithTranslations.php
+++ b/plugins/LanguagesManager/Commands/PluginsWithTranslations.php
@@ -27,9 +27,14 @@ class PluginsWithTranslations extends TranslationBase
{
$output->writeln("Following plugins contain their own translation files:");
- $pluginFiles = glob(sprintf('%s*/lang/en.json', Manager::getPluginsDirectory()));
+ $pluginFiles = array();
+ foreach (Manager::getPluginsDirectories() as $pluginsDir) {
+ $pluginFiles = array_merge($pluginsDir, glob(sprintf('%s*/lang/en.json', $pluginsDir)));
+ }
$pluginFiles = array_map(function($elem){
- return str_replace(array(Manager::getPluginsDirectory(), '/lang/en.json'), '', $elem);
+ $replace = Manager::getPluginsDirectories();
+ $replace[] = '/lang/en.json';
+ return str_replace($replace, '', $elem);
}, $pluginFiles);
$output->writeln(join("\n", $pluginFiles));
diff --git a/plugins/LanguagesManager/Commands/Update.php b/plugins/LanguagesManager/Commands/Update.php
index 4e79b8383b..bb9efbcc1e 100644
--- a/plugins/LanguagesManager/Commands/Update.php
+++ b/plugins/LanguagesManager/Commands/Update.php
@@ -158,9 +158,14 @@ class Update extends TranslationBase
return $pluginsWithTranslations;
}
- $pluginsWithTranslations = glob(sprintf('%s*/lang/en.json', Manager::getPluginsDirectory()));
+ $pluginsWithTranslations = array();
+ foreach (Manager::getPluginsDirectories() as $pluginsDir) {
+ $pluginsWithTranslations = array_merge($pluginsWithTranslations, glob(sprintf('%s*/lang/en.json', $pluginsDir)));
+ }
$pluginsWithTranslations = array_map(function ($elem) {
- return str_replace(array(Manager::getPluginsDirectory(), '/lang/en.json'), '', $elem);
+ $replace = Manager::getPluginsDirectories();
+ $replace[] = '/lang/en.json';
+ return str_replace($replace, '', $elem);
}, $pluginsWithTranslations);
return $pluginsWithTranslations;
@@ -188,10 +193,14 @@ class Update extends TranslationBase
$newPlugins = $matches[1];
$pluginsNotInCore = array_merge($submodulePlugins, $newPlugins);
-
- $pluginsWithTranslations = glob(sprintf('%s*/lang/en.json', Manager::getPluginsDirectory()));
+ $pluginsWithTranslations = array();
+ foreach (Manager::getPluginsDirectories() as $pluginsDir) {
+ $pluginsWithTranslations = array_merge($pluginsWithTranslations, glob(sprintf('%s*/lang/en.json', $pluginsDir)));
+ }
$pluginsWithTranslations = array_map(function ($elem) {
- return str_replace(array(Manager::getPluginsDirectory(), '/lang/en.json'), '', $elem);
+ $replace = Manager::getPluginsDirectories();
+ $replace[] = '/lang/en.json';
+ return str_replace($replace, '', $elem);
}, $pluginsWithTranslations);
$pluginsInCore = array_diff($pluginsWithTranslations, $pluginsNotInCore);
diff --git a/plugins/LanguagesManager/TranslationWriter/Writer.php b/plugins/LanguagesManager/TranslationWriter/Writer.php
index 8dc2260cdb..679433b34e 100644
--- a/plugins/LanguagesManager/TranslationWriter/Writer.php
+++ b/plugins/LanguagesManager/TranslationWriter/Writer.php
@@ -202,7 +202,7 @@ class Writer
if ($base == 'tmp') {
return sprintf('%s/plugins/%s/lang/%s.json', StaticContainer::get('path.tmp'), $this->pluginName, $lang);
} else {
- return sprintf('%s%s/lang/%s.json', Manager::getPluginsDirectory(), $this->pluginName, $lang);
+ return sprintf('%s/lang/%s.json', Manager::getPluginDirectory($this->pluginName), $lang);
}
}
diff --git a/tests/PHPUnit/Fixtures/UITestFixture.php b/tests/PHPUnit/Fixtures/UITestFixture.php
index 49b480f793..1c6c92b9bb 100644
--- a/tests/PHPUnit/Fixtures/UITestFixture.php
+++ b/tests/PHPUnit/Fixtures/UITestFixture.php
@@ -70,6 +70,7 @@ class UITestFixture extends SqlDump
self::resetPluginsInstalledConfig();
self::updateDatabase();
self::installAndActivatePlugins($this->getTestEnvironment());
+ self::updateDatabase();
// make sure site has an early enough creation date (for period selector tests)
Db::get()->update(Common::prefixTable("site"),
@@ -127,6 +128,9 @@ class UITestFixture extends SqlDump
$this->extraTestEnvVars = array(
'loadRealTranslations' => 1,
);
+ $this->extraPluginsToLoad = array(
+ 'CustomDirPlugin'
+ );
parent::performSetUp($setupEnvironmentOnly);
diff --git a/tests/PHPUnit/bootstrap.php b/tests/PHPUnit/bootstrap.php
index 444728bafd..1fae5acbf9 100644
--- a/tests/PHPUnit/bootstrap.php
+++ b/tests/PHPUnit/bootstrap.php
@@ -36,6 +36,13 @@ if (!defined('PIWIK_INCLUDE_SEARCH_PATH')) {
@set_include_path(PIWIK_INCLUDE_SEARCH_PATH);
@ini_set('memory_limit', -1);
+$GLOBALS['MATOMO_PLUGIN_DIRS'] = array(
+ array(
+ 'pluginsPathAbsolute' => PIWIK_INCLUDE_PATH . '/tests/resources/custompluginsdir',
+ 'webrootDirRelativeToMatomo' => 'tests/resources/custompluginsdir'
+ ),
+);
+
require_once PIWIK_INCLUDE_PATH . '/core/bootstrap.php';
require_once PIWIK_INCLUDE_PATH . '/libs/PiwikTracker/PiwikTracker.php';
diff --git a/tests/PHPUnit/phpunit.xml.dist b/tests/PHPUnit/phpunit.xml.dist
index caf5cf2d16..0aab2c5178 100644
--- a/tests/PHPUnit/phpunit.xml.dist
+++ b/tests/PHPUnit/phpunit.xml.dist
@@ -23,6 +23,8 @@
<directory>./System</directory>
<directory>../../plugins/*/tests</directory><!-- There should be actually a tests/System but this way we make sure to execute all tests even if some are not moved to correct subdirectory. We will execute Unit and Integration tests twice :( ... -->
<directory>../../plugins/*/Test</directory>
+ <directory>../../tests/resources/custompluginsdir/*/tests/System</directory>
+ <directory>../../tests/resources/custompluginsdir/*/Test/System</directory>
<exclude>../../plugins/*/tests/Integration</exclude>
<exclude>../../plugins/*/Test/Integration</exclude>
<exclude>../../plugins/*/tests/Unit</exclude>
@@ -34,6 +36,8 @@
<testsuite name="SystemTestsPlugins">
<directory>../../plugins/*/tests</directory><!-- There should be actually a tests/System but this way we make sure to execute all tests even if some are not moved to correct subdirectory. We will execute Unit and Integration tests twice :( ... -->
<directory>../../plugins/*/Test</directory>
+ <directory>../../tests/resources/custompluginsdir/*/tests/System</directory>
+ <directory>../../tests/resources/custompluginsdir/*/Test/System</directory>
<exclude>../../plugins/*/tests/Integration</exclude>
<exclude>../../plugins/*/Test/Integration</exclude>
<exclude>../../plugins/*/tests/Unit</exclude>
@@ -43,11 +47,15 @@
<directory>./Integration</directory>
<directory>../../plugins/*/tests/Integration</directory>
<directory>../../plugins/*/Test/Integration</directory>
+ <directory>../../tests/resources/custompluginsdir/*/tests/Integration</directory>
+ <directory>../../tests/resources/custompluginsdir/*/Test/Integration</directory>
</testsuite>
<testsuite name="UnitTests">
<directory>./Unit</directory>
<directory>../../plugins/*/tests/Unit</directory>
<directory>../../plugins/*/Test/Unit</directory>
+ <directory>../../tests/resources/custompluginsdir/*/tests/Unit</directory>
+ <directory>../../tests/resources/custompluginsdir/*/Test/Unit</directory>
</testsuite>
<testsuite name="PluginTests">
<directory>../../plugins/*/tests</directory>
diff --git a/tests/PHPUnit/proxy/includes.php b/tests/PHPUnit/proxy/includes.php
index 2b3407f5d5..c6f301623c 100644
--- a/tests/PHPUnit/proxy/includes.php
+++ b/tests/PHPUnit/proxy/includes.php
@@ -10,6 +10,13 @@ if (!defined('PIWIK_TEST_MODE')) {
define('PIWIK_TEST_MODE', true);
}
+$GLOBALS['MATOMO_PLUGIN_DIRS'] = array(
+ array(
+ 'pluginsPathAbsolute' => realpath(PIWIK_INCLUDE_PATH . 'tests/resources/custompluginsdir'),
+ 'webrootDirRelativeToMatomo' => '../../resources/custompluginsdir'
+ ),
+);
+
require_once PIWIK_INCLUDE_PATH . '/core/bootstrap.php';
Piwik\SettingsServer::setMaxExecutionTime(0);
diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_admin_settings_general.png b/tests/UI/expected-screenshots/UIIntegrationTest_admin_settings_general.png
index d692196c24..065a48ac19 100644
--- a/tests/UI/expected-screenshots/UIIntegrationTest_admin_settings_general.png
+++ b/tests/UI/expected-screenshots/UIIntegrationTest_admin_settings_general.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:f3b6c8c65e4aa9a62b618825e4cde3839f119ca28b839fbf607c788f2f0c3846
-size 1071976
+oid sha256:0766ddf41affb5c22ad653a4bd364dc374ba0b70421fdad8622393d7025cd590
+size 1085586
diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_customdirplugin.png b/tests/UI/expected-screenshots/UIIntegrationTest_customdirplugin.png
new file mode 100644
index 0000000000..7809e57f21
--- /dev/null
+++ b/tests/UI/expected-screenshots/UIIntegrationTest_customdirplugin.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4e5cf38c4abbfc04c5d1de2903bc4a242db8e9a6a8c7be2bf2fd99d24f89808e
+size 66157
diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png b/tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png
index aacf0c3d47..c3779e6ac1 100644
--- a/tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png
+++ b/tests/UI/expected-screenshots/UIIntegrationTest_fatal_error_safemode.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:68503ef9dafa19792aba12552ab81872b25247b619851d0ba1b743b31a51c919
-size 203273
+oid sha256:204573e81aa9a39a5cffdacc90e49b6f71735fdd1dec9bc6fa20a77b68525edd
+size 208738
diff --git a/tests/UI/specs/UIIntegration_spec.js b/tests/UI/specs/UIIntegration_spec.js
index 55729cce0a..f63d3dc814 100644
--- a/tests/UI/specs/UIIntegration_spec.js
+++ b/tests/UI/specs/UIIntegration_spec.js
@@ -27,6 +27,9 @@ describe("UIIntegrationTest", function () { // TODO: Rename to Piwik?
testEnvironment.save();
testEnvironment.callApi("SitesManager.setSiteAliasUrls", {idSite: 3, urls: []}, done);
+
+ testEnvironment.pluginsToLoad = ['CustomDirPlugin'];
+ testEnvironment.save();
});
beforeEach(function () {
@@ -83,6 +86,11 @@ describe("UIIntegrationTest", function () { // TODO: Rename to Piwik?
}, done);
});
+ it("should load the page of a plugin located in a custom directory", function (done) {
+ expect.screenshot("customdirplugin").to.be.captureSelector('.pageWrap', function (page) {
+ page.load("?module=CustomDirPlugin&action=index&idSite=1&period=day&date=yesterday");
+ }, done);
+ });
// shortcuts help
it("should show shortcut help", function (done) {
expect.screenshot("shortcuts").to.be.captureSelector('.modal.open', function (page) {
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/.gitignore b/tests/resources/custompluginsdir/CustomDirPlugin/.gitignore
new file mode 100644
index 0000000000..c8c9480010
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/.gitignore
@@ -0,0 +1 @@
+tests/System/processed/*xml \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/API.php b/tests/resources/custompluginsdir/CustomDirPlugin/API.php
new file mode 100644
index 0000000000..08a0a957c5
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/API.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDirPlugin;
+
+class API extends \Piwik\Plugin\API
+{
+ public function getCustomAnswerToLive($truth = true)
+ {
+ if ($truth) {
+ return 42;
+ }
+
+ return 24;
+ }
+
+}
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/Controller.php b/tests/resources/custompluginsdir/CustomDirPlugin/Controller.php
new file mode 100644
index 0000000000..81631f81f2
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/Controller.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDirPlugin;
+
+use Piwik\Config;
+use Piwik\Container\StaticContainer;
+
+class Controller extends \Piwik\Plugin\Controller
+{
+ public function index()
+ {
+ return $this->renderTemplate('index', array(
+ 'answerToLife' => 42,
+ 'diTest' => StaticContainer::get('customDirPluginTest')
+ ));
+ }
+}
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/CustomClass.php b/tests/resources/custompluginsdir/CustomDirPlugin/CustomClass.php
new file mode 100644
index 0000000000..01d3fe9c49
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/CustomClass.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDirPlugin;
+
+class CustomClass
+{
+
+ // to test auto loading
+}
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/CustomDirPlugin.php b/tests/resources/custompluginsdir/CustomDirPlugin/CustomDirPlugin.php
new file mode 100644
index 0000000000..8ae44b4bd9
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/CustomDirPlugin.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDirPlugin;
+
+use Piwik\Common;
+use Piwik\Db;
+
+class CustomDirPlugin extends \Piwik\Plugin
+{
+ /**
+ * @see \Piwik\Plugin::registerEvents
+ */
+ public function registerEvents()
+ {
+ return array(
+ 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
+ 'AssetManager.getJavaScriptFiles' => 'getJsFiles',
+ );
+ }
+
+ public function getStylesheetFiles(&$stylesheets)
+ {
+ $stylesheets[] = "plugins/CustomDirPlugin/stylesheets/test.less";
+ }
+
+ public function getJsFiles(&$jsFiles)
+ {
+ $jsFiles[] = "tests/resources/custompluginsdir/javascripts/test.js";
+ }
+
+ public function postLoad()
+ {
+ // we make sure auto loading works for these directories
+ return new CustomClass();
+ }
+
+ public function isTrackerPlugin()
+ {
+ return true;
+ }
+}
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/SystemSettings.php b/tests/resources/custompluginsdir/CustomDirPlugin/SystemSettings.php
new file mode 100644
index 0000000000..d219451fa9
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/SystemSettings.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDirPlugin;
+
+use Piwik\Settings\Setting;
+use Piwik\Settings\FieldConfig;
+use Piwik\Validators\NotEmpty;
+
+class SystemSettings extends \Piwik\Settings\Plugin\SystemSettings
+{
+ /** @var Setting */
+ public $custom;
+
+ protected function init()
+ {
+ $this->custom = $this->createMetricSetting();
+ }
+
+ private function createMetricSetting()
+ {
+ return $this->makeSetting('custom', $default = '', FieldConfig::TYPE_STRING, function (FieldConfig $field) {
+ $field->title = 'Custom setting';
+ $field->uiControl = FieldConfig::UI_CONTROL_TEXT;
+ $field->description = 'Enter some custom text here';
+ $field->validators[] = new NotEmpty();
+ });
+ }
+}
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/config/config.php b/tests/resources/custompluginsdir/CustomDirPlugin/config/config.php
new file mode 100644
index 0000000000..d546932d91
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/config/config.php
@@ -0,0 +1,4 @@
+<?php
+return array(
+ 'customDirPluginTest' => 'hello world!'
+); \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/images/ok.png b/tests/resources/custompluginsdir/CustomDirPlugin/images/ok.png
new file mode 100644
index 0000000000..148319419d
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/images/ok.png
Binary files differ
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/lang/en.json b/tests/resources/custompluginsdir/CustomDirPlugin/lang/en.json
new file mode 100644
index 0000000000..20927d76ef
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/lang/en.json
@@ -0,0 +1,5 @@
+{
+ "CustomDirPlugin": {
+ "CustomName": "Custom Name"
+ }
+} \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/plugin.json b/tests/resources/custompluginsdir/CustomDirPlugin/plugin.json
new file mode 100644
index 0000000000..2d4b3c19a2
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/plugin.json
@@ -0,0 +1,29 @@
+{
+ "name": "CustomDirPlugin",
+ "description": "Custom Plugin located in different directory",
+ "version": "0.1.0",
+ "theme": false,
+ "require": {
+ "piwik": ">=3.8.1-stable,<4.0.0-b1"
+ },
+ "authors": [
+ {
+ "name": "Matomo",
+ "email": "",
+ "homepage": "https://matomo.org"
+ }
+ ],
+ "support": {
+ "email": "",
+ "issues": "",
+ "forum": "",
+ "irc": "",
+ "wiki": "",
+ "source": "",
+ "docs": "",
+ "rss": ""
+ },
+ "homepage": "https://matomo.org",
+ "license": "GPL v3+",
+ "keywords": []
+} \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/stylesheets/import.less b/tests/resources/custompluginsdir/CustomDirPlugin/stylesheets/import.less
new file mode 100644
index 0000000000..707bfa8e94
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/stylesheets/import.less
@@ -0,0 +1,3 @@
+.customPluginDirImportTest {
+ color: green;
+} \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/stylesheets/test.less b/tests/resources/custompluginsdir/CustomDirPlugin/stylesheets/test.less
new file mode 100644
index 0000000000..d2885913bc
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/stylesheets/test.less
@@ -0,0 +1,15 @@
+.fooBarBazCustom {
+ font-weight: bold;
+ color: red;
+}
+
+@import "import.less";
+
+.customPluginDirBackgroundTestRelative {
+ background: url('../images/ok.png');
+ min-height: 30px;
+}
+.customPluginDirBackgroundTestPluginRelative {
+ background: url('plugins/CustomDirPlugin/images/ok.png');
+ min-height: 30px;
+} \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/templates/index.twig b/tests/resources/custompluginsdir/CustomDirPlugin/templates/index.twig
new file mode 100644
index 0000000000..24bce30dca
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/templates/index.twig
@@ -0,0 +1,27 @@
+{% extends 'dashboard.twig' %}
+
+{% block content %}
+ <strong>This is our custom plugin!</strong>
+ <br/>
+ <span class="fooBarBazCustom">
+ The answer to life is {{ answerToLife }}
+ </span>
+ <br />
+ <h3>Translation test</h3>
+ <p>This should be translated: {{ 'CustomDirPlugin_CustomName'|translate }}</p>
+
+ <h3>image test</h3>
+ <p>You should see an image showing a check here: <img src="plugins/CustomDirPlugin/images/ok.png"></p>
+
+ <h3>CSS import test</h3>
+ <p class="customPluginDirImportTest">this text should appear green!</p>
+
+ <h3>CSS background images test</h3>
+ <p class="customPluginDirBackgroundTestRelative">This should have a background image relative</p>
+
+ <p class="customPluginDirBackgroundTestPluginRelative">This should have a background image relative to plugins</p>
+
+ <h3>Dependency Injection Test</h3>
+ <p>You should now see hello world: {{ diTest }}</p>
+
+{% endblock %} \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/tests/Fixtures/SimpleFixtureTrackFewVisits.php b/tests/resources/custompluginsdir/CustomDirPlugin/tests/Fixtures/SimpleFixtureTrackFewVisits.php
new file mode 100644
index 0000000000..f5d58969d0
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/tests/Fixtures/SimpleFixtureTrackFewVisits.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+namespace Piwik\Plugins\CustomDirPlugin\tests\Fixtures;
+
+use Piwik\Tests\Framework\Fixture;
+
+class SimpleFixtureTrackFewVisits extends Fixture
+{
+ public $dateTime = '2013-01-23 01:23:45';
+ public $idSite = 1;
+
+ public function setUp()
+ {
+ $this->setUpWebsite();
+ }
+
+ public function tearDown()
+ {
+ // empty
+ }
+
+ private function setUpWebsite()
+ {
+ if (!self::siteCreated($this->idSite)) {
+ $idSite = self::createWebsite($this->dateTime, $ecommerce = 1);
+ $this->assertSame($this->idSite, $idSite);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/tests/Integration/SystemSettingsTest.php b/tests/resources/custompluginsdir/CustomDirPlugin/tests/Integration/SystemSettingsTest.php
new file mode 100644
index 0000000000..a9dd935a1e
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/tests/Integration/SystemSettingsTest.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDirPlugin\tests\Integration;
+
+use Piwik\Plugins\CustomDirPlugin\SystemSettings;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomDirPlugin
+ * @group SystemSettingsTest
+ * @group Plugins
+ */
+class SystemSettingsTest extends IntegrationTestCase
+{
+ /**
+ * @var SystemSettings
+ */
+ private $settings;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->settings = new SystemSettings();
+ }
+
+ public function test_get_pluginName()
+ {
+ $this->assertSame('CustomDirPlugin', $this->settings->getPluginName());
+ }
+
+ public function test_get_default()
+ {
+ $this->assertSame('', $this->settings->custom->getValue());
+ }
+
+ public function test_set_value()
+ {
+ $this->settings->custom->setValue('%');
+ $this->assertSame('%', $this->settings->custom->getValue());
+ }
+
+}
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/APITest.php b/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/APITest.php
new file mode 100644
index 0000000000..11fd73a4f5
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/APITest.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDirPlugin\tests\System;
+
+use Piwik\API\Request;
+use Piwik\Plugins\CustomDirPlugin\tests\Fixtures\SimpleFixtureTrackFewVisits;
+use Piwik\Tests\Framework\TestCase\SystemTestCase;
+
+/**
+ * @group CustomDirPlugin
+ * @group APITest
+ * @group Plugins
+ */
+class APITest extends SystemTestCase
+{
+ /**
+ * @var SimpleFixtureTrackFewVisits
+ */
+ public static $fixture = null; // initialized below class definition
+
+ public function test_api()
+ {
+ $this->assertEquals(42, Request::processRequest('CustomDirPlugin.getCustomAnswerToLive'));
+ }
+
+ public static function getOutputPrefix()
+ {
+ return '';
+ }
+
+ public static function getPathToTestDirectory()
+ {
+ return dirname(__FILE__);
+ }
+
+}
+
+APITest::$fixture = new SimpleFixtureTrackFewVisits(); \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/expected/test___API.get_day.xml b/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/expected/test___API.get_day.xml
new file mode 100644
index 0000000000..213b91b66a
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/expected/test___API.get_day.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <nb_uniq_visitors>2</nb_uniq_visitors>
+ <nb_visits>2</nb_visits>
+ <nb_users>0</nb_users>
+ <nb_actions>4</nb_actions>
+ <max_actions>2</max_actions>
+ <bounce_count>0</bounce_count>
+ <sum_visit_length>1264</sum_visit_length>
+ <nb_visits_returning>0</nb_visits_returning>
+ <nb_actions_returning>0</nb_actions_returning>
+ <nb_uniq_visitors_returning>0</nb_uniq_visitors_returning>
+ <nb_users_returning>0</nb_users_returning>
+ <max_actions_returning>0</max_actions_returning>
+ <bounce_rate_returning>0%</bounce_rate_returning>
+ <nb_actions_per_visit_returning>0</nb_actions_per_visit_returning>
+ <avg_time_on_site_returning>0</avg_time_on_site_returning>
+ <nb_conversions>1</nb_conversions>
+ <nb_visits_converted>1</nb_visits_converted>
+ <revenue>2541</revenue>
+ <conversion_rate>50%</conversion_rate>
+ <nb_conversions_new_visit>1</nb_conversions_new_visit>
+ <nb_visits_converted_new_visit>1</nb_visits_converted_new_visit>
+ <revenue_new_visit>2541</revenue_new_visit>
+ <conversion_rate_new_visit>50%</conversion_rate_new_visit>
+ <nb_conversions_returning_visit>0</nb_conversions_returning_visit>
+ <nb_visits_converted_returning_visit>0</nb_visits_converted_returning_visit>
+ <revenue_returning_visit>0</revenue_returning_visit>
+ <conversion_rate_returning_visit>0%</conversion_rate_returning_visit>
+ <nb_total_overall_bandwidth>0</nb_total_overall_bandwidth>
+ <nb_total_pageview_bandwidth>0</nb_total_pageview_bandwidth>
+ <nb_total_download_bandwidth>0</nb_total_download_bandwidth>
+ <nb_pageviews>3</nb_pageviews>
+ <nb_uniq_pageviews>3</nb_uniq_pageviews>
+ <nb_downloads>0</nb_downloads>
+ <nb_uniq_downloads>0</nb_uniq_downloads>
+ <nb_outlinks>0</nb_outlinks>
+ <nb_uniq_outlinks>0</nb_uniq_outlinks>
+ <nb_searches>1</nb_searches>
+ <nb_keywords>1</nb_keywords>
+ <bounce_rate>0%</bounce_rate>
+ <nb_actions_per_visit>2</nb_actions_per_visit>
+ <avg_time_on_site>632</avg_time_on_site>
+</result> \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/expected/test___Goals.getItemsSku_day.xml b/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/expected/test___Goals.getItemsSku_day.xml
new file mode 100644
index 0000000000..ba820ed649
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/tests/System/expected/test___Goals.getItemsSku_day.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<result>
+ <row>
+ <label>SKU_ID</label>
+ <revenue>25641</revenue>
+ <quantity>33</quantity>
+ <orders>1</orders>
+ <avg_price>777</avg_price>
+ <avg_quantity>33</avg_quantity>
+ <conversion_rate>0%</conversion_rate>
+ </row>
+</result> \ No newline at end of file
diff --git a/tests/resources/custompluginsdir/CustomDirPlugin/tests/Unit/CustomClassTest.php b/tests/resources/custompluginsdir/CustomDirPlugin/tests/Unit/CustomClassTest.php
new file mode 100644
index 0000000000..0018108c66
--- /dev/null
+++ b/tests/resources/custompluginsdir/CustomDirPlugin/tests/Unit/CustomClassTest.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomDirPlugin\tests\Unit;
+use Piwik\Plugins\CustomDirPlugin\CustomClass;
+
+/**
+ * @group CustomDirPlugin
+ * @group CustomClassTest
+ * @group Plugins
+ */
+class CustomClassTest extends \PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ // set up here if needed
+ }
+
+ public function tearDown()
+ {
+ // tear down here if needed
+ }
+
+ public function test_autoloading_customplugin_works()
+ {
+ $customClass = new CustomClass();
+ $this->assertTrue($customClass instanceof CustomClass);
+ }
+
+}
diff --git a/tests/resources/custompluginsdir/javascripts/test.js b/tests/resources/custompluginsdir/javascripts/test.js
new file mode 100644
index 0000000000..1b98ac75de
--- /dev/null
+++ b/tests/resources/custompluginsdir/javascripts/test.js
@@ -0,0 +1 @@
+window.fooBarBazBarFoo = 'fooBarBaz'; \ No newline at end of file