PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@ -0,0 +1,38 @@
<?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\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

@ -0,0 +1,125 @@
<?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\CronArchive;
use Piwik\Plugin\ConsoleCommand;
use Piwik\Site;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CoreArchiver extends ConsoleCommand
{
protected function configure()
{
$this->configureArchiveCommand($this);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$archiver = self::makeArchiver($input->getOption('url'), $input);
$archiver->main();
}
// 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->maxConcurrentArchivers = $input->getOption('concurrent-archivers');
$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
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 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 feedback@matomo.org
* Enjoy!");
$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('concurrent-archivers', null, InputOption::VALUE_OPTIONAL,
"The number of max archivers to run in parallel. Depending on how you start the archiver as a cronjob, you may need to double the amount of archivers allowed if the same process appears twice in the `ps ex` output.", false);
$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

@ -0,0 +1,66 @@
<?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\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 DevelopmentManageTestFiles extends ConsoleCommand
{
public function isEnabled()
{
return Development::isEnabled();
}
protected function configure()
{
$this->setName('development:test-files');
$this->setDescription("Manage test files.");
$this->addArgument('operation', InputArgument::REQUIRED, 'The operation to apply. Supported operations include: '
. '"copy"');
$this->addOption('file', null, InputOption::VALUE_REQUIRED, "The file (or files) to apply the operation to.");
// TODO: allow copying by regex pattern
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$operation = $input->getArgument('operation');
if ($operation == 'copy') {
$this->copy($input, $output);
} else {
throw new \Exception("Invalid operation '$operation'.");
}
}
private function copy($input, $output)
{
$file = $input->getOption('file');
$prefix = PIWIK_INCLUDE_PATH . '/tests/PHPUnit/System/processed/';
$guesses = array(
'/' . $file,
$prefix . $file,
$prefix . $file . '.xml'
);
foreach ($guesses as $guess) {
if (is_file($guess)) {
$file = $guess;
}
}
copy($file, PIWIK_INCLUDE_PATH . '/tests/PHPUnit/System/expected/' . basename($file));
}
}

View File

@ -0,0 +1,149 @@
<?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\Filesystem;
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
{
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 local processed directories');
$this->addArgument('buildnumber', InputArgument::REQUIRED, 'Travis build number you want to sync, eg "14820".');
$this->addOption('expected', 'e', InputOption::VALUE_NONE, 'If given file will be copied in expected directories instead of processed');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->updateCoreFiles($input, $output);
$this->updatePluginsFiles($input, $output);
}
protected function updateCoreFiles(InputInterface $input, OutputInterface $output)
{
$buildNumber = $input->getArgument('buildnumber');
$expected = $input->getOption('expected');
$targetDir = sprintf(PIWIK_INCLUDE_PATH . '/tests/PHPUnit/System/%s', $expected ? 'expected' : 'processed');
$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('https://builds-artifacts.matomo.org/matomo-org/matomo/%s', $filename);
$tests = Http::sendHttpRequest($urlBase, $timeout = 120);
$tarFile = $tmpDir . $filename;
file_put_contents($tarFile, $tests);
$tar = new Tar($tarFile, 'bz2');
if ($tar->extract($targetDir)) {
$this->writeSuccessMessage($output, array(
'All processed system test results were copied to <comment>' . $targetDir . '</comment>',
'Compare them with the expected test results and commit them if needed.'
));
} else {
$output->write('<error>' . $tar->errorInfo() . '.</error>');
$output->writeln('<error> Check that you have the PHP bz2 extension installed and try again.');
}
unlink($tarFile);
}
protected function updatePluginsFiles(InputInterface $input, OutputInterface $output)
{
$buildNumber = $input->getArgument('buildnumber');
$expected = $input->getOption('expected');
$targetDir = sprintf(PIWIK_INCLUDE_PATH . '/plugins/%%s/tests/System/%s/', $expected ? 'expected' : 'processed');
$tmpDir = StaticContainer::get('path.tmp') . '/';
if (Common::stringEndsWith($buildNumber, '.1')) {
// eg make '14820.1' to '14820' to be backwards compatible
$buildNumber = substr($buildNumber, 0, -2);
}
$filename = sprintf('system.plugin.%s.tar.bz2', $buildNumber);
$urlBase = sprintf('https://builds-artifacts.matomo.org/matomo-org/matomo/%s', $filename);
$tests = Http::sendHttpRequest($urlBase, $timeout = 120);
$tarFile = $tmpDir . $filename;
file_put_contents($tarFile, $tests);
$tar = new Tar($tarFile, 'bz2');
$extractionTarget = $tmpDir . '/artifacts';
Filesystem::mkdir($extractionTarget);
$success = $tar->extract($extractionTarget);
if (! $success) {
$output->write('<error>' . $tar->errorInfo() . '.</error>');
$output->writeln('<error> Check that you have the PHP bz2 extension installed and try again.');
unlink($tarFile);
return;
}
$artifacts = Filesystem::globr($extractionTarget, '*~~*');
foreach($artifacts as $artifact) {
$artifactName = basename($artifact);
list($plugin, $file) = explode('~~', $artifactName);
$pluginTargetDir = sprintf($targetDir, $plugin);
Filesystem::mkdir($pluginTargetDir);
Filesystem::copy($artifact, $pluginTargetDir . $file);
}
Filesystem::unlinkRecursive($extractionTarget, true);
$this->writeSuccessMessage($output, array(
'All processed plugin system test results were copied to <comment>' . $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,75 @@
<?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\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GenerateAngularComponent extends GenerateAngularConstructBase
{
protected function configure()
{
$this->setName('generate:angular-component')
->setDescription('Generates a template for an AngularJS component')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin')
->addOption('component', null, InputOption::VALUE_REQUIRED, 'The name of the component you want to create.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$component = $this->getConstructName($input, $output, $optionName = 'component', $constructType = 'component');
$pluginPath = $this->getPluginPath($pluginName);
$componentLower = $this->getSnakeCaseName($component);
$targetDir = $pluginPath . '/angularjs/' . $componentLower;
if (is_dir($targetDir) || file_exists($targetDir)) {
throw new \Exception('The AngularJS component ' . $componentLower . ' already exists in plugin '
. $pluginName);
}
$exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array(
'ExamplePlugin' => $pluginName,
'example-component' => $componentLower,
'componentClass' => lcfirst($component),
'componentAs' => lcfirst($component),
'Component' => $component,
);
$componentPath = '/angularjs/example-component';
$whitelistFiles = array(
'/angularjs',
$componentPath,
$componentPath . '/example-component.component.html',
$componentPath . '/example-component.component.js',
$componentPath . '/example-component.component.less',
);
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$replacedBasePath = '/angularjs/' . $componentLower . '/' . $componentLower;
$js1 = $replacedBasePath . '.component.js';
$less1 = $replacedBasePath . '.component.less';
$this->writeSuccessMessage($output, array(
sprintf('AngularJS directive "%s" for plugin "%s" in "%s" generated', $component, $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>', $pluginPath, $js1),
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>https://developer.matomo.org/guides/working-with-piwiks-ui</comment>'
));
}
}

View File

@ -0,0 +1,72 @@
<?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\Output\OutputInterface;
abstract class GenerateAngularConstructBase extends GeneratePluginBase
{
/**
* Convert MyComponentName => my-component-name
* @param string $directiveCamelCase
* @return string
*/
protected function getSnakeCaseName($camelCase)
{
return strtolower(preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1-', $camelCase));
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $optionName the name of the option to use.
* @param string $constructType 'directive', 'component', etc.
* @return string
* @throws \RuntimeException
*/
protected function getConstructName(InputInterface $input, OutputInterface $output, $optionName, $constructType)
{
$testname = $input->getOption($optionName);
$validate = function ($testname) use ($constructType) {
if (empty($testname)) {
throw new \InvalidArgumentException("You have to enter a name for the $constructType");
}
if (!ctype_alnum($testname)) {
throw new \InvalidArgumentException("Only alphanumeric characters are allowed as a $constructType "
. "name. Use CamelCase if the name of your $constructType contains multiple words.");
}
return $testname;
};
if (empty($testname)) {
$dialog = $this->getHelperSet()->get('dialog');
$testname = $dialog->askAndValidate($output, "Enter the name of the $constructType 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

@ -0,0 +1,79 @@
<?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\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateAngularDirective extends GenerateAngularConstructBase
{
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->getConstructName($input, $output, $optionName = 'directive', $constructType = 'directive');
$pluginPath = $this->getPluginPath($pluginName);
$directiveLower = $this->getSnakeCaseName($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 = Manager::getPluginDirectory('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>https://developer.matomo.org/guides/working-with-piwiks-ui</comment>'
));
}
}

View File

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

View File

@ -0,0 +1,60 @@
<?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\Manager;
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 = Manager::getPluginDirectory('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

@ -0,0 +1,105 @@
<?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\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateCommand extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:command')
->setDescription('Adds a command to an existing plugin')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin')
->addOption('command', null, InputOption::VALUE_REQUIRED, 'The name of the command you want to create');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$commandName = $this->getCommandName($input, $output);
$exampleFolder = Manager::getPluginDirectory('ExampleCommand');
$replace = array(
'ExampleCommandDescription' => $commandName,
'ExampleCommand' => $pluginName,
'examplecommand:helloworld' => strtolower($pluginName) . ':' . $this->buildCommandName($commandName),
'examplecommand' => strtolower($pluginName),
'HelloWorld' => $commandName,
'helloworld' => strtolower($commandName)
);
$whitelistFiles = array('/Commands', '/Commands/HelloWorld.php');
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('Command %s for plugin %s generated', $commandName, $pluginName)
));
}
/**
* 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
*/
private function getCommandName(InputInterface $input, OutputInterface $output)
{
$testname = $input->getOption('command');
$validate = function ($testname) {
if (empty($testname)) {
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 (CamelCase): ', $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

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

View File

@ -0,0 +1,260 @@
<?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\Manager;
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 = Manager::getPluginDirectory('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,60 @@
<?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\Manager;
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 = Manager::getPluginDirectory('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

@ -0,0 +1,215 @@
<?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\Plugins\ExamplePlugin\ExamplePlugin;
use Piwik\Plugin;
use Piwik\Version;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GeneratePlugin extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:plugin')
->setAliases(array('generate:theme'))
->setDescription('Generates a new plugin/theme including all needed files')
->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('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);
$this->generatePluginFolder($pluginName);
$plugin = new ExamplePlugin();
$info = $plugin->getInformation();
$exampleDescription = $info['description'];
if ($isTheme) {
$exampleFolder = Plugin\Manager::getPluginDirectory('ExampleTheme');
$replace = array(
'ExampleTheme' => $pluginName,
$exampleDescription => $description,
'0.1.0' => $version,
'3.0.0-b1' => Version::VERSION
);
$whitelistFiles = array();
} else {
$exampleFolder = Plugin\Manager::getPluginDirectory('ExamplePlugin');
$replace = array(
'ExamplePlugin' => $pluginName,
$exampleDescription => $description,
'0.1.0' => $version,
'3.0.0-b1' => Version::VERSION
);
$whitelistFiles = array(
'/ExamplePlugin.php',
'/plugin.json',
'/README.md',
'/CHANGELOG.md',
'/screenshots',
'/screenshots/.gitkeep',
'/docs',
'/docs/faq.md',
'/docs/index.md',
);
}
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, new NullOutput());
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>https://developer.matomo.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>https://developer.matomo.org/guides</comment>',
'To see a list of available generators execute <comment>./console list generate</comment>',
'Enjoy!'
));
}
}
/**
* @param InputInterface $input
* @return bool
*/
private function isTheme(InputInterface $input)
{
$commandName = $input->getFirstArgument();
return false !== strpos($commandName, 'theme');
}
protected function generatePluginFolder($pluginName)
{
$pluginPath = $this->getPluginPath($pluginName);
Filesystem::mkdir($pluginPath);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output)
{
$overwrite = $input->getOption('overwrite');
$self = $this;
$validate = function ($pluginName) use ($self, $overwrite) {
if (empty($pluginName)) {
throw new \RuntimeException('You have to enter a plugin name');
}
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)
&& !$overwrite
) {
throw new \RuntimeException('A plugin with this name already exists');
}
return $pluginName;
};
$pluginName = $input->getOption('name');
if (empty($pluginName)) {
$dialog = $this->getHelperSet()->get('dialog');
$pluginName = $dialog->askAndValidate($output, 'Enter a plugin name: ', $validate);
} else {
$validate($pluginName);
}
$pluginName = ucfirst($pluginName);
return $pluginName;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return mixed
* @throws \RuntimeException
*/
protected function getPluginDescription(InputInterface $input, OutputInterface $output)
{
$validate = function ($description) {
if (empty($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.');
}
return $description;
};
$description = $input->getOption('description');
if (empty($description)) {
$dialog = $this->getHelperSet()->get('dialog');
$description = $dialog->askAndValidate($output, 'Enter a plugin description: ', $validate);
} else {
$validate($description);
}
return $description;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string
*/
protected function getPluginVersion(InputInterface $input, OutputInterface $output)
{
$version = $input->getOption('pluginversion');
if (is_null($version)) {
$dialog = $this->getHelperSet()->get('dialog');
$version = $dialog->ask($output, 'Enter a plugin version number (default to 0.1.0): ', '0.1.0');
}
return $version;
}
}

View File

@ -0,0 +1,396 @@
<?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\Development;
use Piwik\Filesystem;
use Piwik\Plugin\ConsoleCommand;
use Piwik\Plugin\Dependency;
use Piwik\Plugin\Manager;
use Piwik\Version;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
abstract class GeneratePluginBase extends ConsoleCommand
{
public function isEnabled()
{
return Development::isEnabled();
}
public function getPluginPath($pluginName)
{
return Manager::getPluginDirectory($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($pluginNameOrCore, $fileName, $content)
{
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
* @param string $translationKey Optional, by default the key will be generated automatically
* @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, $translationKey = '')
{
$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();
}
if (!empty($translationKey)) {
$key = $translationKey;
} else {
$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 = Manager::getPluginDirectory($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;
$nextMajorVersion = (int) substr($piwikVersion, 0, strpos($piwikVersion, '.')) + 1;
$secondPartPiwikVersionRequire = ',<' . $nextMajorVersion . '.0.0-b1';
if (false === strpos($piwikVersion, '-')) {
// see https://github.com/composer/composer/issues/4080 we need to specify -stable otherwise it would match
// $piwikVersion-dev meaning it would also match all pre-released. However, we only want to match a stable
// release
$piwikVersion.= '-stable';
}
$newRequiredVersion = sprintf('>=%s,<%d.0.0-b1', $piwikVersion, $nextMajorVersion);
if (!empty($pluginJson['require']['piwik'])) {
$requiredVersion = trim($pluginJson['require']['piwik']);
if ($requiredVersion === $newRequiredVersion) {
// there is nothing to updated
return;
}
// our generated versions look like ">=2.25.4,<3.0.0-b1".
// We only updated the Piwik version in the first part if the piwik version looks like that or if it has only
// one piwik version defined. In all other cases, eg user uses || etc we do not update it as user has customized
// the piwik version.
foreach (['<>','!=', '<=','==', '^'] as $comparison) {
if (strpos($requiredVersion, $comparison) === 0) {
// user is using custom piwik version require, we do not overwrite anything.
return;
}
}
if (strpos($requiredVersion, '||') !== false || strpos($requiredVersion, ' ') !== false) {
// user is using custom piwik version require, we do not overwrite anything.
return;
}
$requiredPiwikVersions = explode(',', (string) $requiredVersion);
$numRequiredPiwikVersions = count($requiredPiwikVersions);
if ($numRequiredPiwikVersions > 2) {
// user is using custom piwik version require, we do not overwrite anything.
return;
}
if ($numRequiredPiwikVersions === 2 &&
!Common::stringEndsWith($requiredVersion, $secondPartPiwikVersionRequire)) {
// user is using custom piwik version require, we do not overwrite anything
return;
}
// if only one piwik version is defined we update it to make sure it does now specify an upper version limit
$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
* @param array $replace array(key => value) $key will be replaced by $value in all templates
* @param array $whitelistFiles If not empty, only given files/directories will be copied.
* For instance array('/Controller.php', '/templates', '/templates/index.twig')
*/
protected function copyTemplateToPlugin($templateFolder, $pluginName, array $replace = array(), $whitelistFiles = array())
{
$replace['PLUGINNAME'] = $pluginName;
$files = array_merge(
Filesystem::globr($templateFolder, '*'),
// Also copy files starting with . such as .gitignore
Filesystem::globr($templateFolder, '.*')
);
foreach ($files as $file) {
$fileNamePlugin = str_replace($templateFolder, '', $file);
if (!empty($whitelistFiles) && !in_array($fileNamePlugin, $whitelistFiles)) {
continue;
}
if (is_dir($file)) {
$fileNamePlugin = $this->replaceContent($replace, $fileNamePlugin);
$this->createFolderWithinPluginIfNotExists($pluginName, $fileNamePlugin);
} else {
$template = file_get_contents($file);
$template = $this->replaceContent($replace, $template);
$fileNamePlugin = $this->replaceContent($replace, $fileNamePlugin);
$this->createFileWithinPluginIfNotExists($pluginName, $fileNamePlugin, $template);
}
}
}
protected function getPluginNames()
{
$pluginNames = array();
foreach (Manager::getPluginsDirectories() as $pluginsDir) {
$pluginDirs = \_glob($pluginsDir . '*', GLOB_ONLYDIR);
foreach ($pluginDirs as $pluginDir) {
$pluginNames[] = basename($pluginDir);
}
}
return $pluginNames;
}
protected function getPluginNamesHavingNotSpecificFile($filename)
{
$pluginNames = array();
foreach (Manager::getPluginsDirectories() as $pluginsDir) {
$pluginDirs = \_glob($pluginsDir . '*', GLOB_ONLYDIR);
foreach ($pluginDirs as $pluginDir) {
if (!file_exists($pluginDir . '/' . $filename)) {
$pluginNames[] = basename($pluginDir);
}
}
}
return $pluginNames;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string
* @throws \RuntimeException
*/
protected function askPluginNameAndValidate(InputInterface $input, OutputInterface $output, $pluginNames, $invalidArgumentException)
{
$validate = function ($pluginName) use ($pluginNames, $invalidArgumentException) {
if (!in_array($pluginName, $pluginNames)) {
throw new \InvalidArgumentException($invalidArgumentException);
}
return $pluginName;
};
$pluginName = $input->getOption('pluginname');
if (empty($pluginName)) {
$dialog = $this->getHelperSet()->get('dialog');
$pluginName = $dialog->askAndValidate($output, 'Enter the name of your plugin: ', $validate, false, null, $pluginNames);
} else {
$validate($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,308 @@
<?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\Piwik;
use Piwik\Plugin\Manager;
use Piwik\Plugin\Report;
use Piwik\Plugin\ReportsProvider;
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 = Manager::getPluginDirectory('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('plugins/%s/Reports/%s.php for %s generated.', $pluginName, ucfirst($apiName)),
'You should now implement the method called <comment>"' . lcfirst($apiName) . '()"</comment> in API.php',
// 'Read more about this here: link to developer guide',
'Enjoy!'
));
}
private function getOrder($category)
{
$order = 1;
$reports = new ReportsProvider();
foreach ($reports->getAllReports() as $report) {
if ($report->getCategoryId() === $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');
$reports = new ReportsProvider();
$categories = array();
foreach ($reports->getAllReports() as $report) {
if ($report->getCategoryId()) {
$categories[] = Piwik::translate($report->getCategoryId());
}
}
$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();
$reports = new ReportsProvider();
foreach ($reports->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,60 @@
<?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\Manager;
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 = Manager::getPluginDirectory('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

@ -0,0 +1,94 @@
<?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\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateSettings extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:settings')
->setDescription('Adds a SystemSetting, UserSetting or MeasurableSetting class to an existing plugin')
->addOption('pluginname', null, InputOption::VALUE_REQUIRED, 'The name of an existing plugin which does not have settings yet')
->addOption('settingstype', null, InputOption::VALUE_REQUIRED, 'The type of settings you want to create. Should be one of these values: ' . implode(', ', $this->getSettingTypes()));
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$settingsType = $this->getSettingsType($input, $output);
$settingsFilename = $settingsType . '.php';
$pluginName = $this->getPluginName($input, $output, $settingsType, $settingsFilename);
$this->checkAndUpdateRequiredPiwikVersion($pluginName, $output);
$exampleFolder = Manager::getPluginDirectory('ExampleSettingsPlugin');
$replace = array('ExampleSettingsPlugin' => $pluginName);
$whitelistFiles = array('/' . $settingsFilename);
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('%s for %s generated.', $settingsFilename, $pluginName),
'You can now start defining your ' . $settingsType,
'Enjoy!'
));
}
private function getSettingTypes()
{
return array('system', 'user', 'measurable');
}
private function getSettingsType(InputInterface $input, OutputInterface $output)
{
$availableTypes = $this->getSettingTypes();
$validate = function ($type) use ($availableTypes) {
if (empty($type) || !in_array($type, $availableTypes)) {
throw new \InvalidArgumentException('Please enter a valid settings type (' . implode(', ', $availableTypes) . '). ');
}
return $type;
};
$settingsType = $input->getOption('settingstype');
if (empty($settingsType)) {
$dialog = $this->getHelperSet()->get('dialog');
$settingsType = $dialog->askAndValidate($output, 'Please choose the type of settings you want to create (' . implode(', ', $availableTypes) . '): ', $validate, false, null, $availableTypes);
} else {
$validate($settingsType);
}
return ucfirst($settingsType) . 'Settings';
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @param string $settingsType
* @return array
* @throws \RuntimeException
*/
protected function getPluginName(InputInterface $input, OutputInterface $output, $settingsType, $settingsFile)
{
$pluginNames = $this->getPluginNamesHavingNotSpecificFile($settingsFile);
$invalidName = 'You have to enter the name of an existing plugin which does not already have ' . $settingsType;
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View File

@ -0,0 +1,197 @@
<?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\Plugin\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateTest extends GeneratePluginBase
{
protected function configure()
{
$this->setName('generate:test')
->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", "system", or "ui" test');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pluginName = $this->getPluginName($input, $output);
$testType = $this->getTestType($input, $output);
$testName = $this->getTestName($input, $output, $testType);
$exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array(
'ExamplePlugin' => $pluginName,
'SimpleTest' => $testName,
'SimpleSystemTest' => $testName,
'SimpleUITest_spec.js' => $testName . '_spec.js',
'SimpleUITest' => $testName,
);
$whitelistFiles = $this->getTestFilesWhitelist($testType);
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$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>';
}
$messages[] = 'Enjoy!';
$this->writeSuccessMessage($output, $messages);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string
* @throws \RuntimeException
*/
private function getTestName(InputInterface $input, OutputInterface $output, $testType)
{
$testname = $input->getOption('testname');
$validate = function ($testname) {
if (empty($testname)) {
throw new \InvalidArgumentException('You have to enter a valid test name ');
}
return $testname;
};
if (empty($testname)) {
$dialog = $this->getHelperSet()->get('dialog');
$testname = $dialog->askAndValidate($output, 'Enter the name of the test: ', $validate);
} else {
$validate($testname);
}
if (strtolower($testType) !== 'ui' && !Common::stringEndsWith(strtolower($testname), 'test')) {
$testname = $testname . 'Test';
}
$testname = ucfirst($testname);
return $testname;
}
/**
* @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 the name of an existing plugin';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
public function getValidTypes()
{
return array('unit', 'integration', 'system', 'ui');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string Unit, Integration, System
*/
private function getTestType(InputInterface $input, OutputInterface $output)
{
$testtype = $input->getOption('testtype');
$self = $this;
$validate = function ($testtype) use ($self) {
if (empty($testtype) || !in_array($testtype, $self->getValidTypes())) {
throw new \InvalidArgumentException('You have to enter a valid test type: ' . implode(" or ", $self->getValidTypes()));
}
return $testtype;
};
if (empty($testtype)) {
$dialog = $this->getHelperSet()->get('dialog');
$testtype = $dialog->askAndValidate($output, 'Enter the type of the test to generate ('. implode(", ", $this->getValidTypes()).'): ', $validate, false, null, $this->getValidTypes());
} else {
$validate($testtype);
}
$testtype = ucfirst($testtype);
return $testtype;
}
/**
* @return array
*/
protected function getTestFilesWhitelist($testType)
{
if ('Ui' == $testType) {
return array(
'/tests',
'/tests/UI',
'/tests/UI/.gitignore',
'/tests/UI/expected-screenshots',
'/tests/UI/expected-screenshots/.gitkeep',
'/tests/UI/SimpleUITest_spec.js',
);
}
if ('System' == $testType) {
return array(
'/.gitignore',
'/tests',
'/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/Unit',
'/tests/Unit/SimpleTest.php'
);
}
}

View File

@ -0,0 +1,118 @@
<?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 = Plugin\Manager::getPluginDirectory('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, '\\');
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

@ -0,0 +1,96 @@
<?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\Manager;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GenerateVisualizationPlugin extends GeneratePlugin
{
protected function configure()
{
$this->setName('generate:visualizationplugin')
->setDescription('Generates a new visualization plugin including all needed files')
->addOption('name', null, InputOption::VALUE_REQUIRED, 'Plugin name ([a-Z0-9_-])')
->addOption('visualizationname', null, InputOption::VALUE_REQUIRED, 'Visualization name ([a-Z0-9])')
->addOption('description', null, InputOption::VALUE_REQUIRED, 'Plugin description, max 150 characters')
->addOption('pluginversion', null, InputOption::VALUE_OPTIONAL, 'Plugin version')
->addOption('overwrite', null, InputOption::VALUE_NONE, 'Generate even if plugin directory already exists.')
->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.');
}
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);
$this->generatePluginFolder($pluginName);
$exampleFolder = Manager::getPluginDirectory('ExampleVisualization');
$replace = array(
'SimpleTable' => $visualizationName,
'simpleTable' => lcfirst($visualizationName),
'Simple Table' => $this->makeTranslationIfPossible($pluginName, $visualizationName),
'ExampleVisualization' => $pluginName,
'ExampleVisualizationDescription' => $description
);
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles = array());
$this->writeSuccessMessage($output, array(
sprintf('Visualization plugin %s %s generated.', $pluginName, $version),
'Enjoy!'
));
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return string
* @throws \RuntimeException
*/
private function getVisualizationName(InputInterface $input, OutputInterface $output)
{
$self = $this;
$validate = function ($visualizationName) use ($self) {
if (empty($visualizationName)) {
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 (only AlNum allowed)', $visualizationName));
}
return $visualizationName;
};
$visualizationName = $input->getOption('visualizationname');
if (empty($visualizationName)) {
$dialog = $this->getHelperSet()->get('dialog');
$visualizationName = $dialog->askAndValidate($output, 'Enter a visualization name (only AlNum allowed): ', $validate);
} else {
$validate($visualizationName);
}
$visualizationName = ucfirst($visualizationName);
return $visualizationName;
}
}

View File

@ -0,0 +1,175 @@
<?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\Manager;
use Piwik\Translate;
use Piwik\Widget\WidgetsList;
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('widgetname', null, InputOption::VALUE_REQUIRED, 'The name of the widget you want to create')
->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);
$widgetName = $this->getWidgetName($input, $output);
$category = $this->getCategory($input, $output);
if ($category === Piwik::translate($category)) {
// no translation found...
$category = $this->makeTranslationIfPossible($pluginName, $category);
}
$widgetMethod = $this->getWidgetMethodName($widgetName);
$widgetClass = ucfirst($widgetMethod);
$exampleFolder = Manager::getPluginDirectory('ExamplePlugin');
$replace = array('ExamplePlugin' => $pluginName,
'MyExampleWidget' => $widgetClass,
'Example Widget Name' => $this->makeTranslationIfPossible($pluginName, $widgetName),
'About Matomo' => $category);
$whitelistFiles = array('/Widgets', '/Widgets/MyExampleWidget.php');
$this->copyTemplateToPlugin($exampleFolder, $pluginName, $replace, $whitelistFiles);
$this->writeSuccessMessage($output, array(
sprintf('plugins/%s/Widgets/%s.php generated.', $pluginName, $widgetClass),
'You can now start implementing the <comment>render()</comment> method.',
'Enjoy!'
));
}
private function getWidgetMethodName($methodName)
{
$methodName = trim($methodName);
$methodName = str_replace(' ', '', $methodName);
$methodName = preg_replace("/[^A-Za-z0-9]/", '', $methodName);
if (0 !== strpos(strtolower($methodName), 'get')) {
$methodName = 'get' . ucfirst($methodName);
}
return lcfirst($methodName);
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return array
* @throws \RuntimeException
*/
protected function getWidgetName(InputInterface $input, OutputInterface $output)
{
$validate = function ($widgetName) {
if (empty($widgetName)) {
throw new \InvalidArgumentException('Please enter the name of your widget');
}
if (preg_match("/[^A-Za-z0-9 ]/", $widgetName)) {
throw new \InvalidArgumentException('Only alpha numerical characters and whitespaces are allowed');
}
return $widgetName;
};
$widgetName = $input->getOption('widgetname');
if (empty($widgetName)) {
$dialog = $this->getHelperSet()->get('dialog');
$widgetName = $dialog->askAndValidate($output, 'Enter the name of your Widget, for instance "Browser Families": ', $validate);
} else {
$validate($widgetName);
}
$widgetName = ucfirst($widgetName);
return $widgetName;
}
protected function getExistingCategories()
{
$categories = array();
foreach (WidgetsList::get()->getWidgetConfigs() as $widget) {
if ($widget->getCategoryId()) {
$categories[] = Piwik::translate($widget->getCategoryId());
}
}
$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->getPluginNames();
$invalidName = 'You have to enter a name of an existing plugin.';
return $this->askPluginNameAndValidate($input, $output, $pluginNames, $invalidName);
}
}

View File

@ -0,0 +1,148 @@
<?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\Development;
use Piwik\Plugin\ConsoleCommand;
use Piwik\SettingsPiwik;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
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')
->setDescription('Commit')
->addOption('message', 'm', InputOption::VALUE_REQUIRED, 'Commit Message');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$submodules = $this->getSubmodulePaths();
foreach ($submodules as $submodule) {
if (empty($submodule)) {
continue;
}
$status = $this->getStatusOfSubmodule($submodule);
if (false !== strpos($status, '?? ')) {
$output->writeln(sprintf('<error>%s has untracked files or folders. Delete or add them and try again.</error>', $submodule));
$output->writeln('<error>Status:</error>');
$output->writeln(sprintf('<comment>%s</comment>', $status));
return;
}
}
$commitMessage = $input->getOption('message');
if (empty($commitMessage)) {
$output->writeln('No message specified. Use option -m or --message.');
return;
}
if (!$this->hasChangesToBeCommitted()) {
$dialog = $this->getHelperSet()->get('dialog');
$question = '<question>There are no changes to be committed in the super repo, do you just want to commit and converge submodules?</question>';
if (!$dialog->askConfirmation($output, $question, false)) {
$output->writeln('<info>Cool, nothing done. Stage files using "git add" and try again.</info>');
return;
}
}
foreach ($submodules as $submodule) {
if (empty($submodule)) {
continue;
}
$status = $this->getStatusOfSubmodule($submodule);
if (empty($status)) {
$output->writeln(sprintf('%s has no changes, will ignore', $submodule));
continue;
}
$cmd = sprintf('cd %s/%s && git pull && git add . && git commit -am "%s"', PIWIK_DOCUMENT_ROOT, $submodule, $commitMessage);
$this->passthru($cmd, $output);
}
if ($this->hasChangesToBeCommitted()) {
$cmd = sprintf('cd %s && git commit -m "%s"', PIWIK_DOCUMENT_ROOT, $commitMessage);
$this->passthru($cmd, $output);
}
foreach ($submodules as $submodule) {
if (empty($submodule)) {
continue;
}
$cmd = sprintf('cd %s && git add %s', PIWIK_DOCUMENT_ROOT, $submodule);
$this->passthru($cmd, $output);
}
if ($this->hasChangesToBeCommitted()) {
$cmd = sprintf('cd %s && git commit -m "Updating submodules"', PIWIK_DOCUMENT_ROOT);
$this->passthru($cmd, $output);
}
}
private function passthru($cmd, OutputInterface $output)
{
$output->writeln('Executing command: ' . $cmd);
passthru($cmd);
}
private function hasChangesToBeCommitted()
{
$cmd = sprintf('cd %s && git status --porcelain', PIWIK_DOCUMENT_ROOT);
$result = shell_exec($cmd);
$result = trim($result);
if (false !== strpos($result, 'M ')) {
// stages
return true;
}
if (false !== strpos($result, 'MM ')) {
// staged and modified
return true;
}
return false;
}
/**
* @return array
*/
private function getSubmodulePaths()
{
$cmd = sprintf("grep path .gitmodules | sed 's/.*= //'");
$submodules = shell_exec($cmd);
$submodules = explode("\n", $submodules);
return $submodules;
}
protected function getStatusOfSubmodule($submodule)
{
$cmd = sprintf('cd %s/%s && git status --porcelain', PIWIK_DOCUMENT_ROOT, $submodule);
$status = trim(shell_exec($cmd));
return $status;
}
}

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 Piwik\Plugin\ConsoleCommand;
use Piwik\SettingsPiwik;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class GitPull extends ConsoleCommand
{
public function isEnabled()
{
return SettingsPiwik::isGitDeployment();
}
protected function configure()
{
$this->setName('git:pull');
$this->setDescription('Pull Piwik repo and all submodules');
}
protected function getBranchName()
{
$cmd = sprintf('cd %s && git rev-parse --abbrev-ref HEAD', PIWIK_DOCUMENT_ROOT);
$branch = shell_exec($cmd);
return trim($branch);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if ('master' != $this->getBranchName()) {
$output->writeln('<info>Doing nothing because you are not on the master branch in super repo.</info>');
return;
}
$cmd = sprintf('cd %s && git checkout master && git pull && git submodule update --init --recursive --remote', PIWIK_DOCUMENT_ROOT);
$this->passthru($cmd, $output);
$cmd = 'git submodule foreach "(git checkout master; git pull)&"';
$this->passthru($cmd, $output);
}
private function passthru($cmd, OutputInterface $output)
{
$output->writeln('Executing command: ' . $cmd);
passthru($cmd);
}
}

View File

@ -0,0 +1,48 @@
<?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\Development;
use Piwik\Plugin\ConsoleCommand;
use Piwik\SettingsPiwik;
use Symfony\Component\Console\Input\InputInterface;
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');
$this->setDescription('Push Piwik repo and all submodules');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$cmd = sprintf('cd %s && git push --recurse-submodules=on-demand', PIWIK_DOCUMENT_ROOT);
$output->writeln('Executing command: ' . $cmd);
passthru($cmd);
}
private function hasUnpushedCommits()
{
$cmd = sprintf('cd %s && git log @{u}..',PIWIK_DOCUMENT_ROOT);
$hasUnpushedCommits = shell_exec($cmd);
$hasUnpushedCommits = trim($hasUnpushedCommits);
return !empty($hasUnpushedCommits);
}
}

View File

@ -0,0 +1,35 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Plugins\CoreConsole\Commands;
use Piwik\Container\StaticContainer;
use Piwik\Plugin\ConsoleCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
*/
class WatchLog extends ConsoleCommand
{
protected function configure()
{
$this->setName('log:watch');
$this->setDescription('Outputs the last parts of the log files and follows as the log file grows. Does not work on Windows');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$path = StaticContainer::get('path.tmp') . '/logs/';
$cmd = sprintf('tail -f %s*.log', $path);
$output->writeln('Executing command: ' . $cmd);
passthru($cmd);
}
}