diff options
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | app/Controller/Calendar.php | 15 | ||||
-rw-r--r-- | app/Controller/Config.php | 2 | ||||
-rw-r--r-- | app/Core/Base.php | 1 | ||||
-rw-r--r-- | app/Core/Plugin/Hook.php | 70 | ||||
-rw-r--r-- | app/Helper/Hook.php | 10 | ||||
-rw-r--r-- | app/Model/SubtaskForecast.php | 124 | ||||
-rw-r--r-- | app/Schema/Mysql.php | 1 | ||||
-rw-r--r-- | app/Schema/Postgres.php | 1 | ||||
-rw-r--r-- | app/Schema/Sqlite.php | 1 | ||||
-rw-r--r-- | app/ServiceProvider/ClassProvider.php | 4 | ||||
-rw-r--r-- | app/Template/app/sidebar.php | 2 | ||||
-rw-r--r-- | app/Template/config/calendar.php | 1 | ||||
-rw-r--r-- | app/Template/config/sidebar.php | 2 | ||||
-rw-r--r-- | app/Template/export/sidebar.php | 2 | ||||
-rw-r--r-- | app/Template/layout.php | 6 | ||||
-rw-r--r-- | app/Template/project/dropdown.php | 2 | ||||
-rw-r--r-- | app/Template/project/sidebar.php | 2 | ||||
-rw-r--r-- | app/Template/project_user/sidebar.php | 2 | ||||
-rw-r--r-- | app/Template/task/sidebar.php | 4 | ||||
-rw-r--r-- | app/Template/user/sidebar.php | 4 | ||||
-rw-r--r-- | doc/plugins.markdown | 84 | ||||
-rw-r--r-- | tests/units/Core/Plugin/HookTest.php | 62 | ||||
-rw-r--r-- | tests/units/Helper/HookHelperTest.php | 40 |
24 files changed, 275 insertions, 172 deletions
@@ -10,9 +10,10 @@ New features: * Added API procedures: getMyOverdueTasks, getOverdueTasksByProject and GetMyProjects * Added user API access for procedure getProjectActivity() -Breaking changes: +Core functionalities moved to plugins: -* Budget planning is now a plugin and it's not part of core +* Budget planning: https://github.com/kanboard/plugin-budget +* SubtaskForecast: https://github.com/kanboard/plugin-subtask-forecast Improvements: diff --git a/app/Controller/Calendar.php b/app/Controller/Calendar.php index 8a24d705..5ac92622 100644 --- a/app/Controller/Calendar.php +++ b/app/Controller/Calendar.php @@ -52,6 +52,12 @@ class Calendar extends Base // Tasks with due date $events = array_merge($events, $filter->copy()->filterByDueDateRange($start, $end)->toAllDayCalendarEvents()); + $events = $this->hook->merge('controller:calendar:project:events', $events, array( + 'project_id' => $project_id, + 'start' => $start, + 'end' => $end, + )); + $this->response->json($events); } @@ -83,10 +89,11 @@ class Calendar extends Base $events = array_merge($events, $this->subtaskTimeTracking->getUserCalendarEvents($user_id, $start, $end)); } - // Subtask estimates - if ($this->config->get('calendar_user_subtasks_forecast') == 1) { - $events = array_merge($events, $this->subtaskForecast->getCalendarEvents($user_id, $end)); - } + $events = $this->hook->merge('controller:calendar:user:events', $events, array( + 'user_id' => $user_id, + 'start' => $start, + 'end' => $end, + )); $this->response->json($events); } diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 6f14cc31..790bdcd3 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -48,7 +48,7 @@ class Config extends Base $values += array('integration_slack_webhook' => 0, 'integration_hipchat' => 0, 'integration_gravatar' => 0, 'integration_jabber' => 0); break; case 'calendar': - $values += array('calendar_user_subtasks_forecast' => 0, 'calendar_user_subtasks_time_tracking' => 0); + $values += array('calendar_user_subtasks_time_tracking' => 0); break; } diff --git a/app/Core/Base.php b/app/Core/Base.php index 5ed8f40a..2dec4b29 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -55,7 +55,6 @@ use Pimple\Container; * @property \Model\ProjectPermission $projectPermission * @property \Model\Subtask $subtask * @property \Model\SubtaskExport $subtaskExport - * @property \Model\SubtaskForecast $subtaskForecast * @property \Model\SubtaskTimeTracking $subtaskTimeTracking * @property \Model\Swimlane $swimlane * @property \Model\Task $task diff --git a/app/Core/Plugin/Hook.php b/app/Core/Plugin/Hook.php new file mode 100644 index 00000000..4fb55569 --- /dev/null +++ b/app/Core/Plugin/Hook.php @@ -0,0 +1,70 @@ +<?php + +namespace Core\Plugin; + +/** + * Plugin Hooks Handler + * + * @package plugin + * @author Frederic Guillot + */ +class Hook +{ + /** + * List of hooks + * + * @access private + * @var array + */ + private $hooks = array(); + + /** + * Bind something on a hook + * + * @access public + * @param string $hook + * @param mixed $value + */ + public function on($hook, $value) + { + if (! isset($this->hooks[$hook])) { + $this->hooks[$hook] = array(); + } + + $this->hooks[$hook][] = $value; + } + + /** + * Get all bindings for a hook + * + * @access public + * @param string $hook + * @return array + */ + public function getListeners($hook) + { + return isset($this->hooks[$hook]) ? $this->hooks[$hook] : array(); + } + + /** + * Merge listener results with input array + * + * @access public + * @param string $hook + * @param array $values + * @param array $params + * @return array + */ + public function merge($hook, array &$values, array $params = array()) + { + foreach ($this->getListeners($hook) as $listener) { + $result = call_user_func_array($listener, $params); + + if (is_array($result) && ! empty($result)) { + $values = array_merge($values, $result); + } + } + + return $values; + } +} diff --git a/app/Helper/Hook.php b/app/Helper/Hook.php index 77756757..d7fe3d34 100644 --- a/app/Helper/Hook.php +++ b/app/Helper/Hook.php @@ -10,8 +10,6 @@ namespace Helper; */ class Hook extends \Core\Base { - private $hooks = array(); - /** * Render all attached hooks * @@ -24,10 +22,8 @@ class Hook extends \Core\Base { $buffer = ''; - foreach ($this->hooks as $name => $template) { - if ($hook === $name) { - $buffer .= $this->template->render($template, $variables); - } + foreach ($this->hook->getListeners($hook) as $template) { + $buffer .= $this->template->render($template, $variables); } return $buffer; @@ -43,7 +39,7 @@ class Hook extends \Core\Base */ public function attach($hook, $template) { - $this->hooks[$hook] = $template; + $this->hook->on($hook, $template); return $this; } } diff --git a/app/Model/SubtaskForecast.php b/app/Model/SubtaskForecast.php deleted file mode 100644 index 263aa27a..00000000 --- a/app/Model/SubtaskForecast.php +++ /dev/null @@ -1,124 +0,0 @@ -<?php - -namespace Model; - -use DateTime; -use DateInterval; - -/** - * Subtask Forecast - * - * @package model - * @author Frederic Guillot - */ -class SubtaskForecast extends Base -{ - /** - * Get not completed subtasks with an estimate sorted by postition - * - * @access public - * @param integer $user_id - * @return array - */ - public function getSubtasks($user_id) - { - return $this->db - ->table(Subtask::TABLE) - ->columns(Subtask::TABLE.'.id', Task::TABLE.'.project_id', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title', Subtask::TABLE.'.time_estimated') - ->join(Task::TABLE, 'id', 'task_id') - ->asc(Task::TABLE.'.position') - ->asc(Subtask::TABLE.'.position') - ->gt(Subtask::TABLE.'.time_estimated', 0) - ->eq(Subtask::TABLE.'.status', Subtask::STATUS_TODO) - ->eq(Subtask::TABLE.'.user_id', $user_id) - ->findAll(); - } - - /** - * Get the start date for the forecast - * - * @access public - * @param integer $user_id - * @return array - */ - public function getStartDate($user_id) - { - $subtask = $this->db->table(Subtask::TABLE) - ->columns(Subtask::TABLE.'.time_estimated', SubtaskTimeTracking::TABLE.'.start') - ->eq(SubtaskTimeTracking::TABLE.'.user_id', $user_id) - ->eq(SubtaskTimeTracking::TABLE.'.end', 0) - ->status('status', Subtask::STATUS_INPROGRESS) - ->join(SubtaskTimeTracking::TABLE, 'subtask_id', 'id') - ->findOne(); - - if ($subtask && $subtask['time_estimated'] && $subtask['start']) { - return date('Y-m-d H:i', $subtask['start'] + $subtask['time_estimated'] * 3600); - } - - return date('Y-m-d H:i'); - } - - /** - * Get all calendar events according to the user timetable and the subtasks estimates - * - * @access public - * @param integer $user_id - * @param string $end End date of the calendar - * @return array - */ - public function getCalendarEvents($user_id, $end) - { - $events = array(); - $start_date = new DateTime($this->getStartDate($user_id)); - $timetable = $this->timetable->calculate($user_id, $start_date, new DateTime($end)); - $subtasks = $this->getSubtasks($user_id); - $total = count($subtasks); - $offset = 0; - - foreach ($timetable as $slot) { - - $interval = $this->dateParser->getHours($slot[0], $slot[1]); - $start = $slot[0]->getTimestamp(); - - if ($slot[0] < $start_date) { - - if (! $this->dateParser->withinDateRange($start_date, $slot[0], $slot[1])) { - continue; - } - - $interval = $this->dateParser->getHours(new DateTime, $slot[1]); - $start = time(); - } - - while ($offset < $total) { - - $event = array( - 'id' => $subtasks[$offset]['id'].'-'.$subtasks[$offset]['task_id'].'-'.$offset, - 'subtask_id' => $subtasks[$offset]['id'], - 'title' => t('#%d', $subtasks[$offset]['task_id']).' '.$subtasks[$offset]['title'], - 'url' => $this->helper->url->to('task', 'show', array('task_id' => $subtasks[$offset]['task_id'], 'project_id' => $subtasks[$offset]['project_id'])), - 'editable' => false, - 'start' => date('Y-m-d\TH:i:s', $start), - ); - - if ($subtasks[$offset]['time_estimated'] <= $interval) { - - $start += $subtasks[$offset]['time_estimated'] * 3600; - $interval -= $subtasks[$offset]['time_estimated']; - $offset++; - - $event['end'] = date('Y-m-d\TH:i:s', $start); - $events[] = $event; - } - else { - $subtasks[$offset]['time_estimated'] -= $interval; - $event['end'] = $slot[1]->format('Y-m-d\TH:i:s'); - $events[] = $event; - break; - } - } - } - - return $events; - } -} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index e5dff0d5..23a7a90a 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -166,7 +166,6 @@ function version_69($pdo) $result = $rq->fetch(PDO::FETCH_ASSOC); $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); - $rq->execute(array('calendar_user_subtasks_forecast', isset($result['subtask_forecast']) && $result['subtask_forecast'] == 1 ? 1 : 0)); $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); $rq->execute(array('calendar_user_tasks', 'date_started')); $rq->execute(array('calendar_project_tasks', 'date_started')); diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index e7422e8c..cd4c295e 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -161,7 +161,6 @@ function version_50($pdo) $result = $rq->fetch(PDO::FETCH_ASSOC); $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); - $rq->execute(array('calendar_user_subtasks_forecast', isset($result['subtask_forecast']) && $result['subtask_forecast'] == 1 ? 1 : 0)); $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); $rq->execute(array('calendar_user_tasks', 'date_started')); $rq->execute(array('calendar_project_tasks', 'date_started')); diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index b76e902a..175a583c 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -138,7 +138,6 @@ function version_68($pdo) $result = $rq->fetch(PDO::FETCH_ASSOC); $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); - $rq->execute(array('calendar_user_subtasks_forecast', isset($result['subtask_forecast']) && $result['subtask_forecast'] == 1 ? 1 : 0)); $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); $rq->execute(array('calendar_user_tasks', 'date_started')); $rq->execute(array('calendar_project_tasks', 'date_started')); diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 899574e9..ee5ddbe4 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -41,7 +41,6 @@ class ClassProvider implements ServiceProviderInterface 'ProjectPermission', 'Subtask', 'SubtaskExport', - 'SubtaskForecast', 'SubtaskTimeTracking', 'Swimlane', 'Task', @@ -80,6 +79,9 @@ class ClassProvider implements ServiceProviderInterface 'Core\Cache' => array( 'MemoryCache', ), + 'Core\Plugin' => array( + 'Hook', + ), 'Integration' => array( 'BitbucketWebhook', 'GithubWebhook', diff --git a/app/Template/app/sidebar.php b/app/Template/app/sidebar.php index f4a455f8..c1de0dbe 100644 --- a/app/Template/app/sidebar.php +++ b/app/Template/app/sidebar.php @@ -19,7 +19,7 @@ <li <?= $this->app->getRouterAction() === 'activity' ? 'class="active"' : '' ?>> <?= $this->url->link(t('My activity stream'), 'app', 'activity', array('user_id' => $user['id'])) ?> </li> - <?= $this->hook->render('dashboard:sidebar') ?> + <?= $this->hook->render('template:dashboard:sidebar') ?> </ul> <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div> <div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div> diff --git a/app/Template/config/calendar.php b/app/Template/config/calendar.php index 1cc985c8..25f4b43d 100644 --- a/app/Template/config/calendar.php +++ b/app/Template/config/calendar.php @@ -23,7 +23,6 @@ <h4><?= t('Subtasks time tracking') ?></h4> <?= $this->form->checkbox('calendar_user_subtasks_time_tracking', t('Show subtasks based on the time tracking'), 1, $values['calendar_user_subtasks_time_tracking'] == 1) ?> - <?= $this->form->checkbox('calendar_user_subtasks_forecast', t('Show subtask estimates (forecast of future work)'), 1, $values['calendar_user_subtasks_forecast'] == 1) ?> </div> <div class="form-actions"> diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php index 083da283..ed4f01e7 100644 --- a/app/Template/config/sidebar.php +++ b/app/Template/config/sidebar.php @@ -34,7 +34,7 @@ <li> <?= $this->url->link(t('Documentation'), 'doc', 'show') ?> </li> - <?= $this->hook->render('config:sidebar') ?> + <?= $this->hook->render('template:config:sidebar') ?> </ul> <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div> <div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div> diff --git a/app/Template/export/sidebar.php b/app/Template/export/sidebar.php index 7e39a5af..44448520 100644 --- a/app/Template/export/sidebar.php +++ b/app/Template/export/sidebar.php @@ -13,7 +13,7 @@ <li <?= $this->app->getRouterAction() === 'summary' ? 'class="active"' : '' ?>> <?= $this->url->link(t('Daily project summary'), 'export', 'summary', array('project_id' => $project['id'])) ?> </li> - <?= $this->hook->render('export:sidebar') ?> + <?= $this->hook->render('template:export:sidebar') ?> </ul> <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div> <div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div> diff --git a/app/Template/layout.php b/app/Template/layout.php index 934fb62c..49ac2a08 100644 --- a/app/Template/layout.php +++ b/app/Template/layout.php @@ -29,7 +29,7 @@ <title><?= isset($title) ? $this->e($title) : 'Kanboard' ?></title> - <?= $this->hook->render('layout:head') ?> + <?= $this->hook->render('template:layout:head') ?> </head> <body data-status-url="<?= $this->url->href('app', 'status') ?>" data-login-url="<?= $this->url->href('auth', 'login') ?>" @@ -40,7 +40,7 @@ <?php if (isset($no_layout) && $no_layout): ?> <?= $content_for_layout ?> <?php else: ?> - <?= $this->hook->render('layout:top') ?> + <?= $this->hook->render('template:layout:top') ?> <?= $this->render('header', array( 'title' => $title, 'description' => isset($description) ? $description : '', @@ -50,7 +50,7 @@ <?= $this->app->flashMessage() ?> <?= $content_for_layout ?> </section> - <?= $this->hook->render('layout:bottom') ?> + <?= $this->hook->render('template:layout:bottom') ?> <?php endif ?> </body> </html> diff --git a/app/Template/project/dropdown.php b/app/Template/project/dropdown.php index ee1ca3f7..96b6a43a 100644 --- a/app/Template/project/dropdown.php +++ b/app/Template/project/dropdown.php @@ -9,7 +9,7 @@ </li> <?php endif ?> -<?= $this->hook->render('project:dropdown', array('project' => $project)) ?> +<?= $this->hook->render('template:project:dropdown', array('project' => $project)) ?> <?php if ($this->user->isProjectManagementAllowed($project['id'])): ?> <li> diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php index 84bbb6b1..482a95d2 100644 --- a/app/Template/project/sidebar.php +++ b/app/Template/project/sidebar.php @@ -49,7 +49,7 @@ <?php endif ?> <?php endif ?> - <?= $this->hook->render('project:sidebar') ?> + <?= $this->hook->render('template:project:sidebar') ?> </ul> <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div> <div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div> diff --git a/app/Template/project_user/sidebar.php b/app/Template/project_user/sidebar.php index 98219a87..b81ba14a 100644 --- a/app/Template/project_user/sidebar.php +++ b/app/Template/project_user/sidebar.php @@ -25,6 +25,6 @@ <?= $this->url->link(t('Closed tasks'), 'projectuser', 'closed', $filter) ?> </li> - <?= $this->hook->render('project-user:sidebar') ?> + <?= $this->hook->render('template:project-user:sidebar') ?> </ul> </div>
\ No newline at end of file diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index cf0e9f76..9ee1e7df 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -19,7 +19,7 @@ </li> <?php endif ?> - <?= $this->hook->render('task:sidebar:information') ?> + <?= $this->hook->render('template:task:sidebar:information') ?> </ul> <h2><?= t('Actions') ?></h2> <ul> @@ -69,7 +69,7 @@ </li> <?php endif ?> - <?= $this->hook->render('task:sidebar:actions') ?> + <?= $this->hook->render('template:task:sidebar:actions') ?> </ul> <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div> <div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div> diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php index 80fe8684..6a4e9c47 100644 --- a/app/Template/user/sidebar.php +++ b/app/Template/user/sidebar.php @@ -21,7 +21,7 @@ </li> <?php endif ?> - <?= $this->hook->render('user:sidebar:information') ?> + <?= $this->hook->render('template:user:sidebar:information') ?> </ul> <h2><?= t('Actions') ?></h2> @@ -67,7 +67,7 @@ </li> <?php endif ?> - <?= $this->hook->render('user:sidebar:actions', array('user' => $user)) ?> + <?= $this->hook->render('template:user:sidebar:actions', array('user' => $user)) ?> <?php if ($this->user->isAdmin() && ! $this->user->isCurrentUser($user['id'])): ?> <li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'remove' ? 'class="active"' : '' ?>> diff --git a/doc/plugins.markdown b/doc/plugins.markdown index 4388f7c6..c8222a81 100644 --- a/doc/plugins.markdown +++ b/doc/plugins.markdown @@ -53,7 +53,7 @@ class Plugin extends Plugin\Base { public function initialize() { - $this->template->hook->attach('layout:head', 'theme:layout/head'); + $this->template->hook->attach('template:layout:head', 'theme:layout/head'); } } ``` @@ -65,7 +65,7 @@ The only required method is `initialize()`. This method is called for each reque Plugin methods -------------- -Available methods from `Plugin\Base`: +Available methods from `Core\Plugin\Base`: - `initialize()`: Executed when the plugin is loaded - `getClasses()`: Return all classes that should be stored in the dependency injection container @@ -79,6 +79,60 @@ This example will fetch the user #123: $this->user->getById(123); ``` +Application Hooks +----------------- + +Hooks can extend, replace, filter data or change the default behavior. Each hook is identified with a unique name, example: `controller:calendar:user:events` + +### Listen on hook events + +In your `initialize()` method you need to call the method `on()` of the class `Core\Plugin\Hook`: + +```php +$this->hook->on('hook_name', $callable); +``` + +The first argument is the name of the hook and the second is a PHP callable. + +### Merge hooks + +"Merge hooks" act in the same way as the function `array_merge`. The hook callback must return an array. This array will be merged with the default one. + +Example to add events in the user calendar: + +```php +class Plugin extends Base +{ + public function initialize() + { + $container = $this->container; + + $this->hook->on('controller:calendar:user:events', function($user_id, $start, $end) use ($container) { + $model = new SubtaskForecast($container); + return $model->getCalendarEvents($user_id, $end); // Return new events + }); + } +} +``` + +List of merge hooks: + +#### controller:calendar:project:events + +- Add more events to the project calendar +- Arguments: + - `$project_id` (integer) + - `$start` Calendar start date (string, ISO-8601 format) + - `$end` Calendar` end date (string, ISO-8601 format) + +#### controller:calendar:user:events + +- Add more events to the user calendar +- Arguments: + - `$user_id` (integer) + - `$start` Calendar start date (string, ISO-8601 format) + - `$end` Calendar end date (string, ISO-8601 format) + Template hooks -------------- @@ -87,7 +141,7 @@ Template hooks allow to add new content in existing templates. Example to add new content in the dashboard sidebar: ```php -$this->template->hook->attach('dashboard:sidebar', 'myplugin:dashboard/sidebar'); +$this->template->hook->attach('template:dashboard:sidebar', 'myplugin:dashboard/sidebar'); ``` This call is usually defined in the `initialize()` method. @@ -106,18 +160,18 @@ Template name without prefix are core templates. List of template hooks: -- `dashboard:sidebar` -- `config:sidebar` -- `export:sidebar` -- `layout:head` -- `layout:top` -- `layout:bottom` -- `project:dropdown` -- `project-user:sidebar` -- `task:sidebar:information` -- `task:sidebar:actions` -- `user:sidebar:information` -- `user:sidebar:actions` +- `template:dashboard:sidebar` +- `template:config:sidebar` +- `template:export:sidebar` +- `template:layout:head` +- `template:layout:top` +- `template:layout:bottom` +- `template:project:dropdown` +- `template:project-user:sidebar` +- `template:task:sidebar:information` +- `template:task:sidebar:actions` +- `template:user:sidebar:information` +- `template:user:sidebar:actions` Other template hooks can be added if necessary, just ask on the issue tracker. diff --git a/tests/units/Core/Plugin/HookTest.php b/tests/units/Core/Plugin/HookTest.php new file mode 100644 index 00000000..95434ce4 --- /dev/null +++ b/tests/units/Core/Plugin/HookTest.php @@ -0,0 +1,62 @@ +<?php + +require_once __DIR__.'/../../Base.php'; + +use Core\Plugin\Hook; + +class HookTest extends Base +{ + public function testGetListeners() + { + $h = new Hook; + $this->assertEmpty($h->getListeners('myhook')); + + $h->on('myhook', 'A'); + $h->on('myhook', 'B'); + + $this->assertEquals(array('A', 'B'), $h->getListeners('myhook')); + } + + public function testMergeWithNoBinding() + { + $h = new Hook; + $values = array('A', 'B'); + + $result = $h->merge('myhook', $values, array('p' => 'c')); + $this->assertEquals($values, $result); + } + + public function testMergeWithBindings() + { + $h = new Hook; + $values = array('A', 'B'); + $expected = array('A', 'B', 'c', 'D'); + + $h->on('myhook', function($p) { + return array($p); + }); + + $h->on('myhook', function() { + return array('D'); + }); + + $result = $h->merge('myhook', $values, array('p' => 'c')); + $this->assertEquals($expected, $result); + $this->assertEquals($expected, $values); + } + + public function testMergeWithBindingButReturningBadData() + { + $h = new Hook; + $values = array('A', 'B'); + $expected = array('A', 'B'); + + $h->on('myhook', function() { + return 'string'; + }); + + $result = $h->merge('myhook', $values); + $this->assertEquals($expected, $result); + $this->assertEquals($expected, $values); + } +} diff --git a/tests/units/Helper/HookHelperTest.php b/tests/units/Helper/HookHelperTest.php new file mode 100644 index 00000000..6661c90b --- /dev/null +++ b/tests/units/Helper/HookHelperTest.php @@ -0,0 +1,40 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Helper\Hook; + +class HookHelperTest extends Base +{ + public function testMultipleHooks() + { + $this->container['template'] = $this + ->getMockBuilder('\Core\Template') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('render')) + ->getMock(); + + $this->container['template'] + ->expects($this->at(0)) + ->method('render') + ->with( + $this->equalTo('tpl1'), + $this->equalTo(array()) + ) + ->will($this->returnValue('tpl1_content')); + + $this->container['template'] + ->expects($this->at(1)) + ->method('render') + ->with( + $this->equalTo('tpl2'), + $this->equalTo(array()) + ) + ->will($this->returnValue('tpl2_content')); + + $h = new Hook($this->container); + $h->attach('test', 'tpl1'); + $h->attach('test', 'tpl2'); + $this->assertEquals('tpl1_contenttpl2_content', $h->render('test')); + } +}
\ No newline at end of file |