summaryrefslogtreecommitdiff
path: root/app/Model
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-02-13 17:50:20 -0500
committerFrederic Guillot <fred@kanboard.net>2015-02-13 17:50:20 -0500
commit364382b1b58db8bf1bd2c8866e21c869a7a5d6d0 (patch)
treee1bdaf8c76bbb6036b3bb7a3a19e8cc1e2e99604 /app/Model
parent124f7cad284d7ce867666def5731ad34a9265e63 (diff)
Add task links (Merge pull-request #610)
Diffstat (limited to 'app/Model')
-rw-r--r--app/Model/Base.php2
-rw-r--r--app/Model/Link.php437
-rw-r--r--app/Model/TaskDuplication.php1
-rw-r--r--app/Model/TaskFinder.php25
-rw-r--r--app/Model/TaskLink.php361
5 files changed, 826 insertions, 0 deletions
diff --git a/app/Model/Base.php b/app/Model/Base.php
index 319e53fc..f836231c 100644
--- a/app/Model/Base.php
+++ b/app/Model/Base.php
@@ -25,6 +25,7 @@ use Pimple\Container;
* @property \Model\File $file
* @property \Model\Helper $helper
* @property \Model\LastLogin $lastLogin
+ * @property \Model\Link $link
* @property \Model\Notification $notification
* @property \Model\Project $project
* @property \Model\ProjectDuplication $projectDuplication
@@ -38,6 +39,7 @@ use Pimple\Container;
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
* @property \Model\TaskHistory $taskHistory
+ * @property \Model\TaskLink $taskLink
* @property \Model\TaskPosition $taskPosition
* @property \Model\TaskValidator $taskValidator
* @property \Model\TimeTracking $timeTracking
diff --git a/app/Model/Link.php b/app/Model/Link.php
new file mode 100644
index 00000000..eddf1c6c
--- /dev/null
+++ b/app/Model/Link.php
@@ -0,0 +1,437 @@
+<?php
+namespace Model;
+
+use SimpleValidator\Validator;
+use SimpleValidator\Validators;
+use PDO;
+
+/**
+ * Link model
+ * A link is made of one bidirectional (<->), or two sided (<- and ->) link labels.
+ *
+ * @package model
+ * @author Olivier Maridat
+ */
+class Link extends Base
+{
+
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'link';
+
+ const TABLE_LABEL = 'link_label';
+
+ /**
+ * Direction: left to right ->
+ *
+ * @var integer
+ */
+ const BEHAVIOUR_LEFT2RIGTH = 0;
+
+ /**
+ * Direction: right to left <-
+ *
+ * @var integer
+ */
+ const BEHAVIOUR_RIGHT2LEFT = 1;
+
+ /**
+ * Bidirectional <->
+ *
+ * @var integer
+ */
+ const BEHAVIOUR_BOTH = 2;
+
+ /**
+ * Get a link by the id
+ *
+ * @access public
+ * @param integer $link_id
+ * Link id
+ * @param integer $project_id
+ * Specify a project id. Default: -1 to target all projects
+ * @return array
+ */
+ public function getById($link_id, $project_id = -1)
+ {
+ return $this->db->table(self::TABLE)
+ ->eq(self::TABLE . '.link_id', $link_id)
+ ->in('project_id', array(
+ - 1,
+ $project_id
+ ))
+ ->join(self::TABLE_LABEL, 'link_id', 'link_id')
+ ->findAll();
+ }
+
+ /**
+ * Get the id of the inverse link label by a link label id
+ *
+ * @access public
+ * @param integer $link_id
+ * Link id
+ * @param integer $link_label_id
+ * Link label id
+ * @return integer
+ */
+ public function getInverseLinkLabelId($link_label_id)
+ {
+ $sql = 'SELECT
+ la2.id
+ FROM ' . self::TABLE_LABEL . ' la1
+ JOIN '.self::TABLE_LABEL.' la2 ON la2.link_id = la1.link_id AND (la2.behaviour=2 OR la2.id != la1.id)
+ WHERE la1.id = ?
+ ';
+ $rq = $this->db->execute($sql, array(
+ $link_label_id
+ ));
+ return $rq->fetchColumn(0);
+ }
+
+ /**
+ * Return all link labels for a given project
+ *
+ * @access public
+ * @param integer $project_id
+ * Specify a project id. Default: -1 to target all projects
+ * @return array
+ */
+ public function getLinkLabels($project_id = -1)
+ {
+ return $this->db->table(self::TABLE_LABEL)
+ ->in(self::TABLE . '.project_id', array(
+ - 1,
+ $project_id
+ ))
+ ->join(self::TABLE, 'link_id', 'link_id')
+ ->asc(self::TABLE_LABEL.'.link_id', 'behaviour')
+ ->columns('id', self::TABLE . '.project_id', self::TABLE_LABEL.'.link_id', 'label', 'behaviour')
+ ->findAll();
+ }
+
+ /**
+ * Return the list of all link labels
+ * Used to select a link label
+ *
+ * @access public
+ * @param integer $project_id
+ * Specify a project id. Default: -1 to target all projects
+ * @return array
+ */
+ public function getLinkLabelList($project_id = -1)
+ {
+ $listing = $this->getLinkLabels($project_id);
+ $mergedListing = array();
+ foreach ($listing as $link) {
+ $suffix = '';
+ $prefix = '';
+ if (self::BEHAVIOUR_BOTH == $link['behaviour'] || self::BEHAVIOUR_LEFT2RIGTH == $link['behaviour']) {
+ $suffix = ' &raquo;';
+ }
+ if (self::BEHAVIOUR_BOTH == $link['behaviour'] || self::BEHAVIOUR_RIGHT2LEFT == $link['behaviour']) {
+ $prefix = '&laquo; ';
+ }
+ $mergedListing[$link['id']] = $prefix . t($link['label']) . $suffix;
+ }
+ $listing = $mergedListing;
+ return $listing;
+ }
+
+ /**
+ * Return the list of all links (label + inverse label)
+ *
+ * @access public
+ * @param integer $project_id
+ * Specify a project id. Default: -1 to target all projects
+ * @return array
+ */
+ public function getMergedList($project_id = -1)
+ {
+ $listing = $this->getLinkLabels($project_id);
+ $mergedListing = array();
+ $current = null;
+ foreach ($listing as $link) {
+ if (self::BEHAVIOUR_BOTH == $link['behaviour'] || self::BEHAVIOUR_LEFT2RIGTH == $link['behaviour']) {
+ $current = $link;
+ }
+ else {
+ $current['label_inverse'] = $link['label'];
+ }
+ if (self::BEHAVIOUR_BOTH == $link['behaviour'] || self::BEHAVIOUR_RIGHT2LEFT == $link['behaviour']) {
+ $mergedListing[] = $current;
+ $current = null;
+ }
+ }
+ $listing = $mergedListing;
+ return $listing;
+ }
+
+ /**
+ * Prepare data before insert/update
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ */
+ public function prepare(array &$values)
+ {
+ // Prepare label 1
+ $link = array(
+ 'project_id' => $values['project_id']
+ );
+ $label1 = array(
+ 'label' => @$values['label'][0],
+ 'behaviour' => (isset($values['behaviour'][0]) || !isset($values['label'][1]) || null == $values['label'][1]) ? self::BEHAVIOUR_BOTH : self::BEHAVIOUR_LEFT2RIGTH
+ );
+ $label2 = array(
+ 'label' => @$values['label'][1],
+ 'behaviour' => self::BEHAVIOUR_RIGHT2LEFT
+ );
+ if (isset($values['link_id'])) {
+ $link['link_id'] = $values['link_id'];
+ $label1['id'] = $values['id'][0];
+ $label2['id'] = @$values['id'][1];
+ $label1['link_id'] = $values['link_id'];
+ $label2['link_id'] = $values['link_id'];
+ }
+
+ $values = $link;
+ $values[] = $label1;
+ $values[] = $label2;
+ return array(
+ $link,
+ $label1,
+ $label2
+ );
+ }
+
+ /**
+ * Create a link
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ * @return bool integer
+ */
+ public function create(array $values)
+ {
+ list ($link, $label1, $label2) = $this->prepare($values);
+ // Create link
+ $this->db->startTransaction();
+ $res = $this->db->table(self::TABLE)->save($link);
+ if (! $res) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
+ // Create label 1
+ $label1['link_id'] = $this->db->getConnection()->lastInsertId(self::TABLE);
+ $res = $this->db->table(self::TABLE_LABEL)->save($label1);
+ if (! $res) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
+ // Create label 2 if any
+ if (null != $label2 && self::BEHAVIOUR_BOTH != $label1['behaviour']) {
+ $label2['link_id'] = $label1['link_id'];
+ $res = $this->db->table(self::TABLE_LABEL)->save($label2);
+ if (! $res) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ }
+ $this->db->closeTransaction();
+ return $res;
+ }
+
+ /**
+ * Update a link
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ * @return bool
+ */
+ public function update(array $values)
+ {
+ list($link, $label1, $label2) = $this->prepare($values);
+ // Update link
+ $this->db->startTransaction();
+ $res = $this->db->table(self::TABLE)
+ ->eq('link_id', $link['link_id'])
+ ->save($link);
+ if (! $res) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
+ // Update label 1
+ $this->db->startTransaction();
+ $res = $this->db->table(self::TABLE_LABEL)
+ ->eq('id', $label1['id'])
+ ->save($label1);
+ if (! $res) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
+ // Update label 2 (if label 1 not bidirectional)
+ if (null != $label2 && self::BEHAVIOUR_BOTH != $label1['behaviour']) {
+ // Create
+ if (! isset($label2['id']) || null == $label2['id']) {
+ unset($label2['id']);
+ $res = $this->db->table(self::TABLE_LABEL)->save($label2);
+ if (! $res) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ $label2['id'] = $this->db->getConnection()->lastInsertId(self::TABLE_LABEL);
+ $this->taskLink->changeLinkLabel($link['link_id'], $label2['id'], true);
+ }
+ // Update
+ else {
+ $res = $this->db->table(self::TABLE_LABEL)
+ ->eq('id', $label2['id'])
+ ->save($label2);
+ if (! $res) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ }
+ }
+ // Remove label 2 (if label 1 bidirectional)
+ else {
+ $this->taskLink->changeLinkLabel($link['link_id'], $label1['id']);
+ $this->db->table(self::TABLE_LABEL)
+ ->eq('link_id', $link['link_id'])
+ ->neq('id', $label1['id'])
+ ->remove();
+ }
+ $this->db->closeTransaction();
+ return $res;
+ }
+
+ /**
+ * Remove a link
+ *
+ * @access public
+ * @param integer $link_id
+ * Link id
+ * @return bool
+ */
+ public function remove($link_id)
+ {
+ $this->db->startTransaction();
+ if (! $this->db->table(self::TABLE)
+ ->eq('link_id', $link_id)
+ ->remove()) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ $this->db->closeTransaction();
+ return true;
+ }
+
+ /**
+ * Duplicate links from a project to another one, must be executed inside a transaction
+ *
+ * @param integer $src_project_id
+ * Source project id
+ * @return integer $dst_project_id Destination project id
+ * @return boolean
+ */
+ public function duplicate($src_project_id, $dst_project_id)
+ {
+ $labels = $this->db->table(self::TABLE_LABEL)
+ ->columns(self::TABLE_LABEL.'.link_id', 'label', 'behaviour')
+ ->eq('project_id', $src_project_id)
+ ->join(self::TABLE, 'link_id', 'link_id')
+ ->asc(self::TABLE_LABEL.'.link_id', 'behaviour')
+ ->findAll();
+
+ $this->db->startTransaction();
+ $link = array('project_id' => $dst_project_id);
+ if (! $this->db->table(self::TABLE)->save($link)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ $link['id'] = $this->db->getConnection()->lastInsertId(self::TABLE);
+
+ foreach ($labels as $label) {
+ $label['link_id'] = $link['id'];
+ if (! $this->db->table(self::TABLE_LABEL)->save($label)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ }
+ $this->db->closeTransaction();
+ return true;
+ }
+
+ /**
+ * Validate link creation
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validateCreation(array $values)
+ {
+ $v = new Validator($values, $this->commonValidationRules());
+
+ return array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ }
+
+ /**
+ * Validate link modification
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validateModification(array $values)
+ {
+ $rules = array(
+ new Validators\Required('link_id', t('The id is required')),
+// new Validators\Required('id[0]', t('The label id is required'))
+ );
+ $v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
+
+ return array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ }
+
+ /**
+ * Common validation rules
+ *
+ * @access private
+ * @return array
+ */
+ private function commonValidationRules()
+ {
+ // TODO Update simple-validator to support array in forms
+ return array(
+ new Validators\Required('project_id', t('The project id required')),
+ // new Validators\Required('label[0]', t('The link label is required')),
+ new Validators\Integer('project_id', t('The project id must be an integer')),
+ new Validators\Integer('link_id', t('The link id must be an integer')),
+// new Validators\Integer('id[0]', t('The link label id must be an integer')),
+// new Validators\Integer('id[1]', t('The link label id must be an integer')),
+// new Validators\Integer('behaviour[0]', t('The link label id must be an integer')),
+// new Validators\Integer('behaviour[1]', t('The link label id must be an integer')),
+// new Validators\MaxLength('label[0]', t('The maximum length is %d characters', 200), 200),
+// new Validators\MaxLength('label[1]', t('The maximum length is %d characters', 200), 200)
+ );
+ }
+}
diff --git a/app/Model/TaskDuplication.php b/app/Model/TaskDuplication.php
index bd593dc1..faa5467f 100644
--- a/app/Model/TaskDuplication.php
+++ b/app/Model/TaskDuplication.php
@@ -159,6 +159,7 @@ class TaskDuplication extends Base
if ($new_task_id) {
$this->subtask->duplicate($task_id, $new_task_id);
+ $this->taskLink->duplicate($task_id, $new_task_id);
}
return $new_task_id;
diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php
index 27fa8150..cf756cd8 100644
--- a/app/Model/TaskFinder.php
+++ b/app/Model/TaskFinder.php
@@ -3,6 +3,7 @@
namespace Model;
use PDO;
+use Model\TaskLink;
/**
* Task Finder model
@@ -84,6 +85,7 @@ class TaskFinder extends Base
'(SELECT count(*) FROM task_has_files WHERE task_id=tasks.id) AS nb_files',
'(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id) AS nb_subtasks',
'(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id AND status=2) AS nb_completed_subtasks',
+ '(SELECT count(*) FROM ' . TaskLink::TABLE . ' WHERE ' . TaskLink::TABLE . '.task_id = tasks.id) AS nb_links',
'tasks.id',
'tasks.reference',
'tasks.title',
@@ -128,6 +130,29 @@ class TaskFinder extends Base
->asc('tasks.position')
->findAll();
}
+
+ /**
+ * Get ids and names of all (limited by $limit) tasks for a given project and status
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $status_id Status id
+ * @param integer $exclude_id Exclude this task id in the result
+ * @param integer $limit Number of tasks to list
+ * @return array
+ */
+ public function getList($project_id, $status_id = Task::STATUS_OPEN, $exclude_id=null, $limit=50)
+ {
+ $sql = $this->db
+ ->hashtable(Task::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', $status_id)
+ ->limit($limit);
+ if (null != $exclude_id) {
+ $sql->neq('id', $exclude_id);
+ }
+ return $sql->getAll('id', 'title');
+ }
/**
* Get all tasks for a given project and status
diff --git a/app/Model/TaskLink.php b/app/Model/TaskLink.php
new file mode 100644
index 00000000..09f37d2e
--- /dev/null
+++ b/app/Model/TaskLink.php
@@ -0,0 +1,361 @@
+<?php
+namespace Model;
+
+use Core\Helper;
+use SimpleValidator\Validator;
+use SimpleValidator\Validators;
+use PDO;
+
+/**
+ * TaskLink model
+ *
+ * @package model
+ * @author Olivier Maridat
+ */
+class TaskLink extends Base
+{
+
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'task_has_links';
+
+ /**
+ * Get a link by the task link id
+ *
+ * @access public
+ * @param integer $task_link_id
+ * Task link id
+ * @return array
+ */
+ public function getById($task_link_id)
+ {
+ $sql = 'SELECT
+ tl1.id AS id,
+ tl1.link_label_id AS link_label_id,
+ tl1.task_id AS task_id,
+ tl1.task_inverse_id AS task_inverse_id,
+ tl2.id AS task_link_inverse_id
+ FROM ' . self::TABLE . ' tl1
+ LEFT JOIN ' . Link::TABLE_LABEL . ' l1 ON l1.id = tl1.link_label_id
+ LEFT JOIN ' . Link::TABLE_LABEL . ' l2 ON l2.link_id = l1.link_id
+ LEFT JOIN ' . self::TABLE . ' tl2 ON tl2.task_id = tl1.task_inverse_id
+ AND ( (l1.behaviour = 2 AND tl2.link_label_id = l1.id) OR (tl2.link_label_id = l2.id) )
+ WHERE tl1.id = ?
+ ';
+ $rq = $this->db->execute($sql, array(
+ $task_link_id
+ ));
+ return $rq->fetch();
+ }
+
+ /**
+ * Get the id of the inverse task link by a task link id
+ *
+ * @access public
+ * @param integer $link_id
+ * Task link id
+ * @return integer
+ */
+ public function getInverseTaskLinkId($task_link_id)
+ {
+ $sql = 'SELECT
+ tl2.id
+ FROM ' . self::TABLE . ' tl1
+ LEFT JOIN ' . Link::TABLE_LABEL . ' l1 ON l1.id = tl1.link_label_id
+ LEFT JOIN ' . Link::TABLE_LABEL . ' l2 ON l2.link_id = l1.link_id
+ LEFT JOIN ' . self::TABLE . ' tl2 ON tl2.task_id = tl1.task_inverse_id
+ AND ( (l1.behaviour = 2 AND tl2.link_label_id = l1.id) OR (tl2.link_label_id = l2.id) )
+ WHERE tl1.id = ?
+ ';
+ $rq = $this->db->execute($sql, array(
+ $task_link_id
+ ));
+ return $rq->fetchColumn(0);
+ }
+
+ /**
+ * Return all links for a given task
+ *
+ * @access public
+ * @param integer $task_id
+ * Task id
+ * @return array
+ */
+ public function getAll($task_id)
+ {
+ $sql = 'SELECT
+ tl1.id,
+ l.label AS label,
+ t2.id AS task_inverse_id,
+ t2.project_id AS task_inverse_project_id,
+ t2.title AS task_inverse_title,
+ t2.is_active AS task_inverse_is_active,
+ t2cat.name AS task_inverse_category
+ FROM ' . self::TABLE . ' tl1
+ LEFT JOIN ' . Link::TABLE_LABEL . ' l ON l.id = tl1.link_label_id
+ LEFT JOIN ' . Task::TABLE . ' t2 ON t2.id = tl1.task_inverse_id
+ LEFT JOIN ' . Category::TABLE . ' t2cat ON t2cat.id = t2.category_id
+ WHERE tl1.task_id = ?
+ ORDER BY l.label, t2cat.name, t2.id
+ ';
+ $rq = $this->db->execute($sql, array(
+ $task_id
+ ));
+ $res = $rq->fetchAll(PDO::FETCH_ASSOC);
+ return $res;
+ }
+
+ /**
+ * Prepare data before insert/update
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ */
+ public function prepare(array &$values)
+ {
+ $this->removeFields($values, array(
+ 'another_link'
+ ));
+ $taskLink1 = array(
+ 'link_label_id' => $values['link_label_id'],
+ 'task_id' => $values['task_id'],
+ 'task_inverse_id' => $values['task_inverse_id']
+ );
+ $taskLink2 = array(
+ 'link_label_id' => $this->link->getInverseLinkLabelId($taskLink1['link_label_id']),
+ 'task_id' => $values['task_inverse_id'],
+ 'task_inverse_id' => $values['task_id']
+ );
+ if (isset($values['id']) && isset($values['task_link_inverse_id'])) {
+ $taskLink1['id'] = $values['id'];
+ $taskLink2['id'] = $values['task_link_inverse_id'];
+ }
+ return array(
+ $taskLink1,
+ $taskLink2
+ );
+ }
+
+ /**
+ * Create a link
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ * @return bool integer
+ */
+ public function create(array $values)
+ {
+ list($taskLink1, $taskLink2) = $this->prepare($values);
+ $this->db->startTransaction();
+ if (! $this->db->table(self::TABLE)->save($taskLink1)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ if (! $this->db->table(self::TABLE)->save($taskLink2)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ $this->db->closeTransaction();
+ return true;
+ }
+
+ /**
+ * Update a link
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ * @return bool
+ */
+ public function update(array $values)
+ {
+ list($taskLink1, $taskLink2) = $this->prepare($values);
+ $this->db->startTransaction();
+ if (! $this->db->table(self::TABLE)
+ ->eq('id', $taskLink1['id'])
+ ->save($taskLink1)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ if (! $this->db->table(self::TABLE)
+ ->eq('id', $taskLink2['id'])
+ ->save($taskLink2)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ $this->db->closeTransaction();
+ return true;
+ }
+
+ /**
+ * Remove a link
+ *
+ * @access public
+ * @param integer $task_link_id
+ * Task Link id
+ * @return bool
+ */
+ public function remove($task_link_id)
+ {
+ $task_link_inverse_id = $this->getInverseTaskLinkId($task_link_id);
+ $this->db->startTransaction();
+ if (! $this->db->table(self::TABLE)
+ ->eq('id', $task_link_id)
+ ->remove()) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ if (! $this->db->table(self::TABLE)
+ ->eq('id', $task_link_inverse_id)
+ ->remove()) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+ $this->db->closeTransaction();
+ return true;
+ }
+
+ /**
+ * Duplicate all links to another task
+ *
+ * @access public
+ * @param integer $src_task_id
+ * Source task id
+ * @param integer $dst_task_id
+ * Destination task id
+ * @return bool
+ */
+ public function duplicate($src_task_id, $dst_task_id)
+ {
+ return $this->db->transaction(function ($db) use($src_task_id, $dst_task_id)
+ {
+ $links = $db->table(TaskLink::TABLE)
+ ->columns('link_label_id', 'task_id', 'task_inverse_id')
+ ->eq('task_id', $src_task_id)
+ ->asc('id')
+ ->findAll();
+ foreach ($links as &$link) {
+ $link['task_id'] = $dst_task_id;
+ if (! $db->table(TaskLink::TABLE)
+ ->save($link)) {
+ return false;
+ }
+ }
+
+ $links = $db->table(TaskLink::TABLE)
+ ->columns('link_label_id', 'task_id', 'task_inverse_id')
+ ->eq('task_inverse_id', $src_task_id)
+ ->asc('id')
+ ->findAll();
+ foreach ($links as &$link) {
+ $link['task_inverse_id'] = $dst_task_id;
+ if (! $db->table(TaskLink::TABLE)
+ ->save($link)) {
+ return false;
+ }
+ }
+ });
+ }
+
+ /**
+ * Move a task link from a link label to an other
+ *
+ * @access public
+ * @param integer $link_id
+ * Link id
+ * @param integer $dst_link_label_id
+ * Destination link label id
+ * @return bool
+ */
+ public function changeLinkLabel($link_id, $dst_link_label_id, $alternate=false)
+ {
+ $taskLinks = $this->db->table(Link::TABLE_LABEL)
+ ->eq('link_id', $link_id)
+ ->neq(Link::TABLE_LABEL.'.id', $dst_link_label_id)
+ ->join(self::TABLE, 'link_label_id', 'id')
+ ->asc(self::TABLE.'.id')
+ ->findAllByColumn(self::TABLE.'.id');
+ foreach ($taskLinks as $i => $taskLinkId) {
+ if (null == $taskLinkId || ($alternate && 0 != $i % 2)) {
+ continue;
+ }
+ if (! $this->db->table(self::TABLE)
+ ->eq('id', $taskLinkId)
+ ->save(array('link_label_id' => $dst_link_label_id))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Validate link creation
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validateCreation(array $values)
+ {
+ $v = new Validator($values, $this->commonValidationRules());
+ $res = array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ return $res;
+ }
+
+ /**
+ * Validate link modification
+ *
+ * @access public
+ * @param array $values
+ * Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validateModification(array $values)
+ {
+ $rules = array(
+ new Validators\Required('id', t('The id is required'))
+ );
+ $v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
+ $res = array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ return $res;
+ }
+
+ /**
+ * Common validation rules
+ *
+ * @access private
+ * @return array
+ */
+ private function commonValidationRules()
+ {
+ return array(
+ new Validators\Required('link_label_id', t('The link type is required')),
+ new Validators\Required('task_id', t('The task id is required')),
+ new Validators\Required('task_inverse_id', t('The linked task id is required')),
+ new Validators\Integer('id', t('The id must be an integer')),
+ new Validators\Integer('link_label_id', t('The link id must be an integer')),
+ new Validators\Integer('task_id', t('The task id must be an integer')),
+ new Validators\Integer('task_inverse_id', t('The related task id must be an integer')),
+ new Validators\Integer('task_link_inverse_id', t('The related task link id must be an integer')),
+ new Validators\NotEquals('task_inverse_id', 'task_id', t('A task can not be linked to itself')),
+ new Validators\Exists('task_inverse_id', t('This linked task id doesn\'t exist'), $this->db->getConnection(), Task::TABLE, 'id'),
+ new Validators\Unique(array(
+ 'task_inverse_id',
+ 'link_label_id',
+ 'task_id'
+ ), t('The exact same link already exists'), $this->db->getConnection(), self::TABLE)
+ );
+ }
+}