update Piwik to version 2.16 (fixes #91)

This commit is contained in:
oliver 2016-04-10 18:55:57 +02:00
commit d885a4baa9
5833 changed files with 418860 additions and 226988 deletions

View file

@ -0,0 +1,39 @@
<?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\CoreConsole\Commands;
use Piwik\Filesystem;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class ClearCaches extends ConsoleCommand
{
protected function configure()
{
$this->setName('core:clear-caches');
$this->setAliases(array('cache:clear'));
$this->setDescription('Cleares all caches. This command can be useful for instance after updating Piwik files manually.');
}
/**
* Execute command like: ./console core:clear-caches
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Note: the logic for this command must be refactored in this helper function below.
Filesystem::deleteAllCacheOnUpdate();
$this->writeSuccessMessage($output, array('Caches cleared'));
}
}

View file

@ -1,90 +0,0 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class CodeCoverage extends ConsoleCommand
{
protected function configure()
{
$this->setName('tests:coverage');
$this->setDescription('Run all phpunit tests and generate a combined code coverage');
$this->addArgument('group', InputArgument::OPTIONAL, 'Run only a specific test group. Separate multiple groups by comma, for instance core,integration', '');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$phpCovPath = trim(shell_exec('which phpcov'));
if (empty($phpCovPath)) {
$output->writeln('phpcov not installed. please install pear.phpunit.de/phpcov.');
return;
}
$command = $this->getApplication()->find('tests:run');
$arguments = array(
'command' => 'tests:run',
'--options' => sprintf('--coverage-php %s/tests/results/logs/%%group%%.cov', PIWIK_DOCUMENT_ROOT),
);
$groups = $input->getArgument('group');
if (!empty($groups)) {
$arguments['group'] = $groups;
} else {
shell_exec(sprintf('rm %s/tests/results/logs/*.cov', PIWIK_DOCUMENT_ROOT));
}
$inputObject = new ArrayInput($arguments);
$inputObject->setInteractive($input->isInteractive());
$command->run($inputObject, $output);
$command = 'phpcov';
// force xdebug usage for coverage options
if (!extension_loaded('xdebug')) {
$output->writeln('<info>xdebug extension required for code coverage.</info>');
$output->writeln('<info>searching for xdebug extension...</info>');
$extensionDir = shell_exec('php-config --extension-dir');
$xdebugFile = trim($extensionDir) . DIRECTORY_SEPARATOR . 'xdebug.so';
if (!file_exists($xdebugFile)) {
$dialog = $this->getHelperSet()->get('dialog');
$xdebugFile = $dialog->askAndValidate($output, 'xdebug not found. Please provide path to xdebug.so', function($xdebugFile) {
return file_exists($xdebugFile);
});
} else {
$output->writeln('<info>xdebug extension found in extension path.</info>');
}
$output->writeln("<info>using $xdebugFile as xdebug extension.</info>");
$command = sprintf('php -d zend_extension=%s %s', $xdebugFile, $phpCovPath);
}
shell_exec(sprintf('rm -rf %s/tests/results/coverage/*', PIWIK_DOCUMENT_ROOT));
passthru(sprintf('cd %1$s && %2$s --merge --html tests/results/coverage/ --whitelist ./core/ --whitelist ./plugins/ --add-uncovered %1$s/tests/results/logs/', PIWIK_DOCUMENT_ROOT, $command));
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -9,7 +9,7 @@ namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\CronArchive;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Piwik\Site;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@ -23,33 +23,100 @@ class CoreArchiver extends ConsoleCommand
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('piwik-domain') && !$input->getOption('url')) {
$_SERVER['argv'][] = '--url=' . $input->getOption('piwik-domain');
}
$archiver = self::makeArchiver($input->getOption('url'), $input);
$archiver->main();
}
include PIWIK_INCLUDE_PATH . '/misc/cron/archive.php';
// also used by another console command
public static function makeArchiver($url, InputInterface $input)
{
$archiver = new CronArchive();
$archiver->disableScheduledTasks = $input->getOption('disable-scheduled-tasks');
$archiver->acceptInvalidSSLCertificate = $input->getOption("accept-invalid-ssl-certificate");
$archiver->shouldArchiveAllSites = (bool) $input->getOption("force-all-websites");
$archiver->shouldStartProfiler = (bool) $input->getOption("xhprof");
$archiver->shouldArchiveSpecifiedSites = self::getSitesListOption($input, "force-idsites");
$archiver->shouldSkipSpecifiedSites = self::getSitesListOption($input, "skip-idsites");
$archiver->forceTimeoutPeriod = $input->getOption("force-timeout-for-periods");
$archiver->shouldArchiveAllPeriodsSince = $input->getOption("force-all-periods");
$archiver->restrictToDateRange = $input->getOption("force-date-range");
$archiver->phpCliConfigurationOptions = $input->getOption("php-cli-options");
$restrictToPeriods = $input->getOption("force-periods");
$restrictToPeriods = explode(',', $restrictToPeriods);
$archiver->restrictToPeriods = array_map('trim', $restrictToPeriods);
$archiver->dateLastForced = $input->getOption('force-date-last-n');
$archiver->concurrentRequestsPerWebsite = $input->getOption('concurrent-requests-per-website');
$archiver->disableSegmentsArchiving = $input->getOption('skip-all-segments');
$segmentIds = $input->getOption('force-idsegments');
$segmentIds = explode(',', $segmentIds);
$segmentIds = array_map('trim', $segmentIds);
$archiver->setSegmentsToForceFromSegmentIds($segmentIds);
$archiver->setUrlToPiwik($url);
return $archiver;
}
private static function getSitesListOption(InputInterface $input, $optionName)
{
return Site::getIdSitesFromIdSitesString($input->getOption($optionName));
}
// This is reused by another console command
static public function configureArchiveCommand(ConsoleCommand $command)
public static function configureArchiveCommand(ConsoleCommand $command)
{
$command->setName('core:archive');
$command->setDescription("Runs the CLI archiver. It is an important tool for general maintenance and to keep Piwik very fast.");
$command->setHelp("* It is recommended to run the script with the option --piwik-domain=[piwik-server-url] only. Other options are not required.
$command->setHelp("* It is recommended to run the script without any option.
* This script should be executed every hour via crontab, or as a daemon.
* You can also run it via http:// by specifying the Super User &token_auth=XYZ as a parameter ('Web Cron'),
but it is recommended to run it via command line/CLI instead.
* If you have any suggestion about this script, please let the team know at hello@piwik.org
* If you have any suggestion about this script, please let the team know at feedback@piwik.org
* Enjoy!");
$command->addOption('url', null, InputOption::VALUE_REQUIRED, "Mandatory option as an alternative to '--piwik-domain'. Must be set to the Piwik base URL.\nFor example: --url=http://analytics.example.org/ or --url=https://example.org/piwik/");
$command->addOption('force-all-websites', null, InputOption::VALUE_NONE, "If specified, the script will trigger archiving on all websites and all past dates.\nYou may use --force-all-periods=[seconds] to trigger archiving on those websites\nthat had visits in the last [seconds] seconds.");
$command->addOption('force-all-periods', null, InputOption::VALUE_OPTIONAL, "Limits archiving to websites with some traffic in the last [seconds] seconds. \nFor example --force-all-periods=86400 will archive websites that had visits in the last 24 hours. \nIf [seconds] is not specified, all websites with visits in the last " . CronArchive::ARCHIVE_SITES_WITH_TRAFFIC_SINCE . "\n seconds (" . round(CronArchive::ARCHIVE_SITES_WITH_TRAFFIC_SINCE / 86400) . " days) will be archived.");
$command->addOption('force-timeout-for-periods', null, InputOption::VALUE_OPTIONAL, "The current week/ current month/ current year will be processed at most every [seconds].\nIf not specified, defaults to " . CronArchive::SECONDS_DELAY_BETWEEN_PERIOD_ARCHIVES . ".");
$command->addOption('force-date-last-n', null, InputOption::VALUE_REQUIRED, "This script calls the API with period=lastN. You can force the N in lastN by specifying this value.");
$command->addOption('force-idsites', null, InputOption::VALUE_OPTIONAL, 'If specified, archiving will be processed only for these Sites Ids (comma separated)');
$command->addOption('skip-idsites', null, InputOption::VALUE_OPTIONAL, 'If specified, archiving will be skipped for these websites (in case these website ids would have been archived).');
$command->addOption('disable-scheduled-tasks', null, InputOption::VALUE_NONE, "Skips executing Scheduled tasks (sending scheduled reports, db optimization, etc.).");
$command->addOption('xhprof', null, InputOption::VALUE_NONE, "Enables XHProf profiler for this archive.php run. Requires XHPRof (see tests/README.xhprof.md).");
$command->addOption('accept-invalid-ssl-certificate', null, InputOption::VALUE_NONE, "It is _NOT_ recommended to use this argument. Instead, you should use a valid SSL certificate!\nIt can be useful if you specified --url=https://... or if you are using Piwik with force_ssl=1");
$command->addOption('url', null, InputOption::VALUE_REQUIRED,
"Forces the value of this option to be used as the URL to Piwik. \nIf your system does not support"
. " archiving with CLI processes, you may need to set this in order for the archiving HTTP requests to use"
. " the desired URLs.");
$command->addOption('force-all-websites', null, InputOption::VALUE_NONE,
"If specified, the script will trigger archiving on all websites.\nUse with --force-all-periods=[seconds] "
. "to also process those websites that had visits in the last [seconds] seconds.\nLaunching several processes"
. " with this option will make them share the list of sites to process.");
$command->addOption('force-all-periods', null, InputOption::VALUE_OPTIONAL,
"Limits archiving to websites with some traffic in the last [seconds] seconds. \nFor example "
. "--force-all-periods=86400 will archive websites that had visits in the last 24 hours. \nIf [seconds] is "
. "not specified, all websites with visits in the last " . CronArchive::ARCHIVE_SITES_WITH_TRAFFIC_SINCE
. " seconds (" . round(CronArchive::ARCHIVE_SITES_WITH_TRAFFIC_SINCE / 86400) . " days) will be archived.");
$command->addOption('force-timeout-for-periods', null, InputOption::VALUE_OPTIONAL,
"The current week/ current month/ current year will be processed at most every [seconds].\nIf not "
. "specified, defaults to " . CronArchive::SECONDS_DELAY_BETWEEN_PERIOD_ARCHIVES . ".");
$command->addOption('skip-idsites', null, InputOption::VALUE_OPTIONAL,
'If specified, archiving will be skipped for these websites (in case these website ids would have been archived).');
$command->addOption('skip-all-segments', null, InputOption::VALUE_NONE,
'If specified, all segments will be skipped during archiving.');
$command->addOption('force-idsites', null, InputOption::VALUE_OPTIONAL,
'If specified, archiving will be processed only for these Sites Ids (comma separated)');
$command->addOption('force-periods', null, InputOption::VALUE_OPTIONAL,
"If specified, archiving will be processed only for these Periods (comma separated eg. day,week,month,year,range)");
$command->addOption('force-date-last-n', null, InputOption::VALUE_REQUIRED,
"This script calls the API with period=lastN. You can force the N in lastN by specifying this value.");
$command->addOption('force-date-range', null, InputOption::VALUE_OPTIONAL,
"If specified, archiving will be processed only for periods included in this date range. Format: YYYY-MM-DD,YYYY-MM-DD");
$command->addOption('force-idsegments', null, InputOption::VALUE_REQUIRED,
'If specified, only these segments will be processed (if the segment should be applied to a site in the first place).'
. "\nSpecify stored segment IDs, not the segments themselves, eg, 1,2,3. "
. "\nNote: if identical segments exist w/ different IDs, they will both be skipped, even if you only supply one ID.");
$command->addOption('concurrent-requests-per-website', null, InputOption::VALUE_OPTIONAL,
"When processing a website and its segments, number of requests to process in parallel", CronArchive::MAX_CONCURRENT_API_REQUESTS);
$command->addOption('disable-scheduled-tasks', null, InputOption::VALUE_NONE,
"Skips executing Scheduled tasks (sending scheduled reports, db optimization, etc.).");
$command->addOption('accept-invalid-ssl-certificate', null, InputOption::VALUE_NONE,
"It is _NOT_ recommended to use this argument. Instead, you should use a valid SSL certificate!\nIt can be "
. "useful if you specified --url=https://... or if you are using Piwik with force_ssl=1");
$command->addOption('php-cli-options', null, InputOption::VALUE_OPTIONAL, 'Forwards the PHP configuration options to the PHP CLI command. For example "-d memory_limit=8G". Note: These options are only applied if the archiver actually uses CLI and not HTTP.', $default = '');
}
}
}

View file

@ -0,0 +1,56 @@
<?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\CoreConsole\Commands;
use Piwik\Config;
use Piwik\Filesystem;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class DevelopmentEnable extends ConsoleCommand
{
protected function configure()
{
$this->setName('development:enable');
$this->setAliases(array('development:disable'));
$this->setDescription('Enable or disable development mode. See config/global.ini.php in section [Development] for more information');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$commandName = $input->getFirstArgument();
$enable = (false !== strpos($commandName, 'enable'));
$config = Config::getInstance();
$development = $config->Development;
if ($enable) {
$development['enabled'] = 1;
$development['disable_merged_assets'] = 1;
$message = 'Development mode enabled';
} else {
$development['enabled'] = 0;
$development['disable_merged_assets'] = 0;
$message = 'Development mode disabled';
}
$config->Development = $development;
$config->forceSave();
Filesystem::deleteAllCacheOnUpdate();
$this->writeSuccessMessage($output, array($message));
}
}

View file

@ -1,20 +1,26 @@
<?php
/**
* Piwik - Open source web analytics
* 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\CoreConsole\Commands;
use Piwik\Development;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ManageTestFiles extends ConsoleCommand
class DevelopmentManageTestFiles extends ConsoleCommand
{
public function isEnabled()
{
return Development::isEnabled();
}
protected function configure()
{
$this->setName('development:test-files');
@ -42,7 +48,7 @@ class ManageTestFiles extends ConsoleCommand
{
$file = $input->getOption('file');
$prefix = PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Integration/processed/';
$prefix = PIWIK_INCLUDE_PATH . '/tests/PHPUnit/System/processed/';
$guesses = array(
'/' . $file,
$prefix . $file,
@ -55,6 +61,6 @@ class ManageTestFiles extends ConsoleCommand
}
}
copy($file, PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Integration/expected/' . basename($file));
copy($file, PIWIK_INCLUDE_PATH . '/tests/PHPUnit/System/expected/' . basename($file));
}
}

View file

@ -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\CoreConsole\Commands;
use Piwik\Common;
use Piwik\Container\StaticContainer;
use Piwik\Decompress\Tar;
use Piwik\Development;
use Piwik\Http;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class DevelopmentSyncProcessedSystemTests extends ConsoleCommand
{
private $targetDir = 'tests/PHPUnit/System/processed';
public function isEnabled()
{
return Development::isEnabled();
}
protected function configure()
{
$this->setName('development:sync-system-test-processed');
$this->setDescription('For Piwik core devs. Copies processed system tests from travis artifacts to ' . $this->targetDir);
$this->addArgument('buildnumber', InputArgument::REQUIRED, 'Travis build number you want to sync, eg "14820".');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$buildNumber = $input->getArgument('buildnumber');
$targetDir = PIWIK_INCLUDE_PATH . '/' . dirname($this->targetDir);
$tmpDir = StaticContainer::get('path.tmp');
$this->validate($buildNumber, $targetDir, $tmpDir);
if (Common::stringEndsWith($buildNumber, '.1')) {
// eg make '14820.1' to '14820' to be backwards compatible
$buildNumber = substr($buildNumber, 0, -2);
}
$filename = sprintf('system.%s.tar.bz2', $buildNumber);
$urlBase = sprintf('http://builds-artifacts.piwik.org/piwik/piwik/%s', $filename);
$tests = Http::sendHttpRequest($urlBase, $timeout = 120);
$tarFile = $tmpDir . $filename;
file_put_contents($tarFile, $tests);
$tar = new Tar($tarFile, 'bz2');
$tar->extract($targetDir);
$this->writeSuccessMessage($output, array(
'All processed system test results were copied to <comment>' . $this->targetDir . '</comment>',
'Compare them with the expected test results and commit them if needed.'
));
unlink($tarFile);
}
private function validate($buildNumber, $targetDir, $tmpDir)
{
if (empty($buildNumber)) {
throw new \InvalidArgumentException('Missing build number.');
}
if (!is_writable($targetDir)) {
throw new \RuntimeException('Target dir is not writable: ' . $targetDir);
}
if (!is_writable($tmpDir)) {
throw new \RuntimeException('Tempdir is not writable: ' . $tmpDir);
}
}
}

View file

@ -0,0 +1,130 @@
<?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\CoreConsole\Commands;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateAngularDirective extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:angular-directive')
->setDescription('Generates a template for an AngularJS directive')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin')
->addOption('directive', null, InputOption::VALUE_REQUIRED, 'The name of the directive you want to create.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$directive = $this->getDirectiveName($input, $output);
$pluginPath = $this->getPluginPath($pluginName);
$directiveLower = $this->getDirectiveComponentName($directive);
$targetDir = $pluginPath . '/angularjs/' . $directiveLower;
if (is_dir($targetDir) || file_exists($targetDir)) {
throw new \Exception('The AngularJS directive ' . $directiveLower . ' already exists in plugin ' . $pluginName);
}
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array(
'ExamplePlugin' => $pluginName,
'directive-component' => $directiveLower,
'componentClass' => lcfirst($directive),
'componentAs' => lcfirst($directive),
'component' => $directiveLower,
'Component' => $directive
);
$componentPath = '/angularjs/directive-component';
$whitelistFiles = array(
'/angularjs',
$componentPath,
$componentPath . '/component.controller.js',
$componentPath . '/component.directive.html',
$componentPath . '/component.directive.js',
$componentPath . '/component.directive.less',
);
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$replacedBasePath = '/angularjs/' . $directiveLower . '/' . $directiveLower;
$js1 = $replacedBasePath . '.controller.js';
$js2 = $replacedBasePath . '.directive.js';
$less1 = $replacedBasePath . '.directive.less';
$this->writeSuccessMessage($output, array(
sprintf('AngularJS directive "%s" for plugin "%s" in "%s" generated', $directive, $pluginName, $targetDir),
sprintf('In <comment>%1$s/%2$s.php</comment> you should now require the JS files', $pluginPath, $pluginName),
sprintf('<comment>%1$s%2$s</comment>, <comment>%1$s%3$s</comment>', $pluginPath, $js1, $js2),
sprintf('and the less file <comment>%1$s%2$s</comment>.', $pluginPath, $less1),
'If you are not familiar with this have a look at <comment>http://developer.piwik.org/guides/working-with-piwiks-ui</comment>'
));
}
/**
* Convert MyComponentName => my-component-name
* @param string $directiveCamelCase
* @return string
*/
protected function getDirectiveComponentName($directiveCamelCase)
{
return strtolower(preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1-', $directiveCamelCase));
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string
* @throws \RuntimeException
*/
private function getDirectiveName(InputInterface $input, OutputInterface $output)
{
$testname = $input->getOption('directive');
$validate = function ($testname) {
if (empty($testname)) {
throw new \InvalidArgumentException('You have to enter a name for the directive');
}
if (!ctype_alnum($testname)) {
throw new \InvalidArgumentException('Only alphanumeric characters are allowed as a directive name. Use CamelCase if the name of your directive contains multiple words.');
}
return $testname;
};
if (empty($testname)) {
$dialog = $this->getHelperSet()->get('dialog');
$testname = $dialog->askAndValidate($output, 'Enter the name of the directive you want to create: ', $validate);
} else {
$validate($testname);
}
$testname = ucfirst($testname);
return $testname;
}
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$pluginNames = $this->getPluginNames();
$invalidName = 'You have to enter the name of an existing plugin';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -27,6 +27,7 @@ class GenerateApi extends GeneratePluginBase
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array('ExamplePlugin' => $pluginName);
@ -45,7 +46,7 @@ class GenerateApi extends GeneratePluginBase
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RunTimeException
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{

View file

@ -0,0 +1,59 @@
<?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\CoreConsole\Commands;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateArchiver extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:archiver')
->setDescription('Adds an Archiver to an existing plugin')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin which does not have an Archiver yet');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array('ExamplePlugin' => $pluginName, 'EXAMPLEPLUGIN' => strtoupper($pluginName));
$whitelistFiles = array('/Archiver.php');
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Archiver.php for %s generated.', $pluginName),
'You can now start implementing Archiver methods',
'Enjoy!'
));
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$pluginNames = $this->getPluginNamesHavingNotSpecificFile('Archiver.php');
$invalidName = 'You have to enter the name of an existing plugin which does not already have an Archiver';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -9,8 +9,6 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Common;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@ -29,12 +27,16 @@ class GenerateCommand extends GeneratePluginBase
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$commandName = $this->getCommandName($input, $output);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleCommand';
$replace = array(
'ExampleCommandDescription' => $commandName,
'ExampleCommand' => $pluginName,
'examplecommand:helloworld' => strtolower($pluginName) . ':' . $this->buildCommandName($commandName),
'examplecommand' => strtolower($pluginName),
'HelloWorld' => $commandName,
'helloworld' => strtolower($commandName)
@ -49,11 +51,20 @@ class GenerateCommand extends GeneratePluginBase
));
}
/**
* Convert MyComponentName => my-component-name
* @param string $commandNameCamelCase
* @return string
*/
protected function buildCommandName($commandNameCamelCase)
{
return strtolower(preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1-', $commandNameCamelCase));
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string
* @throws \RunTimeException
* @throws \RuntimeException
*/
private function getCommandName(InputInterface $input, OutputInterface $output)
{
@ -64,12 +75,16 @@ class GenerateCommand extends GeneratePluginBase
throw new \InvalidArgumentException('You have to enter a command name');
}
if (!ctype_alnum($testname)) {
throw new \InvalidArgumentException('Only alphanumeric characters are allowed as a command name. Use CamelCase if the name of your command contains multiple words.');
}
return $testname;
};
if (empty($testname)) {
$dialog = $this->getHelperSet()->get('dialog');
$testname = $dialog->askAndValidate($output, 'Enter the name of the command: ', $validate);
$testname = $dialog->askAndValidate($output, 'Enter the name of the command (CamelCase): ', $validate);
} else {
$validate($testname);
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -27,6 +27,7 @@ class GenerateController extends GeneratePluginBase
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array('ExamplePlugin' => $pluginName);
@ -45,7 +46,7 @@ class GenerateController extends GeneratePluginBase
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RunTimeException
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{

View file

@ -0,0 +1,259 @@
<?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\CoreConsole\Commands;
use Piwik\Common;
use Piwik\DbHelper;
use Piwik\Plugin\Report;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GenerateDimension extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:dimension')
->setDescription('Adds a new dimension to an existing plugin. This allows you to persist new values during tracking.')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin which does not have a menu defined yet')
->addOption('type', null, InputOption::VALUE_REQUIRED, 'Whether you want to create a "Visit", an "Action" or a "Conversion" dimension')
->addOption('dimensionname', null, InputOption::VALUE_REQUIRED, 'A human readable name of the dimension which will be for instance visible in the UI')
->addOption('columnname', null, InputOption::VALUE_REQUIRED, 'The name of the column in the MySQL database the dimension will be stored under')
->addOption('columntype', null, InputOption::VALUE_REQUIRED, 'The MySQL type for your dimension, for instance "VARCHAR(255) NOT NULL".');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @throws \InvalidArgumentException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$type = $this->getDimensionType($input, $output);
$dimensionName = $this->getDimensionName($input, $output);
if ('non-tracking-dimension' === $type) {
$columnName = '';
$columType = '';
} else {
$columnName = $this->getColumnName($input, $output, $type);
$columType = $this->getColumnType($input, $output);
}
$dimensionClassName = $this->getDimensionClassName($dimensionName);
$translatedDimensionName = $this->makeTranslationIfPossible($pluginName, ucfirst($dimensionName));
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleTracker';
$replace = array('example_action_dimension' => strtolower($columnName),
'example_visit_dimension' => strtolower($columnName),
'example_conversion_dimension' => strtolower($columnName),
'INTEGER(11) DEFAULT 0 NOT NULL' => strtoupper($columType),
'VARCHAR(255) DEFAULT NULL' => strtoupper($columType),
'ExampleDimension' => $dimensionClassName,
'ExampleVisitDimension' => $dimensionClassName,
'ExampleActionDimension' => $dimensionClassName,
'ExampleConversionDimension' => $dimensionClassName,
'ExampleTracker_DimensionName' => $translatedDimensionName,
'ExampleTracker' => $pluginName,
);
$whitelistFiles = array('/Columns');
if ('visit' == $type) {
$whitelistFiles[] = '/Columns/ExampleVisitDimension.php';
} elseif ('action' == $type) {
$whitelistFiles[] = '/Columns/ExampleActionDimension.php';
} elseif ('conversion' == $type) {
$whitelistFiles[] = '/Columns/ExampleConversionDimension.php';
} elseif ('non-tracking-dimension' == $type) {
$whitelistFiles[] = '/Columns/ExampleDimension.php';
} else {
throw new \InvalidArgumentException('This dimension type is not available');
}
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Columns/%s.php for %s generated.', ucfirst($dimensionClassName), $pluginName),
'You should now implement the events within this file',
'Enjoy!'
));
}
private function getDimensionClassName($dimensionName)
{
$dimensionName = trim($dimensionName);
$dimensionName = str_replace(' ', '', $dimensionName);
$dimensionName = preg_replace("/[^A-Za-z0-9]/", '', $dimensionName);
$dimensionName = ucfirst($dimensionName);
return $dimensionName;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getDimensionName(InputInterface $input, OutputInterface $output)
{
$validate = function ($dimensionName) {
if (empty($dimensionName)) {
throw new \InvalidArgumentException('Please enter the name of your dimension');
}
if (preg_match("/[^A-Za-z0-9 ]/", $dimensionName)) {
throw new \InvalidArgumentException('Only alpha numerical characters and whitespaces are allowed');
}
return $dimensionName;
};
$dimensionName = $input->getOption('dimensionname');
if (empty($dimensionName)) {
$dialog = $this->getHelperSet()->get('dialog');
$dimensionName = $dialog->askAndValidate($output, 'Enter a human readable name of your dimension, for instance "Browser": ', $validate);
} else {
$validate($dimensionName);
}
$dimensionName = ucfirst($dimensionName);
return $dimensionName;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $type
* @return array
* @throws \RuntimeException
*/
protected function getColumnName(InputInterface $input, OutputInterface $output, $type)
{
$validate = function ($columnName) use ($type) {
if (empty($columnName)) {
throw new \InvalidArgumentException('Please enter the name of the dimension column');
}
if (preg_match("/[^A-Za-z0-9_ ]/", $columnName)) {
throw new \InvalidArgumentException('Only alpha numerical characters, underscores and whitespaces are allowed');
}
if ('visit' == $type) {
$columns = array_keys(DbHelper::getTableColumns(Common::prefixTable('log_visit')));
} elseif ('action' == $type) {
$columns = array_keys(DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action')));
} elseif ('conversion' == $type) {
$columns = array_keys(DbHelper::getTableColumns(Common::prefixTable('log_conversion')));
} else {
$columns = array();
}
foreach ($columns as $column) {
if (strtolower($column) === strtolower($columnName)) {
throw new \InvalidArgumentException('This column name is already in use.');
}
}
return $columnName;
};
$columnName = $input->getOption('columnname');
if (empty($columnName)) {
$dialog = $this->getHelperSet()->get('dialog');
$columnName = $dialog->askAndValidate($output, 'Enter the name of the column under which it should be stored in the MySQL database, for instance "visit_total_time": ', $validate);
} else {
$validate($columnName);
}
return $columnName;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getColumnType(InputInterface $input, OutputInterface $output)
{
$validate = function ($columnType) {
if (empty($columnType)) {
throw new \InvalidArgumentException('Please enter the type of the dimension column');
}
return $columnType;
};
$columnType = $input->getOption('columntype');
if (empty($columnType)) {
$dialog = $this->getHelperSet()->get('dialog');
$columnType = $dialog->askAndValidate($output, 'Enter the type of the column under which it should be stored in the MySQL database, for instance "VARCHAR(255) NOT NULL": ', $validate);
} else {
$validate($columnType);
}
return $columnType;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getDimensionType(InputInterface $input, OutputInterface $output)
{
$acceptedValues = array('visit', 'action', 'conversion', 'non-tracking-dimension');
$validate = function ($type) use ($acceptedValues) {
if (empty($type) || !in_array($type, $acceptedValues)) {
throw new \InvalidArgumentException('Please enter a valid dimension type (' . implode(', ', $acceptedValues) . '). Choose "non-tracking-dimension" if you only need a blank dimension having a name: ');
}
return $type;
};
$type = $input->getOption('type');
if (empty($type)) {
$dialog = $this->getHelperSet()->get('dialog');
$type = $dialog->askAndValidate($output, 'Please choose the type of dimension you want to create (' . implode(', ', $acceptedValues) . '). Choose "non-tracking-dimension" if you only need a blank dimension having a name: ', $validate, false, null, $acceptedValues);
} else {
$validate($type);
}
return $type;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$pluginNames = $this->getPluginNames();
$invalidName = 'You have to enter a name of an existing plugin.';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View file

@ -0,0 +1,59 @@
<?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\CoreConsole\Commands;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateMenu extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:menu')
->setDescription('Adds a plugin menu class to an existing plugin')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin which does not have a menu defined yet');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array('ExamplePlugin' => $pluginName);
$whitelistFiles = array('/Menu.php');
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Menu.php for %s generated.', $pluginName),
'You can now start defining your plugin menu',
'Enjoy!'
));
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$pluginNames = $this->getPluginNamesHavingNotSpecificFile('Menu.php');
$invalidName = 'You have to enter the name of an existing plugin which does not already have a menu defined';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -9,8 +9,10 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Filesystem;
use Piwik\Plugins\ExamplePlugin\ExamplePlugin;
use Piwik\Version;
use Piwik\Plugin;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@ -28,25 +30,29 @@ class GeneratePlugin extends GeneratePluginBase
->addOption('name', null, InputOption::VALUE_REQUIRED, 'Plugin name ([a-Z0-9_-])')
->addOption('description', null, InputOption::VALUE_REQUIRED, 'Plugin description, max 150 characters')
->addOption('pluginversion', null, InputOption::VALUE_OPTIONAL, 'Plugin version')
->addOption('full', null, InputOption::VALUE_OPTIONAL, 'If a value is set, an API and a Controller will be created as well. Option is only available for creating plugins, not for creating themes.');
->addOption('overwrite', null, InputOption::VALUE_NONE, 'Generate even if plugin directory already exists.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$isTheme = $this->isTheme($input);
$pluginName = $this->getPluginName($input, $output);
$description = $this->getPluginDescription($input, $output);
$version = $this->getPluginVersion($input, $output);
$createFullPlugin = !$isTheme && $this->getCreateFullPlugin($input, $output);
$isTheme = $this->isTheme($input);
$pluginName = $this->getPluginName($input, $output);
$description = $this->getPluginDescription($input, $output);
$version = $this->getPluginVersion($input, $output);
$this->generatePluginFolder($pluginName);
$plugin = new ExamplePlugin();
$info = $plugin->getInformation();
$exampleDescription = $info['description'];
if ($isTheme) {
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleTheme';
$replace = array(
'ExampleTheme' => $pluginName,
'ExampleDescription' => $description,
'0.1.0' => $version
$exampleDescription => $description,
'0.1.0' => $version,
'PIWIK_VERSION' => Version::VERSION
);
$whitelistFiles = array();
@ -55,47 +61,37 @@ class GeneratePlugin extends GeneratePluginBase
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array(
'ExamplePlugin' => $pluginName,
'ExampleDescription' => $description,
'0.1.0' => $version
$exampleDescription => $description,
'0.1.0' => $version,
'PIWIK_VERSION' => Version::VERSION
);
$whitelistFiles = array(
'/ExamplePlugin.php',
'/plugin.json',
'/README.md',
'/.travis.yml',
'/screenshots',
'/screenshots/.gitkeep',
'/javascripts',
'/javascripts/plugin.js',
);
}
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('%s %s %s generated.', $isTheme ? 'Theme' : 'Plugin', $pluginName, $version),
'Enjoy!'
));
if ($createFullPlugin) {
$this->executePluginCommand($output, 'generate:api', $pluginName);
$this->executePluginCommand($output, 'generate:controller', $pluginName);
if ($isTheme) {
$this->writeSuccessMessage($output, array(
sprintf('Theme %s %s generated.', $pluginName, $version),
'If you have not done yet check out our Theming guide <comment>http://developer.piwik.org/guides/theming</comment>',
'Enjoy!'
));
} else {
$this->writeSuccessMessage($output, array(
sprintf('Plugin %s %s generated.', $pluginName, $version),
'Our developer guides will help you developing this plugin, check out <comment>http://developer.piwik.org/guides</comment>',
'To see a list of available generators execute <comment>./console list generate</comment>',
'Enjoy!'
));
}
}
private function executePluginCommand(OutputInterface $output, $commandName, $pluginName)
{
$command = $this->getApplication()->find($commandName);
$arguments = array(
'command' => $commandName,
'--pluginname' => $pluginName
);
$input = new ArrayInput($arguments);
$command->run($input, $output);
}
/**
* @param InputInterface $input
* @return bool
@ -110,32 +106,40 @@ class GeneratePlugin extends GeneratePluginBase
protected function generatePluginFolder($pluginName)
{
$pluginPath = $this->getPluginPath($pluginName);
Filesystem::mkdir($pluginPath, true);
Filesystem::mkdir($pluginPath);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RunTimeException
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$overwrite = $input->getOption('overwrite');
$self = $this;
$validate = function ($pluginName) use ($self) {
$validate = function ($pluginName) use ($self, $overwrite) {
if (empty($pluginName)) {
throw new \RunTimeException('You have to enter a plugin name');
throw new \RuntimeException('You have to enter a plugin name');
}
if (!Filesystem::isValidFilename($pluginName)) {
throw new \RunTimeException(sprintf('The plugin name %s is not valid', $pluginName));
if(strlen($pluginName) > 40) {
throw new \RuntimeException('Your plugin name cannot be longer than 40 characters');
}
if (!Plugin\Manager::getInstance()->isValidPluginName($pluginName)) {
throw new \RuntimeException(sprintf('The plugin name %s is not valid. The name must start with a letter and is only allowed to contain numbers and letters.', $pluginName));
}
$pluginPath = $self->getPluginPath($pluginName);
if (file_exists($pluginPath)) {
throw new \RunTimeException('A plugin with this name already exists');
if (file_exists($pluginPath)
&& !$overwrite
) {
throw new \RuntimeException('A plugin with this name already exists');
}
return $pluginName;
@ -159,16 +163,16 @@ class GeneratePlugin extends GeneratePluginBase
* @param InputInterface $input
* @param OutputInterface $output
* @return mixed
* @throws \RunTimeException
* @throws \RuntimeException
*/
protected function getPluginDescription(InputInterface $input, OutputInterface $output)
{
$validate = function ($description) {
if (empty($description)) {
throw new \RunTimeException('You have to enter a description');
throw new \RuntimeException('You have to enter a description');
}
if (150 < strlen($description)) {
throw new \RunTimeException('Description is too long, max 150 characters allowed.');
throw new \RuntimeException('Description is too long, max 150 characters allowed.');
}
return $description;
@ -203,21 +207,4 @@ class GeneratePlugin extends GeneratePluginBase
return $version;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return mixed
*/
protected function getCreateFullPlugin(InputInterface $input, OutputInterface $output)
{
$full = $input->getOption('full');
if (is_null($full)) {
$dialog = $this->getHelperSet()->get('dialog');
$full = $dialog->askConfirmation($output, 'Shall we also create an API and a Controller? (y/N)', false);
}
return !empty($full);
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -9,39 +9,232 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Development;
use Piwik\Filesystem;
use Piwik\Plugin\ConsoleCommand;
use Piwik\Plugin\Dependency;
use Piwik\Version;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
abstract class GeneratePluginBase extends ConsoleCommand
{
public function getPluginPath($pluginName)
public function isEnabled()
{
return PIWIK_INCLUDE_PATH . '/plugins/' . ucfirst($pluginName);
return Development::isEnabled();
}
private function createFolderWithinPluginIfNotExists($pluginName, $folder)
public function getPluginPath($pluginName)
{
$pluginPath = $this->getPluginPath($pluginName);
return PIWIK_INCLUDE_PATH . $this->getRelativePluginPath($pluginName);
}
if (!file_exists($pluginName . $folder)) {
Filesystem::mkdir($pluginPath . $folder, true);
private function getRelativePluginPath($pluginName)
{
return '/plugins/' . $pluginName;
}
private function createFolderWithinPluginIfNotExists($pluginNameOrCore, $folder)
{
if ($pluginNameOrCore === 'core') {
$pluginPath = $this->getPathToCore();
} else {
$pluginPath = $this->getPluginPath($pluginNameOrCore);
}
if (!file_exists($pluginPath . $folder)) {
Filesystem::mkdir($pluginPath . $folder);
}
}
protected function createFileWithinPluginIfNotExists($pluginName, $fileName, $content)
protected function createFileWithinPluginIfNotExists($pluginNameOrCore, $fileName, $content)
{
$pluginPath = $this->getPluginPath($pluginName);
if ($pluginNameOrCore === 'core') {
$pluginPath = $this->getPathToCore();
} else {
$pluginPath = $this->getPluginPath($pluginNameOrCore);
}
if (!file_exists($pluginPath . $fileName)) {
file_put_contents($pluginPath . $fileName, $content);
}
}
/**
* Creates a lang/en.json within the plugin in case it does not exist yet and adds a translation for the given
* text.
*
* @param $pluginName
* @param $translatedText
* @return string Either the generated translation key or the original text if a different translation for this
* generated translation key already exists.
*/
protected function makeTranslationIfPossible($pluginName, $translatedText)
{
$defaultLang = array($pluginName => array());
$this->createFolderWithinPluginIfNotExists($pluginName, '/lang');
$this->createFileWithinPluginIfNotExists($pluginName, '/lang/en.json', $this->toJson($defaultLang));
$langJsonPath = $this->getPluginPath($pluginName) . '/lang/en.json';
$translations = file_get_contents($langJsonPath);
$translations = json_decode($translations, true);
if (empty($translations[$pluginName])) {
$translations[$pluginName] = array();
}
$key = $this->buildTranslationKey($translatedText);
if (array_key_exists($key, $translations[$pluginName])) {
// we do not want to overwrite any existing translations
if ($translations[$pluginName][$key] === $translatedText) {
return $pluginName . '_' . $key;
}
return $translatedText;
}
$translations[$pluginName][$key] = $this->removeNonJsonCompatibleCharacters($translatedText);
file_put_contents($langJsonPath, $this->toJson($translations));
return $pluginName . '_' . $key;
}
protected function checkAndUpdateRequiredPiwikVersion($pluginName, OutputInterface $output)
{
$pluginJsonPath = $this->getPluginPath($pluginName) . '/plugin.json';
$relativePluginJson = $this->getRelativePluginPath($pluginName) . '/plugin.json';
if (!file_exists($pluginJsonPath) || !is_writable($pluginJsonPath)) {
return;
}
$pluginJson = file_get_contents($pluginJsonPath);
$pluginJson = json_decode($pluginJson, true);
if (empty($pluginJson)) {
return;
}
if (empty($pluginJson['require'])) {
$pluginJson['require'] = array();
}
$piwikVersion = Version::VERSION;
$newRequiredVersion = '>=' . $piwikVersion;
if (!empty($pluginJson['require']['piwik'])) {
$requiredVersion = $pluginJson['require']['piwik'];
if ($requiredVersion === $newRequiredVersion) {
return;
}
$dependency = new Dependency();
$missingVersion = $dependency->getMissingVersions($piwikVersion, $requiredVersion);
if (!empty($missingVersion)) {
$msg = sprintf('We cannot generate this component as the plugin "%s" requires the Piwik version "%s" in the file "%s". Generating this component requires "%s". If you know your plugin is compatible with your Piwik version remove the required Piwik version in "%s" and try to execute this command again.', $pluginName, $requiredVersion, $relativePluginJson, $newRequiredVersion, $relativePluginJson);
throw new \Exception($msg);
}
$output->writeln('');
$output->writeln(sprintf('<comment>We have updated the required Piwik version from "%s" to "%s" in "%s".</comment>', $requiredVersion, $newRequiredVersion, $relativePluginJson));
} else {
$output->writeln('');
$output->writeln(sprintf('<comment>We have updated your "%s" to require the Piwik version "%s".</comment>', $relativePluginJson, $newRequiredVersion));
}
$pluginJson['require']['piwik'] = $newRequiredVersion;
file_put_contents($pluginJsonPath, $this->toJson($pluginJson));
}
private function toJson($value)
{
if (defined('JSON_PRETTY_PRINT')) {
return json_encode($value, JSON_PRETTY_PRINT);
}
return json_encode($value);
}
private function buildTranslationKey($translatedText)
{
$translatedText = preg_replace('/(\s+)/', '', $translatedText);
$translatedText = preg_replace("/[^A-Za-z0-9]/", '', $translatedText);
$translatedText = trim($translatedText);
return $this->removeNonJsonCompatibleCharacters($translatedText);
}
private function removeNonJsonCompatibleCharacters($text)
{
return preg_replace('/[^(\x00-\x7F)]*/', '', $text);
}
/**
* Copies the given method and all needed use statements into an existing class. The target class name will be
* built based on the given $replace argument.
* @param string $sourceClassName
* @param string $methodName
* @param array $replace
*/
protected function copyTemplateMethodToExisitingClass($sourceClassName, $methodName, $replace)
{
$targetClassName = $this->replaceContent($replace, $sourceClassName);
if (Development::methodExists($targetClassName, $methodName)) {
// we do not want to add the same method twice
return;
}
Development::checkMethodExists($sourceClassName, $methodName, 'Cannot copy template method: ');
$targetClass = new \ReflectionClass($targetClassName);
$file = new \SplFileObject($targetClass->getFileName());
$methodCode = Development::getMethodSourceCode($sourceClassName, $methodName);
$methodCode = $this->replaceContent($replace, $methodCode);
$methodLine = $targetClass->getEndLine() - 1;
$sourceUses = Development::getUseStatements($sourceClassName);
$targetUses = Development::getUseStatements($targetClassName);
$usesToAdd = array_diff($sourceUses, $targetUses);
if (empty($usesToAdd)) {
$useCode = '';
} else {
$useCode = "\nuse " . implode("\nuse ", $usesToAdd) . "\n";
}
// search for namespace line before the class starts
$useLine = 0;
foreach (new \LimitIterator($file, 0, $targetClass->getStartLine()) as $index => $line) {
if (0 === strpos(trim($line), 'namespace ')) {
$useLine = $index + 1;
break;
}
}
$newClassCode = '';
foreach(new \LimitIterator($file) as $index => $line) {
if ($index == $methodLine) {
$newClassCode .= $methodCode;
}
if (0 !== $useLine && $index == $useLine) {
$newClassCode .= $useCode;
}
$newClassCode .= $line;
}
file_put_contents($targetClass->getFileName(), $newClassCode);
}
/**
* @param string $templateFolder full path like /home/...
* @param string $pluginName
@ -67,16 +260,13 @@ abstract class GeneratePluginBase extends ConsoleCommand
}
if (is_dir($file)) {
$fileNamePlugin = $this->replaceContent($replace, $fileNamePlugin);
$this->createFolderWithinPluginIfNotExists($pluginName, $fileNamePlugin);
} else {
$template = file_get_contents($file);
foreach ($replace as $key => $value) {
$template = str_replace($key, $value, $template);
}
$template = $this->replaceContent($replace, $template);
foreach ($replace as $key => $value) {
$fileNamePlugin = str_replace($key, $value, $fileNamePlugin);
}
$fileNamePlugin = $this->replaceContent($replace, $fileNamePlugin);
$this->createFileWithinPluginIfNotExists($pluginName, $fileNamePlugin, $template);
}
@ -114,7 +304,7 @@ abstract class GeneratePluginBase extends ConsoleCommand
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RunTimeException
* @throws \RuntimeException
*/
protected function askPluginNameAndValidate(InputInterface $input, OutputInterface $output, $pluginNames, $invalidArgumentException)
{
@ -135,9 +325,22 @@ abstract class GeneratePluginBase extends ConsoleCommand
$validate($pluginName);
}
$pluginName = ucfirst($pluginName);
return $pluginName;
}
private function getPathToCore()
{
$path = PIWIK_INCLUDE_PATH . '/core';
return $path;
}
private function replaceContent($replace, $contentToReplace)
{
foreach ((array) $replace as $key => $value) {
$contentToReplace = str_replace($key, $value, $contentToReplace);
}
return $contentToReplace;
}
}

View file

@ -0,0 +1,300 @@
<?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\CoreConsole\Commands;
use Piwik\Columns\Dimension;
use Piwik\Plugin\Manager;
use Piwik\Plugin\Report;
use Piwik\Translate;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GenerateReport extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:report')
->setDescription('Adds a new report to an existing plugin')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin which does not have a menu defined yet')
->addOption('reportname', null, InputOption::VALUE_REQUIRED, 'The name of the report you want to create')
->addOption('category', null, InputOption::VALUE_REQUIRED, 'The name of the category the report belongs to')
->addOption('dimension', null, InputOption::VALUE_OPTIONAL, 'The name of the dimension in case your report has a dimension')
->addOption('documentation', null, InputOption::VALUE_REQUIRED, 'A documentation that explains what your report is about');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$reportName = $this->getReportName($input, $output);
$category = $this->getCategory($input, $output, $pluginName);
$documentation = $this->getDocumentation($input, $output);
list($dimension, $dimensionClass) = $this->getDimension($input, $output, $pluginName);
$order = $this->getOrder($category);
$apiName = $this->getApiName($reportName);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleReport';
$replace = array('GetExampleReport' => ucfirst($apiName),
'getExampleReport' => lcfirst($apiName),
'getApiReport' => lcfirst($apiName),
'ExampleCategory' => $category,
'ExampleReportName' => $this->makeTranslationIfPossible($pluginName, $reportName),
'ExampleReportDocumentation' => $documentation,
'999' => $order,
'new ExitPageUrl()' => $dimension,
'use Piwik\Plugins\Actions\Columns\ExitPageUrl;' => $dimensionClass,
'ExampleReport' => $pluginName,
);
$whitelistFiles = array('/Reports', '/Reports/Base.php', '/Reports/GetExampleReport.php');
if (file_exists($this->getPluginPath($pluginName) . '/API.php')) {
$this->copyTemplateMethodToExisitingClass('Piwik\Plugins\ExampleReport\API', 'getExampleReport', $replace);
} else {
$whitelistFiles[] = '/API.php';
}
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Reports/%s.php for %s generated.', ucfirst($apiName), $pluginName),
'You should now implement the method called "' . lcfirst($apiName) . '" in API.php',
// 'Read more about this here: link to developer guide',
'Enjoy!'
));
}
private function getOrder($category)
{
$order = 1;
foreach (Report::getAllReports() as $report) {
if ($report->getCategory() === $category) {
if ($report->getOrder() > $order) {
$order = $report->getOrder() + 1;
}
}
}
return $order;
}
private function getApiName($reportName)
{
$reportName = trim($reportName);
$reportName = str_replace(' ', '', $reportName);
$reportName = preg_replace("/[^A-Za-z0-9]/", '', $reportName);
$apiName = 'get' . ucfirst($reportName);
return $apiName;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getReportName(InputInterface $input, OutputInterface $output)
{
$validate = function ($reportName) {
if (empty($reportName)) {
throw new \InvalidArgumentException('Please enter the name of your report');
}
if (preg_match("/[^A-Za-z0-9 ]/", $reportName)) {
throw new \InvalidArgumentException('Only alpha numerical characters and whitespaces are allowed');
}
return $reportName;
};
$reportName = $input->getOption('reportname');
if (empty($reportName)) {
$dialog = $this->getHelperSet()->get('dialog');
$reportName = $dialog->askAndValidate($output, 'Enter the name of your report, for instance "Browser Families": ', $validate);
} else {
$validate($reportName);
}
$reportName = ucfirst($reportName);
return $reportName;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getDocumentation(InputInterface $input, OutputInterface $output)
{
$validate = function ($documentation) {
if (empty($documentation)) {
return '';
}
return $documentation;
};
$documentation = $input->getOption('documentation');
if (empty($documentation)) {
$dialog = $this->getHelperSet()->get('dialog');
$documentation = $dialog->askAndValidate($output, 'Enter a documentation that describes the data of your report (you can leave it empty and define it later): ', $validate);
} else {
$validate($documentation);
}
$documentation = ucfirst($documentation);
return $documentation;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $pluginName
* @return array
* @throws \RuntimeException
*/
protected function getCategory(InputInterface $input, OutputInterface $output, $pluginName)
{
$path = $this->getPluginPath($pluginName) . '/Reports/Base.php';
if (file_exists($path)) {
// category is already defined in base.php
return '';
}
$validate = function ($category) {
if (empty($category)) {
throw new \InvalidArgumentException('Please enter the name of the category your report belongs to');
}
return $category;
};
$category = $input->getOption('category');
$categories = array();
foreach (Report::getAllReports() as $report) {
if ($report->getCategory()) {
$categories[] = $report->getCategory();
}
}
$categories = array_values(array_unique($categories));
if (empty($category)) {
$dialog = $this->getHelperSet()->get('dialog');
$category = $dialog->askAndValidate($output, 'Enter the report category, for instance "Visitor" (you can reuse any existing category or define a new one): ', $validate, false, null, $categories);
} else {
$validate($category);
}
$translationKey = Translate::findTranslationKeyForTranslation($category);
if (!empty($translationKey)) {
return $translationKey;
}
$category = ucfirst($category);
return $category;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $pluginName
* @return array
* @throws \RuntimeException
*/
protected function getDimension(InputInterface $input, OutputInterface $output, $pluginName)
{
$dimensions = array();
$dimensionNames = array();
foreach (Report::getAllReports() as $report) {
$dimension = $report->getDimension();
if (is_object($dimension)) {
$name = $dimension->getName();
if (!empty($name)) {
$dimensions[$name] = get_class($dimension);
$dimensionNames[] = $name;
}
}
}
$plugin = Manager::getInstance()->loadPlugin($pluginName);
$dimensions = Dimension::getAllDimensions();
$dimensions = array_merge($dimensions, Dimension::getDimensions($plugin));
foreach ($dimensions as $dimension) {
$name = $dimension->getName();
if (!empty($name)) {
$dimensions[$name] = get_class($dimension);
$dimensionNames[] = $name;
}
}
$dimensionNames = array_values(array_unique($dimensionNames));
$validate = function ($dimension) use ($dimensions) {
if (empty($dimension)) {
return '';
}
if (!empty($dimension) && !array_key_exists($dimension, $dimensions)) {
throw new \InvalidArgumentException('Leave dimension either empty or use an existing one. You can also create a new dimension by calling .console generate:dimension before generating this report.');
}
return $dimension;
};
$actualDimension = $input->getOption('dimension');
if (null === $actualDimension) {
$dialog = $this->getHelperSet()->get('dialog');
$actualDimension = $dialog->askAndValidate($output, 'Enter the report dimension, for instance "Browser" (you can leave it either empty or use an existing one): ', $validate, false, null, $dimensionNames);
} else {
$validate($actualDimension);
}
if (empty($actualDimension)) {
return array('null', '');
}
$className = $dimensions[$actualDimension];
$parts = explode('\\', $className);
$name = end($parts);
return array('new ' . $name . '()', 'use ' . $className . ';');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$pluginNames = $this->getPluginNames();
$invalidName = 'You have to enter a name of an existing plugin.';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View file

@ -0,0 +1,59 @@
<?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\CoreConsole\Commands;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateScheduledTask extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:scheduledtask')
->setDescription('Adds a tasks class to an existing plugin which allows you to specify scheduled tasks')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin which does not have any tasks defined yet');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array('ExamplePlugin' => $pluginName);
$whitelistFiles = array('/Tasks.php');
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Tasks.php for %s generated.', $pluginName),
'You can now start specifying your scheduled tasks',
'Enjoy!'
));
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$pluginNames = $this->getPluginNamesHavingNotSpecificFile('Tasks.php');
$invalidName = 'You have to enter the name of an existing plugin which does not already have any tasks defined';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -27,6 +27,7 @@ class GenerateSettings extends GeneratePluginBase
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExampleSettingsPlugin';
$replace = array('ExampleSettingsPlugin' => $pluginName);
@ -45,7 +46,7 @@ class GenerateSettings extends GeneratePluginBase
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RunTimeException
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -24,54 +24,53 @@ class GenerateTest extends GeneratePluginBase
->setDescription('Adds a test to an existing plugin')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin')
->addOption('testname', null, InputOption::VALUE_REQUIRED, 'The name of the test to create')
->addOption('testtype', null, InputOption::VALUE_REQUIRED, 'Whether you want to create a "unit", "integration" or "database" test');
->addOption('testtype', null, InputOption::VALUE_REQUIRED, 'Whether you want to create a "unit", "integration", "system", or "ui" test');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$testName = $this->getTestName($input, $output);
$testType = $this->getTestType($input, $output);
$testName = $this->getTestName($input, $output, $testType);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array(
'ExamplePlugin' => $pluginName,
'SimpleTest' => $testName,
'SimpleIntegrationTest' => $testName,
'@group Plugins' => '@group ' . $testType
'ExamplePlugin' => $pluginName,
'SimpleTest' => $testName,
'SimpleSystemTest' => $testName,
'SimpleUITest_spec.js' => $testName . '_spec.js',
'SimpleUITest' => $testName,
);
$testClass = $this->getTestClass($testType);
if(!empty($testClass)) {
$replace['\PHPUnit_Framework_TestCase'] = $testClass;
}
$whitelistFiles = $this->getTestFilesWhitelist($testType);
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Test %s for plugin %s generated.', $testName, $pluginName),
'You can now start writing beautiful tests!',
$messages = array(
sprintf('Test %s for plugin %s generated.', $testName, $pluginName),
);
));
if (strtolower($testType) === 'ui') {
$messages[] = 'To run this test execute the command: ';
$messages[] = '<comment>' . sprintf('./console tests:run-ui %s', $testName) . '</comment>';
} else {
$messages[] = 'To run all your plugin tests, execute the command: ';
$messages[] = '<comment>' . sprintf('./console tests:run %s', $pluginName) . '</comment>';
$messages[] = 'To run only this test: ';
$messages[] = '<comment>' . sprintf('./console tests:run %s', $testName) . '</comment>';
}
$this->writeSuccessMessage($output, array(
'To run all your plugin tests, execute the command: ',
sprintf('./console tests:run %s', $pluginName),
'To run only this test: ',
sprintf('./console tests:run %s', $testName),
'Enjoy!'
));
$messages[] = 'Enjoy!';
$this->writeSuccessMessage($output, $messages);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string
* @throws \RunTimeException
* @throws \RuntimeException
*/
private function getTestName(InputInterface $input, OutputInterface $output)
private function getTestName(InputInterface $input, OutputInterface $output, $testType)
{
$testname = $input->getOption('testname');
@ -90,7 +89,7 @@ class GenerateTest extends GeneratePluginBase
$validate($testname);
}
if (!Common::stringEndsWith(strtolower($testname), 'test')) {
if (strtolower($testType) !== 'ui' && !Common::stringEndsWith(strtolower($testname), 'test')) {
$testname = $testname . 'Test';
}
@ -103,7 +102,7 @@ class GenerateTest extends GeneratePluginBase
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RunTimeException
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
@ -113,30 +112,15 @@ class GenerateTest extends GeneratePluginBase
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
/**
* @param InputInterface $input
* @return string
*/
private function getTestClass($testType)
{
if ('Database' == $testType) {
return '\DatabaseTestCase';
}
if ('Unit' == $testType) {
return '\PHPUnit_Framework_TestCase';
}
return false;
}
public function getValidTypes()
{
return array('unit', 'integration', 'database');
return array('unit', 'integration', 'system', 'ui');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string Unit, Integration, Database
* @return string Unit, Integration, System
*/
private function getTestType(InputInterface $input, OutputInterface $output)
{
@ -167,23 +151,46 @@ class GenerateTest extends GeneratePluginBase
*/
protected function getTestFilesWhitelist($testType)
{
if('Integration' == $testType) {
if ('Ui' == $testType) {
return array(
'/tests',
'/tests/UI',
'/tests/UI/.gitignore',
'/tests/UI/expected-ui-screenshots',
'/tests/UI/expected-ui-screenshots/.gitkeep',
'/tests/UI/SimpleUITest_spec.js',
);
}
if ('System' == $testType) {
return array(
'/.gitignore',
'/tests',
'/tests/SimpleIntegrationTest.php',
'/tests/expected',
'/tests/expected/test___API.get_day.xml',
'/tests/expected/test___Goals.getItemsSku_day.xml',
'/tests/processed',
'/tests/processed/.gitignore',
'/tests/fixtures',
'/tests/fixtures/SimpleFixtureTrackFewVisits.php'
'/tests/System',
'/tests/System/SimpleSystemTest.php',
'/tests/System/expected',
'/tests/System/expected/test___API.get_day.xml',
'/tests/System/expected/test___Goals.getItemsSku_day.xml',
'/tests/System/processed',
'/tests/System/processed/.gitignore',
'/tests/Fixtures',
'/tests/Fixtures/SimpleFixtureTrackFewVisits.php'
);
}
if ('Integration' == $testType) {
return array(
'/tests',
'/tests/Integration',
'/tests/Integration/SimpleTest.php'
);
}
return array(
'/tests',
'/tests/SimpleTest.php'
'/tests/Unit',
'/tests/Unit/SimpleTest.php'
);
}
}

View file

@ -0,0 +1,122 @@
<?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\CoreConsole\Commands;
use Piwik\Plugin;
use Piwik\Updater;
use Piwik\Version;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GenerateUpdate extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:update')
->setDescription('Adds a new update to an existing plugin or "core"')
->addOption('component', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin or "core"');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$component = $this->getComponent($input, $output);
$version = $this->getVersion($component);
$namespace = $this->getNamespace($component);
$className = $this->getUpdateClassName($component, $version);
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array('Piwik\Plugins\ExamplePlugin\Updates' => $namespace,
'ExamplePlugin' => $component,
'Updates_0_0_2' => $className,
'0.0.2' => $version);
$whitelistFiles = array('/Updates', '/Updates/0.0.2.php');
$this->copyTemplateToPlugin($exampleFolder, $component, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Updates/%s.php for %s generated.', $version, $component),
'You should have a look at the method update() or getSql() now.',
'Enjoy!'
));
}
private function getUpdateClassName($component, $version)
{
$updater = new Updater();
$className = $updater->getUpdateClassName($component, $version);
$parts = explode('\\', $className);
return end($parts);
}
private function getVersion($component)
{
if ($component === 'core') {
return Version::VERSION;
}
$pluginManager = Plugin\Manager::getInstance();
if ($pluginManager->isPluginLoaded($component)) {
$plugin = $pluginManager->getLoadedPlugin($component);
} else {
$plugin = new Plugin($component);
}
return $plugin->getVersion();
}
private function getNamespace($component)
{
$updater = new Updater();
$className = $updater->getUpdateClassName($component, 'xx');
$className = str_replace('Updates_xx', '', $className);
$className = trim($className, '\\');
if ($component !== 'core') {
$className .= '\Updates';
}
return $className;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
private function getComponent(InputInterface $input, OutputInterface $output)
{
$components = $this->getPluginNames();
$components[] = 'core';
$validate = function ($component) use ($components) {
if (!in_array($component, $components)) {
throw new \InvalidArgumentException('You have to enter a name of an existing plugin or "core".');
}
return $component;
};
$component = $input->getOption('component');
if (empty($component)) {
$dialog = $this->getHelperSet()->get('dialog');
$component = $dialog->askAndValidate($output, 'Enter the name of your plugin or "core": ', $validate, false, null, $components);
} else {
$validate($component);
}
return $component;
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -9,8 +9,6 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@ -33,6 +31,7 @@ class GenerateVisualizationPlugin extends GeneratePlugin
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$description = $this->getPluginDescription($input, $output);
$version = $this->getPluginVersion($input, $output);
$visualizationName = $this->getVisualizationName($input, $output);
@ -43,7 +42,7 @@ class GenerateVisualizationPlugin extends GeneratePlugin
$replace = array(
'SimpleTable' => $visualizationName,
'simpleTable' => lcfirst($visualizationName),
'Simple Table' => $visualizationName,
'Simple Table' => $this->makeTranslationIfPossible($pluginName, $visualizationName),
'ExampleVisualization' => $pluginName,
'ExampleVisualizationDescription' => $description
);
@ -60,7 +59,7 @@ class GenerateVisualizationPlugin extends GeneratePlugin
* @param InputInterface $input
* @param OutputInterface $output
* @return string
* @throws \RunTimeException
* @throws \RuntimeException
*/
private function getVisualizationName(InputInterface $input, OutputInterface $output)
{
@ -68,11 +67,11 @@ class GenerateVisualizationPlugin extends GeneratePlugin
$validate = function ($visualizationName) use ($self) {
if (empty($visualizationName)) {
throw new \RunTimeException('You have to enter a visualization name');
throw new \RuntimeException('You have to enter a visualization name');
}
if (!ctype_alnum($visualizationName)) {
throw new \RunTimeException(sprintf('The visualization name %s is not valid', $visualizationName));
throw new \RuntimeException(sprintf('The visualization name %s is not valid (only AlNum allowed)', $visualizationName));
}
return $visualizationName;
@ -82,7 +81,7 @@ class GenerateVisualizationPlugin extends GeneratePlugin
if (empty($visualizationName)) {
$dialog = $this->getHelperSet()->get('dialog');
$visualizationName = $dialog->askAndValidate($output, 'Enter a visualization name: ', $validate);
$visualizationName = $dialog->askAndValidate($output, 'Enter a visualization name (only AlNum allowed): ', $validate);
} else {
$validate($visualizationName);
}
@ -92,5 +91,4 @@ class GenerateVisualizationPlugin extends GeneratePlugin
return $visualizationName;
}
}

View file

@ -0,0 +1,120 @@
<?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\CoreConsole\Commands;
use Piwik\Piwik;
use Piwik\Plugin\Widgets;
use Piwik\Translate;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateWidget extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:widget')
->setDescription('Adds a plugin widget class to an existing plugin')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin which does not have any widgets defined yet')
->addOption('category', null, InputOption::VALUE_REQUIRED, 'The name of the category the widget should belong to');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$category = $this->getCategory($input, $output);
if ($category === Piwik::translate($category)) {
// no translation found...
$category = $this->makeTranslationIfPossible($pluginName, $category);
}
$exampleFolder = PIWIK_INCLUDE_PATH . '/plugins/ExamplePlugin';
$replace = array('ExamplePlugin' => $pluginName,
'Example Category' => $category);
$whitelistFiles = array('/Widgets.php');
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Widgets.php for %s generated.', $pluginName),
'You can now start defining your plugin widgets',
'Enjoy!'
));
}
protected function getExistingCategories()
{
$categories = array();
foreach (Widgets::getAllWidgets() as $widget) {
if ($widget->getCategory()) {
$categories[] = Piwik::translate($widget->getCategory());
}
}
$categories = array_values(array_unique($categories));
return $categories;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getCategory(InputInterface $input, OutputInterface $output)
{
$validate = function ($category) {
if (empty($category)) {
throw new \InvalidArgumentException('Please enter the name of the category your widget should belong to');
}
return $category;
};
$category = $input->getOption('category');
$categories = $this->getExistingCategories();
if (empty($category)) {
$dialog = $this->getHelperSet()->get('dialog');
$category = $dialog->askAndValidate($output, 'Enter the widget category, for instance "Visitor" (you can reuse any existing category or define a new one): ', $validate, false, null, $categories);
} else {
$validate($category);
}
$translationKey = Translate::findTranslationKeyForTranslation($category);
if (!empty($translationKey)) {
return $translationKey;
}
$category = ucfirst($category);
return $category;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$pluginNames = $this->getPluginNamesHavingNotSpecificFile('Widgets.php');
$invalidName = 'You have to enter the name of an existing plugin which does not already have any widgets defined';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -9,8 +9,9 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Development;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Piwik\SettingsPiwik;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
@ -19,6 +20,11 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class GitCommit extends ConsoleCommand
{
public function isEnabled()
{
return Development::isEnabled() && SettingsPiwik::isGitDeployment();
}
protected function configure()
{
$this->setName('git:commit')

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -10,15 +10,19 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Piwik\SettingsPiwik;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GitPull extends ConsoleCommand
{
public function isEnabled()
{
return SettingsPiwik::isGitDeployment();
}
protected function configure()
{
$this->setName('git:pull');

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -9,16 +9,21 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Development;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Piwik\SettingsPiwik;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GitPush extends ConsoleCommand
{
public function isEnabled()
{
return Development::isEnabled() && SettingsPiwik::isGitDeployment();
}
protected function configure()
{
$this->setName('git:push');

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -8,8 +8,11 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Plugin\Manager;
use Piwik\Plugin\ConsoleCommand;
use Piwik\Plugins\CorePluginsAdmin\Commands\ActivatePlugin;
use Piwik\Plugins\CorePluginsAdmin\Commands\DeactivatePlugin;
use Piwik\Plugins\CorePluginsAdmin\Commands\ListPlugins;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@ -17,6 +20,8 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* core:plugin console command.
*
* @deprecated This command has been replaced with `plugin:*` commands.
*/
class ManagePlugin extends ConsoleCommand
{
@ -26,16 +31,17 @@ class ManagePlugin extends ConsoleCommand
{
$this->setName('core:plugin');
$this->setDescription("Perform various actions regarding one or more plugins.");
$this->addArgument("operation", InputArgument::REQUIRED, "Operation to apply (can be 'activate' or 'deactivate').");
$this->addArgument("plugins", InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'Plugin name(s) to activate.');
$this->addArgument("operation", InputArgument::REQUIRED, "Operation to apply (can be 'activate' or 'deactivate' or 'list').");
$this->addArgument("plugins", InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Plugin name(s) to activate.');
$this->addOption('domain', null, InputOption::VALUE_REQUIRED, "The domain to activate the plugin for.");
$this->operations['activate'] = 'activatePlugin';
$this->operations['deactivate'] = 'deactivatePlugin';
$this->operations['list'] = 'listPlugins';
}
/**
* Execute command like: ./console cloudadmin:plugin activate CustomAlerts --piwik-domain=testcustomer.piwik.pro
* Execute command like: ./console core:plugin activate CustomAlerts --piwik-domain=testcustomer.piwik.pro
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@ -43,10 +49,27 @@ class ManagePlugin extends ConsoleCommand
$plugins = $input->getArgument('plugins');
if (empty($this->operations[$operation])) {
throw new Exception("Invalid operation '$operation'.");
throw new \Exception("Invalid operation '$operation'.");
}
$fn = $this->operations[$operation];
if($fn == 'listPlugins') {
call_user_func(array($this, $fn), $input, $output);
} else {
$this->applyOperationToEachPlugin($input, $output, $plugins, $fn);
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param $plugins
* @param $fn
*/
protected function applyOperationToEachPlugin(InputInterface $input, OutputInterface $output, $plugins, $fn)
{
foreach ($plugins as $plugin) {
call_user_func(array($this, $fn), $input, $output, $plugin);
}
@ -54,15 +77,32 @@ class ManagePlugin extends ConsoleCommand
private function activatePlugin(InputInterface $input, OutputInterface $output, $plugin)
{
Manager::getInstance()->activatePlugin($plugin, $input, $output);
$output->writeln('<comment>Warning: the command core:plugin is deprecated, use plugin:activate instead.</comment>');
$output->writeln("Activated plugin <info>$plugin</info>");
$command = new ActivatePlugin();
$input = new ArrayInput(array(
'plugin' => $plugin,
));
return $command->run($input, $output);
}
private function deactivatePlugin(InputInterface $input, OutputInterface $output, $plugin)
{
Manager::getInstance()->deactivatePlugin($plugin, $input, $output);
$output->writeln('<comment>Warning: the command core:plugin is deprecated, use plugin:deactivate instead.</comment>');
$output->writeln("Deactivated plugin <info>$plugin</info>");
$command = new DeactivatePlugin();
$input = new ArrayInput(array(
'plugin' => $plugin,
));
return $command->run($input, $output);
}
private function listPlugins(InputInterface $input, OutputInterface $output)
{
$output->writeln('<comment>Warning: the command core:plugin is deprecated, use plugin:list instead.</comment>');
$command = new ListPlugins();
$input = new ArrayInput(array());
return $command->run($input, $output);
}
}

View file

@ -1,87 +0,0 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class RunTests extends ConsoleCommand
{
protected function configure()
{
$this->setName('tests:run');
$this->setDescription('Run Piwik PHPUnit tests one group after the other');
$this->addArgument('group', InputArgument::OPTIONAL, 'Run only a specific test group. Separate multiple groups by comma, for instance core,integration', '');
$this->addOption('options', 'o', InputOption::VALUE_OPTIONAL, 'All options will be forwarded to phpunit', '');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$options = $input->getOption('options');
$groups = $input->getArgument('group');
$groups = explode(",", $groups);
$groups = array_map('ucfirst', $groups);
$groups = array_filter($groups, 'strlen');
$command = 'phpunit';
// force xdebug usage for coverage options
if (false !== strpos($options, '--coverage') && !extension_loaded('xdebug')) {
$output->writeln('<info>xdebug extension required for code coverage.</info>');
$output->writeln('<info>searching for xdebug extension...</info>');
$extensionDir = shell_exec('php-config --extension-dir');
$xdebugFile = trim($extensionDir) . DIRECTORY_SEPARATOR . 'xdebug.so';
if (!file_exists($xdebugFile)) {
$dialog = $this->getHelperSet()->get('dialog');
$xdebugFile = $dialog->askAndValidate($output, 'xdebug not found. Please provide path to xdebug.so', function($xdebugFile) {
return file_exists($xdebugFile);
});
} else {
$output->writeln('<info>xdebug extension found in extension path.</info>');
}
$output->writeln("<info>using $xdebugFile as xdebug extension.</info>");
$phpunitPath = trim(shell_exec('which phpunit'));
$command = sprintf('php -d zend_extension=%s %s', $xdebugFile, $phpunitPath);
}
if(empty($groups)) {
$groups = $this->getTestsGroups();
}
foreach($groups as $group) {
$params = '--group ' . $group . ' ' . str_replace('%group%', $group, $options);
$cmd = sprintf('cd %s/tests/PHPUnit && %s %s', PIWIK_DOCUMENT_ROOT, $command, $params);
$output->writeln('Executing command: <info>' . $cmd . '</info>');
passthru($cmd);
$output->writeln("");
}
}
private function getTestsGroups()
{
return array('Core', 'Plugins', 'Integration', 'UI');
}
}

View file

@ -1,70 +0,0 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class RunUITests extends ConsoleCommand
{
protected function configure()
{
$this->setName('tests:run-ui');
$this->setDescription('Run screenshot tests');
$this->addArgument('specs', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Run only a specific test spec. Separate multiple specs by comma, for instance core,integration', array());
$this->addOption("persist-fixture-data", null, InputOption::VALUE_NONE, "Persist test data in a database and do not execute tear down.");
$this->addOption('keep-symlinks', null, InputOption::VALUE_NONE, "Keep recursive directory symlinks so test pages can be viewed in a browser.");
$this->addOption('print-logs', null, InputOption::VALUE_NONE, "Print webpage logs even if tests succeed.");
$this->addOption('drop', null, InputOption::VALUE_NONE, "Drop the existing database and re-setup a persisted fixture.");
$this->addOption('plugin', null, InputOption::VALUE_REQUIRED, "Execute all tests for a plugin.");
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$specs = $input->getArgument('specs');
$persistFixtureData = $input->getOption("persist-fixture-data");
$keepSymlinks = $input->getOption('keep-symlinks');
$printLogs = $input->getOption('print-logs');
$drop = $input->getOption('drop');
$plugin = $input->getOption('plugin');
$options = array();
if ($persistFixtureData) {
$options[] = "--persist-fixture-data";
}
if ($keepSymlinks) {
$options[] = "--keep-symlinks";
}
if ($printLogs) {
$options[] = "--print-logs";
}
if ($drop) {
$options[] = "--drop";
}
if ($plugin) {
$options[] = "--plugin=" . $plugin;
}
$options = implode(" ", $options);
$specs = implode(" ", $specs);
$cmd = "phantomjs '" . PIWIK_INCLUDE_PATH . "/tests/lib/screenshot-testing/run-tests.js' $options $specs";
$output->writeln('Executing command: <info>' . $cmd . '</info>');
$output->writeln('');
passthru($cmd);
}
}

View file

@ -1,156 +0,0 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Url;
use Piwik\Piwik;
use Piwik\Config;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Console commands that sets up a fixture either in a local MySQL database or a remote one.
*
* TODO: use this console command in UI tests instead of setUpDatabase.php/tearDownDatabase.php scripts
*/
class SetupFixture extends ConsoleCommand
{
protected function configure()
{
$this->setName('tests:setup-fixture');
$this->setDescription('Create a database and fill it with data using a Piwik test fixture.');
$this->addArgument('fixture', InputArgument::REQUIRED,
"The class name of the fixture to apply. Doesn't need to have a namespace if it exists in the " .
"Piwik\\Tests\\Fixtures namespace.");
$this->addOption('db-name', null, InputOption::VALUE_REQUIRED,
"The name of the database that will contain the fixture data. This option is required to be set.");
$this->addOption('file', null, InputOption::VALUE_REQUIRED,
"The file location of the fixture. If this option is included the file will be required explicitly.");
$this->addOption('db-host', null, InputOption::VALUE_REQUIRED,
"The hostname of the MySQL database to use. Uses the default config value if not specified.");
$this->addOption('db-user', null, InputOption::VALUE_REQUIRED,
"The name of the MySQL user to use. Uses the default config value if not specified.");
$this->addOption('db-pass', null, InputOption::VALUE_REQUIRED,
"The MySQL user password to use. Uses the default config value if not specified.");
$this->addOption('teardown', null, InputOption::VALUE_NONE,
"If specified, the fixture will be torn down and the database deleted. Won't work if the --db-name " .
"option isn't supplied.");
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$dbName = $input->getOption('db-name');
if (!$dbName) {
throw new \Exception("Required argument --db-name is not set.");
}
$this->requireFixtureFiles();
$this->setIncludePathAsInTestBootstrap();
$file = $input->getOption('file');
if ($file) {
if (is_file($file)) {
require_once $file;
} else if (is_file(PIWIK_INCLUDE_PATH . '/' . $file)) {
require_once PIWIK_INCLUDE_PATH . '/' . $file;
} else {
throw new \Exception("Cannot find --file option file '$file'.");
}
}
$host = Url::getHost();
if (empty($host)) {
Url::setHost('localhost');
}
// get the fixture class
$fixtureClass = $input->getArgument('fixture');
if (class_exists("Piwik\\Tests\\Fixtures\\" . $fixtureClass)) {
$fixtureClass = "Piwik\\Tests\\Fixtures\\" . $fixtureClass;
}
if (!class_exists($fixtureClass)) {
throw new \Exception("Cannot find fixture class '$fixtureClass'.");
}
// create the fixture
$fixture = new $fixtureClass();
$fixture->dbName = $dbName;
$fixture->printToScreen = true;
Config::getInstance()->setTestEnvironment();
$fixture->createConfig = false;
// setup database overrides
$testingEnvironment = $fixture->getTestEnvironment();
$optionsToOverride = array(
'dbname' => $dbName,
'host' => $input->getOption('db-host'),
'user' => $input->getOption('db-user'),
'password' => $input->getOption('db-pass')
);
foreach ($optionsToOverride as $configOption => $value) {
if ($value) {
$configOverride = $testingEnvironment->configOverride;
$configOverride['database_tests'][$configOption] = $configOverride['database'][$configOption] = $value;
$testingEnvironment->configOverride = $configOverride;
Config::getInstance()->database[$configOption] = $value;
}
}
// perform setup and/or teardown
if ($input->getOption('teardown')) {
$testingEnvironment->save();
$fixture->performTearDown();
} else {
$fixture->performSetUp();
}
$this->writeSuccessMessage($output, array("Fixture successfully setup!"));
}
private function requireFixtureFiles()
{
require_once "PHPUnit/Autoload.php";
require_once PIWIK_INCLUDE_PATH . '/libs/PiwikTracker/PiwikTracker.php';
require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/FakeAccess.php';
require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/TestingEnvironment.php';
require_once PIWIK_INCLUDE_PATH . '/tests/PHPUnit/Fixture.php';
$fixturesToLoad = array(
'/tests/PHPUnit/Fixtures/*.php',
'/tests/PHPUnit/UI/Fixtures/*.php',
);
foreach($fixturesToLoad as $fixturePath) {
foreach (glob(PIWIK_INCLUDE_PATH . $fixturePath) as $file) {
require_once $file;
}
}
}
private function setIncludePathAsInTestBootstrap()
{
if (!defined('PIWIK_INCLUDE_SEARCH_PATH')) {
define('PIWIK_INCLUDE_SEARCH_PATH', get_include_path()
. PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/core'
. PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/libs'
. PATH_SEPARATOR . PIWIK_INCLUDE_PATH . '/plugins');
}
@ini_set('include_path', PIWIK_INCLUDE_SEARCH_PATH);
@set_include_path(PIWIK_INCLUDE_SEARCH_PATH);
}
}

View file

@ -1,81 +0,0 @@
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Http;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class SyncUITestScreenshots extends ConsoleCommand
{
protected function configure()
{
$this->setName('development:sync-ui-test-screenshots');
$this->setDescription('For Piwik core devs. Copies screenshots '
. 'from travis artifacts to tests/PHPUnit/UI/expected-ui-screenshots/');
$this->addArgument('buildnumber', InputArgument::REQUIRED, 'Travis build number you want to sync.');
$this->addArgument('screenshotsRegex', InputArgument::OPTIONAL,
'A regex to use when selecting screenshots to copy. If not supplied all screenshots are copied.', '.*');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$buildNumber = $input->getArgument('buildnumber');
$screenshotsRegex = $input->getArgument('screenshotsRegex');
if (empty($buildNumber)) {
throw new \InvalidArgumentException('Missing build number.');
}
$urlBase = sprintf('http://builds-artifacts.piwik.org/ui-tests.master/%s', $buildNumber);
$diffviewer = Http::sendHttpRequest($urlBase . "/screenshot-diffs/diffviewer.html", $timeout = 60);
$dom = new \DOMDocument();
$dom->loadHTML($diffviewer);
foreach ($dom->getElementsByTagName("tr") as $row) {
$columns = $row->getElementsByTagName("td");
$nameColumn = $columns->item(0);
$processedColumn = $columns->item(2);
$testPlugin = null;
if ($nameColumn
&& preg_match("/\(for ([a-zA-Z_]+) plugin\)/", $dom->saveXml($nameColumn), $matches)
) {
$testPlugin = $matches[1];
}
$file = null;
if ($processedColumn
&& preg_match("/href=\".*\/(.*)\"/", $dom->saveXml($processedColumn), $matches)
) {
$file = $matches[1];
}
if ($file !== null
&& preg_match("/" . $screenshotsRegex . "/", $file)
) {
if ($testPlugin == null) {
$downloadTo = "tests/PHPUnit/UI/expected-ui-screenshots/$file";
} else {
$downloadTo = "plugins/$testPlugin/tests/UI/expected-ui-screenshots/$file";
}
$output->write("<info>Downloading $file to .$downloadTo...</info>\n");
Http::sendHttpRequest("$urlBase/processed-ui-screenshots/$file", $timeout = 60, $userAgent = null,
PIWIK_DOCUMENT_ROOT . "/" . $downloadTo);
}
}
}
}

View file

@ -1,6 +1,6 @@
<?php
/**
* Piwik - Open source web analytics
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
@ -9,6 +9,7 @@
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Container\StaticContainer;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@ -25,7 +26,8 @@ class WatchLog extends ConsoleCommand
protected function execute(InputInterface $input, OutputInterface $output)
{
$cmd = sprintf('tail -f %s/tmp/logs/*.log', PIWIK_DOCUMENT_ROOT);
$path = StaticContainer::get('path.tmp') . '/logs/';
$cmd = sprintf('tail -f %s*.log', $path);
$output->writeln('Executing command: ' . $cmd);
passthru($cmd);