diff --git a/src/IDF/Form/IssueCreate.php b/src/IDF/Form/IssueCreate.php index c6f3aef..859adb1 100644 --- a/src/IDF/Form/IssueCreate.php +++ b/src/IDF/Form/IssueCreate.php @@ -36,6 +36,7 @@ class IDF_Form_IssueCreate extends Pluf_Form public $user = null; public $project = null; public $show_full = false; + public $relation_types = null; public function initFields($extra=array()) { @@ -45,9 +46,12 @@ class IDF_Form_IssueCreate extends Pluf_Form or $this->user->hasPerm('IDF.project-member', $this->project)) { $this->show_full = true; } + $this->relation_types = $this->project->getRelationsFromConfig(); + $contentTemplate = $this->project->getConf()->getVal( 'labels_issue_template', IDF_Form_IssueTrackingConf::init_template ); + $this->fields['summary'] = new Pluf_Form_Field_Varchar( array('required' => true, 'label' => __('Summary'), @@ -109,11 +113,10 @@ class IDF_Form_IssueCreate extends Pluf_Form ), )); - $relation_types = $extra['project']->getRelationsFromConfig(); $this->fields['relation_type'] = new Pluf_Form_Field_Varchar( array('required' => false, 'label' => __('This issue'), - 'initial' => $relation_types[0], + 'initial' => current($this->relation_types), 'widget_attrs' => array('size' => 15), )); @@ -250,6 +253,49 @@ class IDF_Form_IssueCreate extends Pluf_Form return $this->cleaned_data['status']; } + function clean_relation_type() + { + $relation_type = trim($this->cleaned_data['relation_type']); + if (empty($relation_type)) + return ''; + + $found = false; + foreach ($this->relation_types as $type) { + if ($type == $relation_type) { + $found = true; + break; + } + } + if (!$found) { + throw new Pluf_Form_Invalid(__('You provided an invalid relation type.')); + } + return $relation_type; + } + + function clean_relation_issue() + { + $issues = trim($this->cleaned_data['relation_issue']); + if (empty($issues)) + return ''; + + $issue_ids = preg_split('/\s*,\s*/', $issues, -1, PREG_SPLIT_NO_EMPTY); + foreach ($issue_ids as $issue_id) { + if (!ctype_digit($issue_id) || (int)$issue_id < 1) { + throw new Pluf_Form_Invalid(sprintf( + __('The value "%s" is not a valid issue id.'), $issue_id + )); + } + $issue = new IDF_Issue($issue_id); + if ($issue->id != $issue_id || $issue->project != $this->project->id) { + throw new Pluf_Form_Invalid(sprintf( + __('The issue "%s" does not exist.'), $issue_id + )); + } + } + + return implode(', ', $issue_ids); + } + /** * Clean the attachments post failure. */ @@ -313,6 +359,27 @@ class IDF_Form_IssueCreate extends Pluf_Form foreach ($tags as $tag) { $issue->setAssoc($tag); } + // add relations + $verb = $this->cleaned_data['relation_type']; + $other_verb = $this->relation_types[$verb]; + $related_issues = preg_split('/\s*,\s*/', $this->cleaned_data['relation_issue'], -1, PREG_SPLIT_NO_EMPTY); + foreach ($related_issues as $related_issue_id) { + $related_issue = new IDF_Issue($related_issue_id); + $rel = new IDF_IssueRelation(); + $rel->issue = $issue; + $rel->verb = $verb; + $rel->other_issue = $related_issue; + $rel->submitter = $this->user; + $rel->create(); + + $other_rel = new IDF_IssueRelation(); + $other_rel->issue = $related_issue; + $other_rel->verb = $other_verb; + $other_rel->other_issue = $issue; + $other_rel->submitter = $this->user; + $other_rel->create(); + } + // add the first comment $comment = new IDF_IssueComment(); $comment->issue = $issue; diff --git a/src/IDF/Form/IssueTrackingConf.php b/src/IDF/Form/IssueTrackingConf.php index c62a10d..738c3e4 100644 --- a/src/IDF/Form/IssueTrackingConf.php +++ b/src/IDF/Form/IssueTrackingConf.php @@ -72,10 +72,24 @@ Performance = Performance issue Usability = Affects program usability Maintainability = Hinders future changes'; const init_one_max = 'Type, Priority, Milestone'; + // ATTENTION: if you change something here, change the values below as well! const init_relations = 'is related to blocks, is blocked by duplicates, is duplicated by'; + // These are actually all noop's, but we have no other chance to + // tell IDF's translation mechanism to mark the strings as translatable + // FIXME: IDF should get a internal translation system for strings like + // that, that can also be easily expanded by users + private function noop() + { + __('is related to'); + __('blocks'); + __('is blocked by'); + __('duplicates'); + __('is duplicated by'); + } + public function initFields($extra=array()) { $this->fields['labels_issue_template'] = new Pluf_Form_Field_Varchar( diff --git a/src/IDF/Form/IssueUpdate.php b/src/IDF/Form/IssueUpdate.php index 9835bc3..4571ab1 100644 --- a/src/IDF/Form/IssueUpdate.php +++ b/src/IDF/Form/IssueUpdate.php @@ -39,6 +39,7 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate or $this->user->hasPerm('IDF.project-member', $this->project)) { $this->show_full = true; } + $this->relation_types = $this->project->getRelationsFromConfig(); if ($this->show_full) { $this->fields['summary'] = new Pluf_Form_Field_Varchar( array('required' => true, @@ -103,11 +104,10 @@ class IDF_Form_IssueUpdate extends IDF_Form_IssueCreate ), )); - $relation_types = $extra['project']->getRelationsFromConfig(); $this->fields['relation_type'] = new Pluf_Form_Field_Varchar( array('required' => false, 'label' => __('This issue'), - 'initial' => $relation_types[0], + 'initial' => current($this->relation_types), 'widget_attrs' => array('size' => 15), )); diff --git a/src/IDF/Issue.php b/src/IDF/Issue.php index b173209..6346621 100644 --- a/src/IDF/Issue.php +++ b/src/IDF/Issue.php @@ -169,6 +169,24 @@ class IDF_Issue extends Pluf_Model } } + function getGroupedRelatedIssues($opts = array()) + { + $rels = $this->get_related_issues_list(array_merge($opts, array( + 'view' => 'with_other_issue', + ))); + + $res = array(); + foreach ($rels as $rel) { + $verb = $rel->verb; + if (!array_key_exists($verb, $res)) { + $res[$verb] = array(); + } + $res[$verb][] = $rel; + } + + return $res; + } + /** * Returns an HTML fragment used to display this issue in the * timeline. diff --git a/src/IDF/IssueRelation.php b/src/IDF/IssueRelation.php index f918631..b0048dd 100644 --- a/src/IDF/IssueRelation.php +++ b/src/IDF/IssueRelation.php @@ -45,7 +45,7 @@ class IDF_IssueRelation extends Pluf_Model 'model' => 'IDF_Issue', 'blank' => false, 'verbose' => __('issue'), - 'relate_name' => 'issues', + 'relate_name' => 'related_issues', ), 'verb' => array( @@ -59,7 +59,7 @@ class IDF_IssueRelation extends Pluf_Model 'model' => 'IDF_Issue', 'blank' => false, 'verbose' => __('other issue'), - 'relate_name' => 'other_issues', + 'relate_name' => 'related_other_issues', ), 'submitter' => array( @@ -82,6 +82,13 @@ class IDF_IssueRelation extends Pluf_Model 'type' => 'normal', ), ); + $issuetbl = $this->_con->pfx.'idf_issues'; + $this->_a['views'] = array( + 'with_other_issue' => array( + 'join' => 'INNER JOIN '.$issuetbl.' ON other_issue='.$issuetbl.'.id', + 'select' => $this->getSelect().', summary', + 'props' => array('summary' => 'other_summary'), + )); } function preSave($create=false) diff --git a/src/IDF/Project.php b/src/IDF/Project.php index 211a9ed..2f09219 100644 --- a/src/IDF/Project.php +++ b/src/IDF/Project.php @@ -234,7 +234,10 @@ class IDF_Project extends Pluf_Model } /** - * Returns a list of relations which are available in this project + * Returns a list of relations which are available in this project as + * associative array. Each key-value pair marks a set of orthogonal + * relations. To ease processing, each of these pairs is included twice + * in the array, once as key1 => key2 and once as key2 => key1. * * @return array List of relation names */ @@ -244,7 +247,11 @@ class IDF_Project extends Pluf_Model $rel = $conf->getVal('issue_relations', IDF_Form_IssueTrackingConf::init_relations); $relations = array(); foreach (preg_split("/\015\012|\015|\012/", $rel, -1, PREG_SPLIT_NO_EMPTY) as $s) { - $relations = array_merge($relations, preg_split("/\s*,\s*/", $s, 2)); + $verbs = preg_split("/\s*,\s*/", $s, 2); + if (count($verbs) == 1) + $relations += array($verbs[0] => $verbs[0]); + else + $relations += array($verbs[0] => $verbs[1], $verbs[1] => $verbs[0]); } return $relations; } diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index 21800ff..90c8de8 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -404,6 +404,8 @@ class IDF_Views_Issue $issue = Pluf_Shortcuts_GetObjectOr404('IDF_Issue', $match[2]); $prj->inOr404($issue); $comments = $issue->get_comments_list(array('order' => 'id ASC')); + $related_issues = $issue->getGroupedRelatedIssues(array('order' => 'creation_dtime DESC')); + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', array($prj->shortname, $issue->id)); $title = Pluf_Template::markSafe(sprintf(__('Issue %d: %s'), $url, $issue->id, $issue->summary)); @@ -471,7 +473,8 @@ class IDF_Views_Issue 'preview' => $preview, 'interested' => $interested->count(), 'previous_issue_id' => $previous_issue_id, - 'next_issue_id' => $next_issue_id + 'next_issue_id' => $next_issue_id, + 'related_issues' => $related_issues, ), $arrays), $request); diff --git a/src/IDF/templates/idf/issues/view.html b/src/IDF/templates/idf/issues/view.html index e27be87..069cd0b 100644 --- a/src/IDF/templates/idf/issues/view.html +++ b/src/IDF/templates/idf/issues/view.html @@ -170,6 +170,19 @@ {$tag.class}:{$tag.name}
{/foreach}

{/if} +{if count($related_issues) > 0} +{foreach $related_issues as $verb => $rel_issues} +{blocktrans}This issue {$verb}{/blocktrans}
+ {foreach $rel_issues as $rel_issue} + + + {$rel_issue.other_issue} - {$rel_issue.other_summary|shorten:30} + +
+ {/foreach} +{/foreach} +{/if} {/block} {block javascript}{if $form}{include 'idf/issues/js-autocomplete.html'}