diff --git a/src/IDF/Project.php b/src/IDF/Project.php index 66a070e..f23b34b 100644 --- a/src/IDF/Project.php +++ b/src/IDF/Project.php @@ -132,7 +132,7 @@ class IDF_Project extends Pluf_Model } return $projects[0]; } - + /** * Returns the number of open/closed issues. * @@ -167,7 +167,7 @@ GROUP BY uid"; $key = ($v['id'] === '-1') ? null : $v['id']; $ownerStatistics[$key] = (int)$v['nb']; } - + arsort($ownerStatistics); return $ownerStatistics; @@ -178,9 +178,10 @@ GROUP BY uid"; * * @param string Status ('open'), 'closed' * @param IDF_Tag Subfilter with a label (null) + * @param array Restrict further to a list of ids * @return int Count */ - public function getIssueCountByStatus($status='open', $label=null) + public function getIssueCountByStatus($status='open', $label=null, $ids=array()) { switch ($status) { case 'open': @@ -203,6 +204,10 @@ GROUP BY uid"; $sql2 = new Pluf_SQL('idf_tag_id=%s', array($label->id)); $sql->SAnd($sql2); } + if (count($ids) > 0) { + $sql2 = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids))); + $sql->SAnd($sql2); + } $params = array('filter' => $sql->gen()); if (!is_null($label)) { $params['view'] = 'join_tags'; } $gissue = new IDF_Issue(); @@ -210,6 +215,38 @@ GROUP BY uid"; } /** + * Get the tags for a specific list of issues. + * + * @param string Status ('open') or 'closed' + * @param array A list of issue ids + * @return array An array of tag objects + */ + public function getTagsByIssues($issue_ids=array()) + { + // make the below query always a valid one + if (count($issue_ids) == 0) $issue_ids[] = 0; + + $assocTable = $this->_con->pfx.'idf_issue_idf_tag_assoc'; + $query = sprintf( + 'SELECT DISTINCT idf_tag_id FROM %s '. + 'WHERE idf_issue_id IN (%s) '. + 'GROUP BY idf_tag_id', + $assocTable, implode(',', $issue_ids) + ); + + $db = Pluf::db(); + $dbData = $db->select($query); + $ids = array(0); + foreach ($dbData as $data) { + $ids[] = $data['idf_tag_id']; + } + + $sql = new Pluf_SQL(sprintf('id IN (%s)', implode(', ', $ids))); + $model = new IDF_Tag(); + return $model->getList(array('filter' => $sql->gen())); + } + + /** * Get the open/closed tag ids as they are often used when doing * listings. * @@ -415,7 +452,11 @@ GROUP BY uid"; foreach ($this->_con->select($sql) as $idc) { $tag = new IDF_Tag($idc['id']); $tag->nb_use = $idc['nb_use']; - $tags[] = $tag; + // group by class + if (!array_key_exists($tag->class, $tags)) { + $tags[$tag->class] = array(); + } + $tags[$tag->class][] = $tag; } return new Pluf_Template_ContextVars($tags); } diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index 953557c..03850dd 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -432,44 +432,141 @@ class IDF_Views_Issue public $search_precond = array('IDF_Precondition::accessIssues'); public function search($request, $match) { + $query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q']; + return $this->doSearch($request, $query, 'open'); + } + + public $searchStatus_precond = array('IDF_Precondition::accessIssues'); + public function searchStatus($request, $match) + { + $query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q']; + $status = in_array($match[2], array('open', 'closed')) ? $match[2] : 'open'; + return $this->doSearch($request, $query, $status); + } + + public $searchLabel_precond = array('IDF_Precondition::accessIssues'); + public function searchLabel($request, $match) + { + $query = !isset($request->REQUEST['q']) ? '' : $request->REQUEST['q']; + $tag_id = intval($match[2]); + $status = in_array($match[3], array('open', 'closed')) ? $match[3] : 'open'; + return $this->doSearch($request, $query, $status, $tag_id); + } + + private function doSearch($request, $query, $status, $tag_id=null) + { $prj = $request->project; - if (!isset($request->REQUEST['q']) or trim($request->REQUEST['q']) == '') { - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index', - array($prj->shortname)); + if (trim($query) == '') { + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index', array($prj->shortname)); return new Pluf_HTTP_Response_Redirect($url); } - $q = $request->REQUEST['q']; - $title = sprintf(__('Search Issues - %s'), $q); - $issues = new Pluf_Search_ResultSet(IDF_Search::mySearch($q, $prj, 'IDF_Issue')); - if (count($issues) > 100) { - // no more than 100 results as we do not care - $issues->results = array_slice($issues->results, 0, 100); + + $tag = null; + if ($tag_id !== null) { + $tag = Pluf_Shortcuts_GetObjectOr404('IDF_Tag', $tag_id); + } + + $title = sprintf(__('Search issues - %s'), $query); + if ($status === 'closed') { + $title = sprintf(__('Search closed issues - %s'), $query); + } + + // using Plufs ResultSet implementation here is inefficient, because + // it makes a SELECT for each item and does not allow for further + // filtering neither, so we just return the ids and filter by them + // and other things in the next round + $results = IDF_Search::mySearch($query, $prj, 'IDF_Issue'); + + $issue_ids = array(); + foreach ($results as $result) { + $issue_ids[] = $result['model_id']; + } + + $otags = $prj->getTagIdsByStatus($status); + if (count($otags) == 0) $otags[] = 0; + $sql = new Pluf_SQL( + 'id IN ('.implode(',', $issue_ids).') '. + 'AND status IN ('.implode(', ', $otags).') '. + ($tag_id !== null ? 'AND idf_tag_id='.$tag_id.' ' : '') + ); + $model = new IDF_Issue(); + $issues = $model->getList(array('filter' => $sql->gen(), 'view' => 'join_tags')); + + // we unfortunately loose the original sort order, + // so we manually have to apply it here again + $sorted_issues = new ArrayObject(); + $filtered_issue_ids = array(); + foreach ($issue_ids as $issue_id) { + foreach ($issues as $issue) { + if ($issue->id != $issue_id) + continue; + if (array_key_exists($issue_id, $sorted_issues)) + continue; + $sorted_issues[$issue_id] = $issue; + $filtered_issue_ids[] = $issue_id; + } } + $pag = new Pluf_Paginator(); - $pag->items = $issues; $pag->class = 'recent-issues'; - $pag->item_extra_props = array('project_m' => $prj, - 'shortname' => $prj->shortname, - 'current_user' => $request->user); + $pag->items = $sorted_issues; + $pag->item_extra_props = array( + 'project_m' => $prj, + 'shortname' => $prj->shortname, + 'current_user' => $request->user + ); $pag->summary = __('This table shows the found issues.'); - $pag->action = array('IDF_Views_Issue::search', array($prj->shortname), array('q'=> $q)); $pag->extra_classes = array('a-c', '', 'a-c', ''); - $list_display = array( - 'id' => __('Id'), - array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')), - array('status', 'IDF_Views_Issue_ShowStatus', __('Status')), - array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')), - ); - $pag->configure($list_display); - $pag->items_per_page = 100; + $pag->configure(array( + 'id' => __('Id'), + array('summary', 'IDF_Views_Issue_SummaryAndLabels', __('Summary')), + array('status', 'IDF_Views_Issue_ShowStatus', __('Status')), + array('modif_dtime', 'Pluf_Paginator_DateAgo', __('Last Updated')), + )); + // disable paginating + $pag->items_per_page = PHP_INT_MAX; $pag->no_results_text = __('No issues were found.'); $pag->setFromRequest($request); - $params = array('page_title' => $title, - 'issues' => $pag, - 'q' => $q, - ); - return Pluf_Shortcuts_RenderToResponse('idf/issues/search.html', $params, $request); + if ($tag_id === null) { + $pag->action = array('IDF_Views_Issue::searchStatus', + array($prj->shortname, $status), + array('q'=> $query), + ); + } else { + $pag->action = array('IDF_Views_Issue::searchLabel', + array($prj->shortname, $tag_id, $status), + array('q'=> $query), + ); + } + + // get stats about the issues + $open = $prj->getIssueCountByStatus('open', $tag, $issue_ids); + $closed = $prj->getIssueCountByStatus('closed', $tag, $issue_ids); + + // query the available tags for this search result + $all_tags = $prj->getTagsByIssues($filtered_issue_ids); + $grouped_tags = array(); + foreach ($all_tags as $atag) { + // group by class + if (!array_key_exists($atag->class, $grouped_tags)) { + $grouped_tags[$atag->class] = array(); + } + $grouped_tags[$atag->class][] = $atag; + } + + $params = array( + 'page_title' => $title, + 'issues' => $pag, + 'query' => $query, + 'status' => $status, + 'open' => $open, + 'closed' => $closed, + 'tag' => $tag, + 'all_tags' => $grouped_tags, + ); + + return Pluf_Shortcuts_RenderToResponse('idf/issues/search.html', $params, $request); } public $view_precond = array('IDF_Precondition::accessIssues'); diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index 03779c0..e5b8081 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -117,7 +117,7 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/$#', 'base' => $base, 'model' => 'IDF_Views_Issue', 'method' => 'index'); - + $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/summary/$#', 'base' => $base, 'model' => 'IDF_Views_Issue', @@ -128,6 +128,16 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/$#', 'model' => 'IDF_Views_Issue', 'method' => 'search'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/status/(\w+)/$#', + 'base' => $base, + 'model' => 'IDF_Views_Issue', + 'method' => 'searchStatus'); + +$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/label/(\d+)/(\w+)/$#', + 'base' => $base, + 'model' => 'IDF_Views_Issue', + 'method' => 'searchLabel'); + $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/(\d+)/$#', 'base' => $base, 'model' => 'IDF_Views_Issue', diff --git a/src/IDF/templates/idf/issues/base.html b/src/IDF/templates/idf/issues/base.html index 636d5da..60f9d85 100644 --- a/src/IDF/templates/idf/issues/base.html +++ b/src/IDF/templates/idf/issues/base.html @@ -7,7 +7,7 @@ {if !$user.isAnonymous()} | {trans 'New Issue'} | {trans 'My Issues'} | {trans 'My watch list'}{/if} |
{if $inIssue} | diff --git a/src/IDF/templates/idf/issues/by-label.html b/src/IDF/templates/idf/issues/by-label.html index 3d57a3b..fdac576 100644 --- a/src/IDF/templates/idf/issues/by-label.html +++ b/src/IDF/templates/idf/issues/by-label.html @@ -8,16 +8,15 @@ {/block} {block context} -{trans 'Label:'} -{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')} -{$label.class}:{$label.name}
{aurl 'open_url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')} {aurl 'closed_url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'closed')} {blocktrans}Open issues: {$open}
Closed issues: {$closed}
-{/blocktrans}{if $completion} +{/blocktrans} +{trans 'Label:'} +{aurl 'url', 'IDF_Views_Issue::listLabel', array($project.shortname, $label.id, 'open')} +{$label.class}:{$label.name}
+{if $completion}{trans 'Completion:'} {$completion}
{/if} - - {/block} diff --git a/src/IDF/templates/idf/issues/search.html b/src/IDF/templates/idf/issues/search.html index 71388ed..4c0b720 100644 --- a/src/IDF/templates/idf/issues/search.html +++ b/src/IDF/templates/idf/issues/search.html @@ -8,5 +8,25 @@ {/block} {block context} -{trans 'Found issues:'} {$issues.nb_items}
+{aurl 'open_url', 'IDF_Views_Issue::searchStatus', array($project.shortname, 'open'), array('q' => $query)} +{aurl 'closed_url', 'IDF_Views_Issue::searchStatus', array($project.shortname, 'closed'), array('q' => $query)} +{if $tag != null} +{aurl 'open_url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $tag.id, 'open'), array('q' => $query)} +{aurl 'closed_url', 'IDF_Views_Issue::searchLabel', array($project.shortname, $tag.id, 'closed'), array('q' => $query)} +{/if} +{blocktrans} +Found open issues: {$open}
+Found closed issues: {$closed}
{/blocktrans} +{if $tag !== null} +{blocktrans}Label: +{$tag.class}:{$tag.name}
{/blocktrans} +{else} +{* yes, this is duplicated from tags-cloud.html, but the code there cannot be easily overridden *} +