Offer the possibility to override internal formatter objects from plugins

This commit is contained in:
Frederic Guillot 2016-12-20 20:06:39 -05:00
parent a957195952
commit ae708a712a
28 changed files with 200 additions and 72 deletions

View File

@ -5,6 +5,7 @@ New features:
* Add slideshow for images
* Add API calls to manage tags
* Offer the possibility to override internal formatter objects from plugins
Improvements:

View File

@ -3,7 +3,6 @@
namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\ProjectAuthorization;
use Kanboard\Formatter\BoardFormatter;
/**
* Board API controller
@ -17,7 +16,7 @@ class BoardProcedure extends BaseProcedure
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getBoard', $project_id);
return BoardFormatter::getInstance($this->container)
return $this->boardFormatter
->withProjectId($project_id)
->withQuery($this->taskFinderModel->getExtendedQuery())
->format();

View File

@ -3,7 +3,6 @@
namespace Kanboard\Controller;
use Kanboard\Core\Controller\AccessForbiddenException;
use Kanboard\Formatter\BoardFormatter;
use Kanboard\Model\UserMetadataModel;
/**
@ -139,7 +138,7 @@ class BoardAjaxController extends BaseController
'board_highlight_period' => $this->configModel->get('board_highlight_period'),
'swimlanes' => $this->taskLexer
->build($this->userSession->getFilters($project_id))
->format(BoardFormatter::getInstance($this->container)->withProjectId($project_id))
->format($this->boardFormatter->withProjectId($project_id))
));
}
}

View File

@ -3,7 +3,6 @@
namespace Kanboard\Controller;
use Kanboard\Core\Controller\AccessForbiddenException;
use Kanboard\Formatter\BoardFormatter;
use Kanboard\Model\TaskModel;
/**
@ -35,7 +34,7 @@ class BoardViewController extends BaseController
$this->response->html($this->helper->layout->app('board/view_public', array(
'project' => $project,
'swimlanes' => BoardFormatter::getInstance($this->container)
'swimlanes' => $this->boardFormatter
->withProjectId($project['id'])
->withQuery($query)
->format()
@ -68,7 +67,7 @@ class BoardViewController extends BaseController
'board_highlight_period' => $this->configModel->get('board_highlight_period'),
'swimlanes' => $this->taskLexer
->build($search)
->format(BoardFormatter::getInstance($this->container)->withProjectId($project['id']))
->format($this->boardFormatter->withProjectId($project['id']))
)));
}
}

View File

@ -2,8 +2,6 @@
namespace Kanboard\Controller;
use Kanboard\Formatter\GroupAutoCompleteFormatter;
/**
* Group Ajax Controller
*
@ -20,7 +18,7 @@ class GroupAjaxController extends BaseController
public function autocomplete()
{
$search = $this->request->getStringParam('term');
$formatter = new GroupAutoCompleteFormatter($this->groupManager->find($search));
$this->response->json($formatter->format());
$groups = $this->groupManager->find($search);
$this->response->json($this->groupAutoCompleteFormatter->withGroups($groups)->format());
}
}

View File

@ -7,7 +7,6 @@ use Kanboard\Core\Filter\QueryBuilder;
use Kanboard\Filter\TaskAssigneeFilter;
use Kanboard\Filter\TaskProjectFilter;
use Kanboard\Filter\TaskStatusFilter;
use Kanboard\Formatter\TaskICalFormatter;
use Kanboard\Model\TaskModel;
use Eluceo\iCal\Component\Calendar as iCalendar;
@ -94,8 +93,6 @@ class ICalendarController extends BaseController
$end = $this->request->getStringParam('end', strtotime('+6 months'));
$this->helper->ical->addTaskDateDueEvents($queryBuilder, $calendar, $start, $end);
$formatter = new TaskICalFormatter($this->container);
$this->response->ical($formatter->setCalendar($calendar)->format());
$this->response->ical($this->taskICalFormatter->setCalendar($calendar)->format());
}
}

View File

@ -5,7 +5,6 @@ namespace Kanboard\Controller;
use Kanboard\Filter\ProjectIdsFilter;
use Kanboard\Filter\ProjectStatusFilter;
use Kanboard\Filter\ProjectTypeFilter;
use Kanboard\Formatter\ProjectGanttFormatter;
use Kanboard\Model\ProjectModel;
/**
@ -30,7 +29,7 @@ class ProjectGanttController extends BaseController
$filter->getQuery()->asc(ProjectModel::TABLE.'.start_date');
$this->response->html($this->helper->layout->app('project_gantt/show', array(
'projects' => $filter->format(new ProjectGanttFormatter($this->container)),
'projects' => $filter->format($this->projectGanttFormatter),
'title' => t('Gantt chart for all projects'),
)));
}

View File

@ -8,8 +8,6 @@ use Kanboard\Filter\TaskProjectsFilter;
use Kanboard\Filter\TaskStartsWithIdFilter;
use Kanboard\Filter\TaskStatusFilter;
use Kanboard\Filter\TaskTitleFilter;
use Kanboard\Formatter\TaskAutoCompleteFormatter;
use Kanboard\Formatter\TaskSuggestMenuFormatter;
use Kanboard\Model\TaskModel;
/**
@ -46,7 +44,7 @@ class TaskAjaxController extends BaseController
$filter->withFilter(new TaskTitleFilter($search));
}
$this->response->json($filter->format(new TaskAutoCompleteFormatter($this->container)));
$this->response->json($filter->format($this->taskAutoCompleteFormatter));
}
}
@ -66,7 +64,7 @@ class TaskAjaxController extends BaseController
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
->withFilter(new TaskStartsWithIdFilter($taskId));
$this->response->json($filter->format(new TaskSuggestMenuFormatter($this->container)));
$this->response->json($filter->format($this->taskSuggestMenuFormatter));
}
}
}

View File

@ -3,7 +3,6 @@
namespace Kanboard\Controller;
use Kanboard\Filter\TaskProjectFilter;
use Kanboard\Formatter\TaskGanttFormatter;
use Kanboard\Model\TaskModel;
/**
@ -35,7 +34,7 @@ class TaskGanttController extends BaseController
'title' => $project['name'],
'description' => $this->helper->projectHeader->getDescription($project),
'sorting' => $sorting,
'tasks' => $filter->format(new TaskGanttFormatter($this->container)),
'tasks' => $filter->format($this->taskGanttFormatter),
)));
}

View File

@ -3,7 +3,6 @@
namespace Kanboard\Controller;
use Kanboard\Core\Controller\AccessForbiddenException;
use Kanboard\Formatter\BoardFormatter;
use Kanboard\Model\TaskModel;
/**
@ -20,7 +19,7 @@ class TaskMovePositionController extends BaseController
$this->response->html($this->template->render('task_move_position/show', array(
'task' => $task,
'board' => BoardFormatter::getInstance($this->container)
'board' => $this->boardFormatter
->withProjectId($task['project_id'])
->withQuery($this->taskFinderModel->getExtendedQuery()
->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN)

View File

@ -3,8 +3,6 @@
namespace Kanboard\Controller;
use Kanboard\Filter\UserNameFilter;
use Kanboard\Formatter\UserAutoCompleteFormatter;
use Kanboard\Formatter\UserMentionFormatter;
use Kanboard\Model\UserModel;
/**
@ -25,7 +23,7 @@ class UserAjaxController extends BaseController
$search = $this->request->getStringParam('term');
$filter = $this->userQuery->withFilter(new UserNameFilter($search));
$filter->getQuery()->asc(UserModel::TABLE.'.name')->asc(UserModel::TABLE.'.username');
$this->response->json($filter->format(new UserAutoCompleteFormatter($this->container)));
$this->response->json($filter->format($this->userAutoCompleteFormatter));
}
/**
@ -39,11 +37,7 @@ class UserAjaxController extends BaseController
$query = $this->request->getStringParam('search');
$users = $this->projectPermissionModel->findUsernames($project_id, $query);
$this->response->json(
UserMentionFormatter::getInstance($this->container)
->withUsers($users)
->format()
);
$this->response->json($this->userMentionFormatter->withUsers($users)->format());
}
/**

View File

@ -62,6 +62,21 @@ use Pimple\Container;
* @property \Kanboard\Decorator\ColumnRestrictionCacheDecorator $columnRestrictionCacheDecorator
* @property \Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator $columnMoveRestrictionCacheDecorator
* @property \Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator $projectRoleRestrictionCacheDecorator
* @property \Kanboard\Formatter\BoardColumnFormatter $boardColumnFormatter
* @property \Kanboard\Formatter\BoardFormatter $boardFormatter
* @property \Kanboard\Formatter\BoardSwimlaneFormatter $boardSwimlaneFormatter
* @property \Kanboard\Formatter\BoardTaskFormatter $boardTaskFormatter
* @property \Kanboard\Formatter\GroupAutoCompleteFormatter $groupAutoCompleteFormatter
* @property \Kanboard\Formatter\ProjectActivityEventFormatter $projectActivityEventFormatter
* @property \Kanboard\Formatter\ProjectGanttFormatter $projectGanttFormatter
* @property \Kanboard\Formatter\SubtaskTimeTrackingCalendarFormatter $subtaskTimeTrackingCalendarFormatter
* @property \Kanboard\Formatter\TaskAutoCompleteFormatter $taskAutoCompleteFormatter
* @property \Kanboard\Formatter\TaskCalendarFormatter $taskCalendarFormatter
* @property \Kanboard\Formatter\TaskGanttFormatter $taskGanttFormatter
* @property \Kanboard\Formatter\TaskICalFormatter $taskICalFormatter
* @property \Kanboard\Formatter\TaskSuggestMenuFormatter $taskSuggestMenuFormatter
* @property \Kanboard\Formatter\UserAutoCompleteFormatter $userAutoCompleteFormatter
* @property \Kanboard\Formatter\UserMentionFormatter $userMentionFormatter
* @property \Kanboard\Model\ActionModel $actionModel
* @property \Kanboard\Model\ActionParameterModel $actionParameterModel
* @property \Kanboard\Model\AvatarFileModel $avatarFileModel

View File

@ -17,7 +17,7 @@ interface FormatterInterface
*
* @access public
* @param Table $query
* @return FormatterInterface
* @return $this
*/
public function withQuery(Table $query);

