commit
7459bc1c40
10
ChangeLog
10
ChangeLog
|
|
@ -10,10 +10,20 @@ New features:
|
|||
|
||||
Improvements:
|
||||
|
||||
* Category and user filters do not append anymore in search field
|
||||
* Added more template hooks
|
||||
* Added tasks search with the API
|
||||
* Added priority field to API procedures
|
||||
* Added API procedure "getMemberGroups"
|
||||
* Added parameters for overdue tasks notifications: group by projects and send only to managers
|
||||
* Allow people to install Kanboard outside of the DocumentRoot
|
||||
* Allow plugins to be loaded from another folder
|
||||
* Filter/Lexer/QueryBuilder refactoring
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Fixed wrong URL on comment toggle link for sorting
|
||||
* Fixed form submission with Meta+Enter keyboard shortcut
|
||||
* Removed PHP notices in comment suppression view
|
||||
|
||||
Version 1.0.27
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
use JsonRPC\AuthenticationFailure;
|
||||
use JsonRPC\Exception\AuthenticationFailureException;
|
||||
|
||||
/**
|
||||
* Base class
|
||||
|
|
@ -32,7 +32,7 @@ class Auth extends Base
|
|||
$this->checkProcedurePermission(false, $method);
|
||||
} else {
|
||||
$this->logger->error('API authentication failure for '.$username);
|
||||
throw new AuthenticationFailure('Wrong credentials');
|
||||
throw new AuthenticationFailureException('Wrong credentials');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
use JsonRPC\AccessDeniedException;
|
||||
use JsonRPC\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* Base class
|
||||
|
|
@ -40,6 +40,7 @@ abstract class Base extends \Kanboard\Core\Base
|
|||
'getBoard',
|
||||
'getProjectActivity',
|
||||
'getOverdueTasksByProject',
|
||||
'searchTasks',
|
||||
);
|
||||
|
||||
public function checkProcedurePermission($is_user, $procedure)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ namespace Kanboard\Api;
|
|||
*/
|
||||
class GroupMember extends \Kanboard\Core\Base
|
||||
{
|
||||
public function getMemberGroups($user_id)
|
||||
{
|
||||
return $this->groupMember->getGroups($user_id);
|
||||
}
|
||||
|
||||
public function getGroupMembers($group_id)
|
||||
{
|
||||
return $this->groupMember->getMembers($group_id);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kanboard\Api;
|
||||
|
||||
use Kanboard\Filter\TaskProjectFilter;
|
||||
use Kanboard\Model\Task as TaskModel;
|
||||
|
||||
/**
|
||||
|
|
@ -12,6 +13,12 @@ use Kanboard\Model\Task as TaskModel;
|
|||
*/
|
||||
class Task extends Base
|
||||
{
|
||||
public function searchTasks($project_id, $query)
|
||||
{
|
||||
$this->checkProjectPermission($project_id);
|
||||
return $this->taskLexer->build($query)->withFilter(new TaskProjectFilter($project_id))->toArray();
|
||||
}
|
||||
|
||||
public function getTask($task_id)
|
||||
{
|
||||
$this->checkTaskPermission($task_id);
|
||||
|
|
@ -75,7 +82,7 @@ class Task extends Base
|
|||
}
|
||||
|
||||
public function createTask($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0,
|
||||
$date_due = '', $description = '', $category_id = 0, $score = 0, $swimlane_id = 0,
|
||||
$date_due = '', $description = '', $category_id = 0, $score = 0, $swimlane_id = 0, $priority = 0,
|
||||
$recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0,
|
||||
$recurrence_basedate = 0, $reference = '')
|
||||
{
|
||||
|
|
@ -107,6 +114,7 @@ class Task extends Base
|
|||
'recurrence_timeframe' => $recurrence_timeframe,
|
||||
'recurrence_basedate' => $recurrence_basedate,
|
||||
'reference' => $reference,
|
||||
'priority' => $priority,
|
||||
);
|
||||
|
||||
list($valid, ) = $this->taskValidator->validateCreation($values);
|
||||
|
|
@ -115,7 +123,7 @@ class Task extends Base
|
|||
}
|
||||
|
||||
public function updateTask($id, $title = null, $color_id = null, $owner_id = null,
|
||||
$date_due = null, $description = null, $category_id = null, $score = null,
|
||||
$date_due = null, $description = null, $category_id = null, $score = null, $priority = null,
|
||||
$recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null,
|
||||
$recurrence_timeframe = null, $recurrence_basedate = null, $reference = null)
|
||||
{
|
||||
|
|
@ -146,6 +154,7 @@ class Task extends Base
|
|||
'recurrence_timeframe' => $recurrence_timeframe,
|
||||
'recurrence_basedate' => $recurrence_basedate,
|
||||
'reference' => $reference,
|
||||
'priority' => $priority,
|
||||
);
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use Symfony\Component\Console\Command\Command;
|
|||
* @property \Kanboard\Model\User $user
|
||||
* @property \Kanboard\Model\UserNotification $userNotification
|
||||
* @property \Kanboard\Model\UserNotificationFilter $userNotificationFilter
|
||||
* @property \Kanboard\Model\ProjectUserRole $projectUserRole
|
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
*/
|
||||
abstract class BaseCommand extends Command
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Kanboard\Console;
|
||||
|
||||
use Kanboard\Model\Task;
|
||||
use Kanboard\Core\Security\Role;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
|
@ -15,12 +16,20 @@ class TaskOverdueNotificationCommand extends BaseCommand
|
|||
$this
|
||||
->setName('notification:overdue-tasks')
|
||||
->setDescription('Send notifications for overdue tasks')
|
||||
->addOption('show', null, InputOption::VALUE_NONE, 'Show sent 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');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$tasks = $this->sendOverdueTaskNotifications();
|
||||
if ($input->getOption('group')) {
|
||||
$tasks = $this->sendGroupOverdueTaskNotifications();
|
||||
} elseif ($input->getOption('manager')) {
|
||||
$tasks = $this->sendOverdueTaskNotificationsToManagers();
|
||||
} else {
|
||||
$tasks = $this->sendOverdueTaskNotifications();
|
||||
}
|
||||
|
||||
if ($input->getOption('show')) {
|
||||
$this->showTable($output, $tasks);
|
||||
|
|
@ -49,6 +58,54 @@ class TaskOverdueNotificationCommand extends BaseCommand
|
|||
->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all overdue tasks for one user in one email
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function sendGroupOverdueTaskNotifications()
|
||||
{
|
||||
$tasks = $this->taskFinder->getOverdueTasks();
|
||||
|
||||
foreach ($this->groupByColumn($tasks, 'owner_id') as $user_tasks) {
|
||||
$users = $this->userNotification->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
|
||||
*/
|
||||
public function sendOverdueTaskNotificationsToManagers()
|
||||
{
|
||||
$tasks = $this->taskFinder->getOverdueTasks();
|
||||
|
||||
foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) {
|
||||
$users = $this->userNotification->getUsersWithNotificationEnabled($project_id);
|
||||
$managers = array();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$role = $this->projectUserRole->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
|
||||
*
|
||||
|
|
@ -79,10 +136,12 @@ class TaskOverdueNotificationCommand extends BaseCommand
|
|||
public function sendUserOverdueTaskNotifications(array $user, array $tasks)
|
||||
{
|
||||
$user_tasks = array();
|
||||
$project_names = array();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
if ($this->userNotificationFilter->shouldReceiveNotification($user, array('task' => $task))) {
|
||||
$user_tasks[] = $task;
|
||||
$project_names[$task['project_id']] = $task['project_name'];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,11 +149,27 @@ class TaskOverdueNotificationCommand extends BaseCommand
|
|||
$this->userNotification->sendUserNotification(
|
||||
$user,
|
||||
Task::EVENT_OVERDUE,
|
||||
array('tasks' => $user_tasks, 'project_name' => $tasks[0]['project_name'])
|
||||
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->userNotification->sendUserNotification(
|
||||
$manager,
|
||||
Task::EVENT_OVERDUE,
|
||||
array('tasks' => $tasks, 'project_name' => $tasks[0]['project_name'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group a collection of records by a column
|
||||
*
|
||||
|
|
|
|||
|
|
@ -173,6 +173,6 @@ class Comment extends Base
|
|||
$order = $this->userSession->getCommentSorting() === 'ASC' ? 'DESC' : 'ASC';
|
||||
$this->userSession->setCommentSorting($order);
|
||||
|
||||
$this->response->redirect($this->helper->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'comments'));
|
||||
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), 'comments'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Kanboard\Core\Plugin;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use DirectoryIterator;
|
||||
use PDOException;
|
||||
use LogicException;
|
||||
|
|
@ -39,6 +40,10 @@ class Loader extends \Kanboard\Core\Base
|
|||
public function scan()
|
||||
{
|
||||
if (file_exists(PLUGINS_DIR)) {
|
||||
$loader = new ClassLoader();
|
||||
$loader->addPsr4('Kanboard\Plugin\\', PLUGINS_DIR);
|
||||
$loader->register();
|
||||
|
||||
$dir = new DirectoryIterator(PLUGINS_DIR);
|
||||
|
||||
foreach ($dir as $fileinfo) {
|
||||
|
|
@ -68,8 +73,7 @@ class Loader extends \Kanboard\Core\Base
|
|||
|
||||
$instance = new $class($this->container);
|
||||
|
||||
Tool::buildDic($this->container, $instance->getClasses());
|
||||
|
||||
Tool::buildDIC($this->container, $instance->getClasses());
|
||||
Tool::buildDICHelpers($this->container, $instance->getHelpers());
|
||||
|
||||
$instance->initialize();
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class Template
|
|||
}
|
||||
|
||||
if ($plugin !== 'kanboard' && $plugin !== '') {
|
||||
return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', '..', 'plugins', ucfirst($plugin), 'Template', $template.'.php'));
|
||||
return implode(DIRECTORY_SEPARATOR, array(PLUGINS_DIR, ucfirst($plugin), 'Template', $template.'.php'));
|
||||
}
|
||||
|
||||
return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'Template', $template.'.php'));
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'pregled ploče na Kanboard-u',
|
||||
'The task have been moved to the first swimlane' => 'Zadatak je premješten u prvu swimline traku',
|
||||
'The task have been moved to another swimlane:' => 'Zadatak je premješten u drugu swimline traku',
|
||||
'Overdue tasks for the project "%s"' => 'Zadaci u kašnjenju za projekat "%s"',
|
||||
'Overdue tasks for the project(s) "%s"' => 'Zadaci u kašnjenju za projekat(te) "%s"',
|
||||
'New title: %s' => 'Novi naslov: %s',
|
||||
'The task is not assigned anymore' => 'Zadatak nema više izvršioca',
|
||||
'New assignee: %s' => 'Novi izvršilac: %s',
|
||||
|
|
@ -1163,8 +1163,8 @@ return array(
|
|||
'Search by task status: ' => 'Pretraga po statusu zadatka: ',
|
||||
'Search by task title: ' => 'Pretraga po naslovu zadatka: ',
|
||||
'Activity stream search' => 'Pretraga aktivnosti',
|
||||
// 'Projects where "%s" is manager' => '',
|
||||
// 'Projects where "%s" is member' => '',
|
||||
// 'Open tasks assigned to "%s"' => '',
|
||||
// 'Closed tasks assigned to "%s"' => '',
|
||||
'Projects where "%s" is manager' => 'Projekti gdje je "%s" menadžer',
|
||||
'Projects where "%s" is member' => 'Projekti gdje je "%s" član',
|
||||
'Open tasks assigned to "%s"' => 'Otvoreni zadaci dodijeljeni "%s"',
|
||||
'Closed tasks assigned to "%s"' => 'Zatvoreni zadaci dodijeljeni "%s"',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'Pinnwand in Kanboard anzeigen',
|
||||
'The task have been moved to the first swimlane' => 'Die Aufgabe wurde in die erste Swimlane verschoben',
|
||||
'The task have been moved to another swimlane:' => 'Die Aufgaben wurde in ene andere Swimlane verschoben',
|
||||
'Overdue tasks for the project "%s"' => 'Überfällige Aufgaben für das Projekt "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Überfällige Aufgaben für das Projekt "%s"',
|
||||
'New title: %s' => 'Neuer Titel: %s',
|
||||
'The task is not assigned anymore' => 'Die Aufgabe ist nicht mehr zugewiesen',
|
||||
'New assignee: %s' => 'Neue Zuordnung: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
// 'New title: %s' => '',
|
||||
// 'The task is not assigned anymore' => '',
|
||||
// 'New assignee: %s' => '',
|
||||
|
|
|
|||
|
|
@ -558,8 +558,8 @@ return array(
|
|||
'is blocked by' => 'ist blockiert von',
|
||||
'duplicates' => 'doppelt',
|
||||
'is duplicated by' => 'ist gedoppelt von',
|
||||
'is a child of' => 'ist untergeordnet',
|
||||
'is a parent of' => 'ist übergeordnet',
|
||||
'is a child of' => 'ist ein untergeordnetes Element von',
|
||||
'is a parent of' => 'ist ein übergeordnetes Element von',
|
||||
'targets milestone' => 'betrifft Meilenstein',
|
||||
'is a milestone of' => 'ist ein Meilenstein von',
|
||||
'fixes' => 'behebt',
|
||||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'Pinnwand in Kanboard anzeigen',
|
||||
'The task have been moved to the first swimlane' => 'Die Aufgabe wurde in die erste Swimlane verschoben',
|
||||
'The task have been moved to another swimlane:' => 'Die Aufgaben wurde in ene andere Swimlane verschoben',
|
||||
'Overdue tasks for the project "%s"' => 'Überfällige Aufgaben für das Projekt "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Überfällige Aufgaben für das Projekt "%s"',
|
||||
'New title: %s' => 'Neuer Titel: %s',
|
||||
'The task is not assigned anymore' => 'Die Aufgabe ist nicht mehr zugewiesen',
|
||||
'New assignee: %s' => 'Neue Zuordnung: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'δείτε τον πίνακα στο Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'Η εργασία αυτή έχει μετακινηθεί στην πρώτη λωρίδα',
|
||||
'The task have been moved to another swimlane:' => 'Η εργασία αυτή έχει μετακινηθεί σε άλλη λωρίδα:',
|
||||
'Overdue tasks for the project "%s"' => 'Εκπρόθεσμες εργασίες για το έργο « %s »',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Εκπρόθεσμες εργασίες για το έργο « %s »',
|
||||
'New title: %s' => 'Νέος τίτλος: %s',
|
||||
'The task is not assigned anymore' => 'Η εργασία δεν έχει ανατεθεί πλέον',
|
||||
'New assignee: %s' => 'Καινούργια ανάθεση: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'ver el tablero en Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'Se ha movido la tarea a la primera calle',
|
||||
'The task have been moved to another swimlane:' => 'Se ha movido la tarea a otra calle',
|
||||
'Overdue tasks for the project "%s"' => 'Tareas atrasadas para el proyecto "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Tareas atrasadas para el proyecto "%s"',
|
||||
'New title: %s' => 'Nuevo título: %s',
|
||||
'The task is not assigned anymore' => 'La tarea ya no está asignada',
|
||||
'New assignee: %s' => 'Nuevo concesionario: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
// 'New title: %s' => '',
|
||||
// 'The task is not assigned anymore' => '',
|
||||
// 'New assignee: %s' => '',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'voir le tableau sur Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'La tâche a été déplacée dans la première swimlane',
|
||||
'The task have been moved to another swimlane:' => 'La tâche a été déplacée dans une autre swimlane :',
|
||||
'Overdue tasks for the project "%s"' => 'Tâches en retard pour le projet « %s »',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Tâches en retard pour le projet « %s »',
|
||||
'New title: %s' => 'Nouveau titre : %s',
|
||||
'The task is not assigned anymore' => 'La tâche n\'est plus assignée maintenant',
|
||||
'New assignee: %s' => 'Nouvel assigné : %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
// 'New title: %s' => '',
|
||||
// 'The task is not assigned anymore' => '',
|
||||
// 'New assignee: %s' => '',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'lihat papan di Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'Tugas telah dipindahkan ke swimlane pertama',
|
||||
'The task have been moved to another swimlane:' => 'Tugas telah dipindahkan ke swimlane lain:',
|
||||
'Overdue tasks for the project "%s"' => 'Tugas terlambat untuk proyek « %s »',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Tugas terlambat untuk proyek « %s »',
|
||||
'New title: %s' => 'Judul baru : %s',
|
||||
'The task is not assigned anymore' => 'Tugas tidak ditugaskan lagi',
|
||||
'New assignee: %s' => 'Penerima baru : %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'guarda la bacheca su Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'Il task è stato spostato nella prima corsia',
|
||||
'The task have been moved to another swimlane:' => 'Il task è stato spostato in un\'altra corsia:',
|
||||
'Overdue tasks for the project "%s"' => 'Task scaduti per il progetto "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Task scaduti per il progetto "%s"',
|
||||
'New title: %s' => 'Nuovo titolo: %s',
|
||||
'The task is not assigned anymore' => 'Il task non è più assegnato a nessuno',
|
||||
'New assignee: %s' => 'Nuovo assegnatario: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
// 'New title: %s' => '',
|
||||
// 'The task is not assigned anymore' => '',
|
||||
// 'New assignee: %s' => '',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
'New title: %s' => '제목 변경: %s',
|
||||
'The task is not assigned anymore' => '담당자 없음',
|
||||
'New assignee: %s' => '담당자 변경: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'lihat papan di Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'Tugas telah dipindahkan ke swimlane pertama',
|
||||
'The task have been moved to another swimlane:' => 'Tugas telah dipindahkan ke swimlane lain:',
|
||||
'Overdue tasks for the project "%s"' => 'Tugas terlambat untuk projek « %s »',
|
||||
'Overdue tasks for the project(s) "%s"' => 'Tugas terlambat untuk projek « %s »',
|
||||
'New title: %s' => 'Judul baru : %s',
|
||||
'The task is not assigned anymore' => 'Tugas tidak ditugaskan lagi',
|
||||
'New assignee: %s' => 'Penerima baru : %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
// 'New title: %s' => '',
|
||||
// 'The task is not assigned anymore' => '',
|
||||
// 'New assignee: %s' => '',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
'New title: %s' => 'Nieuw titel: %s',
|
||||
// 'The task is not assigned anymore' => '',
|
||||
// 'New assignee: %s' => '',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
'New title: %s' => 'Nowy tytuł: %s',
|
||||
'The task is not assigned anymore' => 'Brak osoby odpowiedzialnej za zadanie',
|
||||
'New assignee: %s' => 'Nowy odpowiedzialny: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'ver o painel no Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'A tarefa foi movida para a primeira swimlane',
|
||||
'The task have been moved to another swimlane:' => 'A tarefa foi movida para outra swimlane:',
|
||||
'Overdue tasks for the project "%s"' => 'Tarefas atrasadas para o projeto "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Tarefas atrasadas para o projeto "%s"',
|
||||
'New title: %s' => 'Novo título: %s',
|
||||
'The task is not assigned anymore' => 'Agora a tarefa não está mais atribuída',
|
||||
'New assignee: %s' => 'Novo designado: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'ver o painel no Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'A tarefa foi movida para o primeiro Swimlane',
|
||||
'The task have been moved to another swimlane:' => 'A tarefa foi movida para outro Swimlane:',
|
||||
'Overdue tasks for the project "%s"' => 'Tarefas atrasadas para o projecto "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Tarefas atrasadas para o projecto "%s"',
|
||||
'New title: %s' => 'Novo título: %s',
|
||||
'The task is not assigned anymore' => 'Tarefa já não está atribuída',
|
||||
'New assignee: %s' => 'Novo assignado: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'посмотреть доску на Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'Эта задача была перемещена в первую дорожку',
|
||||
'The task have been moved to another swimlane:' => 'Эта задача была перемещена в другую дорожку:',
|
||||
'Overdue tasks for the project "%s"' => 'Просроченные задачи для проекта "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Просроченные задачи для проекта "%s"',
|
||||
'New title: %s' => 'Новый заголовок: %s',
|
||||
'The task is not assigned anymore' => 'Задача больше не назначена',
|
||||
'New assignee: %s' => 'Новый назначенный: %s',
|
||||
|
|
@ -1153,18 +1153,18 @@ return array(
|
|||
'Upload my avatar image' => 'Загрузить моё изображение для аватара',
|
||||
'Remove my image' => 'Удалить моё изображение',
|
||||
'The OAuth2 state parameter is invalid' => 'Параметр состояние OAuth2 неправильный',
|
||||
// 'User not found.' => '',
|
||||
// 'Search in activity stream' => '',
|
||||
// 'My activities' => '',
|
||||
// 'Activity until yesterday' => '',
|
||||
// 'Activity until today' => '',
|
||||
// 'Search by creator: ' => '',
|
||||
// 'Search by creation date: ' => '',
|
||||
// 'Search by task status: ' => '',
|
||||
// 'Search by task title: ' => '',
|
||||
// 'Activity stream search' => '',
|
||||
// 'Projects where "%s" is manager' => '',
|
||||
// 'Projects where "%s" is member' => '',
|
||||
// 'Open tasks assigned to "%s"' => '',
|
||||
// 'Closed tasks assigned to "%s"' => '',
|
||||
'User not found.' => 'Пользователь не найден',
|
||||
'Search in activity stream' => 'Поиск в потоке активности',
|
||||
'My activities' => 'Мои активности',
|
||||
'Activity until yesterday' => 'Активности до вчерашнего дня',
|
||||
'Activity until today' => 'Активности до сегодня',
|
||||
'Search by creator: ' => 'Поиск по создателю: ',
|
||||
'Search by creation date: ' => 'Поиск по дате создания: ',
|
||||
'Search by task status: ' => 'Поиск по статусу задачи: ',
|
||||
'Search by task title: ' => 'Поиск по заголоску задачи: ',
|
||||
'Activity stream search' => 'Поиск в потоке активности; ',
|
||||
'Projects where "%s" is manager' => 'Проекты, где менеджером является "%s"',
|
||||
'Projects where "%s" is member' => 'Проекты, где членом является "%s"',
|
||||
'Open tasks assigned to "%s"' => 'Открытые задачи, назначенные на "%s"',
|
||||
'Closed tasks assigned to "%s"' => 'Закрытые задачи, назначенные на "%s"',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
// 'view the board on Kanboard' => '',
|
||||
// 'The task have been moved to the first swimlane' => '',
|
||||
// 'The task have been moved to another swimlane:' => '',
|
||||
// 'Overdue tasks for the project "%s"' => '',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '',
|
||||
// 'New title: %s' => '',
|
||||
// 'The task is not assigned anymore' => '',
|
||||
// 'New assignee: %s' => '',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'visa tavlan på Kanboard',
|
||||
'The task have been moved to the first swimlane' => 'Uppgiften har flyttats till första swimlane',
|
||||
'The task have been moved to another swimlane:' => 'Uppgiften har flyttats till en annan swimlane:',
|
||||
'Overdue tasks for the project "%s"' => 'Försenade uppgifter för projektet "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'Försenade uppgifter för projektet "%s"',
|
||||
'New title: %s' => 'Ny titel: %s',
|
||||
'The task is not assigned anymore' => 'Uppgiften är inte länge tilldelad',
|
||||
'New assignee: %s' => 'Ny tilldelning: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'แสดงบอร์ดบนคังบอร์ด',
|
||||
'The task have been moved to the first swimlane' => 'งานถูกย้านไปสวิมเลนแรก',
|
||||
'The task have been moved to another swimlane:' => 'งานถูกย้านไปสวิมเลนอื่น:',
|
||||
'Overdue tasks for the project "%s"' => 'งานที่เกินกำหนดสำหรับโปรเจค "%s"',
|
||||
// 'Overdue tasks for the project(s) "%s"' => 'งานที่เกินกำหนดสำหรับโปรเจค "%s"',
|
||||
'New title: %s' => 'ชื่อเรื่องใหม่: %s',
|
||||
'The task is not assigned anymore' => 'ไม่กำหนดผู้รับผิดชอบ',
|
||||
'New assignee: %s' => 'ผู้รับผิดชอบใหม่: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => 'Tabloyu Kanboard\'da görüntüle',
|
||||
'The task have been moved to the first swimlane' => 'Görev birinci kulvara taşındı',
|
||||
'The task have been moved to another swimlane:' => 'Görev başka bir kulvara taşındı:',
|
||||
'Overdue tasks for the project "%s"' => '"%s" projesi için gecikmiş görevler',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '"%s" projesi için gecikmiş görevler',
|
||||
'New title: %s' => 'Yeni başlık: %s',
|
||||
'The task is not assigned anymore' => 'Görev artık atanmamış',
|
||||
'New assignee: %s' => 'Yeni atanan: %s',
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ return array(
|
|||
'view the board on Kanboard' => '在看板上查看面板',
|
||||
'The task have been moved to the first swimlane' => '该任务已被移动到首个里程碑',
|
||||
'The task have been moved to another swimlane:' => '该任务已被移动到别的里程碑:',
|
||||
'Overdue tasks for the project "%s"' => '"%s"项目下的超期任务',
|
||||
// 'Overdue tasks for the project(s) "%s"' => '"%s"项目下的超期任务',
|
||||
'New title: %s' => '新标题:%s',
|
||||
'The task is not assigned anymore' => '该任务没有指派人',
|
||||
'New assignee: %s' => '新指派到:%s',
|
||||
|
|
|
|||
|
|
@ -108,4 +108,21 @@ class GroupMember extends Base
|
|||
->eq('user_id', $user_id)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all groups for a given user
|
||||
*
|
||||
* @access public
|
||||
* @param integer $user_id
|
||||
* @return array
|
||||
*/
|
||||
public function getGroups($user_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)
|
||||
->columns(Group::TABLE.'.id', Group::TABLE.'.name')
|
||||
->join(Group::TABLE, 'id', 'group_id')
|
||||
->eq(self::TABLE.'.user_id', $user_id)
|
||||
->asc(Group::TABLE.'.name')
|
||||
->findAll();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class TaskFinder extends Base
|
|||
Task::TABLE.'.date_started',
|
||||
Task::TABLE.'.project_id',
|
||||
Task::TABLE.'.color_id',
|
||||
Task::TABLE.'.priority',
|
||||
Task::TABLE.'.time_spent',
|
||||
Task::TABLE.'.time_estimated',
|
||||
Project::TABLE.'.name AS project_name',
|
||||
|
|
@ -67,6 +68,7 @@ class TaskFinder extends Base
|
|||
'tasks.date_creation',
|
||||
'tasks.project_id',
|
||||
'tasks.color_id',
|
||||
'tasks.priority',
|
||||
'tasks.time_spent',
|
||||
'tasks.time_estimated',
|
||||
'projects.name AS project_name'
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
<h2><?= t('My notifications') ?></h2>
|
||||
|
||||
<?php if (empty($notifications)): ?>
|
||||
<p class="alert"><?= t('No new notifications.') ?></p>
|
||||
</div>
|
||||
<p class="alert"><?= t('No new notifications.') ?></p>
|
||||
<?php else: ?>
|
||||
<ul>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<th class="column-5"><?= $paginator->order('Id', 'tasks.id') ?></th>
|
||||
<th class="column-20"><?= $paginator->order(t('Project'), 'project_name') ?></th>
|
||||
<th><?= $paginator->order(t('Task'), 'title') ?></th>
|
||||
<th class="column-5"><?= $paginator->order('Priority', 'tasks.priority') ?></th>
|
||||
<th class="column-20"><?= t('Time tracking') ?></th>
|
||||
<th class="column-20"><?= $paginator->order(t('Due date'), 'date_due') ?></th>
|
||||
</tr>
|
||||
|
|
@ -23,6 +24,11 @@
|
|||
<td>
|
||||
<?= $this->url->link($this->text->e($task['title']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($task['priority'] >= 0): ?>
|
||||
P<?= $this->text->e($task['priority'])?>
|
||||
<?php endif?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (! empty($task['time_spent'])): ?>
|
||||
<strong><?= $this->text->e($task['time_spent']).'h' ?></strong> <?= t('spent') ?>
|
||||
|
|
@ -40,4 +46,4 @@
|
|||
</table>
|
||||
|
||||
<?= $paginator ?>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,31 @@
|
|||
<h2><?= t('Overdue tasks for the project "%s"', $project_name) ?></h2>
|
||||
<h2><?= t('Overdue tasks for the project(s) "%s"', $project_name) ?></h2>
|
||||
|
||||
<table style="font-size: .8em; table-layout: fixed; width: 100%; border-collapse: collapse; border-spacing: 0; margin-bottom: 20px;" cellpadding=5 cellspacing=1>
|
||||
<tr style="background: #fbfbfb; text-align: left; padding-top: .5em; padding-bottom: .5em; padding-left: 3px; padding-right: 3px;">
|
||||
<th style="border: 1px solid #eee;"><?= t('ID') ?></th>
|
||||
<th style="border: 1px solid #eee;"><?= t('Title') ?></th>
|
||||
<th style="border: 1px solid #eee;"><?= t('Due date') ?></th>
|
||||
<th style="border: 1px solid #eee;"><?= t('Project') ?></th>
|
||||
<th style="border: 1px solid #eee;"><?= t('Assignee') ?></th>
|
||||
</tr>
|
||||
|
||||
<ul>
|
||||
<?php foreach ($tasks as $task): ?>
|
||||
<li>
|
||||
(<strong>#<?= $task['id'] ?></strong>)
|
||||
<?php if ($application_url): ?>
|
||||
<a href="<?= $this->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', true) ?>"><?= $this->text->e($task['title']) ?></a>
|
||||
<?php else: ?>
|
||||
<?= $this->text->e($task['title']) ?>
|
||||
<?php endif ?>
|
||||
(<?= $this->dt->date($task['date_due']) ?>)
|
||||
<?php if ($task['assignee_username']): ?>
|
||||
(<strong><?= t('Assigned to %s', $task['assignee_name'] ?: $task['assignee_username']) ?></strong>)
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<tr style="overflow: hidden; background: #fff; text-align: left; padding-top: .5em; padding-bottom: .5em; padding-left: 3px; padding-right: 3px;">
|
||||
<td style="border: 1px solid #eee;">#<?= $task['id'] ?></td>
|
||||
<td style="border: 1px solid #eee;">
|
||||
<?php if ($application_url): ?>
|
||||
<a href="<?= $this->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', true) ?>"><?= $this->text->e($task['title']) ?></a>
|
||||
<?php else: ?>
|
||||
<?= $this->text->e($task['title']) ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td style="border: 1px solid #eee;"><?= $this->dt->date($task['date_due']) ?></td>
|
||||
<td style="border: 1px solid #eee;"><?= $task['project_name'] ?></td>
|
||||
<td style="border: 1px solid #eee;">
|
||||
<?php if ($task['assignee_username']): ?>
|
||||
<?= t('%s', $task['assignee_name'] ?: $task['assignee_username']) ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<?php endif ?>
|
||||
|
||||
<?php if ($this->user->hasProjectAccess('ProjectEdit', 'edit', $project['id'])): ?>
|
||||
<li <?= $this->app->checkMenuSelection('ProjectEdit', 'edit') ?>>
|
||||
<li <?= $this->app->checkMenuSelection('ProjectEdit') ?>>
|
||||
<?= $this->url->link(t('Edit project'), 'ProjectEdit', 'edit', array('project_id' => $project['id'])) ?>
|
||||
</li>
|
||||
<li <?= $this->app->checkMenuSelection('project', 'share') ?>>
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
<div class="dropdown">
|
||||
<a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('User filters') ?>"><i class="fa fa-users fa-fw"></i> <i class="fa fa-caret-down"></i></a>
|
||||
<ul>
|
||||
<li><a href="#" class="filter-helper" data-append-filter="assignee:nobody"><?= t('Not assigned') ?></a></li>
|
||||
<li><a href="#" class="filter-helper" data-unique-filter="assignee:nobody"><?= t('Not assigned') ?></a></li>
|
||||
<?php foreach ($users_list as $user): ?>
|
||||
<li><a href="#" class="filter-helper" data-append-filter='assignee:"<?= $this->text->e($user) ?>"'><?= $this->text->e($user) ?></a></li>
|
||||
<li><a href="#" class="filter-helper" data-unique-filter='assignee:"<?= $this->text->e($user) ?>"'><?= $this->text->e($user) ?></a></li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -34,9 +34,9 @@
|
|||
<div class="dropdown">
|
||||
<a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Category filters') ?>"><i class="fa fa-tags fa-fw"></i><i class="fa fa-caret-down"></i></a>
|
||||
<ul>
|
||||
<li><a href="#" class="filter-helper" data-append-filter="category:none"><?= t('No category') ?></a></li>
|
||||
<li><a href="#" class="filter-helper" data-unique-filter="category:none"><?= t('No category') ?></a></li>
|
||||
<?php foreach ($categories_list as $category): ?>
|
||||
<li><a href="#" class="filter-helper" data-append-filter='category:"<?= $this->text->e($category) ?>"'><?= $this->text->e($category) ?></a></li>
|
||||
<li><a href="#" class="filter-helper" data-unique-filter='category:"<?= $this->text->e($category) ?>"'><?= $this->text->e($category) ?></a></li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<section id="task-summary">
|
||||
<h2><?= $this->text->e($task['title']) ?></h2>
|
||||
|
||||
<?= $this->hook->render('template:task:details:top', array('task' => $task)) ?>
|
||||
|
||||
<div class="task-summary-container color-<?= $task['color_id'] ?>">
|
||||
<div class="task-summary-column">
|
||||
<ul class="no-bullet">
|
||||
|
|
@ -40,6 +42,8 @@
|
|||
</li>
|
||||
<?php endif ?>
|
||||
<li class="smaller">
|
||||
|
||||
<?= $this->hook->render('template:task:details:first-column', array('task' => $task)) ?>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="task-summary-column">
|
||||
|
|
@ -64,6 +68,8 @@
|
|||
<strong><?= t('Position:') ?></strong>
|
||||
<span><?= $task['position'] ?></span>
|
||||
</li>
|
||||
|
||||
<?= $this->hook->render('template:task:details:second-column', array('task' => $task)) ?>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="task-summary-column">
|
||||
|
|
@ -102,6 +108,8 @@
|
|||
<span><?= t('%s hours', $task['time_spent']) ?></span>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $this->hook->render('template:task:details:third-column', array('task' => $task)) ?>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="task-summary-column">
|
||||
|
|
@ -132,6 +140,8 @@
|
|||
<span><?= $this->dt->datetime($task['date_moved']) ?></span>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $this->hook->render('template:task:details:fourth-column', array('task' => $task)) ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -141,4 +151,6 @@
|
|||
<?= $this->url->button('fa-play', t('Set start date'), 'taskmodification', 'start', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $this->hook->render('template:task:details:bottom', array('task' => $task)) ?>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,6 @@
|
|||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $this->hook->render('template:task:dropdown') ?>
|
||||
<?= $this->hook->render('template:task:dropdown', array('task' => $task)) ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<section id="main">
|
||||
<?= $this->projectHeader->render($project, 'Listing', 'show') ?>
|
||||
<?= $this->hook->render('template:task:layout:top', array('task' => $task)) ?>
|
||||
<section
|
||||
class="sidebar-container" id="task-view"
|
||||
data-edit-url="<?= $this->url->href('taskmodification', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"
|
||||
|
|
@ -14,4 +15,4 @@
|
|||
<?= $content_for_sublayout ?>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
'project' => $project,
|
||||
)) ?>
|
||||
|
||||
<?= $this->hook->render('template:task:show:before-attachements', array('task' => $task, 'project' => $project)) ?>
|
||||
<?= $this->hook->render('template:task:show:before-attachments', array('task' => $task, 'project' => $project)) ?>
|
||||
<?= $this->render('task_file/show', array(
|
||||
'task' => $task,
|
||||
'files' => $files,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
<?= $this->url->link(t('Time tracking'), 'task', 'timetracking', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $this->hook->render('template:task:sidebar:information', array('task' => $task)) ?>
|
||||
</ul>
|
||||
|
||||
<?php if ($this->user->hasProjectAccess('taskmodification', 'edit', $task['project_id'])): ?>
|
||||
|
|
@ -91,8 +93,8 @@
|
|||
<?= $this->url->link(t('Remove'), 'task', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $this->hook->render('template:task:sidebar:actions', array('task' => $task)) ?>
|
||||
</ul>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $this->hook->render('template:task:sidebar', array('task' => $task)) ?>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<?= $this->form->checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?= $this->hook->render('template:task:form:left-column', array('values'=>$values, 'errors'=>$errors)) ?>
|
||||
<?= $this->hook->render('template:task:form:left-column', array('values' => $values, 'errors' => $errors)) ?>
|
||||
</div>
|
||||
|
||||
<div class="form-column">
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
<?= $this->task->selectTimeEstimated($values, $errors) ?>
|
||||
<?= $this->task->selectDueDate($values, $errors) ?>
|
||||
|
||||
<?= $this->hook->render('template:task:form:right-column', array('values'=>$values, 'errors'=>$errors)) ?>
|
||||
<?= $this->hook->render('template:task:form:right-column', array('values' => $values, 'errors' => $errors)) ?>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
<?= $this->task->selectCategory($categories_list, $values, $errors) ?>
|
||||
<?= $this->task->selectPriority($project, $values) ?>
|
||||
<?= $this->task->selectScore($values, $errors) ?>
|
||||
|
||||
<?= $this->hook->render('template:task:form:left-column', array('values' => $values, 'errors' => $errors)) ?>
|
||||
</div>
|
||||
|
||||
<div class="form-column">
|
||||
|
|
@ -21,6 +23,8 @@
|
|||
<?= $this->task->selectTimeSpent($values, $errors) ?>
|
||||
<?= $this->task->selectStartDate($values, $errors) ?>
|
||||
<?= $this->task->selectDueDate($values, $errors) ?>
|
||||
|
||||
<?= $this->hook->render('template:task:form:right-column', array('values' => $values, 'errors' => $errors)) ?>
|
||||
</div>
|
||||
|
||||
<div class="form-clear">
|
||||
|
|
@ -32,4 +36,4 @@
|
|||
<?= t('or') ?>
|
||||
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ if (version_compare(PHP_VERSION, '5.4.0', '<')) {
|
|||
}
|
||||
|
||||
// Check data folder if sqlite
|
||||
if (DB_DRIVER === 'sqlite' && ! is_writable('data')) {
|
||||
throw new Exception('The directory "data" must be writeable by your web server user');
|
||||
if (DB_DRIVER === 'sqlite' && ! is_writable(dirname(DB_FILENAME))) {
|
||||
throw new Exception('The directory "'.dirname(DB_FILENAME).'" must be writeable by your web server user');
|
||||
}
|
||||
|
||||
// Check PDO extensions
|
||||
|
|
|
|||
|
|
@ -14,12 +14,16 @@ if (getenv('DATABASE_URL')) {
|
|||
define('DB_NAME', ltrim($dbopts["path"], '/'));
|
||||
}
|
||||
|
||||
if (file_exists('config.php')) {
|
||||
require 'config.php';
|
||||
$config_file = implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'config.php'));
|
||||
|
||||
if (file_exists($config_file)) {
|
||||
require $config_file;
|
||||
}
|
||||
|
||||
if (file_exists('data'.DIRECTORY_SEPARATOR.'config.php')) {
|
||||
require 'data'.DIRECTORY_SEPARATOR.'config.php';
|
||||
$config_file = implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'data', 'config.php'));
|
||||
|
||||
if (file_exists($config_file)) {
|
||||
require $config_file;
|
||||
}
|
||||
|
||||
require __DIR__.'/constants.php';
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
<?php
|
||||
|
||||
// Data directory location
|
||||
defined('DATA_DIR') or define('DATA_DIR', implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'data')));
|
||||
|
||||
// Files directory (attachments)
|
||||
defined('FILES_DIR') or define('FILES_DIR', DATA_DIR.DIRECTORY_SEPARATOR.'files');
|
||||
|
||||
// Plugins directory
|
||||
defined('PLUGINS_DIR') or define('PLUGINS_DIR', implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'plugins')));
|
||||
|
||||
// Enable/disable debug
|
||||
defined('DEBUG') or define('DEBUG', getenv('DEBUG'));
|
||||
defined('DEBUG_FILE') or define('DEBUG_FILE', getenv('DEBUG_FILE') ?: __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'data'.DIRECTORY_SEPARATOR.'debug.log');
|
||||
|
||||
// Plugin directory
|
||||
defined('PLUGINS_DIR') or define('PLUGINS_DIR', __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'plugins');
|
||||
defined('DEBUG_FILE') or define('DEBUG_FILE', getenv('DEBUG_FILE') ?: DATA_DIR.DIRECTORY_SEPARATOR.'debug.log');
|
||||
|
||||
// Application version
|
||||
defined('APP_VERSION') or define('APP_VERSION', build_app_version('$Format:%d$', '$Format:%H$'));
|
||||
|
|
@ -14,7 +20,7 @@ defined('APP_VERSION') or define('APP_VERSION', build_app_version('$Format:%d$',
|
|||
defined('DB_DRIVER') or define('DB_DRIVER', 'sqlite');
|
||||
|
||||
// Sqlite configuration
|
||||
defined('DB_FILENAME') or define('DB_FILENAME', 'data'.DIRECTORY_SEPARATOR.'db.sqlite');
|
||||
defined('DB_FILENAME') or define('DB_FILENAME', DATA_DIR.DIRECTORY_SEPARATOR.'db.sqlite');
|
||||
|
||||
// Mysql/Postgres configuration
|
||||
defined('DB_USERNAME') or define('DB_USERNAME', 'root');
|
||||
|
|
@ -82,9 +88,6 @@ defined('ENABLE_XFRAME') or define('ENABLE_XFRAME', true);
|
|||
// Syslog
|
||||
defined('ENABLE_SYSLOG') or define('ENABLE_SYSLOG', getenv('ENABLE_SYSLOG'));
|
||||
|
||||
// Default files directory
|
||||
defined('FILES_DIR') or define('FILES_DIR', 'data'.DIRECTORY_SEPARATOR.'files');
|
||||
|
||||
// Escape html inside markdown text
|
||||
defined('MARKDOWN_ESCAPE_HTML') or define('MARKDOWN_ESCAPE_HTML', true);
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -42,7 +42,17 @@ Kanboard.App.prototype.keyboardShortcuts = function() {
|
|||
|
||||
// Submit form
|
||||
Mousetrap.bindGlobal("mod+enter", function() {
|
||||
$("form").submit();
|
||||
var forms = $("form");
|
||||
|
||||
if (forms.length == 1) {
|
||||
forms.submit();
|
||||
} else if (forms.length > 1) {
|
||||
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
|
||||
$(document.activeElement).parents("form").submit();
|
||||
} else if (self.get("Popover").isOpen()) {
|
||||
$("#popover-container form").submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Open board selector
|
||||
|
|
|
|||
|
|
@ -45,10 +45,12 @@ Kanboard.Popover.prototype.isOpen = function() {
|
|||
Kanboard.Popover.prototype.open = function(link) {
|
||||
var self = this;
|
||||
|
||||
$.get(link, function(content) {
|
||||
$("body").prepend('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
|
||||
self.executeOnOpenedListeners();
|
||||
});
|
||||
if (!self.isOpen()) {
|
||||
$.get(link, function(content) {
|
||||
$("body").prepend('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
|
||||
self.executeOnOpenedListeners();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Kanboard.Popover.prototype.close = function(e) {
|
||||
|
|
|
|||
|
|
@ -16,15 +16,21 @@ Kanboard.Search.prototype.focus = function() {
|
|||
};
|
||||
|
||||
Kanboard.Search.prototype.listen = function() {
|
||||
// Filter helper for search
|
||||
$(document).on("click", ".filter-helper", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var filter = $(this).data("filter");
|
||||
var appendFilter = $(this).data("append-filter");
|
||||
var uniqueFilter = $(this).data("unique-filter");
|
||||
var input = $("#form-search");
|
||||
|
||||
if (appendFilter) {
|
||||
if (uniqueFilter) {
|
||||
var attribute = uniqueFilter.substr(0, uniqueFilter.indexOf(':'));
|
||||
filter = input.val().replace(new RegExp('(' + attribute + ':[#a-z0-9]+)', 'g'), '');
|
||||
filter = filter.replace(new RegExp('(' + attribute + ':"(.+)")', 'g'), '');
|
||||
filter = filter.trim();
|
||||
filter += ' ' + uniqueFilter;
|
||||
} else if (appendFilter) {
|
||||
filter = input.val() + " " + appendFilter;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"christian-riesen/otp" : "1.4",
|
||||
"eluceo/ical": "0.8.0",
|
||||
"erusev/parsedown" : "1.6.0",
|
||||
"fguillot/json-rpc" : "1.0.3",
|
||||
"fguillot/json-rpc" : "1.1.0",
|
||||
"fguillot/picodb" : "1.0.8",
|
||||
"fguillot/simpleLogger" : "1.0.0",
|
||||
"fguillot/simple-validator" : "1.0.0",
|
||||
|
|
@ -41,7 +41,6 @@
|
|||
"autoload" : {
|
||||
"classmap" : ["app/"],
|
||||
"psr-4" : {
|
||||
"Kanboard\\Plugin\\": "plugins/",
|
||||
"Kanboard\\" : "app/"
|
||||
},
|
||||
"files" : [
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "ecdd93c089273876816339ff22d67cc7",
|
||||
"content-hash": "a5edc6f9c9ae2cd356e3f8ac96ef5532",
|
||||
"hash": "715601e3833e0ee04d8d00d266302f8b",
|
||||
"content-hash": "ef38cdd1e92bd2cd299db9c6d429d24f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "christian-riesen/base32",
|
||||
|
|
@ -203,16 +203,16 @@
|
|||
},
|
||||
{
|
||||
"name": "fguillot/json-rpc",
|
||||
"version": "v1.0.3",
|
||||
"version": "v1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fguillot/JsonRPC.git",
|
||||
"reference": "0a77cd311783431c851e4c8eed33858663c17277"
|
||||
"reference": "e915dab71940e7ac251955c785570048f460d332"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/0a77cd311783431c851e4c8eed33858663c17277",
|
||||
"reference": "0a77cd311783431c851e4c8eed33858663c17277",
|
||||
"url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/e915dab71940e7ac251955c785570048f460d332",
|
||||
"reference": "e915dab71940e7ac251955c785570048f460d332",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -235,7 +235,7 @@
|
|||
],
|
||||
"description": "Simple Json-RPC client/server library that just works",
|
||||
"homepage": "https://github.com/fguillot/JsonRPC",
|
||||
"time": "2015-09-19 02:27:10"
|
||||
"time": "2016-04-27 02:48:10"
|
||||
},
|
||||
{
|
||||
"name": "fguillot/picodb",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,42 @@
|
|||
Group Member API Procedures
|
||||
===========================
|
||||
|
||||
## getMemberGroups
|
||||
|
||||
- Purpose: **Get all groups for a given user**
|
||||
- Parameters:
|
||||
- **user_id** (integer, required)
|
||||
- Result on success: **List of groups**
|
||||
- Result on failure: **false**
|
||||
|
||||
Request example:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "getMemberGroups",
|
||||
"id": 1987176726,
|
||||
"params": [
|
||||
"1"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Response example:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1987176726,
|
||||
"result": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "My Group A"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## getGroupMembers
|
||||
|
||||
- Purpose: **Get all members of a group**
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ API Task Procedures
|
|||
- **category_id** (integer, optional)
|
||||
- **score** (integer, optional)
|
||||
- **swimlane_id** (integer, optional)
|
||||
- **priority** (integer, optional)
|
||||
- **recurrence_status** (integer, optional)
|
||||
- **recurrence_trigger** (integer, optional)
|
||||
- **recurrence_factor** (integer, optional)
|
||||
|
|
@ -398,6 +399,7 @@ Response example:
|
|||
- **description** Markdown content (string, optional)
|
||||
- **category_id** (integer, optional)
|
||||
- **score** (integer, optional)
|
||||
- **priority** (integer, optional)
|
||||
- **recurrence_status** (integer, optional)
|
||||
- **recurrence_trigger** (integer, optional)
|
||||
- **recurrence_factor** (integer, optional)
|
||||
|
|
@ -634,3 +636,62 @@ Response example:
|
|||
"result": 6
|
||||
}
|
||||
```
|
||||
|
||||
## searchTasks
|
||||
|
||||
- Purpose: **Find tasks by using the search engine**
|
||||
- Parameters:
|
||||
- **project_id** (integer, required)
|
||||
- **query** (string, required)
|
||||
- Result on success: **list of tasks**
|
||||
- Result on failure: **false**
|
||||
|
||||
Request example:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "searchTasks",
|
||||
"id": 1468511716,
|
||||
"params": {
|
||||
"project_id": 2,
|
||||
"query": "assignee:nobody"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response example:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1468511716,
|
||||
"result": [
|
||||
{
|
||||
"nb_comments": "0",
|
||||
"nb_files": "0",
|
||||
"nb_subtasks": "0",
|
||||
"nb_completed_subtasks": "0",
|
||||
"nb_links": "0",
|
||||
"nb_external_links": "0",
|
||||
"is_milestone": null,
|
||||
"id": "3",
|
||||
"reference": "",
|
||||
"title": "T3",
|
||||
"description": "",
|
||||
"date_creation": "1461365164",
|
||||
"date_modification": "1461365164",
|
||||
"date_completed": null,
|
||||
"date_started": null,
|
||||
"date_due": "0",
|
||||
"color_id": "yellow",
|
||||
"project_id": "2",
|
||||
"column_id": "5",
|
||||
"swimlane_id": "0",
|
||||
"owner_id": "0",
|
||||
"creator_id": "0"
|
||||
// ...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -119,6 +119,12 @@ Emails will be sent to all users with notifications enabled.
|
|||
./kanboard notification:overdue-tasks
|
||||
```
|
||||
|
||||
Optional parameters:
|
||||
|
||||
- `--show`: Display notifications sent
|
||||
- `--group`: Group all overdue tasks for one user (from all projects) in one email
|
||||
- `--manager`: Send all overdue tasks to project manager(s) in one email
|
||||
|
||||
You can also display the overdue tasks with the flag `--show`:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -49,6 +49,18 @@ php_value arg_separator.output "&"
|
|||
Otherwise Kanboard will try to override the value directly in PHP.
|
||||
|
||||
|
||||
Authentication failure with the API and Apache + PHP-FPM
|
||||
--------------------------------------------------------
|
||||
|
||||
php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default.
|
||||
For this workaround to work, add these lines to your `.htaccess` file:
|
||||
|
||||
```
|
||||
RewriteCond %{HTTP:Authorization} ^(.+)$
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
```
|
||||
|
||||
|
||||
Known issues with eAccelerator
|
||||
------------------------------
|
||||
|
||||
|
|
@ -109,6 +121,8 @@ Where can I find a list of related projects?
|
|||
- [Trello import script by @matueranet](https://github.com/matueranet/kanboard-import-trello)
|
||||
- [Chrome extension by Timo](https://chrome.google.com/webstore/detail/kanboard-quickmenu/akjbeplnnihghabpgcfmfhfmifjljneh?utm_source=chrome-ntp-icon), [Source code](https://github.com/BlueTeck/kanboard_chrome_extension)
|
||||
- [Python client script by @dzudek](https://gist.github.com/fguillot/84c70d4928eb1e0cb374)
|
||||
- [Shell script for SQLite to MySQL/MariaDB migration by @oliviermaridat](https://github.com/oliviermaridat/kanboard-sqlite2mysql)
|
||||
- [Git hooks for integration with Kanboard by Gene Pavlovsky](https://github.com/gene-pavlovsky/kanboard-git-hooks)
|
||||
|
||||
|
||||
Are there some tutorials about Kanboard in other languages?
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ Technical details
|
|||
- [Run Kanboard with Docker](docker.markdown)
|
||||
- [Run Kanboard with Vagrant](vagrant.markdown)
|
||||
- [Run Kanboard on Cloudron](cloudron.markdown)
|
||||
- [Run Kanboard on Nitrous](nitrous.markdown)
|
||||
|
||||
### Configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,25 @@ You must install [composer](https://getcomposer.org/) to use this method.
|
|||
|
||||
Note: This method will install the **current development version**, use at your own risk.
|
||||
|
||||
Installation outside of the document root
|
||||
-----------------------------------------
|
||||
|
||||
If you would like to install Kanboard outside of the web server document root, you need to create at least these symlinks:
|
||||
|
||||
```bash
|
||||
.
|
||||
├── assets -> ../kanboard/assets
|
||||
├── doc -> ../kanboard/doc
|
||||
├── favicon.ico -> ../kanboard/favicon.ico
|
||||
├── index.php -> ../kanboard/index.php
|
||||
├── jsonrpc.php -> ../kanboard/jsonrpc.php
|
||||
└── robots.txt -> ../kanboard/robots.txt
|
||||
```
|
||||
|
||||
The `.htaccess` is optional because its content can be included directly in the Apache configuration.
|
||||
|
||||
You can also define a custom location for the plugins and files folders by changing the [config file](config.markdown).
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
Nitrous Quickstart
|
||||
==================
|
||||
|
||||
Create a free development environment for this Kanboard project in the cloud on [Nitrous.io](https://www.nitrous.io) by clicking the button below.
|
||||
|
||||
<a href="https://www.nitrous.io/quickstart">
|
||||
<img src="https://nitrous-image-icons.s3.amazonaws.com/quickstart.png" alt="Nitrous Quickstart" width=142 height=34>
|
||||
</a>
|
||||
|
||||
Simply access your site via the `Preview > 3000` link in the IDE.
|
||||
|
|
@ -169,8 +169,16 @@ List of template hooks:
|
|||
| `template:project:integrations` | Integration page in projects settings |
|
||||
| `template:project:sidebar` | Sidebar in project settings |
|
||||
| `template:project-user:sidebar` | Sidebar on project user overview page |
|
||||
| `template:task:layout:top` | Task layout top (after page header) |
|
||||
| `template:task:details:top` | Task summary top |
|
||||
| `template:task:details:bottom` | Task summary bottom |
|
||||
| `template:task:details:first-column` | Task summary first column |
|
||||
| `template:task:details:second-column` | Task summary second column |
|
||||
| `template:task:details:third-column` | Task summary third column |
|
||||
| `template:task:details:fourth-column` | Task summary fourth column |
|
||||
| `template:task:dropdown` | Task dropdown menu in listing pages |
|
||||
| `template:task:sidebar` | Sidebar on task page |
|
||||
| `template:task:sidebar:actions` | Sidebar on task page (section actions) |
|
||||
| `template:task:sidebar:information` | Sidebar on task page (section information) |
|
||||
| `template:task:form:left-column` | Left column in task form |
|
||||
| `template:task:form:right-column` | Right column in task form |
|
||||
| `template:task:show:top ` | Show task page: top |
|
||||
|
|
@ -179,7 +187,7 @@ List of template hooks:
|
|||
| `template:task:show:before-tasklinks` | Show task page: before tasklinks |
|
||||
| `template:task:show:before-subtasks` | Show task page: before subtasks |
|
||||
| `template:task:show:before-timetracking` | Show task page: before timetracking |
|
||||
| `template:task:show:before-attachements` | Show task page: before attachments |
|
||||
| `template:task:show:before-attachments` | Show task page: before attachments |
|
||||
| `template:task:show:before-comments` | Show task page: before comments |
|
||||
| `template:user:authentication:form` | "Edit authentication" form in user profile |
|
||||
| `template:user:create-remote:form` | "Create remote user" form |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
rm -rf ~/code/public_html
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y php5-sqlite
|
||||
sudo apt-get clean
|
||||
|
||||
cd ~/code
|
||||
mv kanboard public_html
|
||||
cd public_html
|
||||
composer install
|
||||
cd ~/code
|
||||
sudo chown -R nitrous:www-data public_html
|
||||
sudo service apache2 reload
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"template": "php-apache",
|
||||
"ports": [3000],
|
||||
"name": "Kanboard",
|
||||
"description": "Kanban project management software"
|
||||
}
|
||||
|
|
@ -35,15 +35,15 @@ abstract class Base extends PHPUnit_Framework_TestCase
|
|||
{
|
||||
$this->app = new JsonRPC\Client(API_URL);
|
||||
$this->app->authentication('jsonrpc', API_KEY);
|
||||
// $this->app->debug = true;
|
||||
$this->app->getHttpClient()->withDebug();
|
||||
|
||||
$this->admin = new JsonRPC\Client(API_URL);
|
||||
$this->admin->authentication('admin', 'admin');
|
||||
// $this->admin->debug = true;
|
||||
$this->admin->getHttpClient()->withDebug();
|
||||
|
||||
$this->user = new JsonRPC\Client(API_URL);
|
||||
$this->user->authentication('user', 'password');
|
||||
// $this->user->debug = true;
|
||||
$this->user->getHttpClient()->withDebug();
|
||||
}
|
||||
|
||||
protected function getProjectId()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,14 @@ class GroupMemberTest extends Base
|
|||
$this->assertFalse($this->app->isGroupMember($groupId, 2));
|
||||
}
|
||||
|
||||
public function testGetGroups()
|
||||
{
|
||||
$groups = $this->app->getMemberGroups(1);
|
||||
$this->assertCount(1, $groups);
|
||||
$this->assertEquals(1, $groups[0]['id']);
|
||||
$this->assertEquals('My Group A', $groups[0]['name']);
|
||||
}
|
||||
|
||||
public function testRemove()
|
||||
{
|
||||
$groupId = $this->getGroupId();
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class MeTest extends Base
|
|||
}
|
||||
|
||||
/**
|
||||
* @expectedException JsonRPC\AccessDeniedException
|
||||
* @expectedException JsonRPC\Exception\AccessDeniedException
|
||||
*/
|
||||
public function testNotAllowedAppProcedure()
|
||||
{
|
||||
|
|
@ -23,7 +23,7 @@ class MeTest extends Base
|
|||
}
|
||||
|
||||
/**
|
||||
* @expectedException JsonRPC\AccessDeniedException
|
||||
* @expectedException JsonRPC\Exception\AccessDeniedException
|
||||
*/
|
||||
public function testNotAllowedUserProcedure()
|
||||
{
|
||||
|
|
@ -31,7 +31,7 @@ class MeTest extends Base
|
|||
}
|
||||
|
||||
/**
|
||||
* @expectedException JsonRPC\AccessDeniedException
|
||||
* @expectedException JsonRPC\Exception\AccessDeniedException
|
||||
*/
|
||||
public function testNotAllowedProjectForUser()
|
||||
{
|
||||
|
|
@ -140,7 +140,7 @@ class MeTest extends Base
|
|||
}
|
||||
|
||||
/**
|
||||
* @expectedException JsonRPC\AccessDeniedException
|
||||
* @expectedException JsonRPC\Exception\AccessDeniedException
|
||||
*/
|
||||
public function testGetAdminTask()
|
||||
{
|
||||
|
|
@ -148,7 +148,7 @@ class MeTest extends Base
|
|||
}
|
||||
|
||||
/**
|
||||
* @expectedException JsonRPC\AccessDeniedException
|
||||
* @expectedException JsonRPC\Exception\AccessDeniedException
|
||||
*/
|
||||
public function testGetProjectActivityDenied()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,42 @@ require_once __DIR__.'/Base.php';
|
|||
|
||||
class TaskTest extends Base
|
||||
{
|
||||
public function testSearchTasks()
|
||||
{
|
||||
$project_id1 = $this->app->createProject('My project');
|
||||
$project_id2 = $this->app->createProject('My project');
|
||||
$this->assertNotFalse($project_id1);
|
||||
$this->assertNotFalse($project_id2);
|
||||
|
||||
$this->assertNotFalse($this->app->createTask(array('project_id' => $project_id1, 'title' => 'T1')));
|
||||
$this->assertNotFalse($this->app->createTask(array('project_id' => $project_id1, 'title' => 'T2')));
|
||||
$this->assertNotFalse($this->app->createTask(array('project_id' => $project_id2, 'title' => 'T3')));
|
||||
|
||||
$tasks = $this->app->searchTasks($project_id1, 't2');
|
||||
$this->assertCount(1, $tasks);
|
||||
$this->assertEquals('T2', $tasks[0]['title']);
|
||||
|
||||
$tasks = $this->app->searchTasks(array('project_id' => $project_id2, 'query' => 'assignee:nobody'));
|
||||
$this->assertCount(1, $tasks);
|
||||
$this->assertEquals('T3', $tasks[0]['title']);
|
||||
}
|
||||
|
||||
public function testPriorityAttribute()
|
||||
{
|
||||
$project_id = $this->app->createProject('My project');
|
||||
$this->assertNotFalse($project_id);
|
||||
|
||||
$task_id = $this->app->createTask(array('project_id' => $project_id, 'title' => 'My task', 'priority' => 2));
|
||||
|
||||
$task = $this->app->getTask($task_id);
|
||||
$this->assertEquals(2, $task['priority']);
|
||||
|
||||
$this->assertTrue($this->app->updateTask(array('id' => $task_id, 'project_id' => $project_id, 'priority' => 3)));
|
||||
|
||||
$task = $this->app->getTask($task_id);
|
||||
$this->assertEquals(3, $task['priority']);
|
||||
}
|
||||
|
||||
public function testChangeAssigneeToAssignableUser()
|
||||
{
|
||||
$project_id = $this->app->createProject('My project');
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class TemplateTest extends Base
|
|||
{
|
||||
$template = new Template($this->container['helper']);
|
||||
$this->assertStringEndsWith(
|
||||
implode(DIRECTORY_SEPARATOR, array('app', 'Core', '..', '..', 'plugins', 'Myplugin', 'Template', 'a', 'b.php')),
|
||||
implode(DIRECTORY_SEPARATOR, array(PLUGINS_DIR, 'Myplugin', 'Template', 'a', 'b.php')),
|
||||
$template->getTemplateFile('myplugin:a'.DIRECTORY_SEPARATOR.'b')
|
||||
);
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ class TemplateTest extends Base
|
|||
$template->setTemplateOverride('a'.DIRECTORY_SEPARATOR.'b', 'myplugin:c');
|
||||
|
||||
$this->assertStringEndsWith(
|
||||
implode(DIRECTORY_SEPARATOR, array('app', 'Core', '..', '..', 'plugins', 'Myplugin', 'Template', 'c.php')),
|
||||
implode(DIRECTORY_SEPARATOR, array(PLUGINS_DIR, 'Myplugin', 'Template', 'c.php')),
|
||||
$template->getTemplateFile('a'.DIRECTORY_SEPARATOR.'b')
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -72,5 +72,35 @@ class GroupMemberTest extends Base
|
|||
$this->assertCount(2, $users);
|
||||
$this->assertEquals('admin', $users[0]['username']);
|
||||
$this->assertEquals('user1', $users[1]['username']);
|
||||
|
||||
$groups = $groupMemberModel->getGroups(1);
|
||||
$this->assertCount(1, $groups);
|
||||
$this->assertEquals(1, $groups[0]['id']);
|
||||
$this->assertEquals('Group A', $groups[0]['name']);
|
||||
|
||||
$groups = $groupMemberModel->getGroups(2);
|
||||
$this->assertCount(1, $groups);
|
||||
$this->assertEquals(1, $groups[0]['id']);
|
||||
$this->assertEquals('Group A', $groups[0]['name']);
|
||||
|
||||
$groups = $groupMemberModel->getGroups(3);
|
||||
$this->assertCount(1, $groups);
|
||||
$this->assertEquals(2, $groups[0]['id']);
|
||||
$this->assertEquals('Group B', $groups[0]['name']);
|
||||
|
||||
$groups = $groupMemberModel->getGroups(4);
|
||||
$this->assertCount(1, $groups);
|
||||
$this->assertEquals(2, $groups[0]['id']);
|
||||
$this->assertEquals('Group B', $groups[0]['name']);
|
||||
|
||||
$groups = $groupMemberModel->getGroups(5);
|
||||
$this->assertCount(2, $groups);
|
||||
$this->assertEquals(1, $groups[0]['id']);
|
||||
$this->assertEquals('Group A', $groups[0]['name']);
|
||||
$this->assertEquals(2, $groups[1]['id']);
|
||||
$this->assertEquals('Group B', $groups[1]['name']);
|
||||
|
||||
$groups = $groupMemberModel->getGroups(6);
|
||||
$this->assertCount(0, $groups);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue