看板初始化提交
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Pimple\Container;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
/**
|
||||
* Base command class
|
||||
*
|
||||
* @package console
|
||||
* @author Frederic Guillot
|
||||
*
|
||||
* @property \PicoDb\Database $db
|
||||
* @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator
|
||||
* @property \Kanboard\Export\SubtaskExport $subtaskExport
|
||||
* @property \Kanboard\Export\TaskExport $taskExport
|
||||
* @property \Kanboard\Export\TransitionExport $transitionExport
|
||||
* @property \Kanboard\Model\NotificationModel $notificationModel
|
||||
* @property \Kanboard\Model\ProjectModel $projectModel
|
||||
* @property \Kanboard\Model\ProjectActivityModel $projectActivityModel
|
||||
* @property \Kanboard\Model\ProjectPermissionModel $projectPermissionModel
|
||||
* @property \Kanboard\Model\ProjectDailyColumnStatsModel $projectDailyColumnStatsModel
|
||||
* @property \Kanboard\Model\ProjectDailyStatsModel $projectDailyStatsModel
|
||||
* @property \Kanboard\Model\TaskModel $taskModel
|
||||
* @property \Kanboard\Model\TaskFinderModel $taskFinderModel
|
||||
* @property \Kanboard\Model\UserModel $userModel
|
||||
* @property \Kanboard\Model\UserNotificationModel $userNotificationModel
|
||||
* @property \Kanboard\Model\UserNotificationFilterModel $userNotificationFilterModel
|
||||
* @property \Kanboard\Model\ProjectUserRoleModel $projectUserRoleModel
|
||||
* @property \Kanboard\Core\Plugin\Loader $pluginLoader
|
||||
* @property \Kanboard\Core\Http\Client $httpClient
|
||||
* @property \Kanboard\Core\Queue\QueueManager $queueManager
|
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
*/
|
||||
abstract class BaseCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Container instance
|
||||
*
|
||||
* @access protected
|
||||
* @var \Pimple\Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param \Pimple\Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load automatically models
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Model name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->container[$name];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
|
||||
class CronjobCommand extends BaseCommand
|
||||
{
|
||||
private $commands = array(
|
||||
'projects:daily-stats',
|
||||
'notification:overdue-tasks',
|
||||
'trigger:tasks',
|
||||
);
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('cronjob')
|
||||
->setDescription('Execute daily cronjob');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
foreach ($this->commands as $command) {
|
||||
$job = $this->getApplication()->find($command);
|
||||
$job->run(new ArrayInput(array('command' => $command)), new NullOutput());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use MatthiasMullie\Minify;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
$path = __DIR__ . '/../../libs';
|
||||
require_once $path . '/minify/src/Minify.php';
|
||||
require_once $path . '/minify/src/CSS.php';
|
||||
require_once $path . '/minify/src/JS.php';
|
||||
require_once $path . '/minify/src/Exception.php';
|
||||
require_once $path . '/minify/src/Exceptions/BasicException.php';
|
||||
require_once $path . '/minify/src/Exceptions/FileImportException.php';
|
||||
require_once $path . '/minify/src/Exceptions/IOException.php';
|
||||
require_once $path . '/path-converter/src/ConverterInterface.php';
|
||||
require_once $path . '/path-converter/src/Converter.php';
|
||||
|
||||
/**
|
||||
* Class CssCommand
|
||||
*
|
||||
* @package Kanboard\Console
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class CssCommand extends BaseCommand
|
||||
{
|
||||
const CSS_SRC_PATH = 'assets/css/src/';
|
||||
const CSS_VENDOR_PATH = 'assets/vendor/';
|
||||
const CSS_DIST_PATH = 'assets/css/';
|
||||
|
||||
private $appFiles = [
|
||||
'base.css',
|
||||
'links.css',
|
||||
'titles.css',
|
||||
'table.css',
|
||||
'table_drag_and_drop.css',
|
||||
'table_list.css',
|
||||
'form.css',
|
||||
'input_addon.css',
|
||||
'icon.css',
|
||||
'alert.css',
|
||||
'button.css',
|
||||
'tooltip.css',
|
||||
'dropdown.css',
|
||||
'accordion.css',
|
||||
'select_dropdown.css',
|
||||
'suggest_menu.css',
|
||||
'modal.css',
|
||||
'pagination.css',
|
||||
'header.css',
|
||||
'logo.css',
|
||||
'page_header.css',
|
||||
'sidebar.css',
|
||||
'avatar.css',
|
||||
'file_upload.css',
|
||||
'thumbnails.css',
|
||||
'color_picker.css',
|
||||
'filter_box.css',
|
||||
'project.css',
|
||||
'views.css',
|
||||
'dashboard.css',
|
||||
'board.css',
|
||||
'task_board.css',
|
||||
'task_icons.css',
|
||||
'task_category.css',
|
||||
'task_date.css',
|
||||
'task_tags.css',
|
||||
'task_summary.css',
|
||||
'task_form.css',
|
||||
'comment.css',
|
||||
'subtasks.css',
|
||||
'task_list.css',
|
||||
'task_links.css',
|
||||
'text_editor.css',
|
||||
'markdown.css',
|
||||
'panel.css',
|
||||
'activity_stream.css',
|
||||
'user_mention.css',
|
||||
'slideshow.css',
|
||||
'list_items.css',
|
||||
'bulk_change.css',
|
||||
];
|
||||
|
||||
private $printFiles = [
|
||||
'print.css',
|
||||
];
|
||||
|
||||
private $vendorFiles = [
|
||||
self::CSS_VENDOR_PATH.'jquery-ui/jquery-ui.min.css',
|
||||
self::CSS_VENDOR_PATH.'jqueryui-timepicker-addon/jquery-ui-timepicker-addon.min.css',
|
||||
self::CSS_VENDOR_PATH.'select2/css/select2.min.css',
|
||||
self::CSS_VENDOR_PATH.'font-awesome/css/font-awesome.min.css',
|
||||
self::CSS_VENDOR_PATH.'c3/c3.min.css',
|
||||
];
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('css')
|
||||
->setDescription('Minify CSS files')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->minifyFiles(self::CSS_SRC_PATH, array_merge(['themes'.DIRECTORY_SEPARATOR.'light.css'], $this->appFiles), 'light.min.css');
|
||||
$this->minifyFiles(self::CSS_SRC_PATH, array_merge(['themes'.DIRECTORY_SEPARATOR.'dark.css'], $this->appFiles), 'dark.min.css');
|
||||
$this->minifyFiles(self::CSS_SRC_PATH, array_merge(['themes'.DIRECTORY_SEPARATOR.'auto.css'], $this->appFiles), 'auto.min.css');
|
||||
$this->minifyFiles(self::CSS_SRC_PATH, $this->printFiles, 'print.min.css');
|
||||
|
||||
$vendorBundle = concat_files($this->vendorFiles);
|
||||
file_put_contents('assets/css/vendor.min.css', $vendorBundle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function minifyFiles($folder, array $files, $destination)
|
||||
{
|
||||
$minifier = new Minify\CSS();
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filename = $folder.$file;
|
||||
if (! file_exists($filename)) {
|
||||
die("$filename not found\n");
|
||||
}
|
||||
$minifier->add($filename);
|
||||
}
|
||||
|
||||
$minifier->minify(self::CSS_DIST_PATH . $destination);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\ServiceProvider\DatabaseProvider;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class DatabaseMigrationCommand extends DatabaseVersionCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('db:migrate')
|
||||
->setDescription('Execute SQL migrations');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
parent::execute($input, $output);
|
||||
DatabaseProvider::runMigrations($this->container['db']);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\ServiceProvider\DatabaseProvider;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class DatabaseVersionCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('db:version')
|
||||
->setDescription('Show database schema version');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln('<info>Current version: '.DatabaseProvider::getSchemaVersion($this->container['db']).'</info>');
|
||||
$output->writeln('<info>Last version: '.\Schema\VERSION.'</info>');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Queue\JobHandler;
|
||||
use SimpleQueue\Job;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class JobCommand
|
||||
*
|
||||
* @package Kanboard\Console
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class JobCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('job')
|
||||
->setDescription('Execute individual job (read payload from stdin)')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$payload = fgets(STDIN);
|
||||
|
||||
$job = new Job();
|
||||
$job->unserialize($payload);
|
||||
|
||||
JobHandler::getInstance($this->container)->executeJob($job);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use MatthiasMullie\Minify;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
$path = __DIR__ . '/../../libs';
|
||||
require_once $path . '/minify/src/Minify.php';
|
||||
require_once $path . '/minify/src/CSS.php';
|
||||
require_once $path . '/minify/src/JS.php';
|
||||
require_once $path . '/minify/src/Exception.php';
|
||||
require_once $path . '/minify/src/Exceptions/BasicException.php';
|
||||
require_once $path . '/minify/src/Exceptions/FileImportException.php';
|
||||
require_once $path . '/minify/src/Exceptions/IOException.php';
|
||||
require_once $path . '/path-converter/src/ConverterInterface.php';
|
||||
require_once $path . '/path-converter/src/Converter.php';
|
||||
|
||||
/**
|
||||
* Class JsCommand
|
||||
*
|
||||
* @package Kanboard\Console
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class JsCommand extends BaseCommand
|
||||
{
|
||||
const CSS_DIST_PATH = 'assets/js/';
|
||||
|
||||
private $appFiles = [
|
||||
'assets/vendor/text-caret/index.js',
|
||||
'assets/js/polyfills/*.js',
|
||||
'assets/js/core/base.js',
|
||||
'assets/js/core/dom.js',
|
||||
'assets/js/core/html.js',
|
||||
'assets/js/core/http.js',
|
||||
'assets/js/core/modal.js',
|
||||
'assets/js/core/tooltip.js',
|
||||
'assets/js/core/utils.js',
|
||||
'assets/js/components/*.js',
|
||||
'assets/js/core/bootstrap.js',
|
||||
'assets/js/src/Namespace.js',
|
||||
'assets/js/src/App.js',
|
||||
'assets/js/src/BoardCollapsedMode.js',
|
||||
'assets/js/src/BoardColumnView.js',
|
||||
'assets/js/src/BoardHorizontalScrolling.js',
|
||||
'assets/js/src/BoardPolling.js',
|
||||
'assets/js/src/BoardVerticalScrolling.js',
|
||||
'assets/js/src/Column.js',
|
||||
'assets/js/src/Dropdown.js',
|
||||
'assets/js/src/Search.js',
|
||||
'assets/js/src/Swimlane.js',
|
||||
'assets/js/src/Task.js',
|
||||
'assets/js/src/BoardDragAndDrop.js',
|
||||
'assets/js/src/Bootstrap.js'
|
||||
];
|
||||
|
||||
private $vendorFiles = [
|
||||
'assets/vendor/jquery/jquery-3.6.1.min.js',
|
||||
'assets/vendor/jquery-ui/jquery-ui.min.js',
|
||||
'assets/vendor/jquery-ui/i18n/datepicker-*.js',
|
||||
'assets/vendor/jqueryui-timepicker-addon/jquery-ui-timepicker-addon.min.js',
|
||||
'assets/vendor/jqueryui-timepicker-addon/i18n/jquery-ui-timepicker-addon-i18n.min.js',
|
||||
'assets/vendor/jqueryui-touch-punch/jquery.ui.touch-punch.min.js',
|
||||
'assets/vendor/select2/js/select2.min.js',
|
||||
'assets/vendor/select2/js/i18n/*.js',
|
||||
'assets/vendor/d3/d3.min.js',
|
||||
'assets/vendor/c3/c3.min.js',
|
||||
'assets/vendor/isMobile/isMobile.min.js',
|
||||
];
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('js')
|
||||
->setDescription('Minify Javascript files')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$appBundle = concat_files($this->appFiles);
|
||||
$vendorBundle = concat_files($this->vendorFiles);
|
||||
|
||||
$minifier = new Minify\JS($appBundle);
|
||||
|
||||
file_put_contents('assets/js/app.min.js', $minifier->minify());
|
||||
file_put_contents('assets/js/vendor.min.js', $vendorBundle);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use RecursiveIteratorIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LocaleComparatorCommand extends BaseCommand
|
||||
{
|
||||
const REF_LOCALE = 'fr_FR';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('locale:compare')
|
||||
->setDescription('Compare application translations with the '.self::REF_LOCALE.' locale');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$strings = array();
|
||||
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(APP_DIR));
|
||||
$it->rewind();
|
||||
|
||||
while ($it->valid()) {
|
||||
if (! $it->isDot() && substr($it->key(), -4) === '.php') {
|
||||
$strings = array_merge($strings, $this->search($it->key()));
|
||||
}
|
||||
|
||||
$it->next();
|
||||
}
|
||||
|
||||
$this->compare(array_unique($strings));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function show(array $strings)
|
||||
{
|
||||
foreach ($strings as $string) {
|
||||
echo " '".str_replace("'", "\'", $string)."' => '',".PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
public function compare(array $strings)
|
||||
{
|
||||
$reference_file = APP_DIR.DIRECTORY_SEPARATOR.'Locale'.DIRECTORY_SEPARATOR.self::REF_LOCALE.DIRECTORY_SEPARATOR.'translations.php';
|
||||
$reference = include $reference_file;
|
||||
|
||||
echo str_repeat('#', 70).PHP_EOL;
|
||||
echo 'MISSING STRINGS'.PHP_EOL;
|
||||
echo str_repeat('#', 70).PHP_EOL;
|
||||
$this->show(array_diff($strings, array_keys($reference)));
|
||||
|
||||
echo str_repeat('#', 70).PHP_EOL;
|
||||
echo 'USELESS STRINGS'.PHP_EOL;
|
||||
echo str_repeat('#', 70).PHP_EOL;
|
||||
$this->show(array_diff(array_keys($reference), $strings));
|
||||
}
|
||||
|
||||
public function search($filename)
|
||||
{
|
||||
$content = file_get_contents($filename);
|
||||
$strings = array();
|
||||
|
||||
if (preg_match_all('/\b[et]\s*\(\s*(\'\K.*?\')\s*[\)\,]/', $content, $matches) && isset($matches[1])) {
|
||||
$strings = $matches[1];
|
||||
}
|
||||
|
||||
if (preg_match_all('/\bdt\s*\(\s*(\'\K.*?\')\s*[\)\,]/', $content, $matches) && isset($matches[1])) {
|
||||
$strings = array_merge($strings, $matches[1]);
|
||||
}
|
||||
|
||||
array_walk($strings, function (&$value) {
|
||||
$value = trim($value, "'");
|
||||
$value = str_replace("\'", "'", $value);
|
||||
});
|
||||
|
||||
return $strings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LocaleSyncCommand extends BaseCommand
|
||||
{
|
||||
const REF_LOCALE = 'fr_FR';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('locale:sync')
|
||||
->setDescription('Synchronize all translations based on the '.self::REF_LOCALE.' locale');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$reference_file = APP_DIR.DIRECTORY_SEPARATOR.'Locale'.DIRECTORY_SEPARATOR.self::REF_LOCALE.DIRECTORY_SEPARATOR.'translations.php';
|
||||
$reference = include $reference_file;
|
||||
|
||||
foreach (new DirectoryIterator(APP_DIR.DIRECTORY_SEPARATOR.'Locale') as $fileInfo) {
|
||||
if (! $fileInfo->isDot() && $fileInfo->isDir() && $fileInfo->getFilename() !== self::REF_LOCALE) {
|
||||
$filename = APP_DIR.DIRECTORY_SEPARATOR.'Locale'.DIRECTORY_SEPARATOR.$fileInfo->getFilename().DIRECTORY_SEPARATOR.'translations.php';
|
||||
echo $fileInfo->getFilename().' ('.$filename.')'.PHP_EOL;
|
||||
|
||||
file_put_contents($filename, $this->updateFile($reference, $filename));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function updateFile(array $reference, $outdated_file)
|
||||
{
|
||||
$outdated = include $outdated_file;
|
||||
|
||||
$output = '<?php'.PHP_EOL.PHP_EOL;
|
||||
$output .= 'return ['.PHP_EOL;
|
||||
|
||||
foreach ($reference as $key => $value) {
|
||||
$escapedKey = str_replace("'", "\'", $key);
|
||||
if (! empty($outdated[$key])) {
|
||||
$output .= " '".$escapedKey."' => '".str_replace("'", "\'", $outdated[$key])."',\n";
|
||||
} else {
|
||||
$output .= " // '".$escapedKey."' => '',\n";
|
||||
}
|
||||
}
|
||||
|
||||
$output .= "];\n";
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Plugin\Installer;
|
||||
use Kanboard\Core\Plugin\PluginInstallerException;
|
||||
use LogicException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class PluginInstallCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('plugin:install')
|
||||
->setDescription('Install a plugin from a remote Zip archive')
|
||||
->addArgument('url', InputArgument::REQUIRED, 'Archive URL');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (!Installer::isConfigured()) {
|
||||
throw new LogicException('Kanboard is not configured to install plugins itself');
|
||||
}
|
||||
|
||||
try {
|
||||
$installer = new Installer($this->container);
|
||||
$installer->install($input->getArgument('url'));
|
||||
$output->writeln('<info>Plugin installed successfully</info>');
|
||||
return 0;
|
||||
} catch (PluginInstallerException $e) {
|
||||
$output->writeln('<error>'.$e->getMessage().'</error>');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Plugin\Installer;
|
||||
use Kanboard\Core\Plugin\PluginInstallerException;
|
||||
use LogicException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class PluginUninstallCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('plugin:uninstall')
|
||||
->setDescription('Remove a plugin')
|
||||
->addArgument('pluginId', InputArgument::REQUIRED, 'Plugin directory name');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (!Installer::isConfigured()) {
|
||||
throw new LogicException('Kanboard is not configured to install plugins itself');
|
||||
}
|
||||
|
||||
try {
|
||||
$installer = new Installer($this->container);
|
||||
$installer->uninstall($input->getArgument('pluginId'));
|
||||
$output->writeln('<info>Plugin removed successfully</info>');
|
||||
return 0;
|
||||
} catch (PluginInstallerException $e) {
|
||||
$output->writeln('<error>'.$e->getMessage().'</error>');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Plugin\Base as BasePlugin;
|
||||
use Kanboard\Core\Plugin\Directory;
|
||||
use Kanboard\Core\Plugin\Installer;
|
||||
use LogicException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class PluginUpgradeCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('plugin:upgrade')
|
||||
->setDescription('Update all installed plugins')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (!Installer::isConfigured()) {
|
||||
throw new LogicException('Kanboard is not configured to install plugins itself');
|
||||
}
|
||||
|
||||
$installer = new Installer($this->container);
|
||||
$availablePlugins = Directory::getInstance($this->container)->getAvailablePlugins();
|
||||
|
||||
foreach ($this->pluginLoader->getPlugins() as $installedPlugin) {
|
||||
$pluginDetails = $this->getPluginDetails($availablePlugins, $installedPlugin);
|
||||
|
||||
if ($pluginDetails === null) {
|
||||
$output->writeln('<error>* Plugin not available in the directory: '.$installedPlugin->getPluginName().'</error>');
|
||||
} elseif ($pluginDetails['version'] > $installedPlugin->getPluginVersion()) {
|
||||
$output->writeln('<comment>* Updating plugin: '.$installedPlugin->getPluginName().'</comment>');
|
||||
$installer->update($pluginDetails['download']);
|
||||
} else {
|
||||
$output->writeln('<info>* Plugin up to date: '.$installedPlugin->getPluginName().'</info>');
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function getPluginDetails(array $availablePlugins, BasePlugin $installedPlugin)
|
||||
{
|
||||
foreach ($availablePlugins as $availablePluginName => $availablePlugin) {
|
||||
if ($availablePluginName === $installedPlugin->getPluginName()) {
|
||||
return $availablePlugin;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ProjectActivityArchiveCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('projects:archive-activities')
|
||||
->setDescription('Remove project activities after one year');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->projectActivityModel->cleanup(strtotime('-1 year'));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Model\ProjectModel;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ProjectArchiveCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('projects:archive')
|
||||
->setDescription('Disable projects not touched during one year');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$projects = $this->db->table(ProjectModel::TABLE)
|
||||
->eq('is_active', 1)
|
||||
->lt('last_modified', strtotime('-1 year'))
|
||||
->findAll();
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$output->writeln('Deactivating project: #'.$project['id'].' - '.$project['name']);
|
||||
$this->projectModel->disable($project['id']);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Csv;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ProjectDailyColumnStatsExportCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('export:daily-project-column-stats')
|
||||
->setDescription('Daily project column stats CSV export (number of tasks per column and per day)')
|
||||
->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
|
||||
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
|
||||
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$data = $this->projectDailyColumnStatsModel->getAggregatedMetrics(
|
||||
$input->getArgument('project_id'),
|
||||
$input->getArgument('start_date'),
|
||||
$input->getArgument('end_date')
|
||||
);
|
||||
|
||||
if (is_array($data)) {
|
||||
Csv::output($data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Model\ProjectModel;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ProjectDailyStatsCalculationCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('projects:daily-stats')
|
||||
->setDescription('Calculate daily statistics for all projects');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$projects = $this->projectModel->getAllByStatus(ProjectModel::ACTIVE);
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$output->writeln('Run calculation for '.$project['name']);
|
||||
$this->projectDailyColumnStatsModel->updateTotals($project['id'], date('Y-m-d'));
|
||||
$this->projectDailyStatsModel->updateTotals($project['id'], date('Y-m-d'));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
class ResetPasswordCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('user:reset-password')
|
||||
->setDescription('Change user password')
|
||||
->addArgument('username', InputArgument::REQUIRED, 'Username')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$helper = $this->getHelper('question');
|
||||
$username = $input->getArgument('username');
|
||||
|
||||
$passwordQuestion = new Question('What is the new password for '.$username.'? (characters are not printed)'.PHP_EOL);
|
||||
$passwordQuestion->setHidden(true);
|
||||
$passwordQuestion->setHiddenFallback(false);
|
||||
|
||||
$password = $helper->ask($input, $output, $passwordQuestion);
|
||||
|
||||
$confirmationQuestion = new Question('Confirmation:'.PHP_EOL);
|
||||
$confirmationQuestion->setHidden(true);
|
||||
$confirmationQuestion->setHiddenFallback(false);
|
||||
|
||||
$confirmation = $helper->ask($input, $output, $confirmationQuestion);
|
||||
|
||||
if ($this->validatePassword($output, $password, $confirmation)) {
|
||||
$this->resetPassword($output, $username, $password);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function validatePassword(OutputInterface $output, $password, $confirmation)
|
||||
{
|
||||
list($valid, $errors) = $this->passwordResetValidator->validateModification(array(
|
||||
'password' => $password,
|
||||
'confirmation' => $confirmation,
|
||||
));
|
||||
|
||||
if (!$valid) {
|
||||
foreach ($errors as $error_list) {
|
||||
foreach ($error_list as $error) {
|
||||
$output->writeln('<error>'.$error.'</error>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
private function resetPassword(OutputInterface $output, $username, $password)
|
||||
{
|
||||
$userId = $this->userModel->getIdByUsername($username);
|
||||
|
||||
if (empty($userId)) {
|
||||
$output->writeln('<error>User not found</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->userModel->update(array('id' => $userId, 'password' => $password))) {
|
||||
$output->writeln('<error>Unable to update password</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
$output->writeln('<info>Password updated successfully</info>');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ResetTwoFactorCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('user:reset-2fa')
|
||||
->setDescription('Remove two-factor authentication for a user')
|
||||
->addArgument('username', InputArgument::REQUIRED, 'Username');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$username = $input->getArgument('username');
|
||||
$userId = $this->userModel->getIdByUsername($username);
|
||||
|
||||
if (empty($userId)) {
|
||||
$output->writeln('<error>User not found</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!$this->userModel->update(array('id' => $userId, 'twofactor_activated' => 0, 'twofactor_secret' => ''))) {
|
||||
$output->writeln('<error>Unable to update user profile</error>');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$output->writeln('<info>Two-factor authentication disabled</info>');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Csv;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class SubtaskExportCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('export:subtasks')
|
||||
->setDescription('Subtasks CSV export')
|
||||
->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
|
||||
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
|
||||
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$data = $this->subtaskExport->export(
|
||||
$input->getArgument('project_id'),
|
||||
$input->getArgument('start_date'),
|
||||
$input->getArgument('end_date')
|
||||
);
|
||||
|
||||
if (is_array($data)) {
|
||||
Csv::output($data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Csv;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class TaskExportCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('export:tasks')
|
||||
->setDescription('Tasks CSV export')
|
||||
->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
|
||||
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
|
||||
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$data = $this->taskExport->export(
|
||||
$input->getArgument('project_id'),
|
||||
$input->getArgument('start_date'),
|
||||
$input->getArgument('end_date')
|
||||
);
|
||||
|
||||
if (is_array($data)) {
|
||||
Csv::output($data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Model\ProjectModel;
|
||||
use Kanboard\Model\TaskModel;
|
||||
use Kanboard\Core\Security\Role;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class TaskOverdueNotificationCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('notification:overdue-tasks')
|
||||
->setDescription('Send notifications for overdue tasks')
|
||||
->addOption('show', null, InputOption::VALUE_NONE, 'Show sent overdue tasks')
|
||||
->addOption('group', null, InputOption::VALUE_NONE, 'Group all overdue tasks for one user (from all projects) in one email')
|
||||
->addOption('manager', null, InputOption::VALUE_NONE, 'Send all overdue tasks to project manager(s) in one email')
|
||||
->addOption('project', 'p', InputOption::VALUE_REQUIRED, 'Send notifications only the given project')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if ($input->getOption('project')) {
|
||||
$tasks = $this->taskFinderModel->getOverdueTasksQuery()
|
||||
->beginOr()
|
||||
->eq(TaskModel::TABLE.'.project_id', $input->getOption('project'))
|
||||
->eq(ProjectModel::TABLE.'.identifier', $input->getOption('project'))
|
||||
->closeOr()
|
||||
->findAll();
|
||||
} else {
|
||||
$tasks = $this->taskFinderModel->getOverdueTasks();
|
||||
}
|
||||
|
||||
if ($input->getOption('group')) {
|
||||
$tasks = $this->sendGroupOverdueTaskNotifications($tasks);
|
||||
} elseif ($input->getOption('manager')) {
|
||||
$tasks = $this->sendOverdueTaskNotificationsToManagers($tasks);
|
||||
} else {
|
||||
$tasks = $this->sendOverdueTaskNotifications($tasks);
|
||||
}
|
||||
|
||||
if ($input->getOption('show')) {
|
||||
$this->showTable($output, $tasks);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function showTable(OutputInterface $output, array $tasks)
|
||||
{
|
||||
$rows = array();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$rows[] = array(
|
||||
$task['id'],
|
||||
$task['title'],
|
||||
date('Y-m-d H:i', $task['date_due']),
|
||||
$task['project_id'],
|
||||
$task['project_name'],
|
||||
$task['assignee_name'] ?: $task['assignee_username'],
|
||||
);
|
||||
}
|
||||
|
||||
$table = new Table($output);
|
||||
$table
|
||||
->setHeaders(array('Id', 'Title', 'Due date', 'Project Id', 'Project name', 'Assignee'))
|
||||
->setRows($rows)
|
||||
->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all overdue tasks for one user in one email
|
||||
*
|
||||
* @access public
|
||||
* @param array $tasks
|
||||
* @return array
|
||||
*/
|
||||
public function sendGroupOverdueTaskNotifications(array $tasks)
|
||||
{
|
||||
foreach ($this->groupByColumn($tasks, 'owner_id') as $user_tasks) {
|
||||
$users = $this->userNotificationModel->getUsersWithNotificationEnabled($user_tasks[0]['project_id']);
|
||||
|
||||
foreach ($users as $user) {
|
||||
$this->sendUserOverdueTaskNotifications($user, $user_tasks);
|
||||
}
|
||||
}
|
||||
|
||||
return $tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all overdue tasks in one email to project manager(s)
|
||||
*
|
||||
* @access public
|
||||
* @param array $tasks
|
||||
* @return array
|
||||
*/
|
||||
public function sendOverdueTaskNotificationsToManagers(array $tasks)
|
||||
{
|
||||
foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) {
|
||||
$users = $this->userNotificationModel->getUsersWithNotificationEnabled($project_id);
|
||||
$managers = array();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$role = $this->projectUserRoleModel->getUserRole($project_id, $user['id']);
|
||||
if ($role == Role::PROJECT_MANAGER) {
|
||||
$managers[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($managers as $manager) {
|
||||
$this->sendUserOverdueTaskNotificationsToManagers($manager, $project_tasks);
|
||||
}
|
||||
}
|
||||
|
||||
return $tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send overdue tasks
|
||||
*
|
||||
* @access public
|
||||
* @param array $tasks
|
||||
* @return array
|
||||
*/
|
||||
public function sendOverdueTaskNotifications(array $tasks)
|
||||
{
|
||||
foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) {
|
||||
$users = $this->userNotificationModel->getUsersWithNotificationEnabled($project_id);
|
||||
|
||||
foreach ($users as $user) {
|
||||
$this->sendUserOverdueTaskNotifications($user, $project_tasks);
|
||||
}
|
||||
}
|
||||
|
||||
return $tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send overdue tasks for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param array $user
|
||||
* @param array $tasks
|
||||
*/
|
||||
public function sendUserOverdueTaskNotifications(array $user, array $tasks)
|
||||
{
|
||||
$user_tasks = array();
|
||||
$project_names = array();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
if ($this->userNotificationFilterModel->shouldReceiveNotification($user, array('task' => $task))) {
|
||||
$user_tasks[] = $task;
|
||||
$project_names[$task['project_id']] = $task['project_name'];
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($user_tasks)) {
|
||||
$this->userNotificationModel->sendUserNotification(
|
||||
$user,
|
||||
TaskModel::EVENT_OVERDUE,
|
||||
array('tasks' => $user_tasks, 'project_name' => implode(', ', $project_names))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send overdue tasks for a project manager(s)
|
||||
*
|
||||
* @access public
|
||||
* @param array $manager
|
||||
* @param array $tasks
|
||||
*/
|
||||
public function sendUserOverdueTaskNotificationsToManagers(array $manager, array $tasks)
|
||||
{
|
||||
$this->userNotificationModel->sendUserNotification(
|
||||
$manager,
|
||||
TaskModel::EVENT_OVERDUE,
|
||||
array('tasks' => $tasks, 'project_name' => $tasks[0]['project_name'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group a collection of records by a column
|
||||
*
|
||||
* @access public
|
||||
* @param array $collection
|
||||
* @param string $column
|
||||
* @return array
|
||||
*/
|
||||
public function groupByColumn(array $collection, $column)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
foreach ($collection as $item) {
|
||||
$result[$item[$column]][] = $item;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Kanboard\Model\TaskModel;
|
||||
use Kanboard\Event\TaskListEvent;
|
||||
|
||||
class TaskTriggerCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('trigger:tasks')
|
||||
->setDescription('Trigger scheduler event for all tasks');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
foreach ($this->getProjectIds() as $project_id) {
|
||||
$tasks = $this->taskFinderModel->getAll($project_id);
|
||||
$nb_tasks = count($tasks);
|
||||
|
||||
if ($nb_tasks > 0) {
|
||||
$output->writeln('Trigger task event: project_id='.$project_id.', nb_tasks='.$nb_tasks);
|
||||
$this->sendEvent($tasks, $project_id);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getProjectIds()
|
||||
{
|
||||
$listeners = $this->dispatcher->getListeners(TaskModel::EVENT_DAILY_CRONJOB);
|
||||
$project_ids = array();
|
||||
|
||||
foreach ($listeners as $listener) {
|
||||
$project_ids[] = $listener[0]->getProjectId();
|
||||
}
|
||||
|
||||
return array_unique($project_ids);
|
||||
}
|
||||
|
||||
private function sendEvent(array &$tasks, $project_id)
|
||||
{
|
||||
$event = new TaskListEvent(array('project_id' => $project_id));
|
||||
$event->setTasks($tasks);
|
||||
|
||||
$this->dispatcher->dispatch($event, TaskModel::EVENT_DAILY_CRONJOB);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Csv;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class TransitionExportCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('export:transitions')
|
||||
->setDescription('Task transitions CSV export')
|
||||
->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
|
||||
->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
|
||||
->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$data = $this->transitionExport->export(
|
||||
$input->getArgument('project_id'),
|
||||
$input->getArgument('start_date'),
|
||||
$input->getArgument('end_date')
|
||||
);
|
||||
|
||||
if (is_array($data)) {
|
||||
Csv::output($data);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class VersionCommand
|
||||
*
|
||||
* @package Kanboard\Console
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class VersionCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('version')
|
||||
->setDescription('Display Kanboard version')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln(APP_VERSION);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class WorkerCommand
|
||||
*
|
||||
* @package Kanboard\Console
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class WorkerCommand extends BaseCommand
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('worker')
|
||||
->setDescription('Execute queue worker')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->queueManager->listen();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user