View File

@ -41,7 +41,7 @@ class Tool
}
/**
* Build dependency injection container from an array
* Build dependency injection containers from an array
*
* @static
* @access public
@ -63,6 +63,29 @@ class Tool
return $container;
}
/**
* Build dependency injection container from an array
*
* @static
* @access public
* @param Container $container
* @param array $namespaces
* @return Container
*/
public static function buildFactories(Container $container, array $namespaces)
{
foreach ($namespaces as $namespace => $classes) {
foreach ($classes as $name) {
$class = '\\Kanboard\\'.$namespace.'\\'.$name;
$container[lcfirst($name)] = $container->factory(function ($c) use ($class) {
return new $class($c);
});
}
}
return $container;
}
/**
* Build dependency injection container for custom helpers from an array
*

View File

@ -79,7 +79,7 @@ class BoardColumnFormatter extends BaseFormatter implements FormatterInterface
{
foreach ($this->columns as &$column) {
$column['id'] = (int) $column['id'];
$column['tasks'] = BoardTaskFormatter::getInstance($this->container)
$column['tasks'] = $this->boardTaskFormatter
->withTasks($this->tasks)
->withTags($this->tags)
->withSwimlaneId($this->swimlaneId)

View File

@ -59,7 +59,7 @@ class BoardFormatter extends BaseFormatter implements FormatterInterface
$task_ids = array_column($tasks, 'id');
$tags = $this->taskTagModel->getTagsByTasks($task_ids);
return BoardSwimlaneFormatter::getInstance($this->container)
return $this->boardSwimlaneFormatter
->withSwimlanes($swimlanes)
->withColumns($columns)
->withTasks($tasks)

View File

@ -82,7 +82,7 @@ class BoardSwimlaneFormatter extends BaseFormatter implements FormatterInterface
foreach ($this->swimlanes as &$swimlane) {
$swimlane['id'] = (int) $swimlane['id'];
$swimlane['columns'] = BoardColumnFormatter::getInstance($this->container)
$swimlane['columns'] = $this->boardColumnFormatter
->withSwimlaneId($swimlane['id'])
->withColumns($this->columns)
->withTasks($this->tasks)

View File

@ -12,36 +12,26 @@ use PicoDb\Table;
* @package formatter
* @author Frederic Guillot
*/
class GroupAutoCompleteFormatter implements FormatterInterface
class GroupAutoCompleteFormatter extends BaseFormatter implements FormatterInterface
{
/**
* Groups found
*
* @access private
* @access protected
* @var GroupProviderInterface[]
*/
private $groups;
protected $groups;
/**
* Format groups for the ajax auto-completion
* Set groups
*
* @access public
* @param GroupProviderInterface[] $groups
* @return $this
*/
public function __construct(array $groups)
public function withGroups(array $groups)
{
$this->groups = $groups;
}
/**
* Set query
*
* @access public
* @param Table $query
* @return FormatterInterface
*/
public function withQuery(Table $query)
{
return $this;
}

View File

@ -14,7 +14,7 @@ use Kanboard\Core\Filter\FormatterInterface;
class UserAutoCompleteFormatter extends BaseFormatter implements FormatterInterface
{
/**
* Format the tasks for the ajax autocompletion
* Format the tasks for the ajax auto-completion
*
* @access public
* @return array

View File

@ -5,8 +5,6 @@ namespace Kanboard\Helper;
use Kanboard\Core\Base;
use Kanboard\Core\Filter\QueryBuilder;
use Kanboard\Filter\TaskDueDateRangeFilter;
use Kanboard\Formatter\SubtaskTimeTrackingCalendarFormatter;
use Kanboard\Formatter\TaskCalendarFormatter;
/**
* Calendar Helper
@ -44,7 +42,7 @@ class CalendarHelper extends Base
*/
public function getTaskDateDueEvents(QueryBuilder $queryBuilder, $start, $end)
{
$formatter = new TaskCalendarFormatter($this->container);
$formatter = $this->taskCalendarFormatter;
$formatter->setFullDay();
$formatter->setColumns('date_due');
@ -73,7 +71,7 @@ class CalendarHelper extends Base
'date_due'
));
$formatter = new TaskCalendarFormatter($this->container);
$formatter = $this->taskCalendarFormatter;
$formatter->setColumns($startColumn, 'date_due');
return $queryBuilder->format($formatter);
@ -90,8 +88,7 @@ class CalendarHelper extends Base
*/
public function getSubtaskTimeTrackingEvents($user_id, $start, $end)
{
$formatter = new SubtaskTimeTrackingCalendarFormatter($this->container);
return $formatter
return $this->subtaskTimeTrackingCalendarFormatter
->withQuery($this->subtaskTimeTrackingModel->getUserQuery($user_id)
->addCondition($this->getCalendarCondition(
$this->dateParser->getTimestampFromIsoFormat($start),

View File

@ -5,7 +5,6 @@ namespace Kanboard\Helper;
use Kanboard\Core\Base;
use Kanboard\Core\Filter\QueryBuilder;
use Kanboard\Filter\TaskDueDateRangeFilter;
use Kanboard\Formatter\TaskICalFormatter;
use Eluceo\iCal\Component\Calendar as iCalendar;
/**
@ -29,10 +28,10 @@ class ICalHelper extends Base
{
$queryBuilder->withFilter(new TaskDueDateRangeFilter(array($start, $end)));
$formatter = new TaskICalFormatter($this->container);
$formatter->setColumns('date_due');
$formatter->setCalendar($calendar);
$formatter->withQuery($queryBuilder->getQuery());
$formatter->addFullDayEvents();
$this->taskICalFormatter
->setColumns('date_due')
->setCalendar($calendar)
->withQuery($queryBuilder->getQuery())
->addFullDayEvents();
}
}

View File

@ -6,7 +6,6 @@ use Kanboard\Core\Base;
use Kanboard\Filter\ProjectActivityProjectIdFilter;
use Kanboard\Filter\ProjectActivityProjectIdsFilter;
use Kanboard\Filter\ProjectActivityTaskIdFilter;
use Kanboard\Formatter\ProjectActivityEventFormatter;
use Kanboard\Model\ProjectActivityModel;
/**
@ -38,7 +37,7 @@ class ProjectActivityHelper extends Base
->limit(500)
;
$events = $queryBuilder->format(new ProjectActivityEventFormatter($this->container));
$events = $queryBuilder->format($this->projectActivityEventFormatter);
}
return $events;
@ -62,7 +61,7 @@ class ProjectActivityHelper extends Base
->limit($limit)
;
return $queryBuilder->format(new ProjectActivityEventFormatter($this->container));
return $queryBuilder->format($this->projectActivityEventFormatter);
}
/**
@ -83,7 +82,7 @@ class ProjectActivityHelper extends Base
->limit($limit)
;
return $queryBuilder->format(new ProjectActivityEventFormatter($this->container));
return $queryBuilder->format($this->projectActivityEventFormatter);
}
/**
@ -100,6 +99,6 @@ class ProjectActivityHelper extends Base
$queryBuilder->getQuery()->desc(ProjectActivityModel::TABLE.'.id');
return $queryBuilder->format(new ProjectActivityEventFormatter($this->container));
return $queryBuilder->format($this->projectActivityEventFormatter);
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Kanboard\ServiceProvider;
use Kanboard\Core\Tool;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
/**
* Class FormatterProvider
*
* @package Kanboard\ServiceProvider
* @author Frederic Guillot
*/
class FormatterProvider implements ServiceProviderInterface
{
protected $formatters = array(
'Formatter' => array(
'BoardColumnFormatter',
'BoardFormatter',
'BoardSwimlaneFormatter',
'BoardTaskFormatter',
'GroupAutoCompleteFormatter',
'ProjectActivityEventFormatter',
'ProjectGanttFormatter',
'SubtaskTimeTrackingCalendarFormatter',
'TaskAutoCompleteFormatter',
'TaskCalendarFormatter',
'TaskGanttFormatter',
'TaskICalFormatter',
'TaskSuggestMenuFormatter',
'UserAutoCompleteFormatter',
'UserMentionFormatter',
)
);
/**
* Registers services on the given container.
*
* @param Container $container
* @return Container
*/
public function register(Container $container)
{
Tool::buildFactories($container, $this->formatters);
return $container;
}
}

View File

@ -48,6 +48,7 @@ $container->register(new Kanboard\ServiceProvider\ExternalLinkProvider());
$container->register(new Kanboard\ServiceProvider\ExternalTaskProvider());
$container->register(new Kanboard\ServiceProvider\AvatarProvider());
$container->register(new Kanboard\ServiceProvider\FilterProvider());
$container->register(new Kanboard\ServiceProvider\FormatterProvider());
$container->register(new Kanboard\ServiceProvider\JobProvider());
$container->register(new Kanboard\ServiceProvider\QueueProvider());
$container->register(new Kanboard\ServiceProvider\ApiProvider());

View File

@ -40,3 +40,34 @@ You can still use the original template using the "kanboard:" prefix:
```php
<?= $this->render('kanboard:header') ?>
```
Formatter Overrides
-------------------
Here an example to override formatter objects in Kanboard:
```php
class MyFormatter extends UserAutoCompleteFormatter {
public function format()
{
$users = parent::format();
foreach ($users as &$user) {
$user['label'] = 'something'; // Do something useful here
}
return $users;
}
}
class Plugin extends Base
{
public function initialize()
{
$this->container['userAutoCompleteFormatter'] = $this->container->factory(function ($c) {
return new MyFormatter($c);
});
}
}
```

View File

@ -47,6 +47,7 @@ abstract class Base extends PHPUnit_Framework_TestCase
$this->container->register(new Kanboard\ServiceProvider\RouteProvider());
$this->container->register(new Kanboard\ServiceProvider\AvatarProvider());
$this->container->register(new Kanboard\ServiceProvider\FilterProvider());
$this->container->register(new Kanboard\ServiceProvider\FormatterProvider());
$this->container->register(new Kanboard\ServiceProvider\JobProvider());
$this->container->register(new Kanboard\ServiceProvider\QueueProvider());
$this->container->register(new Kanboard\ServiceProvider\ExternalTaskProvider());

View File

@ -0,0 +1,21 @@
<?php
use Kanboard\ServiceProvider\ClassProvider;
use Pimple\Container;
require_once __DIR__.'/../Base.php';
class ModelProviderTest extends Base
{
public function testServiceInstance()
{
$container = new Container();
$serviceProvider = new ClassProvider($container);
$serviceProvider->register($container);
$instance1 = $container['userModel'];
$instance2 = $container['userModel'];
$this->assertSame($instance1, $instance2);
}
}

View File

@ -0,0 +1,21 @@
<?php
use Kanboard\ServiceProvider\FormatterProvider;
use Pimple\Container;
require_once __DIR__.'/../Base.php';
class FormatterProviderTest extends Base
{
public function testServiceInstance()
{
$container = new Container();
$serviceProvider = new FormatterProvider($container);
$serviceProvider->register($container);
$instance1 = $container['userAutoCompleteFormatter'];
$instance2 = $container['userAutoCompleteFormatter'];
$this->assertNotSame($instance1, $instance2);
}
}