Manage plugins from the user interface and from the command line
This commit is contained in:
parent
cbf896e74e
commit
8d69c49da5
|
|
@ -3,6 +3,7 @@ Version 1.0.29 (unreleased)
|
|||
|
||||
New features:
|
||||
|
||||
* Manage plugin from the user interface and from the command line
|
||||
* Added the possibility to convert a subtask to a task
|
||||
* Added menu entry to add tasks from all project views
|
||||
* Add tasks in bulk from the board
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ use Symfony\Component\Console\Command\Command;
|
|||
* @property \Kanboard\Model\UserNotification $userNotification
|
||||
* @property \Kanboard\Model\UserNotificationFilter $userNotificationFilter
|
||||
* @property \Kanboard\Model\ProjectUserRole $projectUserRole
|
||||
* @property \Kanboard\Core\Plugin\Loader $pluginLoader
|
||||
* @property \Kanboard\Core\Http\Client $httpClient
|
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
*/
|
||||
abstract class BaseCommand extends Command
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Plugin\Installer;
|
||||
use Kanboard\Core\Plugin\PluginInstallerException;
|
||||
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)
|
||||
{
|
||||
if (!Installer::isConfigured()) {
|
||||
$output->writeln('<error>Kanboard is not configured to install plugins itself</error>');
|
||||
}
|
||||
|
||||
try {
|
||||
$installer = new Installer($this->container);
|
||||
$installer->install($input->getArgument('url'));
|
||||
$output->writeln('<info>Plugin installed successfully</info>');
|
||||
} catch (PluginInstallerException $e) {
|
||||
$output->writeln('<error>'.$e->getMessage().'</error>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Plugin\Installer;
|
||||
use Kanboard\Core\Plugin\PluginInstallerException;
|
||||
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)
|
||||
{
|
||||
if (!Installer::isConfigured()) {
|
||||
$output->writeln('<error>Kanboard is not configured to remove plugins itself</error>');
|
||||
}
|
||||
|
||||
try {
|
||||
$installer = new Installer($this->container);
|
||||
$installer->uninstall($input->getArgument('pluginId'));
|
||||
$output->writeln('<info>Plugin removed successfully</info>');
|
||||
} catch (PluginInstallerException $e) {
|
||||
$output->writeln('<error>'.$e->getMessage().'</error>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Core\Plugin\Base as BasePlugin;
|
||||
use Kanboard\Core\Plugin\Installer;
|
||||
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)
|
||||
{
|
||||
if (!Installer::isConfigured()) {
|
||||
$output->writeln('<error>Kanboard is not configured to upgrade plugins itself</error>');
|
||||
}
|
||||
|
||||
$installer = new Installer($this->container);
|
||||
$availablePlugins = $this->httpClient->getJson(PLUGIN_API_URL);
|
||||
|
||||
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>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getPluginDetails(array $availablePlugins, BasePlugin $installedPlugin)
|
||||
{
|
||||
foreach ($availablePlugins as $availablePlugin) {
|
||||
if ($availablePlugin['title'] === $installedPlugin->getPluginName()) {
|
||||
return $availablePlugin;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace Kanboard\Controller;
|
||||
|
||||
use Kanboard\Core\Plugin\Installer;
|
||||
use Kanboard\Core\Plugin\PluginInstallerException;
|
||||
|
||||
/**
|
||||
* Class PluginController
|
||||
*
|
||||
|
|
@ -18,8 +21,9 @@ class PluginController extends BaseController
|
|||
public function show()
|
||||
{
|
||||
$this->response->html($this->helper->layout->plugin('plugin/show', array(
|
||||
'plugins' => $this->pluginLoader->plugins,
|
||||
'plugins' => $this->pluginLoader->getPlugins(),
|
||||
'title' => t('Installed Plugins'),
|
||||
'is_configured' => Installer::isConfigured(),
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
@ -28,11 +32,94 @@ class PluginController extends BaseController
|
|||
*/
|
||||
public function directory()
|
||||
{
|
||||
$plugins = $this->httpClient->getJson(PLUGIN_API_URL);
|
||||
$installedPlugins = array();
|
||||
|
||||
foreach ($this->pluginLoader->getPlugins() as $plugin) {
|
||||
$installedPlugins[$plugin->getPluginName()] = $plugin->getPluginVersion();
|
||||
}
|
||||
|
||||
$this->response->html($this->helper->layout->plugin('plugin/directory', array(
|
||||
'plugins' => $plugins,
|
||||
'installed_plugins' => $installedPlugins,
|
||||
'available_plugins' => $this->httpClient->getJson(PLUGIN_API_URL),
|
||||
'title' => t('Plugin Directory'),
|
||||
'is_configured' => Installer::isConfigured(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Install plugin from URL
|
||||
*
|
||||
* @throws \Kanboard\Core\Controller\AccessForbiddenException
|
||||
*/
|
||||
public function install()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$pluginArchiveUrl = urldecode($this->request->getStringParam('archive_url'));
|
||||
|
||||
try {
|
||||
$installer = new Installer($this->container);
|
||||
$installer->install($pluginArchiveUrl);
|
||||
$this->flash->success(t('Plugin installed successfully.'));
|
||||
} catch (PluginInstallerException $e) {
|
||||
$this->flash->failure($e->getMessage());
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('PluginController', 'show'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update plugin from URL
|
||||
*
|
||||
* @throws \Kanboard\Core\Controller\AccessForbiddenException
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$pluginArchiveUrl = urldecode($this->request->getStringParam('archive_url'));
|
||||
|
||||
try {
|
||||
$installer = new Installer($this->container);
|
||||
$installer->update($pluginArchiveUrl);
|
||||
$this->flash->success(t('Plugin updated successfully.'));
|
||||
} catch (PluginInstallerException $e) {
|
||||
$this->flash->failure($e->getMessage());
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('PluginController', 'show'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation before to remove the plugin
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$pluginId = $this->request->getStringParam('pluginId');
|
||||
$plugins = $this->pluginLoader->getPlugins();
|
||||
|
||||
$this->response->html($this->template->render('plugin/remove', array(
|
||||
'plugin_id' => $pluginId,
|
||||
'plugin' => $plugins[$pluginId],
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a plugin
|
||||
*
|
||||
* @throws \Kanboard\Core\Controller\AccessForbiddenException
|
||||
*/
|
||||
public function uninstall()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$pluginId = $this->request->getStringParam('pluginId');
|
||||
|
||||
try {
|
||||
$installer = new Installer($this->container);
|
||||
$installer->uninstall($pluginId);
|
||||
$this->flash->success(t('Plugin removed successfully.'));
|
||||
} catch (PluginInstallerException $e) {
|
||||
$this->flash->failure($e->getMessage());
|
||||
}
|
||||
|
||||
$this->response->redirect($this->helper->url->to('PluginController', 'show'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Plugin;
|
||||
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* Class Installer
|
||||
*
|
||||
* @package Kanboard\Core\Plugin
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Installer extends \Kanboard\Core\Base
|
||||
{
|
||||
/**
|
||||
* Return true if Kanboard is configured to install plugins
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public static function isConfigured()
|
||||
{
|
||||
return PLUGIN_INSTALLER && is_writable(PLUGINS_DIR) && extension_loaded('zip');
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a plugin
|
||||
*
|
||||
* @access public
|
||||
* @param string $archiveUrl
|
||||
* @throws PluginInstallerException
|
||||
*/
|
||||
public function install($archiveUrl)
|
||||
{
|
||||
$zip = $this->downloadPluginArchive($archiveUrl);
|
||||
|
||||
if (! $zip->extractTo(PLUGINS_DIR)) {
|
||||
$this->cleanupArchive($zip);
|
||||
throw new PluginInstallerException(t('Unable to extract plugin archive.'));
|
||||
}
|
||||
|
||||
$this->cleanupArchive($zip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall a plugin
|
||||
*
|
||||
* @access public
|
||||
* @param string $pluginId
|
||||
* @throws PluginInstallerException
|
||||
*/
|
||||
public function uninstall($pluginId)
|
||||
{
|
||||
$pluginFolder = PLUGINS_DIR.DIRECTORY_SEPARATOR.basename($pluginId);
|
||||
|
||||
if (! file_exists($pluginFolder)) {
|
||||
throw new PluginInstallerException(t('Plugin not found.'));
|
||||
}
|
||||
|
||||
if (! is_writable($pluginFolder)) {
|
||||
throw new PluginInstallerException(e('You don\'t have the permission to remove this plugin.'));
|
||||
}
|
||||
|
||||
$this->removeAllDirectories($pluginFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a plugin
|
||||
*
|
||||
* @access public
|
||||
* @param string $archiveUrl
|
||||
* @throws PluginInstallerException
|
||||
*/
|
||||
public function update($archiveUrl)
|
||||
{
|
||||
$zip = $this->downloadPluginArchive($archiveUrl);
|
||||
|
||||
$firstEntry = $zip->statIndex(0);
|
||||
$this->uninstall($firstEntry['name']);
|
||||
|
||||
if (! $zip->extractTo(PLUGINS_DIR)) {
|
||||
$this->cleanupArchive($zip);
|
||||
throw new PluginInstallerException(t('Unable to extract plugin archive.'));
|
||||
}
|
||||
|
||||
$this->cleanupArchive($zip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download archive from URL
|
||||
*
|
||||
* @access protected
|
||||
* @param string $archiveUrl
|
||||
* @return ZipArchive
|
||||
* @throws PluginInstallerException
|
||||
*/
|
||||
protected function downloadPluginArchive($archiveUrl)
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$archiveData = $this->httpClient->get($archiveUrl);
|
||||
$archiveFile = tempnam(sys_get_temp_dir(), 'kb_plugin');
|
||||
|
||||
if (empty($archiveData)) {
|
||||
unlink($archiveFile);
|
||||
throw new PluginInstallerException(t('Unable to download plugin archive.'));
|
||||
}
|
||||
|
||||
if (file_put_contents($archiveFile, $archiveData) === false) {
|
||||
unlink($archiveFile);
|
||||
throw new PluginInstallerException(t('Unable to write temporary file for plugin.'));
|
||||
}
|
||||
|
||||
if ($zip->open($archiveFile) !== true) {
|
||||
unlink($archiveFile);
|
||||
throw new PluginInstallerException(t('Unable to open plugin archive.'));
|
||||
}
|
||||
|
||||
if ($zip->numFiles === 0) {
|
||||
unlink($archiveFile);
|
||||
throw new PluginInstallerException(t('There is no file in the plugin archive.'));
|
||||
}
|
||||
|
||||
return $zip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove archive file
|
||||
*
|
||||
* @access protected
|
||||
* @param ZipArchive $zip
|
||||
*/
|
||||
protected function cleanupArchive(ZipArchive $zip)
|
||||
{
|
||||
unlink($zip->filename);
|
||||
$zip->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove recursively a directory
|
||||
*
|
||||
* @access protected
|
||||
* @param string $directory
|
||||
*/
|
||||
protected function removeAllDirectories($directory)
|
||||
{
|
||||
$it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($file->isDir()) {
|
||||
rmdir($file->getRealPath());
|
||||
} else {
|
||||
unlink($file->getRealPath());
|
||||
}
|
||||
}
|
||||
|
||||
rmdir($directory);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,16 +18,16 @@ class Loader extends \Kanboard\Core\Base
|
|||
/**
|
||||
* Plugin instances
|
||||
*
|
||||
* @access public
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
public $plugins = array();
|
||||
protected $plugins = array();
|
||||
|
||||
/**
|
||||
* Get list of loaded plugins
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
* @return Base[]
|
||||
*/
|
||||
public function getPlugins()
|
||||
{
|
||||
|
|
@ -52,7 +52,7 @@ class Loader extends \Kanboard\Core\Base
|
|||
if ($fileInfo->isDir() && substr($fileInfo->getFilename(), 0, 1) !== '.') {
|
||||
$pluginName = $fileInfo->getFilename();
|
||||
$this->loadSchema($pluginName);
|
||||
$this->initializePlugin($this->loadPlugin($pluginName));
|
||||
$this->initializePlugin($pluginName, $this->loadPlugin($pluginName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -95,9 +95,10 @@ class Loader extends \Kanboard\Core\Base
|
|||
* Initialize plugin
|
||||
*
|
||||
* @access public
|
||||
* @param Base $plugin
|
||||
* @param string $pluginName
|
||||
* @param Base $plugin
|
||||
*/
|
||||
public function initializePlugin(Base $plugin)
|
||||
public function initializePlugin($pluginName, Base $plugin)
|
||||
{
|
||||
if (method_exists($plugin, 'onStartup')) {
|
||||
$this->dispatcher->addListener('app.bootstrap', array($plugin, 'onStartup'));
|
||||
|
|
@ -107,6 +108,6 @@ class Loader extends \Kanboard\Core\Base
|
|||
Tool::buildDICHelpers($this->container, $plugin->getHelpers());
|
||||
|
||||
$plugin->initialize();
|
||||
$this->plugins[] = $plugin;
|
||||
$this->plugins[$pluginName] = $plugin;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Kanboard\Core\Plugin;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class PluginInstallerException
|
||||
*
|
||||
* @package Kanboard\Core\Plugin
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class PluginInstallerException extends Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -178,7 +178,7 @@ class RouteProvider implements ServiceProviderInterface
|
|||
|
||||
// Plugins
|
||||
$container['route']->addRoute('extensions', 'PluginController', 'show');
|
||||
$container['route']->addRoute('extensions/list', 'PluginController', 'directory');
|
||||
$container['route']->addRoute('extensions/directory', 'PluginController', 'directory');
|
||||
|
||||
// Doc
|
||||
$container['route']->addRoute('documentation/:file', 'doc', 'show');
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
</li>
|
||||
<?php if ($this->user->hasProjectAccess('TaskCreationController', 'show', $column['project_id'])): ?>
|
||||
<li>
|
||||
<i class="fa fa-align-justify" aria-hidden="true"></i>
|
||||
<i class="fa fa-align-justify fa-fw" aria-hidden="true"></i>
|
||||
<?= $this->url->link(t('Create tasks in bulk'), 'TaskBulkController', 'show', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'popover') ?>
|
||||
</li>
|
||||
<?php if ($column['nb_tasks'] > 0): ?>
|
||||
|
|
|
|||
|
|
@ -2,29 +2,54 @@
|
|||
<h2><?= t('Plugin Directory') ?></h2>
|
||||
</div>
|
||||
|
||||
<?php if (empty($plugins)): ?>
|
||||
<?php if (! $is_configured): ?>
|
||||
<p class="alert alert-error">
|
||||
<?= t('Your Kanboard instance is not configured to install plugins from the user interface.') ?>
|
||||
</p>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (empty($available_plugins)): ?>
|
||||
<p class="alert"><?= t('There is no plugin available.') ?></p>
|
||||
<?php else: ?>
|
||||
<table class="table-stripped">
|
||||
<?php foreach ($available_plugins as $plugin): ?>
|
||||
<table>
|
||||
<tr>
|
||||
<th class="column-20"><?= t('Name') ?></th>
|
||||
<th class="column-20"><?= t('Author') ?></th>
|
||||
<th class="column-10"><?= t('Version') ?></th>
|
||||
<th><?= t('Description') ?></th>
|
||||
<th><?= t('Action') ?></th>
|
||||
<th colspan="3">
|
||||
<a href="<?= $plugin['homepage'] ?>" target="_blank" rel="noreferrer"><?= $this->text->e($plugin['title']) ?></a>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="column-40">
|
||||
<?= $this->text->e($plugin['author']) ?>
|
||||
</td>
|
||||
<td class="column-30">
|
||||
<?= $this->text->e($plugin['version']) ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($is_configured): ?>
|
||||
<?php if (! isset($installed_plugins[$plugin['title']])): ?>
|
||||
<i class="fa fa-cloud-download fa-fw" aria-hidden="true"></i>
|
||||
<?= $this->url->link(t('Install'), 'PluginController', 'install', array('archive_url' => urlencode($plugin['download'])), true) ?>
|
||||
<?php elseif ($installed_plugins[$plugin['title']] < $plugin['version']): ?>
|
||||
<i class="fa fa-refresh fa-fw" aria-hidden="true"></i>
|
||||
<?= $this->url->link(t('Update'), 'PluginController', 'update', array('archive_url' => urlencode($plugin['download'])), true) ?>
|
||||
<?php else: ?>
|
||||
<i class="fa fa-check-circle-o" aria-hidden="true"></i>
|
||||
<?= t('Up to date') ?>
|
||||
<?php endif ?>
|
||||
<?php else: ?>
|
||||
<i class="fa fa-ban fa-fw" aria-hidden="true"></i>
|
||||
<?= t('Not available') ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<div class="markdown">
|
||||
<?= $this->text->markdown($plugin['description']) ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php foreach ($plugins as $plugin): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<?= $plugin['homepage'] ?>" target="_blank" rel="noreferrer"><?= $this->text->e($plugin['title']) ?></a>
|
||||
</td>
|
||||
<td><?= $this->text->e($plugin['author']) ?></td>
|
||||
<td><?= $this->text->e($plugin['version']) ?></td>
|
||||
<td><?= $this->text->e($plugin['description']) ?></td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
<?php endforeach ?>
|
||||
<?php endif ?>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Remove plugin') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
<p class="alert alert-info"><?= t('Do you really want to remove this plugin: "%s"?', $plugin->getPluginName()) ?></p>
|
||||
|
||||
<div class="form-actions">
|
||||
<?= $this->url->link(t('Yes'), 'PluginController', 'uninstall', array('pluginId' => $plugin_id), true, 'btn btn-red') ?>
|
||||
<?= t('or') ?>
|
||||
<?= $this->url->link(t('cancel'), 'PluginController', 'show', array(), false, 'close-popover') ?>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -5,15 +5,17 @@
|
|||
<?php if (empty($plugins)): ?>
|
||||
<p class="alert"><?= t('There is no plugin loaded.') ?></p>
|
||||
<?php else: ?>
|
||||
<table class="table-stripped">
|
||||
<table>
|
||||
<tr>
|
||||
<th class="column-20"><?= t('Name') ?></th>
|
||||
<th class="column-20"><?= t('Author') ?></th>
|
||||
<th class="column-35"><?= t('Name') ?></th>
|
||||
<th class="column-30"><?= t('Author') ?></th>
|
||||
<th class="column-10"><?= t('Version') ?></th>
|
||||
<th><?= t('Description') ?></th>
|
||||
<?php if ($is_configured): ?>
|
||||
<th><?= t('Action') ?></th>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
|
||||
<?php foreach ($plugins as $plugin): ?>
|
||||
<?php foreach ($plugins as $pluginFolder => $plugin): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if ($plugin->getPluginHomepage()): ?>
|
||||
|
|
@ -24,7 +26,15 @@
|
|||
</td>
|
||||
<td><?= $this->text->e($plugin->getPluginAuthor()) ?></td>
|
||||
<td><?= $this->text->e($plugin->getPluginVersion()) ?></td>
|
||||
<td><?= $this->text->e($plugin->getPluginDescription()) ?></td>
|
||||
<?php if ($is_configured): ?>
|
||||
<td>
|
||||
<i class="fa fa-trash-o fa-fw" aria-hidden="true"></i>
|
||||
<?= $this->url->link(t('Uninstall'), 'PluginController', 'confirm', array('pluginId' => $pluginFolder), false, 'popover') ?>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="<?= $is_configured ? 4 : 3 ?>"><?= $this->text->e($plugin->getPluginDescription()) ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@ defined('DATA_DIR') or define('DATA_DIR', ROOT_DIR.DIRECTORY_SEPARATOR.'data');
|
|||
// Files directory (attachments)
|
||||
defined('FILES_DIR') or define('FILES_DIR', DATA_DIR.DIRECTORY_SEPARATOR.'files');
|
||||
|
||||
// Plugins directory
|
||||
// Plugins settings
|
||||
defined('PLUGINS_DIR') or define('PLUGINS_DIR', ROOT_DIR.DIRECTORY_SEPARATOR.'plugins');
|
||||
defined('PLUGIN_API_URL') or define('PLUGIN_API_URL', 'https://kanboard.net/plugins.json');
|
||||
defined('PLUGIN_INSTALLER') or define('PLUGIN_INSTALLER', true);
|
||||
|
||||
// Enable/disable debug
|
||||
defined('DEBUG') or define('DEBUG', strtolower(getenv('DEBUG')) === 'true');
|
||||
|
|
@ -131,5 +133,3 @@ defined('HTTP_PROXY_HOSTNAME') or define('HTTP_PROXY_HOSTNAME', '');
|
|||
defined('HTTP_PROXY_PORT') or define('HTTP_PROXY_PORT', '3128');
|
||||
defined('HTTP_PROXY_USERNAME') or define('HTTP_PROXY_USERNAME', '');
|
||||
defined('HTTP_PROXY_PASSWORD') or define('HTTP_PROXY_PASSWORD', '');
|
||||
|
||||
defined('PLUGIN_API_URL') or define('PLUGIN_API_URL', 'https://kanboard.net/plugins.json');
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ Available commands:
|
|||
locale:sync Synchronize all translations based on the fr_FR locale
|
||||
notification
|
||||
notification:overdue-tasks Send notifications for overdue tasks
|
||||
plugin
|
||||
plugin:install Install a plugin from a remote Zip archive
|
||||
plugin:uninstall Remove a plugin
|
||||
plugin:upgrade Update all installed plugins
|
||||
projects
|
||||
projects:daily-stats Calculate daily statistics for all projects
|
||||
trigger
|
||||
|
|
@ -170,3 +174,25 @@ You will be prompted for a password and confirmation. Characters are not printed
|
|||
```bash
|
||||
./kanboard user:reset-2fa my_user
|
||||
```
|
||||
|
||||
### Install a plugin
|
||||
|
||||
```bash
|
||||
./kanboard plugin:install https://github.com/kanboard/plugin-github-auth/releases/download/v1.0.1/GithubAuth-1.0.1.zip
|
||||
```
|
||||
|
||||
Note: Installed files will have the same permissions as the current user
|
||||
|
||||
### Remove a plugin
|
||||
|
||||
```bash
|
||||
./kanboard plugin:uninstall Budget
|
||||
```
|
||||
|
||||
### Upgrade all plugins
|
||||
|
||||
```bash
|
||||
./kanboard plugin:upgrade
|
||||
* Updating plugin: Budget Planning
|
||||
* Plugin up to date: Github Authentication
|
||||
```
|
||||
|
|
|
|||
|
|
@ -15,14 +15,21 @@ define('LOG_DRIVER', 'file'); // Other drivers are: syslog, stdout, stderr or fi
|
|||
The log driver must be defined if you enable the debug mode.
|
||||
The debug mode logs all SQL queries and the time taken to generate pages.
|
||||
|
||||
Plugins folder
|
||||
--------------
|
||||
Plugins
|
||||
-------
|
||||
|
||||
Plugin folder:
|
||||
|
||||
```php
|
||||
// Plugin directory
|
||||
define('PLUGINS_DIR', 'data/plugins');
|
||||
```
|
||||
|
||||
Enable/disable plugin installation from the user interface:
|
||||
|
||||
```php
|
||||
define('PLUGIN_INSTALLER', true); // Default is true
|
||||
```
|
||||
|
||||
Folder for uploaded files
|
||||
-------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ Technical details
|
|||
- [Environment variables](env.markdown)
|
||||
- [Email configuration](email-configuration.markdown)
|
||||
- [URL rewriting](nice-urls.markdown)
|
||||
- [Plugin Directory](plugin-directory.markdown)
|
||||
|
||||
### Database
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
Plugin Directory Configuration
|
||||
==============================
|
||||
|
||||
To install, update and remove plugins from the user interface, you must have those requirements:
|
||||
|
||||
- The plugin directory must be writeable by the web server user
|
||||
- The Zip extension must be available on your server
|
||||
- The config parameter `PLUGIN_INSTALLER` must be set at `true`
|
||||
|
||||
To disable this feature, change the value of `PLUGIN_INSTALLER` to `false` in your config file.
|
||||
You can also change the permissions of the plugin folder on the filesystem.
|
||||
|
||||
Only administrators are allowed to install plugins from the user interface.
|
||||
|
||||
By default, only plugin listed on Kanboard's website are available.
|
||||
48
kanboard
48
kanboard
|
|
@ -1,8 +1,9 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require __DIR__.'/app/common.php';
|
||||
|
||||
use Kanboard\Console\PluginInstallCommand;
|
||||
use Kanboard\Console\PluginUninstallCommand;
|
||||
use Kanboard\Console\PluginUpgradeCommand;
|
||||
use Kanboard\Console\ResetPasswordCommand;
|
||||
use Kanboard\Console\ResetTwoFactorCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
|
@ -18,19 +19,32 @@ use Kanboard\Console\LocaleComparatorCommand;
|
|||
use Kanboard\Console\TaskTriggerCommand;
|
||||
use Kanboard\Console\CronjobCommand;
|
||||
|
||||
$container['dispatcher']->dispatch('app.bootstrap', new Event);
|
||||
|
||||
$application = new Application('Kanboard', APP_VERSION);
|
||||
$application->add(new TaskOverdueNotificationCommand($container));
|
||||
$application->add(new SubtaskExportCommand($container));
|
||||
$application->add(new TaskExportCommand($container));
|
||||
$application->add(new ProjectDailyStatsCalculationCommand($container));
|
||||
$application->add(new ProjectDailyColumnStatsExportCommand($container));
|
||||
$application->add(new TransitionExportCommand($container));
|
||||
$application->add(new LocaleSyncCommand($container));
|
||||
$application->add(new LocaleComparatorCommand($container));
|
||||
$application->add(new TaskTriggerCommand($container));
|
||||
$application->add(new CronjobCommand($container));
|
||||
$application->add(new ResetPasswordCommand($container));
|
||||
$application->add(new ResetTwoFactorCommand($container));
|
||||
$application->run();
|
||||
try {
|
||||
|
||||
require __DIR__.'/app/common.php';
|
||||
|
||||
$container['dispatcher']->dispatch('app.bootstrap', new Event);
|
||||
|
||||
$application = new Application('Kanboard', APP_VERSION);
|
||||
$application->add(new TaskOverdueNotificationCommand($container));
|
||||
$application->add(new SubtaskExportCommand($container));
|
||||
$application->add(new TaskExportCommand($container));
|
||||
$application->add(new ProjectDailyStatsCalculationCommand($container));
|
||||
$application->add(new ProjectDailyColumnStatsExportCommand($container));
|
||||
$application->add(new TransitionExportCommand($container));
|
||||
$application->add(new LocaleSyncCommand($container));
|
||||
$application->add(new LocaleComparatorCommand($container));
|
||||
$application->add(new TaskTriggerCommand($container));
|
||||
$application->add(new CronjobCommand($container));
|
||||
$application->add(new ResetPasswordCommand($container));
|
||||
$application->add(new ResetTwoFactorCommand($container));
|
||||
$application->add(new PluginUpgradeCommand($container));
|
||||
$application->add(new PluginInstallCommand($container));
|
||||
$application->add(new PluginUninstallCommand($container));
|
||||
$application->run();
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo $e->getMessage().PHP_EOL;
|
||||
exit(255);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue