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:
authorStefan Giehl <stefan@matomo.org>2020-02-11 11:05:27 +0300
committerGitHub <noreply@github.com>2020-02-11 11:05:27 +0300
commitab1e70016dde3ef93498bca99d0aa8b38e6d7f38 (patch)
tree5561b2d822215453c8d4e4fb4fe8e43b09b1faac /plugins/CustomJsTracker
parent4f2292b7eb500ddaf1dd0440220647f645b3d289 (diff)
Renames CustomPiwikJs plugin to CustomJsTracker (#15505)
* Renames CustomPiwikJs plugin to CustomTrackerJs * adds bc for old api class name * adds update to rename plugin in exisiting config * update ui tests * rename again * remove old events * improve changelog * remove bc fallback * Improve migration * use tagmanager submodule * updates UI files
Diffstat (limited to 'plugins/CustomJsTracker')
-rw-r--r--plugins/CustomJsTracker/.gitignore2
-rw-r--r--plugins/CustomJsTracker/API.php42
-rw-r--r--plugins/CustomJsTracker/Commands/UpdateTracker.php66
-rw-r--r--plugins/CustomJsTracker/CustomJsTracker.php41
-rw-r--r--plugins/CustomJsTracker/Diagnostic/TrackerJsCheck.php82
-rw-r--r--plugins/CustomJsTracker/Exception/AccessDeniedException.php15
-rw-r--r--plugins/CustomJsTracker/File.php100
-rw-r--r--plugins/CustomJsTracker/Tasks.php25
-rw-r--r--plugins/CustomJsTracker/TrackerUpdater.php165
-rw-r--r--plugins/CustomJsTracker/TrackingCode/JsTestPluginTrackerFiles.php27
-rw-r--r--plugins/CustomJsTracker/TrackingCode/PiwikJsManipulator.php77
-rw-r--r--plugins/CustomJsTracker/TrackingCode/PluginTrackerFiles.php110
-rw-r--r--plugins/CustomJsTracker/config/config.php7
-rw-r--r--plugins/CustomJsTracker/config/tracker.php2
-rw-r--r--plugins/CustomJsTracker/lang/cs.json5
-rw-r--r--plugins/CustomJsTracker/lang/da.json6
-rw-r--r--plugins/CustomJsTracker/lang/de.json8
-rw-r--r--plugins/CustomJsTracker/lang/el.json8
-rw-r--r--plugins/CustomJsTracker/lang/en.json8
-rw-r--r--plugins/CustomJsTracker/lang/eo.json7
-rw-r--r--plugins/CustomJsTracker/lang/es-ar.json8
-rw-r--r--plugins/CustomJsTracker/lang/es.json8
-rw-r--r--plugins/CustomJsTracker/lang/fi.json7
-rw-r--r--plugins/CustomJsTracker/lang/fr.json8
-rw-r--r--plugins/CustomJsTracker/lang/id.json5
-rw-r--r--plugins/CustomJsTracker/lang/it.json8
-rw-r--r--plugins/CustomJsTracker/lang/ja.json8
-rw-r--r--plugins/CustomJsTracker/lang/nb.json6
-rw-r--r--plugins/CustomJsTracker/lang/nl.json6
-rw-r--r--plugins/CustomJsTracker/lang/pl.json6
-rw-r--r--plugins/CustomJsTracker/lang/pt-br.json8
-rw-r--r--plugins/CustomJsTracker/lang/pt.json8
-rw-r--r--plugins/CustomJsTracker/lang/ru.json8
-rw-r--r--plugins/CustomJsTracker/lang/sq.json8
-rw-r--r--plugins/CustomJsTracker/lang/sr.json7
-rw-r--r--plugins/CustomJsTracker/lang/sv.json8
-rw-r--r--plugins/CustomJsTracker/lang/tr.json8
-rw-r--r--plugins/CustomJsTracker/lang/uk.json8
-rw-r--r--plugins/CustomJsTracker/lang/zh-cn.json8
-rw-r--r--plugins/CustomJsTracker/lang/zh-tw.json8
-rw-r--r--plugins/CustomJsTracker/tests/Framework/Mock/PluginTrackerFilesMock.php36
-rw-r--r--plugins/CustomJsTracker/tests/Integration/ApiTest.php84
-rw-r--r--plugins/CustomJsTracker/tests/Integration/FileTest.php210
-rw-r--r--plugins/CustomJsTracker/tests/Integration/PiwikJsManipulatorTest.php73
-rw-r--r--plugins/CustomJsTracker/tests/Integration/PluginTrackerFilesTest.php143
-rw-r--r--plugins/CustomJsTracker/tests/Integration/TrackerUpdaterTest.php246
-rw-r--r--plugins/CustomJsTracker/tests/System/PiwikJsContentTest.php39
-rw-r--r--plugins/CustomJsTracker/tests/resources/MyTestTarget2.js16
-rw-r--r--plugins/CustomJsTracker/tests/resources/test.js2
-rw-r--r--plugins/CustomJsTracker/tests/resources/testpiwik.js6
-rw-r--r--plugins/CustomJsTracker/tests/resources/tracker.js4
-rw-r--r--plugins/CustomJsTracker/tests/resources/tracker.min.js2
52 files changed, 1813 insertions, 0 deletions
diff --git a/plugins/CustomJsTracker/.gitignore b/plugins/CustomJsTracker/.gitignore
new file mode 100644
index 0000000000..8f30992923
--- /dev/null
+++ b/plugins/CustomJsTracker/.gitignore
@@ -0,0 +1,2 @@
+tests/System/processed/*xml
+tests/resources/MyNotExisIngFilessss.js
diff --git a/plugins/CustomJsTracker/API.php b/plugins/CustomJsTracker/API.php
new file mode 100644
index 0000000000..c8349efe2e
--- /dev/null
+++ b/plugins/CustomJsTracker/API.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomJsTracker;
+
+use Piwik\Container\StaticContainer;
+use Piwik\Piwik;
+use Piwik\Plugins\CustomJsTracker\Exception\AccessDeniedException;
+
+/**
+ * API for plugin CustomJsTracker
+ *
+ * @method static \Piwik\Plugins\CustomJsTracker\API getInstance()
+ */
+class API extends \Piwik\Plugin\API
+{
+ /**
+ * Detects whether plugin trackers will be automatically added to piwik.js or not. If not, the plugin tracker files
+ * need to be loaded manually.
+ * @return bool
+ */
+ public function doesIncludePluginTrackersAutomatically()
+ {
+ Piwik::checkUserHasSomeAdminAccess();
+
+ try {
+ $updater = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackerUpdater');
+ $updater->checkWillSucceed();
+ return true;
+ } catch (AccessDeniedException $e) {
+ return false;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+}
diff --git a/plugins/CustomJsTracker/Commands/UpdateTracker.php b/plugins/CustomJsTracker/Commands/UpdateTracker.php
new file mode 100644
index 0000000000..bddd99136a
--- /dev/null
+++ b/plugins/CustomJsTracker/Commands/UpdateTracker.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomJsTracker\Commands;
+
+use Piwik\Container\StaticContainer;
+use Piwik\Plugin\ConsoleCommand;
+use Piwik\Plugins\CustomJsTracker\TrackerUpdater;
+use Piwik\Plugins\CustomJsTracker\TrackingCode\PluginTrackerFiles;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class UpdateTracker extends ConsoleCommand
+{
+ protected function configure()
+ {
+ $this->setName('custom-piwik-js:update');
+ $this->setAliases(array('custom-matomo-js:update'));
+ $this->addOption('source-file', null, InputOption::VALUE_REQUIRED, 'Absolute path to source PiwikJS file.', $this->getPathOriginalPiwikJs());
+ $this->addOption('target-file', null, InputOption::VALUE_REQUIRED, 'Absolute path to target file. Useful if your /matomo.js is not writable and you want to replace the file manually', PIWIK_DOCUMENT_ROOT . TrackerUpdater::TARGET_MATOMO_JS);
+ $this->addOption('ignore-minified', null, InputOption::VALUE_NONE, 'Ignore minified tracker files, useful during development so the original source file can be debugged');
+ $this->setDescription('Update the Javascript Tracker with plugin tracker additions');
+ }
+
+ private function getPathOriginalPiwikJs()
+ {
+ return PIWIK_DOCUMENT_ROOT . TrackerUpdater::ORIGINAL_PIWIK_JS;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $sourceFile = $input->getOption('source-file');
+ $targetFile = $input->getOption('target-file');
+ $ignoreMinified = (bool)$input->getOption('ignore-minified');
+
+ $this->updateTracker($sourceFile, $targetFile, $ignoreMinified);
+
+ $output->writeln('<info>The Javascript Tracker has been updated</info>');
+ }
+
+ public function updateTracker($sourceFile, $targetFile, $ignoreMinified)
+ {
+ $pluginTrackerFiles = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackingCode\PluginTrackerFiles');
+
+ if ($ignoreMinified) {
+ if (empty($sourceFile) || $sourceFile === $this->getPathOriginalPiwikJs()) {
+ // no custom source file was requested
+ $sourceFile = PIWIK_DOCUMENT_ROOT . TrackerUpdater::DEVELOPMENT_PIWIK_JS;
+ }
+ $pluginTrackerFiles->ignoreMinified();
+ }
+
+ $updater = StaticContainer::getContainer()->make('Piwik\Plugins\CustomJsTracker\TrackerUpdater', array(
+ 'fromFile' => $sourceFile, 'toFile' => $targetFile
+ ));
+ $updater->setTrackerFiles($pluginTrackerFiles);
+ $updater->checkWillSucceed();
+ $updater->update();
+ }
+}
diff --git a/plugins/CustomJsTracker/CustomJsTracker.php b/plugins/CustomJsTracker/CustomJsTracker.php
new file mode 100644
index 0000000000..ebf63933fa
--- /dev/null
+++ b/plugins/CustomJsTracker/CustomJsTracker.php
@@ -0,0 +1,41 @@
+<?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\CustomJsTracker;
+
+use Piwik\Container\StaticContainer;
+use Piwik\Log;
+use Piwik\Plugin;
+
+class CustomJsTracker extends Plugin
+{
+ public function registerEvents()
+ {
+ return array(
+ 'CoreUpdater.update.end' => 'updateTracker',
+ 'PluginManager.pluginActivated' => 'updateTracker',
+ 'PluginManager.pluginDeactivated' => 'updateTracker',
+ 'PluginManager.pluginInstalled' => 'updateTracker',
+ 'PluginManager.pluginUninstalled' => 'updateTracker',
+ 'Updater.componentUpdated' => 'updateTracker',
+ 'Controller.CoreHome.checkForUpdates.end' => 'updateTracker'
+ );
+ }
+
+ public function updateTracker()
+ {
+ try {
+ if (Plugin\Manager::getInstance()->isPluginActivated('CustomJsTracker')) {
+ $trackerUpdater = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackerUpdater');
+ $trackerUpdater->update();
+ }
+ } catch (\Exception $e) {
+ Log::error('There was an error while updating the javascript tracker: ' . $e->getMessage());
+ }
+ }
+}
diff --git a/plugins/CustomJsTracker/Diagnostic/TrackerJsCheck.php b/plugins/CustomJsTracker/Diagnostic/TrackerJsCheck.php
new file mode 100644
index 0000000000..5def5fa717
--- /dev/null
+++ b/plugins/CustomJsTracker/Diagnostic/TrackerJsCheck.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+namespace Piwik\Plugins\CustomJsTracker\Diagnostic;
+
+use Piwik\Filechecks;
+use Piwik\Filesystem;
+use Piwik\Plugins\CustomJsTracker\File;
+use Piwik\Plugins\Diagnostics\Diagnostic\Diagnostic;
+use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
+use Piwik\SettingsPiwik;
+use Piwik\SettingsServer;
+use Piwik\Tracker\TrackerCodeGenerator;
+use Piwik\Translation\Translator;
+
+/**
+ * Check Piwik JS is writable
+ */
+class TrackerJsCheck implements Diagnostic
+{
+ /**
+ * @var Translator
+ */
+ private $translator;
+
+ public function __construct(Translator $translator)
+ {
+ $this->translator = $translator;
+ }
+
+ public function execute()
+ {
+ // for users that installed matomo 3.7+ we only check for matomo.js being writable... for all other users we
+ // check both piwik.js and matomo.js as they can use both
+ $filesToCheck = array('matomo.js');
+
+ $jsCodeGenerator = new TrackerCodeGenerator();
+ if (SettingsPiwik::isMatomoInstalled() && $jsCodeGenerator->shouldPreferPiwikEndpoint()) {
+ // if matomo is not installed yet, we definitely prefer matomo.js... check for isMatomoInstalled is needed
+ // cause otherwise it would perform a db query before matomo DB is configured
+ $filesToCheck[] = 'piwik.js';
+ }
+
+ $notWritableFiles = array();
+ foreach ($filesToCheck as $fileToCheck) {
+ $file = new File(PIWIK_DOCUMENT_ROOT . '/' . $fileToCheck);
+
+ if (!$file->hasWriteAccess()) {
+ $notWritableFiles[] = $fileToCheck;
+ }
+ }
+
+ $label = $this->translator->translate('CustomJsTracker_DiagnosticPiwikJsWritable', $this->makeFilesTitles($filesToCheck));
+
+ if (empty($notWritableFiles)) {
+ return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_OK, ''));
+ }
+
+ $comment = $this->translator->translate('CustomJsTracker_DiagnosticPiwikJsNotWritable', $this->makeFilesTitles($notWritableFiles));
+
+ if (!SettingsServer::isWindows()) {
+ $command = '';
+ foreach ($notWritableFiles as $notWritableFile) {
+ $realpath = Filesystem::realpath(PIWIK_INCLUDE_PATH . '/' . $notWritableFile);
+ $command .= "<br/><code> chmod +w $realpath<br/> chown ". Filechecks::getUserAndGroup() ." " . $realpath . "</code><br />";
+ }
+ $comment .= $this->translator->translate('CustomJsTracker_DiagnosticPiwikJsMakeWritable', array($this->makeFilesTitles($notWritableFiles), $command));
+ }
+
+ return array(DiagnosticResult::singleResult($label, DiagnosticResult::STATUS_WARNING, $comment));
+ }
+
+ private function makeFilesTitles($files)
+ {
+ return '"/'. implode('" & "/', $files) .'"';
+ }
+
+}
diff --git a/plugins/CustomJsTracker/Exception/AccessDeniedException.php b/plugins/CustomJsTracker/Exception/AccessDeniedException.php
new file mode 100644
index 0000000000..7cfd6a550e
--- /dev/null
+++ b/plugins/CustomJsTracker/Exception/AccessDeniedException.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomJsTracker\Exception;
+
+use Exception;
+
+class AccessDeniedException extends Exception
+{
+}
diff --git a/plugins/CustomJsTracker/File.php b/plugins/CustomJsTracker/File.php
new file mode 100644
index 0000000000..0b8325e902
--- /dev/null
+++ b/plugins/CustomJsTracker/File.php
@@ -0,0 +1,100 @@
+<?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\CustomJsTracker;
+
+use Piwik\Plugins\CustomJsTracker\Exception\AccessDeniedException;
+
+class File
+{
+ /**
+ * @var string
+ */
+ protected $file;
+
+ public function __construct($file)
+ {
+ $this->file = $file;
+ }
+
+ public function setFile($file)
+ {
+ return new static($file);
+ }
+
+ public function checkReadable()
+ {
+ if (!$this->hasReadAccess()) {
+ throw new AccessDeniedException(sprintf('The file %s is not readable', $this->file));
+ }
+ }
+
+ public function checkWritable()
+ {
+ if (!$this->hasWriteAccess()) {
+ throw new AccessDeniedException(sprintf('The file %s is not writable', $this->file));
+ }
+ }
+
+ public function isFileContentSame($content)
+ {
+ // we determine if file content is the same in here in case a different "file" implementation needs to check
+ // whether multiple files are up to date
+ return $this->getContent() === $content;
+ }
+
+ public function save($content)
+ {
+ if (false === file_put_contents($this->file, $content, LOCK_EX)) {
+ throw new AccessDeniedException(sprintf("Could not write to %s", $this->file));
+ }
+ // we need to return an array of files in case some other "File" implementation actually updates multiple files
+ // eg one file per trusted host
+ return [$this->getPath()];
+ }
+
+ public function getContent()
+ {
+ if (!$this->hasReadAccess()) {
+ return null;
+ }
+
+ return file_get_contents($this->file);
+ }
+
+ public function getPath()
+ {
+ return $this->file;
+ }
+
+ public function getName()
+ {
+ return basename($this->file);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasWriteAccess()
+ {
+ if (file_exists($this->file) && !is_writable($this->file)) {
+ return false;
+ }
+ return is_writable(dirname($this->file)) || is_writable($this->file);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasReadAccess()
+ {
+ return file_exists($this->file) && is_readable($this->file);
+ }
+
+
+}
diff --git a/plugins/CustomJsTracker/Tasks.php b/plugins/CustomJsTracker/Tasks.php
new file mode 100644
index 0000000000..24427a6d43
--- /dev/null
+++ b/plugins/CustomJsTracker/Tasks.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomJsTracker;
+
+use Piwik\Container\StaticContainer;
+
+class Tasks extends \Piwik\Plugin\Tasks
+{
+ public function schedule()
+ {
+ $this->hourly('updateTracker');
+ }
+
+ public function updateTracker()
+ {
+ $updater = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackerUpdater');
+ $updater->update();
+ }
+}
diff --git a/plugins/CustomJsTracker/TrackerUpdater.php b/plugins/CustomJsTracker/TrackerUpdater.php
new file mode 100644
index 0000000000..c301862b90
--- /dev/null
+++ b/plugins/CustomJsTracker/TrackerUpdater.php
@@ -0,0 +1,165 @@
+<?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\CustomJsTracker;
+
+use Piwik\Common;
+use Piwik\Container\StaticContainer;
+use Piwik\Plugins\CustomJsTracker\TrackingCode\PiwikJsManipulator;
+use Piwik\Plugins\CustomJsTracker\TrackingCode\PluginTrackerFiles;
+use Piwik\Piwik;
+
+/**
+ * Updates the Piwik JavaScript Tracker "piwik.js" in case plugins extend the tracker.
+ *
+ * Usage:
+ * StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackerUpdater')->update();
+ */
+class TrackerUpdater
+{
+ const DEVELOPMENT_PIWIK_JS = '/js/piwik.js';
+ const ORIGINAL_PIWIK_JS = '/js/piwik.min.js';
+ const TARGET_MATOMO_JS = '/matomo.js';
+
+ /**
+ * @var File
+ */
+ private $fromFile;
+
+ /**
+ * @var File
+ */
+ private $toFile;
+
+ private $trackerFiles = array();
+
+ /**
+ * @param string|null $fromFile If null then the minified JS tracker file in /js fill be used
+ * @param string|null $toFile If null then the minified JS tracker will be updated.
+ */
+ public function __construct($fromFile = null, $toFile = null)
+ {
+ if (!isset($fromFile)) {
+ $fromFile = PIWIK_DOCUMENT_ROOT . self::ORIGINAL_PIWIK_JS;
+ }
+
+ if (!isset($toFile)) {
+ $toFile = PIWIK_DOCUMENT_ROOT . self::TARGET_MATOMO_JS;
+ }
+
+ $this->setFromFile($fromFile);
+ $this->setToFile($toFile);
+ $this->trackerFiles = StaticContainer::get('Piwik\Plugins\CustomJsTracker\TrackingCode\PluginTrackerFiles');
+ }
+
+ public function setFromFile($fromFile)
+ {
+ if (is_string($fromFile)) {
+ $fromFile = new File($fromFile);
+ }
+ $this->fromFile = $fromFile;
+ }
+
+ public function getFromFile()
+ {
+ return $this->fromFile;
+ }
+
+ public function setToFile($toFile)
+ {
+ if (is_string($toFile)) {
+ $toFile = new File($toFile);
+ }
+ $this->toFile = $toFile;
+ }
+
+ public function getToFile()
+ {
+ return $this->toFile;
+ }
+
+ public function setTrackerFiles(PluginTrackerFiles $trackerFiles)
+ {
+ $this->trackerFiles = $trackerFiles;
+ }
+
+ /**
+ * Checks whether the Piwik JavaScript tracker file "piwik.js" is writable.
+ * @throws \Exception In case the piwik.js file is not writable.
+ *
+ * @api
+ */
+ public function checkWillSucceed()
+ {
+ $this->fromFile->checkReadable();
+ $this->toFile->checkWritable();
+ }
+
+ public function getCurrentTrackerFileContent()
+ {
+ return $this->toFile->getContent();
+ }
+
+ public function getUpdatedTrackerFileContent()
+ {
+ $trackingCode = new PiwikJsManipulator($this->fromFile->getContent(), $this->trackerFiles);
+ $newContent = $trackingCode->manipulateContent();
+
+ return $newContent;
+ }
+
+ /**
+ * Updates / re-generates the Piwik JavaScript tracker "piwik.js".
+ *
+ * It may not be possible to update the "piwik.js" tracker file if the file is not writable. It won't throw
+ * an exception in such a case and instead just to nothing. To check if the update would succeed, call
+ * {@link checkWillSucceed()}.
+ *
+ * @api
+ */
+ public function update()
+ {
+ if (!$this->toFile->hasWriteAccess() || !$this->fromFile->hasReadAccess()) {
+ return;
+ }
+
+ $newContent = $this->getUpdatedTrackerFileContent();
+
+ if (!$this->toFile->isFileContentSame($newContent)) {
+ $savedFiles = $this->toFile->save($newContent);
+ foreach ($savedFiles as $savedFile) {
+
+ /**
+ * Triggered after the tracker JavaScript content (the content of the piwik.js file) is changed.
+ *
+ * @param string $absolutePath The path to the new piwik.js file.
+ */
+ Piwik::postEvent('CustomJsTracker.trackerJsChanged', [$savedFile]);
+ }
+
+ }
+
+ // we need to make sure to sync matomo.js / piwik.js
+ $this->updateAlternative('piwik.js', 'matomo.js', $newContent);
+ $this->updateAlternative('matomo.js', 'piwik.js', $newContent);
+ }
+
+ private function updateAlternative($fromFile, $toFile, $newContent)
+ {
+ if (Common::stringEndsWith($this->toFile->getName(), $fromFile)) {
+ $alternativeFilename = dirname($this->toFile->getPath()) . DIRECTORY_SEPARATOR . $toFile;
+ $file = $this->toFile->setFile($alternativeFilename);
+ if ($file->hasWriteAccess() && !$file->isFileContentSame($newContent)) {
+ $savedFiles = $file->save($newContent);
+ foreach ($savedFiles as $savedFile) {
+ Piwik::postEvent('CustomJsTracker.trackerJsChanged', [$savedFile]);
+ }
+ }
+ }
+ }
+}
diff --git a/plugins/CustomJsTracker/TrackingCode/JsTestPluginTrackerFiles.php b/plugins/CustomJsTracker/TrackingCode/JsTestPluginTrackerFiles.php
new file mode 100644
index 0000000000..7bf8062a8b
--- /dev/null
+++ b/plugins/CustomJsTracker/TrackingCode/JsTestPluginTrackerFiles.php
@@ -0,0 +1,27 @@
+<?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\CustomJsTracker\TrackingCode;
+
+/**
+ * Used for when running Piwik tracker tests. We simply include all custom tracker files there.
+ */
+class JsTestPluginTrackerFiles extends PluginTrackerFiles
+{
+
+ public function __construct()
+ {
+ parent::__construct();
+ $this->ignoreMinified = true;
+ }
+
+ protected function isPluginActivated($pluginName)
+ {
+ return true;
+ }
+
+}
diff --git a/plugins/CustomJsTracker/TrackingCode/PiwikJsManipulator.php b/plugins/CustomJsTracker/TrackingCode/PiwikJsManipulator.php
new file mode 100644
index 0000000000..0fe821c4e6
--- /dev/null
+++ b/plugins/CustomJsTracker/TrackingCode/PiwikJsManipulator.php
@@ -0,0 +1,77 @@
+<?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\CustomJsTracker\TrackingCode;
+
+use Piwik\Piwik;
+
+class PiwikJsManipulator
+{
+ const HOOK = '/*!!! pluginTrackerHook */';
+ /**
+ * @var string
+ */
+ private $content;
+
+ /** @var PluginTrackerFiles */
+ private $pluginTrackerFiles;
+
+ public function __construct($content, PluginTrackerFiles $pluginTrackerFiles)
+ {
+ $this->content = $content;
+ $this->pluginTrackerFiles = $pluginTrackerFiles;
+ }
+
+ public function manipulateContent()
+ {
+ $files = $this->pluginTrackerFiles->find();
+
+ foreach ($files as $file) {
+ $trackerExtension = $this->getSignatureWithContent($file->getName(), $file->getContent());
+
+ // for some reasons it is /*!!! in piwik.js minified and /*!! in js/piwik.js unminified
+ $this->content = str_replace(array(self::HOOK, '/*!! pluginTrackerHook */'), self::HOOK . $trackerExtension, $this->content);
+ }
+
+ $content = $this->content;
+
+ /**
+ * Triggered after the Matomo JavaScript tracker has been generated and shortly before the tracker file
+ * is written to disk. You can listen to this event to for example automatically append some code to the JS
+ * tracker file.
+ *
+ * **Example**
+ *
+ * function onManipulateJsTracker (&$content) {
+ * $content .= "\nPiwik.DOM.onLoad(function () { console.log('loaded'); });";
+ * }
+ *
+ * @param string $content the generated JavaScript tracker code
+ */
+ Piwik::postEvent('CustomMatomoJs.manipulateJsTracker', array(&$content));
+ $this->content = $content;
+
+ return $this->content;
+ }
+
+ /**
+ * @param string $name
+ * @param string $content
+ * @return string
+ */
+ private function getSignatureWithContent($name, $content)
+ {
+ return sprintf(
+ "\n\n/* GENERATED: %s */\n%s\n/* END GENERATED: %s */\n",
+ $name,
+ $content,
+ $name
+ );
+ }
+
+}
diff --git a/plugins/CustomJsTracker/TrackingCode/PluginTrackerFiles.php b/plugins/CustomJsTracker/TrackingCode/PluginTrackerFiles.php
new file mode 100644
index 0000000000..e062a73516
--- /dev/null
+++ b/plugins/CustomJsTracker/TrackingCode/PluginTrackerFiles.php
@@ -0,0 +1,110 @@
+<?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\CustomJsTracker\TrackingCode;
+
+use Piwik\Piwik;
+use Piwik\Plugin;
+use Piwik\Plugins\CustomJsTracker\File;
+
+class PluginTrackerFiles
+{
+ const TRACKER_FILE = 'tracker.js';
+ const MIN_TRACKER_FILE = 'tracker.min.js';
+
+ /**
+ * @var string
+ */
+ protected $dir;
+
+ /**
+ * @var Plugin\Manager
+ */
+ private $pluginManager;
+
+ /**
+ * @var bool
+ */
+ protected $ignoreMinified = false;
+
+ public function __construct()
+ {
+ $this->dir = PIWIK_DOCUMENT_ROOT . '/plugins/';
+ $this->pluginManager = Plugin\Manager::getInstance();
+ }
+
+ public function ignoreMinified()
+ {
+ $this->ignoreMinified = true;
+ }
+
+ /**
+ * @return File[]
+ */
+ public function find()
+ {
+ $jsFiles = array();
+
+ if (!$this->ignoreMinified) {
+ $trackerFiles = \_glob($this->dir . '*/' . self::MIN_TRACKER_FILE);
+
+ foreach ($trackerFiles as $trackerFile) {
+ $plugin = $this->getPluginNameFromFile($trackerFile);
+ if ($this->isPluginActivated($plugin)) {
+ $jsFiles[$plugin] = new File($trackerFile);
+ }
+ }
+ }
+
+ $trackerFiles = \_glob($this->dir . '*/' . self::TRACKER_FILE);
+
+ foreach ($trackerFiles as $trackerFile) {
+ $plugin = $this->getPluginNameFromFile($trackerFile);
+ if (!isset($jsFiles[$plugin])) {
+ if ($this->isPluginActivated($plugin)) {
+ $jsFiles[$plugin] = new File($trackerFile);
+ }
+ }
+ }
+
+ foreach ($jsFiles as $plugin => $file) {
+ if (!$this->shouldIncludeFile($plugin)) {
+ unset($jsFiles[$plugin]);
+ }
+ }
+
+ return $jsFiles;
+ }
+
+ protected function shouldIncludeFile($pluginName)
+ {
+ $shouldAddFile = true;
+
+ /**
+ * Detect if a custom tracker file should be added to the piwik.js tracker or not.
+ *
+ * This is useful for example if a plugin only wants to add its tracker file when the plugin is configured.
+ *
+ * @param bool &$shouldAddFile Decides whether the tracker file belonging to the given plugin should be added or not.
+ * @param string $pluginName The name of the plugin this file belongs to
+ */
+ Piwik::postEvent('CustomJsTracker.shouldAddTrackerFile', array(&$shouldAddFile, $pluginName));
+
+ return $shouldAddFile;
+ }
+
+ protected function isPluginActivated($pluginName)
+ {
+ return $this->pluginManager->isPluginActivated($pluginName);
+ }
+
+ protected function getPluginNameFromFile($file)
+ {
+ $file = str_replace(array($this->dir, self::TRACKER_FILE, self::MIN_TRACKER_FILE), '', $file);
+ return trim($file, '/');
+ }
+}
diff --git a/plugins/CustomJsTracker/config/config.php b/plugins/CustomJsTracker/config/config.php
new file mode 100644
index 0000000000..88d0d6417a
--- /dev/null
+++ b/plugins/CustomJsTracker/config/config.php
@@ -0,0 +1,7 @@
+<?php
+
+return array(
+ 'diagnostics.optional' => DI\add(array(
+ DI\get('Piwik\Plugins\CustomJsTracker\Diagnostic\TrackerJsCheck'),
+ )),
+);
diff --git a/plugins/CustomJsTracker/config/tracker.php b/plugins/CustomJsTracker/config/tracker.php
new file mode 100644
index 0000000000..ca2affec34
--- /dev/null
+++ b/plugins/CustomJsTracker/config/tracker.php
@@ -0,0 +1,2 @@
+<?php
+return array();
diff --git a/plugins/CustomJsTracker/lang/cs.json b/plugins/CustomJsTracker/lang/cs.json
new file mode 100644
index 0000000000..6812140b56
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/cs.json
@@ -0,0 +1,5 @@
+{
+ "CustomJsTracker": {
+ "DiagnosticPiwikJsWritable": "Zapisovatelný JavaScript záznam (%s)"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/da.json b/plugins/CustomJsTracker/lang/da.json
new file mode 100644
index 0000000000..e73375b481
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/da.json
@@ -0,0 +1,6 @@
+{
+ "CustomJsTracker": {
+ "DiagnosticPiwikJsWritable": "JavaScript-sporingsfiler (%s) skrivbare",
+ "DiagnosticPiwikJsMakeWritable": "Vi anbefaler at gøre %1$s skrivbare ved at køre denne kommando: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/de.json b/plugins/CustomJsTracker/lang/de.json
new file mode 100644
index 0000000000..a01246a60a
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/de.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Ermöglicht es jedem Plugin, die Matomo-JavaScript-Tracking-Datei (matomo.js) zu erweitern und neue Funktionalitäts- und Website-Messfunktionen hinzuzufügen.",
+ "DiagnosticPiwikJsWritable": "Schreibbarer JavaScript-Tracker (%s)",
+ "DiagnosticPiwikJsNotWritable": "Für die Matomo JavaScript-Tracker-Datei %s sind keine Schreibrechte vorhanden , das bedeutet, dass andere Plugins den JavaScript-Tracker nicht erweitern können. In Zukunft könnten sogar einige Kernfunktionen nicht wie erwartet funktionieren.",
+ "DiagnosticPiwikJsMakeWritable": "Wir empfehlen, %1$sbeschreibbar zu machen, indem Sie folgenden Befehl ausführen: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/el.json b/plugins/CustomJsTracker/lang/el.json
new file mode 100644
index 0000000000..86b0c82b3b
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/el.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Επιτρέπει σε οποιοδήποτε πρόσθετο να επεκτείνει το αρχείο ιχνηλάτησης σε JavaScript του Matomo (matomo.js) και να προσθέτει νέα λειτουργικότητα και δυνατότητες μετρήσεων των ιστοτόπων.",
+ "DiagnosticPiwikJsWritable": "Εγγράψιμο Αρχείο Ιχνηλάτησης JavaScript (%s)",
+ "DiagnosticPiwikJsNotWritable": "Το αρχείο ιχνηλάτησης της JavaScript του Matomo %s δεν είναι εγγράψιμο, που σημαίνει ότι άλλα πρόσθετα δεν μπορούν να επεκτείνουν την ιχνηλάτηση. Μελλοντικά ενδέχεται ορισμένα χαρακτηριστικά του πυρήνα να μην λειτουργούν όπως πρέπει.",
+ "DiagnosticPiwikJsMakeWritable": "Προτείνουμε να κάνετε το %1$s εγγράψιμο με εκτέλεση της εντολής: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/en.json b/plugins/CustomJsTracker/lang/en.json
new file mode 100644
index 0000000000..9d64e174df
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/en.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Allows any plugin to extend the Matomo JavaScript Tracking file (matomo.js) and add new functionality and website measurement capabilities.",
+ "DiagnosticPiwikJsWritable": "Writable JavaScript Tracker (%s)",
+ "DiagnosticPiwikJsNotWritable": "The Matomo JavaScript tracker file %s is not writable which means other plugins cannot extend the JavaScript tracker. In the future even some core features might not work as expected. ",
+ "DiagnosticPiwikJsMakeWritable": "We recommend to make %1$s writable by running this command: %2$s"
+ }
+}
diff --git a/plugins/CustomJsTracker/lang/eo.json b/plugins/CustomJsTracker/lang/eo.json
new file mode 100644
index 0000000000..8e069b7a31
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/eo.json
@@ -0,0 +1,7 @@
+{
+ "CustomJsTracker": {
+ "DiagnosticPiwikJsWritable": "Skribebla Sekvanta JavaSkripto (%s)",
+ "DiagnosticPiwikJsNotWritable": "La JavaSkriptan sekvanta dosiero %sne stas skriblebla. Pro tio aliaj kromprogramoj ne povas etendi la JavaSkriptan sekvanto. Estontece eĉ kelkaj kernaj funkcioj povus ne funkcii kiel atendite.",
+ "DiagnosticPiwikJsMakeWritable": "Ni rekomendas fari %1$sskribebla per funkciado de ĉi tiu komando: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/es-ar.json b/plugins/CustomJsTracker/lang/es-ar.json
new file mode 100644
index 0000000000..6db0d6c3eb
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/es-ar.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Permite a cualquier plugin extender el archivo de rastreo vía JavaScript de Matomo (\"matomo.js\"), y agrega nuevas capacidades de medición de funcionalidad y sitios web.",
+ "DiagnosticPiwikJsWritable": "Rastreador escribible de JavaScript (%s)",
+ "DiagnosticPiwikJsNotWritable": "El archivo rastreador vía JavaScript de Matomo %s no es escribible, lo cual significa que otros plugins no pueden extender el rastreador de JavaScript. En el futuro es posible que incluso funciones centrales no funcionen como lo esperado.",
+ "DiagnosticPiwikJsMakeWritable": "Recomendamos hacer que %1$s sea escribible mediante este comando: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/es.json b/plugins/CustomJsTracker/lang/es.json
new file mode 100644
index 0000000000..cc338858e8
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/es.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Permite a cualquier módulo ampliar el archivo de seguimiento Javascript Matomo (matomo.js) y agregar nuevas funcionalidades y capacidades de medición de desempeño a un sitio web.",
+ "DiagnosticPiwikJsWritable": "Archivo de seguimiento Javascript con permiso de escritura (%s)",
+ "DiagnosticPiwikJsNotWritable": "El archivo Javascript de seguimiento Matomo %s no posee permisos de escritura, lo que significa que los otros módulos no pueden enriquecerlo. Más aun, en un futuro, algunas funciones básicas podrían no funcionar como debieran.",
+ "DiagnosticPiwikJsMakeWritable": "Recomendamos que %1$s posea permisos de escritura simplemente ejecutando este comando: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/fi.json b/plugins/CustomJsTracker/lang/fi.json
new file mode 100644
index 0000000000..ce556e4dd3
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/fi.json
@@ -0,0 +1,7 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Sallii minkä tahansa liitännäisen laajentaa Matomon JavaScript-seurantatiedostoa (matomo.js) ja lisätä uusia toiminnallisuuksia sekä verkkosivuston mittauskyvykkyyksiä.",
+ "DiagnosticPiwikJsWritable": "Kirjoitettava JavaScript-seurain (%s)",
+ "DiagnosticPiwikJsMakeWritable": "Suosittelemme, että %1$s asetetaan kirjoitettavaksi suorittamalla seuraava komento: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/fr.json b/plugins/CustomJsTracker/lang/fr.json
new file mode 100644
index 0000000000..d4e8979249
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/fr.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Autorise n'importe quel composant à mettre à jour le fichier de suivi Matomo (matomo.js) et à ajouter de nouvelles fonctionnalités ainsi que des capacités de suivi de site web.",
+ "DiagnosticPiwikJsWritable": "Traceur JavaScript inscriptible (%s)",
+ "DiagnosticPiwikJsNotWritable": "Le fichier traceur JavaScript Matomo %sinscriptible ce qui veut dit que d'autres composants ne peuvent pas le modifier. Dans le futur il se pourrait même que certaines fonctionnalités principales ne fonctionnent pas comme prévu.",
+ "DiagnosticPiwikJsMakeWritable": "Nous recommandons de rendre %1$s inscriptible en exécutant la commande suivante : %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/id.json b/plugins/CustomJsTracker/lang/id.json
new file mode 100644
index 0000000000..9f590870cf
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/id.json
@@ -0,0 +1,5 @@
+{
+ "CustomJsTracker": {
+ "DiagnosticPiwikJsWritable": "Pelacak JavaScript yang Dapat Ditulis (\"\/piwik.js\")"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/it.json b/plugins/CustomJsTracker/lang/it.json
new file mode 100644
index 0000000000..e2d47aa161
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/it.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Consente a qualsiasi plug-in di estendere il file di tracking JavaScript di Matomo (matomo.js) e di aggiungere nuove funzionalità e capacità di misurazione del sito web.",
+ "DiagnosticPiwikJsWritable": "JavaScript Tracker scrivibile (%s)",
+ "DiagnosticPiwikJsNotWritable": "Il file tracker JavaScript di Matomo %s non è scrivibile, il che significa che altri plugin non possono estendere il tracker JavaScript. In futuro anche alcune funzionalità di base potrebbero non funzionare come previsto.",
+ "DiagnosticPiwikJsMakeWritable": "Ci raccomandiamo di rendere scrivibile %1$s eseguendo questo comando: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/ja.json b/plugins/CustomJsTracker/lang/ja.json
new file mode 100644
index 0000000000..dfa58e56c7
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/ja.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "任意のプラグインが Matomo JavaScript Tracking ファイル( matomo.js )を拡張し、新しい機能と Web サイトの測定機能を追加することができます。",
+ "DiagnosticPiwikJsWritable": "書き込み可能な JavaScript トラッカー(%s)",
+ "DiagnosticPiwikJsNotWritable": "Matomo JavaScriptトラッカーファイル %s は書き込み可能ではありません。これは、他のプラグインが JavaScriptトラッカー を拡張できないことを意味します。 将来的には、一部のコア機能も期待どおりに機能しない可能性があります。",
+ "DiagnosticPiwikJsMakeWritable": "次のコマンドを実行して、%1$s を書き込み可能にすることを推奨します。: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/nb.json b/plugins/CustomJsTracker/lang/nb.json
new file mode 100644
index 0000000000..ce4b6f7f2b
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/nb.json
@@ -0,0 +1,6 @@
+{
+ "CustomJsTracker": {
+ "DiagnosticPiwikJsWritable": "Skrivbar JavaScript-tracker (\"\/piwik.js\")",
+ "DiagnosticPiwikJsMakeWritable": "Vi anbefaler å gjøre piwik.js skrivbar ved å kjøre denne kommandoen: %s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/nl.json b/plugins/CustomJsTracker/lang/nl.json
new file mode 100644
index 0000000000..c91b025cfc
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/nl.json
@@ -0,0 +1,6 @@
+{
+ "CustomJsTracker": {
+ "DiagnosticPiwikJsWritable": "Schrijfbare JavaScript-tracker (%s)",
+ "DiagnosticPiwikJsMakeWritable": "We adviseren %1$s schrijfbaar te maken door het volgende commando uit te voeren: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/pl.json b/plugins/CustomJsTracker/lang/pl.json
new file mode 100644
index 0000000000..fd21f3a1ae
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/pl.json
@@ -0,0 +1,6 @@
+{
+ "CustomJsTracker": {
+ "DiagnosticPiwikJsWritable": "Zapisywalny Traker JavaScript (%s)",
+ "DiagnosticPiwikJsMakeWritable": "Zalecamy nadanie dla %1$s uprawnień do zapisu wykonując tą komendę: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/pt-br.json b/plugins/CustomJsTracker/lang/pt-br.json
new file mode 100644
index 0000000000..328ef53026
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/pt-br.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Permite que qualquer plugin estenda o arquivo JavaScript de Rastreamento do Matomo (matomo.js) e adicione novas funcionalidades e recursos de medição de site.",
+ "DiagnosticPiwikJsWritable": "Rastreador Javascript gravável (%s)",
+ "DiagnosticPiwikJsNotWritable": "O arquivo JavaScript rastreador do Matomo %s não é gravável, o que significa que outros plugins não podem estender o rastreador JavaScript. No futuro até mesmo alguns recursos principais podem não funcionar como esperado.",
+ "DiagnosticPiwikJsMakeWritable": "Recomendamos tornar %1$s gravável executando este comando: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/pt.json b/plugins/CustomJsTracker/lang/pt.json
new file mode 100644
index 0000000000..f2bbd63711
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/pt.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Permite que qualquer extensão complemente o ficheiro de acompanhamento de JavaScript do Matomo (matomo.js) e adicione novas funcionalidades e capacidades de medição de sites.",
+ "DiagnosticPiwikJsWritable": "Permissões de escrita no JavaScript de acompanhamento (%s)",
+ "DiagnosticPiwikJsNotWritable": "O ficheiro JavaScript de acompanhamento do Matomo %snão tem permissões de escrita, o que significa que outras extensões não conseguem complementar o tracker JavaScript. É possível que no futuro certas funcionalidades centrais não funcionem como o esperado.",
+ "DiagnosticPiwikJsMakeWritable": "Recomendamos que dê permissões de escrita a %1$s, executando o seguinte comando %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/ru.json b/plugins/CustomJsTracker/lang/ru.json
new file mode 100644
index 0000000000..c128245e67
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/ru.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Разрешить любому плагину дополнять JavaScript-код отслеживания Matomo (matomo.js), добавляя новую функциональность и расширяя возможности мониторинга сайтов.",
+ "DiagnosticPiwikJsWritable": "Записываемый JavaScript Tracker (%s)",
+ "DiagnosticPiwikJsNotWritable": "Файл трекера JavaScript Matomo %s недоступен для записи, это означает, что другие плагины не могут расширять трекер JavaScript. В будущем даже некоторые основные функции могут работать не так, как ожидалось.",
+ "DiagnosticPiwikJsMakeWritable": "Мы рекомендуем сделать %1$s доступным для записи, выполнив эту команду: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/sq.json b/plugins/CustomJsTracker/lang/sq.json
new file mode 100644
index 0000000000..788f53286b
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/sq.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "I lejon cilësdo shtojcë të zgjerojë kartelën Matomo JavaScript Tracking (matomo.js) dhe të shtojë aftësi të reja funksionimi dhe matjesh në sajt.",
+ "DiagnosticPiwikJsWritable": "Ndjekës JavaScript i Shkrueshëm (%s)",
+ "DiagnosticPiwikJsNotWritable": "Kartela e ndjekësit JavaScript të Matomo-s %s s’është e shkrueshme, çka do të thotë se shtojcat e tjera s’mund ta zgjerojnë ndjekësin JavaScript. Në të ardhmen mund të mos punojnë siç pritet madje edhe disa veçori bazë.",
+ "DiagnosticPiwikJsMakeWritable": "Këshillojmë bërjen e %1$s të shkrueshme, duke xhiruar urdhrin: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/sr.json b/plugins/CustomJsTracker/lang/sr.json
new file mode 100644
index 0000000000..a95564a8af
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/sr.json
@@ -0,0 +1,7 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Omogućuje bilo kom Matomo dodatku da proširi Matomo JavaScript datoteku za praćenje (piwik.js) i doda nove funkcionalnosti i mogućnosti za praćenje sajtova.",
+ "DiagnosticPiwikJsWritable": "JavaScript treker u koji je moguće pisati (\"\/piwik.js\")",
+ "DiagnosticPiwikJsMakeWritable": "Predlažemo da omogućite pisanje u piwik.js sledećom komandom: %s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/sv.json b/plugins/CustomJsTracker/lang/sv.json
new file mode 100644
index 0000000000..f0e5c00caa
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/sv.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Gör att insticksprogram kan bygga vidare på Matomos JavaScript-spårare (matomo.js) för att lägga till nya funktioner och möjligheter för att mäta webbplatser.",
+ "DiagnosticPiwikJsWritable": "Skrivbar JavaScript-spårare (%s)",
+ "DiagnosticPiwikJsNotWritable": "JavaScript-filen innehållande Matomos spårare (%s) är skrivskyddad, vilket innebär att insticksprogram inte kan utöka JavaScript-spåraren. I framtiden kan även vissa andra grundfunktioner upphöra fungera som väntat.",
+ "DiagnosticPiwikJsMakeWritable": "Vi rekommenderar att ta bort skrivskyddet från filen %1$s genom att använda följande kommando: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/tr.json b/plugins/CustomJsTracker/lang/tr.json
new file mode 100644
index 0000000000..6a45f628b6
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/tr.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Matomo JavaScript İzleme dosyasına (matomo.js) tüm uygulama eklerinin katkıda bulunmasını sağlayarak yeni özellik ve web sitesi ölçüm yetenekleri ekler.",
+ "DiagnosticPiwikJsWritable": "Yazılabilir JavaScript İzleyici (%s)",
+ "DiagnosticPiwikJsNotWritable": "%s Matomo JavaScript izleyici dosyası yazılabilir olmadığından diğer uygulama ekleri JavaScript İzleyiciyi kullanamaz. İleride bazı temel özellikler de beklendiği gibi çalışmayabilir.",
+ "DiagnosticPiwikJsMakeWritable": "Şu komutu kullanarak %1$s dosyasını yazılabilir yapmanız önerilir: %2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/uk.json b/plugins/CustomJsTracker/lang/uk.json
new file mode 100644
index 0000000000..0ffe45b22d
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/uk.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "Дозволяє будь-якому плагіну розширювати Matomo JavaScript код відстеження (piwik.js) і додавати нові можливості функціонування та можливості вимірювання веб-сайту.",
+ "DiagnosticPiwikJsWritable": "Доступний для запису JavaScript трекер (\"\/piwik.js\")",
+ "DiagnosticPiwikJsNotWritable": "Відстеження файлів Matomo через JavaScript \"\/piwik.js\" не підлягає запису, це означає, що інші плагіни не можуть розширити трекер JavaScript. Надалі навіть деякі основні функції можуть не працювати, як очікується.",
+ "DiagnosticPiwikJsMakeWritable": "Ми рекомендуємо piwik.js зробити доступним для запису, виконавши команду: %s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/zh-cn.json b/plugins/CustomJsTracker/lang/zh-cn.json
new file mode 100644
index 0000000000..dede8a4824
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/zh-cn.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "允许任何插件扩展Matomo JavaScript追踪文件(matomo.js)并添加新功能和网站评估功能。",
+ "DiagnosticPiwikJsWritable": "可写的JavaScript追踪器(%s)",
+ "DiagnosticPiwikJsNotWritable": "Matomo JavaScript追踪器文件%s不可写,这意味着其他插件无法扩展JavaScript追踪器。 将来,甚至某些核心功能可能也无法按预期运行。",
+ "DiagnosticPiwikJsMakeWritable": "我们建议通过运行以下命令使%1$s可写:%2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/lang/zh-tw.json b/plugins/CustomJsTracker/lang/zh-tw.json
new file mode 100644
index 0000000000..d7d3087d30
--- /dev/null
+++ b/plugins/CustomJsTracker/lang/zh-tw.json
@@ -0,0 +1,8 @@
+{
+ "CustomJsTracker": {
+ "PluginDescription": "允許任何外掛擴展 Matomo JavaScript 追蹤檔案(piwik.js)來增加新功能和網站追蹤能力。",
+ "DiagnosticPiwikJsWritable": "JavaScript 追蹤檔案可寫入(%s)",
+ "DiagnosticPiwikJsNotWritable": "Matomo 的 JavaScript 追蹤檔案 %s 無法寫入,這代表其他外掛無法擴展 JavaScript 追蹤功能。未來甚至有些核心功能會無法正常運作。",
+ "DiagnosticPiwikJsMakeWritable": "我們推薦讓它%1$s可寫入,請執行此指令:%2$s"
+ }
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/tests/Framework/Mock/PluginTrackerFilesMock.php b/plugins/CustomJsTracker/tests/Framework/Mock/PluginTrackerFilesMock.php
new file mode 100644
index 0000000000..4849753a51
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/Framework/Mock/PluginTrackerFilesMock.php
@@ -0,0 +1,36 @@
+<?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\CustomJsTracker\tests\Framework\Mock;
+
+use Piwik\Plugins\CustomJsTracker\File;
+use Piwik\Plugins\CustomJsTracker\TrackingCode\PluginTrackerFiles;
+
+class PluginTrackerFilesMock extends PluginTrackerFiles
+{
+ /**
+ * @var array
+ */
+ private $files;
+
+ public function __construct($files)
+ {
+ $this->files = $files;
+ }
+
+ public function find()
+ {
+ $files = array();
+ foreach ($this->files as $file) {
+ $files[] = new File(PIWIK_DOCUMENT_ROOT . $file);
+ }
+ return $files;
+ }
+
+
+}
diff --git a/plugins/CustomJsTracker/tests/Integration/ApiTest.php b/plugins/CustomJsTracker/tests/Integration/ApiTest.php
new file mode 100644
index 0000000000..b8ffa36602
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/Integration/ApiTest.php
@@ -0,0 +1,84 @@
+<?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\CustomJsTracker\tests\Integration;
+
+use Piwik\Plugins\CustomJsTracker\API;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\Mock\FakeAccess;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomJsTracker
+ * @group ApiTest
+ * @group Api
+ * @group Plugins
+ */
+class ApiTest extends IntegrationTestCase
+{
+ /**
+ * @var API
+ */
+ private $api;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ Fixture::createSuperUser();
+ Fixture::createWebsite('2014-01-01 01:02:03');
+ $this->api = API::getInstance();
+ }
+
+ /**
+ * @expectedException \Piwik\NoAccessException
+ * @expectedExceptionMessage checkUserHasSomeAdminAccess
+ */
+ public function test_doesIncludePluginTrackersAutomatically_failsIfNotEnoughPermission()
+ {
+ $this->setUser();
+ $this->api->doesIncludePluginTrackersAutomatically();
+ }
+
+ /**
+ * @expectedException \Piwik\NoAccessException
+ * @expectedExceptionMessage checkUserHasSomeAdminAccess
+ */
+ public function test_doesIncludePluginTrackersAutomatically_failsIfNotEnoughPermissionAnonymous()
+ {
+ $this->setAnonymousUser();
+ $this->api->doesIncludePluginTrackersAutomatically();
+ }
+
+ public function test_doesIncludePluginTrackersAutomatically_returnsValueWhenEnoughPermission()
+ {
+ $this->assertTrue($this->api->doesIncludePluginTrackersAutomatically());
+ }
+
+ protected function setUser()
+ {
+ FakeAccess::clearAccess(false);
+ FakeAccess::$identity = 'testUsername';
+ FakeAccess::$idSitesView = array(1);
+ FakeAccess::$idSitesAdmin = array();
+ }
+
+ protected function setAnonymousUser()
+ {
+ FakeAccess::clearAccess();
+ FakeAccess::$identity = 'anonymous';
+ }
+
+ public function provideContainerConfig()
+ {
+ return array(
+ 'Piwik\Access' => new FakeAccess()
+ );
+ }
+
+}
diff --git a/plugins/CustomJsTracker/tests/Integration/FileTest.php b/plugins/CustomJsTracker/tests/Integration/FileTest.php
new file mode 100644
index 0000000000..1457de0336
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/Integration/FileTest.php
@@ -0,0 +1,210 @@
+<?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\CustomJsTracker\tests\Integration;
+
+use Piwik\Plugins\CustomJsTracker\File;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+class CustomTestFile extends File {
+
+}
+
+/**
+ * @group CustomJsTracker
+ * @group FileTest
+ * @group File
+ * @group Plugins
+ */
+class FileTest extends IntegrationTestCase
+{
+ const NOT_EXISTING_FILE_IN_WRITABLE_DIRECTORY = 'notExisTinGFile.js';
+ const NOT_EXISTING_FILE_IN_NON_WRITABLE_DIRECTORY = 'is-not-writable/notExisTinGFile.js';
+
+ /**
+ * @var string
+ */
+ private $dir = '';
+
+ public function setUp()
+ {
+ parent::setUp();
+ $this->dir = PIWIK_DOCUMENT_ROOT . '/plugins/CustomJsTracker/tests/resources/';
+
+ // make directory not writable
+ $nonWritableDir = dirname($this->dir . self::NOT_EXISTING_FILE_IN_NON_WRITABLE_DIRECTORY);
+ @chmod($nonWritableDir, 0444);
+ if(is_writable($nonWritableDir)) {
+ throw new \Exception("The directory $nonWritableDir should have been made non writable by this test, but it didn't work");
+ }
+ }
+
+ public function tearDown()
+ {
+ // restore permissions changed by makeNotWritableFile()
+ chmod($this->dir, 0777);
+
+ if (file_exists($this->dir . self::NOT_EXISTING_FILE_IN_WRITABLE_DIRECTORY)) {
+ unlink($this->dir . self::NOT_EXISTING_FILE_IN_WRITABLE_DIRECTORY);
+ }
+ parent::tearDown();
+ }
+
+ private function makeFile($fileName = 'test.js')
+ {
+ return new File($this->dir . $fileName);
+ }
+
+ private function makeNotWritableFile()
+ {
+ $path = $this->dir . 'file-made-non-writable.js';
+ if(file_exists($path)) {
+ chmod($path, 0777);
+ }
+ $file = new File($path);
+ $file->save('will be saved OK, and then we make it non writable.');
+
+ if (!chmod($path, 0444)) {
+ throw new \Exception("chmod on the file didn't work");
+ }
+ if (!chmod(dirname($path), 0755)) {
+ throw new \Exception("chmod on the directory didn't work");
+ }
+ $this->assertTrue(is_writable(dirname($path)));
+ $this->assertFalse(is_writable($path));
+ $this->assertTrue(file_exists($path));
+ return $file;
+ }
+
+ private function makeNotReadableFile()
+ {
+ return $this->makeNotReadableFile_inWritableDirectory();
+ }
+
+ private function makeNotReadableFile_inNonWritableDirectory()
+ {
+ return $this->makeFile(self::NOT_EXISTING_FILE_IN_NON_WRITABLE_DIRECTORY);
+ }
+
+ private function makeNotReadableFile_inWritableDirectory()
+ {
+ return $this->makeFile(self::NOT_EXISTING_FILE_IN_WRITABLE_DIRECTORY);
+ }
+
+ public function test_getName()
+ {
+ $this->assertSame('test.js', $this->makeFile()->getName());
+ $this->assertSame('notExisTinGFile.js', $this->makeNotReadableFile()->getName());
+ }
+
+ public function test_setFile_createsNewObjectLeavesOldUnchanged()
+ {
+ $file = $this->makeFile();
+ $file2 = $file->setFile('foo/bar.png');
+ $this->assertSame('test.js', $file->getName());
+ $this->assertSame('bar.png', $file2->getName());
+ }
+
+ public function test_setFile_returnsObjectOfSameType()
+ {
+ $file = new CustomTestFile('foo/baz.png');
+ $file2 = $file->setFile('foo/bar.png');
+ $this->assertTrue($file2 instanceof CustomTestFile);
+ }
+
+ public function test_getPath()
+ {
+ $this->assertSame($this->dir . 'notExisTinGFile.js', $this->makeNotReadableFile()->getPath());
+ }
+
+ public function test_hasReadAccess()
+ {
+ $this->assertTrue($this->makeFile()->hasReadAccess());
+ $this->assertFalse($this->makeNotReadableFile()->hasReadAccess());
+ }
+
+ public function test_hasWriteAccess()
+ {
+ $this->assertTrue($this->makeFile()->hasWriteAccess());
+ $this->assertTrue($this->makeNotReadableFile_inWritableDirectory()->hasWriteAccess());
+ $this->assertFalse($this->makeNotReadableFile_inNonWritableDirectory()->hasWriteAccess());
+ }
+
+ public function test_hasWriteAccess_whenFileExistAndIsNotWritable()
+ {
+ $this->assertFalse($this->makeNotWritableFile()->hasWriteAccess());
+ }
+
+ public function test_checkReadable_shouldNotThrowException_IfIsReadable()
+ {
+ $this->makeFile()->checkReadable();
+ $this->assertTrue(true);
+ }
+
+ public function test_checkWritable_shouldNotThrowException_IfIsWritable()
+ {
+ $this->makeFile()->checkWritable();
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException \Piwik\Plugins\CustomJsTracker\Exception\AccessDeniedException
+ * @expectedExceptionMessage not readable
+ */
+ public function test_checkReadable_shouldThrowException_IfNotIsReadable()
+ {
+ $this->makeNotReadableFile()->checkReadable();
+ }
+
+ /**
+ * @expectedException \Piwik\Plugins\CustomJsTracker\Exception\AccessDeniedException
+ * @expectedExceptionMessage not writable
+ */
+ public function test_checkWritable_shouldThrowException_IfNotIsWritable()
+ {
+ $this->makeNotReadableFile_inNonWritableDirectory()->checkWritable();
+ }
+
+ public function test_checkWritable_shouldNotThrowException_IfDirectoryIsWritable()
+ {
+ $this->makeNotReadableFile_inWritableDirectory()->checkWritable();
+ }
+
+ public function test_getContent()
+ {
+ $this->assertSame("// Hello world\nvar fooBar = 'test';", $this->makeFile()->getContent());
+ }
+
+ public function test_isFileContentSame()
+ {
+ $this->assertTrue($this->makeFile()->isFileContentSame("// Hello world\nvar fooBar = 'test';"));
+ $this->assertFalse($this->makeFile()->isFileContentSame("// Hello world\nvar foBar = 'test';"));
+ $this->assertFalse($this->makeFile()->isFileContentSame("// Hello world\nvar foBar = 'test'"));
+ $this->assertFalse($this->makeFile()->isFileContentSame("Hello world\nvar foBar = 'test'"));
+ }
+
+ public function test_getContent_returnsNull_IfFileIsNotReadableOrNotExists()
+ {
+ $this->assertNull($this->makeNotReadableFile()->getContent());
+ }
+
+ public function test_save()
+ {
+ $notExistingFile = $this->makeNotReadableFile_inWritableDirectory();
+ $this->assertFalse($notExistingFile->hasReadAccess());
+ $this->assertTrue($notExistingFile->hasWriteAccess());
+
+ $updatedFile = $notExistingFile->save('myTestContent');
+
+ $this->assertEquals([$this->dir . 'notExisTinGFile.js'], $updatedFile);
+ $this->assertEquals('myTestContent', $notExistingFile->getContent());
+ $this->assertTrue($notExistingFile->hasReadAccess());
+ $this->assertTrue($notExistingFile->hasWriteAccess());
+ }
+
+}
diff --git a/plugins/CustomJsTracker/tests/Integration/PiwikJsManipulatorTest.php b/plugins/CustomJsTracker/tests/Integration/PiwikJsManipulatorTest.php
new file mode 100644
index 0000000000..3decbd21f7
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/Integration/PiwikJsManipulatorTest.php
@@ -0,0 +1,73 @@
+<?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\CustomJsTracker\tests\Integration;
+
+use Piwik\Plugins\CustomJsTracker\tests\Framework\Mock\PluginTrackerFilesMock;
+use Piwik\Plugins\CustomJsTracker\TrackingCode\PiwikJsManipulator;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomJsTracker
+ * @group PiwikJsManipulatorTest
+ * @group PiwikJsManipulator
+ * @group Plugins
+ */
+class PiwikJsManipulatorTest extends IntegrationTestCase
+{
+ private $content = 'var Piwik.js = "mytest";
+/*!!! pluginTrackerHook */
+
+var myArray = [];
+';
+
+ public function test_manipulateContent_shouldAddCodeOfTrackerPlugins()
+ {
+ $manipulator = $this->makeManipulator(array(
+ '/plugins/CustomJsTracker/tests/resources/tracker.js',
+ '/plugins/CustomJsTracker/tests/resources/tracker.min.js',
+ ));
+
+ $updatedContent = $manipulator->manipulateContent();
+
+ $this->assertSame('var Piwik.js = "mytest";
+/*!!! pluginTrackerHook */
+
+/* GENERATED: tracker.min.js */
+/* my license header */
+var mySecondCustomTracker = \'test\';
+/* END GENERATED: tracker.min.js */
+
+
+/* GENERATED: tracker.js */
+/** my license header*/
+var myCustomTracker = \'test\';
+
+var fooBar = \'baz\';
+/* END GENERATED: tracker.js */
+
+
+var myArray = [];
+', $updatedContent);
+ }
+
+ public function test_manipulateContent_shouldNotAddCodeOfTrackerPlugins_IfThereAreNoTrackerFiles()
+ {
+ $manipulator = $this->makeManipulator(array());
+
+ $updatedContent = $manipulator->manipulateContent();
+
+ $this->assertSame($this->content, $updatedContent);
+ }
+
+ private function makeManipulator($files)
+ {
+ return new PiwikJsManipulator($this->content, new PluginTrackerFilesMock($files));
+ }
+
+}
diff --git a/plugins/CustomJsTracker/tests/Integration/PluginTrackerFilesTest.php b/plugins/CustomJsTracker/tests/Integration/PluginTrackerFilesTest.php
new file mode 100644
index 0000000000..b063fbab28
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/Integration/PluginTrackerFilesTest.php
@@ -0,0 +1,143 @@
+<?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\CustomJsTracker\tests\Integration;
+
+use Piwik\Piwik;
+use Piwik\Plugins\CustomJsTracker\TrackingCode\PluginTrackerFiles;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+class CustomPluginTrackerFiles extends PluginTrackerFiles {
+
+ private $pluginNamesForFile = array();
+
+ public function __construct($pluginNameForRegularTrackerFile = 'CustomJsTracker', $pluginNameForMinifiedTracker = 'CustomJsTracker')
+ {
+ parent::__construct();
+
+ $this->dir = PIWIK_DOCUMENT_ROOT . '/plugins/CustomJsTracker/tests/';
+
+ $this->pluginNamesForFile = array(
+ 'tracker.js' => $pluginNameForRegularTrackerFile,
+ 'tracker.min.js' => $pluginNameForMinifiedTracker
+ );
+ }
+
+ protected function getPluginNameFromFile($file)
+ {
+ $fileName = basename($file);
+ return $this->pluginNamesForFile[$fileName];
+ }
+}
+
+class CustomPluginTrackerFiles2 extends PluginTrackerFiles {
+
+ public function getPluginNameFromFile($file)
+ {
+ return parent::getPluginNameFromFile($file);
+ }
+}
+
+/**
+ * @group CustomJsTracker
+ * @group PluginTrackerFilesTest
+ * @group PluginTrackerFiles
+ * @group Plugins
+ */
+class PluginTrackerFilesTest extends IntegrationTestCase
+{
+ public function test_find_ifAPluginDefinesAMinifiedAndARegularTrackerItShouldPreferTheMinifiedVersion()
+ {
+ $trackerFiles = new CustomPluginTrackerFiles();
+ $foundFiles = $trackerFiles->find();
+
+ $this->assertCount(1, $foundFiles);
+ $this->assertTrue(isset($foundFiles['CustomJsTracker']));
+ $this->assertEquals('tracker.min.js', $foundFiles['CustomJsTracker']->getName());
+ }
+
+ public function test_find_shouldIgnoreMinifiedVersion_IfRequested()
+ {
+ $trackerFiles = new CustomPluginTrackerFiles();
+ $trackerFiles->ignoreMinified();
+ $foundFiles = $trackerFiles->find();
+
+ $this->assertCount(1, $foundFiles);
+ $this->assertTrue(isset($foundFiles['CustomJsTracker']));
+ $this->assertEquals('tracker.js', $foundFiles['CustomJsTracker']->getName());
+ }
+
+ public function test_find_ifMultiplePluginsImplementATracker_ShouldReturnEachOfThem()
+ {
+ $trackerFiles = new CustomPluginTrackerFiles('CustomJsTracker', 'Goals');
+ $foundFiles = $trackerFiles->find();
+
+ $this->assertCount(2, $foundFiles);
+ $this->assertTrue(isset($foundFiles['CustomJsTracker']));
+ $this->assertTrue(isset($foundFiles['Goals']));
+ $this->assertEquals('tracker.js', $foundFiles['CustomJsTracker']->getName());
+ $this->assertEquals('tracker.min.js', $foundFiles['Goals']->getName());
+ }
+
+ public function test_find_EventsCanIgnoreFiles()
+ {
+ $trackerFiles = new CustomPluginTrackerFiles('CustomJsTracker', 'Goals');
+ $foundFiles = $trackerFiles->find();
+ $this->assertCount(2, $foundFiles);
+
+ Piwik::addAction('CustomJsTracker.shouldAddTrackerFile', function (&$shouldAdd, $pluginName) {
+ if ($pluginName === 'Goals') {
+ $shouldAdd = false;
+ }
+ });
+
+ $foundFiles = $trackerFiles->find();
+ $this->assertCount(1, $foundFiles);
+ $this->assertTrue(isset($foundFiles['CustomJsTracker']));
+ $this->assertFalse(isset($foundFiles['Goals']));
+ }
+
+ public function test_find_shouldNotReturnATrackerFile_IfPluginIsNotActivatedOrLoaded()
+ {
+ $trackerFiles = new CustomPluginTrackerFiles('MyNotExistingPlugin', 'Goals');
+ $foundFiles = $trackerFiles->find();
+
+ $this->assertCount(1, $foundFiles);
+ $this->assertTrue(isset($foundFiles['Goals']));
+ $this->assertEquals('tracker.min.js', $foundFiles['Goals']->getName());
+
+ $trackerFiles = new CustomPluginTrackerFiles('Goals', 'MyNotExistingPlugin');
+ $foundFiles = $trackerFiles->find();
+
+ $this->assertCount(1, $foundFiles);
+ $this->assertTrue(isset($foundFiles['Goals']));
+ $this->assertEquals('tracker.js', $foundFiles['Goals']->getName());
+ }
+
+ public function test_find_shouldNotReturnFileIfNoPluginActivated()
+ {
+ $trackerFiles = new CustomPluginTrackerFiles('MyNotExistingPlugin', 'MyNotExistingPlugin2');
+ $foundFiles = $trackerFiles->find();
+
+ $this->assertSame(array(), $foundFiles);
+ }
+
+ public function test_getPluginNameFromFile_shouldDetectPluginName()
+ {
+ $trackerFiles = new CustomPluginTrackerFiles2();
+ $pluginName = $trackerFiles->getPluginNameFromFile(PIWIK_DOCUMENT_ROOT . '/plugins/MyFooBarPlugin/tracker.js');
+ $this->assertSame('MyFooBarPlugin', $pluginName);
+
+ $pluginName = $trackerFiles->getPluginNameFromFile(PIWIK_DOCUMENT_ROOT . '/plugins//MyFooBarPlugin//tracker.js');
+ $this->assertSame('MyFooBarPlugin', $pluginName);
+
+ $pluginName = $trackerFiles->getPluginNameFromFile(PIWIK_DOCUMENT_ROOT . '/plugins//MyFooBarPlugin//tracker.min.js');
+ $this->assertSame('MyFooBarPlugin', $pluginName);
+ }
+
+}
diff --git a/plugins/CustomJsTracker/tests/Integration/TrackerUpdaterTest.php b/plugins/CustomJsTracker/tests/Integration/TrackerUpdaterTest.php
new file mode 100644
index 0000000000..0c0130e5b6
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/Integration/TrackerUpdaterTest.php
@@ -0,0 +1,246 @@
+<?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\CustomJsTracker\tests\Integration;
+
+use Piwik\Plugins\CustomJsTracker\File;
+use Piwik\Plugins\CustomJsTracker\tests\Framework\Mock\PluginTrackerFilesMock;
+use Piwik\Plugins\CustomJsTracker\TrackerUpdater;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group CustomJsTracker
+ * @group PiwikJsManipulatorTest
+ * @group PiwikJsManipulator
+ * @group Plugins
+ */
+class TrackerUpdaterTest extends IntegrationTestCase
+{
+ private $dir;
+ private $trackerJsChangedEventPath = null;
+
+ public function setUp()
+ {
+ parent::setUp();
+ $this->dir = PIWIK_DOCUMENT_ROOT . '/plugins/CustomJsTracker/tests/resources/';
+ $this->trackerJsChangedEventPath = null;
+
+ $this->cleanUp();
+ }
+
+ public function tearDown()
+ {
+ parent::tearDown();
+
+ $this->cleanUp();
+ }
+
+ private function cleanUp()
+ {
+ $target = $this->dir . 'MyTestTarget.js';
+ if (file_exists($target)) {
+ unlink($target);
+ }
+
+ $nonExistentFile = $this->dir . 'MyNotExisIngFilessss.js';
+ if (file_exists($nonExistentFile)) {
+ unlink($nonExistentFile);
+ }
+ }
+
+ private function makeUpdater($from = null, $to = null)
+ {
+ return new TrackerUpdater($from, $to);
+ }
+
+ public function test_construct_setsDefaults()
+ {
+ $updater = $this->makeUpdater();
+ $fromFile = $updater->getFromFile();
+ $toFile = $updater->getToFile();
+ $this->assertTrue($fromFile instanceof File);
+ $this->assertTrue($toFile instanceof File);
+
+ $this->assertSame(basename(TrackerUpdater::ORIGINAL_PIWIK_JS), $fromFile->getName());
+ $this->assertSame(basename(TrackerUpdater::TARGET_MATOMO_JS), $toFile->getName());
+ }
+
+ public function test_setFormFile_getFromFile()
+ {
+ $updater = $this->makeUpdater();
+ $testFile = new File('foobar');
+ $updater->setFromFile($testFile);
+
+ $this->assertSame($testFile, $updater->getFromFile());
+ }
+
+ public function test_setFormFile_CanBeString()
+ {
+ $updater = $this->makeUpdater();
+ $updater->setFromFile('foobar');
+
+ $this->assertSame('foobar', $updater->getFromFile()->getName());
+ }
+
+ public function test_setToFile_getToFile()
+ {
+ $updater = $this->makeUpdater();
+ $testFile = new File('foobar');
+ $updater->setToFile($testFile);
+
+ $this->assertSame($testFile, $updater->getToFile());
+ }
+
+ public function test_setToFile_CanBeString()
+ {
+ $updater = $this->makeUpdater();
+ $updater->setToFile('foobar');
+
+ $this->assertSame('foobar', $updater->getToFile()->getName());
+ }
+
+ public function test_checkWillSucceed_shouldNotThrowExceptionIfPiwikJsTargetIsWritable()
+ {
+ $updater = $this->makeUpdater();
+ $updater->checkWillSucceed();
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @expectedException \Piwik\Plugins\CustomJsTracker\Exception\AccessDeniedException
+ * @expectedExceptionMessage not writable
+ */
+ public function test_checkWillSucceed_shouldNotThrowExceptionIfTargetIsNotWritable()
+ {
+ $updater = $this->makeUpdater(null, $this->dir . 'not-writable/MyNotExisIngFilessss.js');
+ $updater->checkWillSucceed();
+ }
+
+ public function test_checkWillSucceed_shouldNotThrowExceptionIfTargetIsWritable()
+ {
+ $updater = $this->makeUpdater(null, $this->dir . 'MyNotExisIngFilessss.js');
+ $updater->checkWillSucceed();
+ }
+
+ public function test_getCurrentTrackerFileContent()
+ {
+ $targetFile = $this->dir . 'testpiwik.js';
+
+ $updater = $this->makeUpdater(null, $targetFile);
+ $content = $updater->getCurrentTrackerFileContent();
+
+ $this->assertSame(file_get_contents($targetFile), $content);
+ }
+
+ public function test_getUpdatedTrackerFileContent_returnsGeneratedPiwikJsWithMergedTrackerFiles_WhenTheyExist()
+ {
+ $source = $this->dir . 'testpiwik.js';
+ $target = $this->dir . 'MyTestTarget.js';
+
+ $updater = $this->makeUpdater($source, $target);
+ $updater->setTrackerFiles(new PluginTrackerFilesMock(array(
+ $this->dir . 'tracker.js', $this->dir . 'tracker.min.js'
+ )));
+ $content = $updater->getUpdatedTrackerFileContent();
+
+ $this->assertSame('/** MyHeader*/
+var PiwikJs = "mytest";
+
+/*!!! pluginTrackerHook */
+
+/* GENERATED: tracker.min.js */
+
+/* END GENERATED: tracker.min.js */
+
+
+/* GENERATED: tracker.js */
+
+/* END GENERATED: tracker.js */
+
+
+var myArray = [];
+', $content);
+ }
+
+ public function test_getUpdatedTrackerFileContent_returnsSourceFile_IfNoTrackerFilesFound()
+ {
+ $source = $this->dir . 'testpiwik.js';
+ $target = $this->dir . 'MyTestTarget.js';
+
+ $updater = $this->makeUpdater($source, $target);
+ $updater->setTrackerFiles(new PluginTrackerFilesMock(array()));
+ $content = $updater->getUpdatedTrackerFileContent();
+
+ $this->assertSame(file_get_contents($source), $content);
+ }
+
+ public function test_update_shouldNotThrowAnError_IfTargetFileIsNotWritable()
+ {
+ $updater = $this->makeUpdater(null, $this->dir . 'not-writable/MyNotExisIngFilessss.js');
+ $updater->update();
+ $this->assertTrue(true);
+ $this->assertNull($this->trackerJsChangedEventPath);
+ }
+
+ public function test_update_shouldNotWriteToFileIfThereIsNothingToChange()
+ {
+ $source = $this->dir . 'testpiwik.js';
+ $target = $this->dir . 'MyTestTarget.js';
+ file_put_contents($target, file_get_contents($source));
+ $updater = $this->makeUpdater($this->dir . 'testpiwik.js', $target);
+ $updater->setTrackerFiles(new PluginTrackerFilesMock(array()));
+ // mock that does not find any files . therefore there is nothing to di
+ $updater->update();
+
+ $this->assertSame(file_get_contents($source), file_get_contents($target));
+ $this->assertNull($this->trackerJsChangedEventPath);
+ }
+
+ public function test_update_targetFileIfPluginsDefineDifferentFiles()
+ {
+ $target = $this->dir . 'MyTestTarget.js';
+ file_put_contents($target, ''); // file has to exist in order to work
+
+ $updater = $this->makeUpdater($this->dir . 'testpiwik.js', $target);
+ $updater->setTrackerFiles(new PluginTrackerFilesMock(array(
+ $this->dir . 'tracker.js', $this->dir . 'tracker.min.js'
+ )));
+ $updater->update();
+
+ $this->assertSame('/** MyHeader*/
+var PiwikJs = "mytest";
+
+/*!!! pluginTrackerHook */
+
+/* GENERATED: tracker.min.js */
+
+/* END GENERATED: tracker.min.js */
+
+
+/* GENERATED: tracker.js */
+
+/* END GENERATED: tracker.js */
+
+
+var myArray = [];
+', file_get_contents($target));
+ $this->assertEquals($target, $this->trackerJsChangedEventPath);
+ }
+
+ public function provideContainerConfig()
+ {
+ return [
+ 'observers.global' => \DI\add([
+ ['CustomJsTracker.trackerJsChanged', function ($path) {
+ $this->trackerJsChangedEventPath = $path;
+ }],
+ ]),
+ ];
+ }
+}
diff --git a/plugins/CustomJsTracker/tests/System/PiwikJsContentTest.php b/plugins/CustomJsTracker/tests/System/PiwikJsContentTest.php
new file mode 100644
index 0000000000..300c312984
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/System/PiwikJsContentTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\CustomJsTracker\tests\System;
+
+use Piwik\Plugins\CustomJsTracker\TrackerUpdater;
+use Piwik\Plugins\CustomJsTracker\TrackingCode\PiwikJsManipulator;
+use Piwik\Tests\Framework\TestCase\SystemTestCase;
+
+/**
+ * @group CustomJsTracker
+ * @group PiwikJsContentTest
+ * @group PiwikJsContent
+ * @group Plugins
+ */
+class PiwikJsContentTest extends SystemTestCase
+{
+ public function test_piwikJsAndPiwikMinJsMustHaveSameContent()
+ {
+ $piwikMin = PIWIK_DOCUMENT_ROOT . TrackerUpdater::ORIGINAL_PIWIK_JS;
+ $piwikJs = PIWIK_DOCUMENT_ROOT . TrackerUpdater::TARGET_MATOMO_JS;
+
+ $this->assertSame(file_get_contents($piwikMin), file_get_contents($piwikJs));
+ }
+
+ public function test_piwikJsContainsHook()
+ {
+ $piwikMin = PIWIK_DOCUMENT_ROOT . '/js/piwik.min.js';
+ $content = file_get_contents($piwikMin);
+
+ $this->assertContains(PiwikJsManipulator::HOOK, $content);
+ }
+
+} \ No newline at end of file
diff --git a/plugins/CustomJsTracker/tests/resources/MyTestTarget2.js b/plugins/CustomJsTracker/tests/resources/MyTestTarget2.js
new file mode 100644
index 0000000000..258c2d3e11
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/resources/MyTestTarget2.js
@@ -0,0 +1,16 @@
+/** MyHeader*/
+var PiwikJs = "mytest";
+
+/*!!! pluginTrackerHook */
+
+/* GENERATED: tracker.min.js */
+
+/* END GENERATED: tracker.min.js */
+
+
+/* GENERATED: tracker.js */
+
+/* END GENERATED: tracker.js */
+
+
+var myArray = [];
diff --git a/plugins/CustomJsTracker/tests/resources/test.js b/plugins/CustomJsTracker/tests/resources/test.js
new file mode 100644
index 0000000000..0ea6fcc24a
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/resources/test.js
@@ -0,0 +1,2 @@
+// Hello world
+var fooBar = 'test'; \ No newline at end of file
diff --git a/plugins/CustomJsTracker/tests/resources/testpiwik.js b/plugins/CustomJsTracker/tests/resources/testpiwik.js
new file mode 100644
index 0000000000..02b60f8bcd
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/resources/testpiwik.js
@@ -0,0 +1,6 @@
+/** MyHeader*/
+var PiwikJs = "mytest";
+
+/*!!! pluginTrackerHook */
+
+var myArray = [];
diff --git a/plugins/CustomJsTracker/tests/resources/tracker.js b/plugins/CustomJsTracker/tests/resources/tracker.js
new file mode 100644
index 0000000000..ae4d228f39
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/resources/tracker.js
@@ -0,0 +1,4 @@
+/** my license header*/
+var myCustomTracker = 'test';
+
+var fooBar = 'baz'; \ No newline at end of file
diff --git a/plugins/CustomJsTracker/tests/resources/tracker.min.js b/plugins/CustomJsTracker/tests/resources/tracker.min.js
new file mode 100644
index 0000000000..587ee0464d
--- /dev/null
+++ b/plugins/CustomJsTracker/tests/resources/tracker.min.js
@@ -0,0 +1,2 @@
+/* my license header */
+var mySecondCustomTracker = 'test'; \ No newline at end of file