Complex classes like Tracker_Report_Renderer_Table often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Tracker_Report_Renderer_Table, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | class Tracker_Report_Renderer_Table extends Tracker_Report_Renderer implements Tracker_Report_Renderer_ArtifactLinkable { |
||
25 | |||
26 | const EXPORT_LIGHT = 1; |
||
27 | const EXPORT_FULL = 0; |
||
28 | |||
29 | public $chunksz; |
||
30 | public $multisort; |
||
31 | |||
32 | /** |
||
33 | * Constructor |
||
34 | * |
||
35 | * @param int $id the id of the renderer |
||
36 | * @param Report $report the id of the report |
||
37 | * @param string $name the name of the renderer |
||
38 | * @param string $description the description of the renderer |
||
39 | * @param int $rank the rank |
||
40 | * @param int $chnuksz the size of the chunk (Browse X at once) |
||
|
|||
41 | * @param bool $multisort use multisort? |
||
42 | */ |
||
43 | public function __construct($id, $report, $name, $description, $rank, $chunksz, $multisort) { |
||
44 | parent::__construct($id, $report, $name, $description, $rank); |
||
45 | $this->chunksz = $chunksz; |
||
46 | $this->multisort = $multisort; |
||
47 | } |
||
48 | |||
49 | public function initiateSession() { |
||
50 | $this->report_session = new Tracker_Report_Session($this->report->id); |
||
51 | $this->report_session->changeSessionNamespace("renderers"); |
||
52 | $this->report_session->set("{$this->id}.chunksz", $this->chunksz); |
||
53 | $this->report_session->set("{$this->id}.multisort", $this->multisort); |
||
54 | } |
||
55 | |||
56 | /** |
||
57 | * Delete the renderer |
||
58 | */ |
||
59 | public function delete() { |
||
60 | $this->getSortDao()->delete($this->id); |
||
61 | $this->getColumnsDao()->delete($this->id); |
||
62 | $this->getAggregatesDao()->deleteByRendererId($this->id); |
||
63 | } |
||
64 | |||
65 | protected $_sort; |
||
66 | /** |
||
67 | * @param array $sort |
||
68 | */ |
||
69 | public function setSort($sort) { |
||
72 | /** |
||
73 | * Get field ids used to (multi)sort results |
||
74 | * @return array [{'field_id' => 12, 'is_desc' => 0, 'rank' => 2}, [...]] |
||
75 | */ |
||
76 | public function getSort($store_in_session = true) { |
||
77 | $sort = null; |
||
78 | if ($store_in_session) { |
||
79 | if (isset($this->report_session)) { |
||
80 | $sort = $this->report_session->get("{$this->id}.sort"); |
||
81 | } |
||
82 | } |
||
83 | |||
84 | if ( $sort ) { |
||
85 | $ff = $this->report->getFormElementFactory(); |
||
86 | foreach ($sort as $field_id => $properties) { |
||
87 | if ($properties) { |
||
88 | if ($field = $ff->getFormElementById($field_id)) { |
||
89 | if ($field->userCanRead()) { |
||
90 | $this->_sort[$field_id] = array( |
||
91 | 'renderer_id '=> $this->id, |
||
92 | 'field_id' => $field_id, |
||
93 | 'is_desc' => $properties['is_desc'], |
||
94 | 'rank' => $properties['rank'], |
||
95 | ); |
||
96 | $this->_sort[$field_id]['field'] = $field; |
||
97 | } |
||
98 | } |
||
99 | } |
||
100 | } |
||
101 | } else if (!isset($this->report_session) || !$this->report_session->hasChanged()){ |
||
102 | |||
103 | if (!is_array($this->_sort)) { |
||
104 | $ff = $this->getFieldFactory(); |
||
105 | $this->_sort = array(); |
||
106 | foreach($this->getSortDao()->searchByRendererId($this->id) as $row) { |
||
107 | if ($field = $ff->getUsedFormElementById($row['field_id'])) { |
||
108 | if ($field->userCanRead()) { |
||
109 | $this->_sort[$row['field_id']] = $row; |
||
110 | $this->_sort[$row['field_id']]['field'] = $field; |
||
111 | } |
||
112 | } |
||
113 | } |
||
114 | } |
||
115 | $sort = $this->_sort; |
||
116 | if ($store_in_session) { |
||
117 | foreach($sort as $field_id => $properties) { |
||
118 | $this->report_session->set("{$this->id}.sort.{$field_id}.is_desc", $properties['is_desc']); |
||
119 | $this->report_session->set("{$this->id}.sort.{$field_id}.rank", $properties['rank']); |
||
120 | } |
||
121 | } |
||
122 | } else { |
||
123 | $this->_sort = array(); |
||
124 | } |
||
125 | return $this->_sort; |
||
126 | } |
||
127 | /** |
||
128 | * Adds sort values to database |
||
129 | * |
||
130 | * @param array $sort |
||
131 | */ |
||
132 | public function saveSort($sort) { |
||
133 | $dao = $this->getSortDao(); |
||
134 | if (is_array($sort)) { |
||
135 | foreach ($sort as $key => $s) { |
||
136 | $dao->create($this->id, $s['field']->id); |
||
137 | } |
||
138 | } |
||
139 | } |
||
140 | |||
141 | protected $_columns; |
||
142 | /** |
||
143 | * @param array $cols |
||
144 | */ |
||
145 | public function setColumns($cols) { |
||
148 | /** |
||
149 | * Adds columns to database |
||
150 | * |
||
151 | * @param array $cols |
||
152 | */ |
||
153 | public function saveColumns($cols) { |
||
154 | $dao = $this->getColumnsDao(); |
||
155 | $rank = -1; |
||
156 | foreach ($cols as $key => $col) { |
||
157 | $rank ++; |
||
158 | $dao->create($this->id, $col['field']->id, null, $rank); |
||
159 | } |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * Get field ids and width used to display results |
||
164 | * @return array [{'field_id' => 12, 'width' => 33, 'rank' => 5}, [...]] |
||
165 | */ |
||
166 | public function getColumns() { |
||
167 | $session_renderer_table_columns = null; |
||
168 | if (isset($this->report_session)) { |
||
169 | $session_renderer_table_columns = $this->report_session->get("{$this->id}.columns"); |
||
170 | } |
||
171 | if ( $session_renderer_table_columns ) { |
||
172 | $columns = $session_renderer_table_columns; |
||
173 | $ff = $this->report->getFormElementFactory(); |
||
174 | $this->_columns = array(); |
||
175 | foreach ($columns as $key => $column) { |
||
176 | if ($formElement = $ff->getUsedFormElementFieldById($key)) { |
||
177 | if ($formElement->userCanRead()) { |
||
178 | $this->_columns[$key] = array( |
||
179 | 'field' => $formElement, |
||
180 | 'field_id' => $key, |
||
181 | 'width' => $column['width'], |
||
182 | 'rank' => $column['rank'], |
||
183 | ); |
||
184 | } |
||
185 | } |
||
186 | } |
||
187 | } else { |
||
188 | if (empty($this->_columns)) { |
||
189 | $this->_columns = $this->getColumnsFromDb(); |
||
190 | } |
||
191 | } |
||
192 | return $this->_columns; |
||
193 | } |
||
194 | |||
195 | protected $_aggregates; |
||
196 | /** |
||
197 | * @param array $aggs |
||
198 | */ |
||
199 | public function setAggregates($aggs) { |
||
202 | /** |
||
203 | * Adds aggregates to database |
||
204 | * |
||
205 | * @param array $cols |
||
206 | */ |
||
207 | public function saveAggregates($aggs) { |
||
208 | $dao = $this->getAggregatesDao(); |
||
209 | foreach ($aggs as $field_id => $aggregates) { |
||
210 | foreach ($aggregates as $aggregate) { |
||
211 | $dao->create($this->id, $field_id, $aggregate); |
||
212 | } |
||
213 | } |
||
214 | } |
||
215 | public function getAggregates() { |
||
216 | $session_renderer_table_functions = &$this->report_session->get("{$this->id}.aggregates"); |
||
217 | if ( $session_renderer_table_functions ) { |
||
218 | $aggregates = $session_renderer_table_functions; |
||
219 | $ff = $this->report->getFormElementFactory(); |
||
220 | foreach ($aggregates as $field_id => $aggregates) { |
||
221 | if ($formElement = $ff->getFormElementById($field_id)) { |
||
222 | if ($formElement->userCanRead()) { |
||
223 | $this->_aggregates[$field_id] = $aggregates; |
||
224 | } |
||
225 | } |
||
226 | } |
||
227 | } else { |
||
228 | if (empty($this->_aggregates)) { |
||
229 | $ff = $this->getFieldFactory(); |
||
230 | $this->_aggregates = array(); |
||
231 | foreach($this->getAggregatesDao()->searchByRendererId($this->id) as $row) { |
||
232 | if ($field = $ff->getUsedFormElementById($row['field_id'])) { |
||
233 | if ($field->userCanRead()) { |
||
234 | if (!isset($this->_aggregates[$row['field_id']])) { |
||
235 | $this->_aggregates[$row['field_id']] = array(); |
||
236 | } |
||
237 | $this->_aggregates[$row['field_id']][] = $row; |
||
238 | } |
||
239 | } |
||
240 | } |
||
241 | } |
||
242 | $aggregates = $this->_aggregates; |
||
243 | foreach($aggregates as $field_id => $agg) { |
||
244 | $this->report_session->set("{$this->id}.aggregates.{$field_id}", $agg); |
||
245 | } |
||
246 | |||
247 | } |
||
248 | return $this->_aggregates; |
||
249 | } |
||
250 | |||
251 | public function storeColumnsInSession() { |
||
258 | |||
259 | /** |
||
260 | * Get field ids and width used to display results |
||
261 | * @return array [{'field_id' => 12, 'width' => 33, 'rank' => 5}, [...]] |
||
262 | */ |
||
263 | public function getColumnsFromDb() { |
||
276 | |||
277 | protected function getSortDao() { |
||
280 | |||
281 | protected function getColumnsDao() { |
||
284 | |||
285 | protected function getAggregatesDao() { |
||
288 | |||
289 | /** |
||
290 | * Fetch content of the renderer |
||
291 | * @return string |
||
292 | */ |
||
293 | public function fetch($matching_ids, $request, $report_can_be_modified, PFUser $user) { |
||
294 | $html = ''; |
||
295 | $total_rows = $matching_ids['id'] ? substr_count($matching_ids['id'], ',') + 1 : 0; |
||
296 | $offset = (int)$request->get('offset'); |
||
297 | if ($offset < 0) { |
||
298 | $offset = 0; |
||
299 | } |
||
300 | if($request->get('renderer')) { |
||
301 | $renderer_data = $request->get('renderer'); |
||
302 | if ( isset($renderer_data[$this->id]) && isset($renderer_data[$this->id]['chunksz'])) { |
||
303 | $this->report_session->set("{$this->id}.chunksz", $renderer_data[$this->id]['chunksz']); |
||
304 | $this->report_session->setHasChanged(); |
||
305 | $this->chunksz = $renderer_data[$this->id]['chunksz']; |
||
306 | } |
||
307 | } |
||
308 | |||
309 | $extracolumn = self::EXTRACOLUMN_MASSCHANGE; |
||
310 | if ((int)$request->get('link-artifact-id')) { |
||
311 | $extracolumn = self::EXTRACOLUMN_LINK; |
||
312 | } |
||
313 | |||
314 | $html .= $this->fetchHeader($report_can_be_modified, $user, $total_rows); |
||
315 | $html .= $this->fetchTable($report_can_be_modified, $matching_ids, $total_rows, $offset, $extracolumn); |
||
316 | |||
317 | //Display next/previous |
||
318 | $html .= $this->fetchNextPrevious($total_rows, $offset, $report_can_be_modified, (int)$request->get('link-artifact-id')); |
||
319 | |||
320 | //Display masschange controls |
||
321 | if ((int)$request->get('link-artifact-id')) { |
||
322 | //TODO |
||
323 | } else { |
||
324 | $html .= $this->fetchMassChange($matching_ids, $total_rows, $offset); |
||
325 | } |
||
326 | |||
327 | return $html; |
||
328 | } |
||
329 | |||
330 | private function fetchHeader($report_can_be_modified, PFUser $user, $total_rows) { |
||
331 | $html = ''; |
||
332 | |||
333 | $html .= $this->fetchViewButtons($report_can_be_modified, $user); |
||
334 | |||
335 | //Display sort info |
||
336 | $html .= '<div class="tracker_report_renderer_table_information">'; |
||
337 | if ($report_can_be_modified) { |
||
338 | $html .= $this->fetchSort(); |
||
339 | } |
||
340 | |||
341 | $html .= $this->fetchMatchingNumber($total_rows); |
||
342 | $html .= '</div>'; |
||
343 | |||
344 | return $html; |
||
345 | } |
||
346 | |||
347 | private function fetchTable($report_can_be_modified, $matching_ids, $total_rows, $offset, $extracolumn) { |
||
348 | $html = ''; |
||
349 | |||
350 | //Display the head of the table |
||
351 | if ($report_can_be_modified) { |
||
352 | $only_one_column = null; |
||
353 | $with_sort_links = true; |
||
354 | } else { |
||
355 | $only_one_column = null; |
||
356 | $with_sort_links = false; |
||
357 | } |
||
358 | $html .= $this->fetchTHead($extracolumn, $only_one_column, $with_sort_links); |
||
359 | |||
360 | //Display the body of the table |
||
361 | $html .= $this->fetchTBody($matching_ids, $total_rows, $offset, $extracolumn); |
||
362 | |||
363 | return $html; |
||
364 | } |
||
365 | |||
366 | /** |
||
367 | * Fetch content of the renderer |
||
368 | * @return string |
||
369 | */ |
||
370 | public function fetchAsArtifactLink($matching_ids, $field_id, $read_only, $prefill_removed_values, $only_rows = false, $from_aid = null) { |
||
371 | $html = ''; |
||
372 | $total_rows = $matching_ids['id'] ? substr_count($matching_ids['id'], ',') + 1 : 0; |
||
373 | $offset = 0; |
||
374 | $use_data_from_db = true; |
||
375 | $extracolumn = $read_only ? self::NO_EXTRACOLUMN : self::EXTRACOLUMN_UNLINK; |
||
376 | $with_sort_links = false; |
||
377 | $only_one_column = null; |
||
378 | $pagination = false; |
||
379 | $read_only = true; |
||
380 | $store_in_session = true; |
||
381 | $head = ''; |
||
382 | |||
383 | //Display the head of the table |
||
384 | $suffix = '_'. $field_id .'_'. $this->report->id .'_'. $this->id; |
||
385 | $head .= $this->fetchTHead($extracolumn, $only_one_column, $with_sort_links, $use_data_from_db, $suffix); |
||
386 | if (!$only_rows) { |
||
387 | $html .= $head; |
||
388 | } |
||
389 | //Display the body of the table |
||
390 | $html .= $this->fetchTBody($matching_ids, $total_rows, $offset, $extracolumn, $only_one_column, $use_data_from_db, $pagination, $field_id, $prefill_removed_values, $only_rows, $read_only, $store_in_session, $from_aid); |
||
391 | |||
392 | if (!$only_rows) { |
||
393 | $html .= $this->fetchArtifactLinkGoToTracker(); |
||
394 | } |
||
395 | |||
396 | if ($only_rows) { |
||
397 | return array('head' => $head, 'rows' => $html); |
||
398 | } |
||
399 | return $html; |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Get the item of the menu options. |
||
404 | * |
||
405 | * If no items is returned, the menu won't be displayed. |
||
406 | * |
||
407 | * @return array of 'item_key' => {url: '', icon: '', label: ''} |
||
408 | */ |
||
409 | public function getOptionsMenuItems() { |
||
410 | $my_items = array('export' => ''); |
||
411 | $my_items['export'] .= '<div class="btn-group">'; |
||
412 | $my_items['export'] .= '<a class="btn btn-mini dropdown-toggle" data-toggle="dropdown" href="#">'; |
||
413 | $my_items['export'] .= '<i class="icon-download-alt"></i> '; |
||
414 | $my_items['export'] .= $GLOBALS['Language']->getText('plugin_tracker_report', 'export'); |
||
415 | $my_items['export'] .= ' <span class="caret"></span>'; |
||
416 | $my_items['export'] .= '</a>'; |
||
417 | $my_items['export'] .= '<ul class="dropdown-menu">'; |
||
418 | $my_items['export'] .= '<li>'; |
||
419 | $my_items['export'] .= '<a href="'. $this->getExportResultURL(self::EXPORT_LIGHT) .'">'; |
||
420 | $my_items['export'] .= $GLOBALS['Language']->getText('plugin_tracker_include_report', 'export_only_report_columns'); |
||
421 | $my_items['export'] .= '</a>'; |
||
422 | $my_items['export'] .= '</li>'; |
||
423 | $my_items['export'] .= '<li>'; |
||
424 | $my_items['export'] .= '<a href="'. $this->getExportResultURL(self::EXPORT_FULL) .'">'; |
||
425 | $my_items['export'] .= $GLOBALS['Language']->getText('plugin_tracker_include_report', 'export_all_columns'); |
||
426 | $my_items['export'] .= '</a>'; |
||
427 | $my_items['export'] .= '</li>'; |
||
428 | $my_items['export'] .= '</ul>'; |
||
429 | $my_items['export'] .= '</div>'; |
||
430 | |||
431 | return $my_items + parent::getOptionsMenuItems(); |
||
432 | } |
||
433 | |||
434 | private function getExportResultURL($export_only_displayed_fields) { |
||
435 | return TRACKER_BASE_URL.'/?'.http_build_query( |
||
436 | array( |
||
437 | 'report' => $this->report->id, |
||
438 | 'renderer' => $this->id, |
||
439 | 'func' => 'renderer', |
||
440 | 'renderer_table' => array( |
||
441 | 'export' => 1, |
||
442 | 'export_only_displayed_fields' => $export_only_displayed_fields, |
||
443 | ), |
||
444 | ) |
||
445 | ); |
||
446 | } |
||
447 | |||
448 | private function fetchFormStart($id = '', $func = 'renderer') { |
||
449 | $html = ''; |
||
450 | $html .= '<form method="POST" action="" id="'. $id .'" class="form-inline">'; |
||
451 | $html .= '<input type="hidden" name="report" value="'. $this->report->id .'" />'; |
||
452 | $html .= '<input type="hidden" name="renderer" value="'. $this->id .'" />'; |
||
453 | $html .= '<input type="hidden" name="func" value="'.$func.'" />'; |
||
454 | return $html; |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Fetch content to be displayed in widget |
||
459 | */ |
||
460 | public function fetchWidget(PFUser $user) { |
||
461 | $html = ''; |
||
462 | $use_data_from_db = true; |
||
463 | $store_in_session = false; |
||
464 | $matching_ids = $this->report->getMatchingIds(null, $use_data_from_db); |
||
465 | $total_rows = $matching_ids['id'] ? substr_count($matching_ids['id'], ',') + 1 : 0; |
||
466 | $offset = 0; |
||
467 | $extracolumn = self::NO_EXTRACOLUMN; |
||
468 | $with_sort_links = false; |
||
469 | $only_one_column = null; |
||
470 | $pagination = true; |
||
471 | $artifactlink_field_id = null; |
||
472 | $prefill_removed_values = null; |
||
473 | $only_rows = false; |
||
474 | $read_only = true; |
||
475 | $id_suffix = ''; |
||
476 | //Display the head of the table |
||
477 | $html .= $this->fetchTHead($extracolumn, $only_one_column, $with_sort_links, $use_data_from_db, $id_suffix, $store_in_session); |
||
478 | //Display the body of the table |
||
479 | $html .= $this->fetchTBody($matching_ids, $total_rows, $offset, $extracolumn, $only_one_column, $use_data_from_db, $pagination, $artifactlink_field_id, $prefill_removed_values, $only_rows, $read_only, $store_in_session); |
||
480 | |||
481 | //Dispaly range |
||
482 | $offset_last = min($offset + $this->chunksz - 1, $total_rows - 1); |
||
483 | $html .= '<div class="tracker_report_table_pager">'; |
||
484 | $html .= $this->fetchRange($offset + 1, $offset_last + 1, $total_rows, $this->fetchWidgetGoToReport()); |
||
485 | $html .= '</div>'; |
||
486 | |||
487 | return $html; |
||
488 | } |
||
489 | |||
490 | private function fetchMatchingNumber($total_rows) { |
||
494 | |||
495 | private function fetchSort() { |
||
496 | $html = ''; |
||
497 | $html .= '<div class="tracker_report_table_sortby_panel">'; |
||
498 | $sort_columns = $this->getSort(); |
||
499 | if ($this->sortHasUsedField()) { |
||
500 | $html .= $GLOBALS['Language']->getText('plugin_tracker_report','sort_by'); |
||
501 | $html .= ' '; |
||
502 | $ff = $this->getFieldFactory(); |
||
503 | $sort = array(); |
||
504 | foreach($sort_columns as $row) { |
||
505 | if ($row['field'] && $row['field']->isUsed()) { |
||
506 | $sort[] = '<a id="tracker_report_table_sort_by_'. $row['field_id'] .'" |
||
507 | href="?' . |
||
508 | http_build_query(array( |
||
509 | 'report' => $this->report->id, |
||
510 | 'renderer' => $this->id, |
||
511 | 'func' => 'renderer', |
||
512 | 'renderer_table[sort_by]' => $row['field_id'], |
||
513 | ) |
||
514 | ) . '">' . |
||
515 | $row['field']->getLabel() . |
||
516 | $this->getSortIcon($row['is_desc']) . |
||
517 | '</a>'; |
||
518 | } |
||
519 | } |
||
520 | $html .= implode(' <i class="icon-angle-right"></i> ', $sort); |
||
521 | } |
||
522 | $html .= '</div>'; |
||
523 | return $html; |
||
524 | } |
||
525 | |||
526 | private function fetchAddColumn() { |
||
527 | $add_columns_presenter = new Templating_Presenter_ButtonDropdownsMini( |
||
528 | 'tracker_report_add_columns_dropdown', |
||
529 | $GLOBALS['Language']->getText('plugin_tracker_report', 'toggle_columns'), |
||
530 | $this->report->getFieldsAsDropdownOptions('tracker_report_add_column', $this->getColumns(), Tracker_Report::TYPE_TABLE) |
||
531 | ); |
||
532 | $add_columns_presenter->setIcon('icon-eye-close'); |
||
533 | |||
534 | return $this->report->getTemplateRenderer()->renderToString('button_dropdowns', $add_columns_presenter); |
||
535 | } |
||
536 | |||
537 | private function fetchRange($from, $to, $total_rows, $additionnal_html) { |
||
538 | $html = ''; |
||
539 | $html .= '<span class="tracker_report_table_pager_range">'; |
||
540 | $html .= $GLOBALS['Language']->getText('plugin_tracker_include_report','items'); |
||
541 | $html .= ' <strong>'. $from .'</strong> – <strong>'. $to .'</strong>'; |
||
542 | $html .= ' ' . $GLOBALS['Language']->getText('plugin_tracker_renderer_table','items_range_of') . ' <strong>'. $total_rows .'</strong>'; |
||
543 | $html .= $additionnal_html; |
||
544 | $html .= '</span>'; |
||
545 | |||
546 | return $html; |
||
547 | } |
||
548 | |||
549 | private function fetchNextPrevious($total_rows, $offset, $report_can_be_modified, $link_artifact_id = null) { |
||
550 | $html = ''; |
||
551 | if ($total_rows) { |
||
552 | $parameters = array( |
||
553 | 'report' => $this->report->id, |
||
554 | 'renderer' => $this->id, |
||
555 | ); |
||
556 | if ($link_artifact_id) { |
||
557 | $parameters['link-artifact-id'] = (int)$link_artifact_id; |
||
558 | $parameters['only-renderer'] = 1; |
||
559 | } |
||
560 | //offset should be the last parameter to ease the concat later |
||
561 | $parameters['offset'] = ''; |
||
562 | $url = '?'. http_build_query($parameters); |
||
563 | |||
564 | $chunk = '<span class="tracker_report_table_pager_chunk">'; |
||
565 | $chunk .= $GLOBALS['Language']->getText('plugin_tracker', 'items_per_page'); |
||
566 | $chunk .= ' '; |
||
567 | if ($report_can_be_modified) { |
||
568 | $chunk .= '<div class="input-append">'; |
||
569 | $chunk .= '<input id="renderer_table_chunksz_input" type="text" name="renderer_table[chunksz]" size="1" maxlength="5" value="'. (int)$this->chunksz.'" />'; |
||
570 | $chunk .= '<button type="submit" class="btn">Ok</button> '; |
||
571 | $chunk .= '</div> '; |
||
572 | } else { |
||
573 | $chunk .= (int)$this->chunksz; |
||
574 | } |
||
575 | $chunk .= '</span>'; |
||
576 | |||
577 | $html .= $this->fetchFormStart('tracker_report_table_next_previous_form'); |
||
578 | $html .= '<div class="tracker_report_table_pager">'; |
||
579 | if ($total_rows < $this->chunksz) { |
||
580 | $html .= $this->fetchRange(1, $total_rows, $total_rows, $chunk); |
||
581 | } else { |
||
582 | if ($offset > 0) { |
||
583 | $html .= $this->getPagerButton($url . 0, 'begin'); |
||
584 | $html .= $this->getPagerButton($url . ($offset - $this->chunksz), 'prev'); |
||
585 | } else { |
||
586 | $html .= $this->getDisabledPagerButton('begin'); |
||
587 | $html .= $this->getDisabledPagerButton('prev'); |
||
588 | } |
||
589 | |||
590 | $offset_last = min($offset + $this->chunksz - 1, $total_rows - 1); |
||
591 | $html .= $this->fetchRange($offset + 1, $offset_last + 1, $total_rows, $chunk); |
||
592 | |||
593 | if (($offset + $this->chunksz) < $total_rows) { |
||
594 | if ($this->chunksz > 0) { |
||
595 | $offset_end = ($total_rows - ($total_rows % $this->chunksz)); |
||
596 | } else { |
||
597 | $offset_end = PHP_INT_MAX; //weird! it will take many steps to reach the last page if the user is browsing 0 artifacts at once |
||
598 | } |
||
599 | if ($offset_end >= $total_rows) { |
||
600 | $offset_end -= $this->chunksz; |
||
601 | } |
||
602 | $html .= $this->getPagerButton($url . ($offset + $this->chunksz), 'next'); |
||
603 | $html .= $this->getPagerButton($url . $offset_end, 'end'); |
||
604 | } else { |
||
605 | $html .= $this->getDisabledPagerButton('next'); |
||
606 | $html .= $this->getDisabledPagerButton('end'); |
||
607 | } |
||
608 | } |
||
609 | $html .= '</div>'; |
||
610 | $html .= '</form>'; |
||
611 | } |
||
612 | return $html; |
||
613 | } |
||
614 | |||
615 | private function getDisabledPagerButton($direction) { |
||
616 | $icons = array( |
||
617 | 'begin' => 'icon-double-angle-left', |
||
618 | 'end' => 'icon-double-angle-right', |
||
619 | 'prev' => 'icon-angle-left', |
||
620 | 'next' => 'icon-angle-right', |
||
621 | ); |
||
622 | $html = ''; |
||
623 | $html .= '<button |
||
624 | class="btn disabled" |
||
625 | type="button" |
||
626 | title="'. $GLOBALS['Language']->getText('global', $direction) .'" |
||
627 | >'; |
||
628 | $html .= '<i class="'. $icons[$direction] .'"></i>'; |
||
629 | $html .= '</button> '; |
||
630 | |||
631 | return $html; |
||
632 | } |
||
633 | |||
634 | private function getPagerButton($url, $direction) { |
||
635 | $icons = array( |
||
636 | 'begin' => 'icon-double-angle-left', |
||
637 | 'end' => 'icon-double-angle-right', |
||
638 | 'prev' => 'icon-angle-left', |
||
639 | 'next' => 'icon-angle-right', |
||
640 | ); |
||
641 | $html = ''; |
||
642 | $html .= '<a |
||
643 | href="'. $url .'" |
||
644 | class="btn" |
||
645 | title="'. $GLOBALS['Language']->getText('global', $direction) .'" |
||
646 | >'; |
||
647 | $html .= '<i class="'. $icons[$direction] .'"></i>'; |
||
648 | $html .= '</a> '; |
||
649 | |||
650 | return $html; |
||
651 | } |
||
652 | |||
653 | protected function reorderColumnsByRank($columns) { |
||
654 | |||
655 | $array_rank = array(); |
||
656 | foreach($columns as $field_id => $properties) { |
||
657 | $array_rank[$field_id] = $properties['rank']; |
||
658 | } |
||
659 | asort($array_rank); |
||
660 | $columns_sort = array(); |
||
661 | foreach ($array_rank as $id => $rank) { |
||
662 | $columns_sort[$id] = $columns[$id]; |
||
663 | } |
||
664 | return $columns_sort; |
||
665 | } |
||
666 | |||
667 | const NO_EXTRACOLUMN = 0; |
||
668 | const EXTRACOLUMN_MASSCHANGE = 1; |
||
669 | const EXTRACOLUMN_LINK = 2; |
||
670 | const EXTRACOLUMN_UNLINK = 3; |
||
671 | |||
672 | private function fetchTHead($extracolumn = 1, $only_one_column = null, $with_sort_links = true, $use_data_from_db = false, $id_suffix = '', $store_in_session = true) { |
||
791 | |||
792 | public function getTableColumns($only_one_column, $use_data_from_db, $store_in_session = true) { |
||
793 | $columns = array(); |
||
794 | if ($use_data_from_db) { |
||
795 | $all_columns = $this->reorderColumnsByRank($this->getColumnsFromDb()); |
||
796 | } else { |
||
797 | $all_columns = $this->reorderColumnsByRank($this->getColumns()); |
||
798 | } |
||
799 | if ($only_one_column) { |
||
800 | if (isset($all_columns[$only_one_column])) { |
||
801 | $columns = array($all_columns[$only_one_column]); |
||
802 | } else { |
||
803 | $columns = array(array( |
||
804 | 'width' => 0, |
||
805 | 'field' => $this->getFieldFactory()->getUsedFormElementFieldById($only_one_column), |
||
806 | )); |
||
807 | } |
||
808 | } else { |
||
809 | $columns = $all_columns; |
||
810 | } |
||
811 | return $columns; |
||
812 | } |
||
813 | |||
814 | /** |
||
815 | * Display the body of the table |
||
816 | * |
||
817 | * @param array $matching_ids The matching ids to display array('id' => '"1,4,8,10", 'last_matching_ids' => "123,145,178,190") |
||
818 | * @param int $total_rows The number of total rows (pagination powwwa) |
||
819 | * @param int $offset The offset of the pagination |
||
820 | * @param int $extracolumn Need for an extracolumn? NO_EXTRACOLUMN | EXTRACOLUMN_MASSCHANGE | EXTRACOLUMN_LINK | EXTRACOLUMN_UNLINK. Default is EXTRACOLUMN_MASSCHANGE. |
||
821 | * @param int $only_one_column The column (field_id) to display. null if all columns are needed. Default is null |
||
822 | * @param bool $use_data_from_db true if we need to retrieve data from the db instead of the session. Default is false. |
||
823 | * @param bool $pagination true if we display the pagination. Default is true. |
||
824 | * @param int $artifactlink_field_id The artifactlink field id. Needed to display report in ArtifactLink field. Default is null |
||
825 | * @param array $prefill_removed_values Array of artifact_id to pre-check. array(123 => X, 345 => X, ...). Default is null |
||
826 | * @param bool $only_rows Display only rows, no aggregates or stuff like that. Default is false. |
||
827 | * @param bool $read_only Display the table in read only mode. Default is false. |
||
828 | * |
||
829 | * @return string html |
||
830 | */ |
||
831 | private function fetchTBody($matching_ids, $total_rows, $offset, $extracolumn = 1, $only_one_column = null, $use_data_from_db = false, $pagination = true, $artifactlink_field_id = null, $prefill_removed_values = null, $only_rows = false, $read_only = false, $store_in_session = true, $from_aid = null) { |
||
832 | $html = ''; |
||
833 | if (!$only_rows) { |
||
834 | $html .= "\n<!-- table renderer body -->\n"; |
||
835 | $html .= '<tbody>'; |
||
836 | $additional_classname = ''; |
||
837 | } else { |
||
838 | $additional_classname = 'additional'; |
||
839 | } |
||
840 | if ($total_rows) { |
||
841 | |||
842 | $columns = $this->getTableColumns($only_one_column, $use_data_from_db); |
||
843 | |||
844 | $extracted_fields = $this->extractFieldsFromColumns($columns); |
||
845 | |||
846 | $aggregates = false; |
||
847 | |||
848 | $queries = $this->buildOrderedQuery($matching_ids, $extracted_fields, $aggregates, $store_in_session); |
||
849 | |||
850 | $dao = new DataAccessObject(); |
||
851 | $results = array(); |
||
852 | foreach($queries as $sql) { |
||
853 | //Limit |
||
854 | if ($total_rows > $this->chunksz && $pagination) { |
||
855 | $sql .= " LIMIT ". (int)$offset .", ". (int)$this->chunksz; |
||
856 | } |
||
857 | $results[] = $dao->retrieve($sql); |
||
858 | } |
||
859 | // test if first result is valid (if yes, we consider that others are valid too) |
||
860 | if (!empty($results[0])) { |
||
861 | //extract the first results |
||
862 | $first_result = array_shift($results); |
||
863 | //loop through it |
||
864 | foreach ($first_result as $row) { //id, f1, f2 |
||
865 | //merge the row with the other results |
||
866 | foreach ($results as $result) { |
||
867 | //[id, f1, f2] + [id, f3, f4] |
||
868 | $row = array_merge($row, $result->getRow()); |
||
869 | //row == id, f1, f2, f3, f4... |
||
870 | } |
||
871 | $html .= '<tr class="'. $additional_classname .'">'; |
||
872 | $current_user = UserManager::instance()->getCurrentUser(); |
||
873 | if ($extracolumn) { |
||
874 | $display_extracolumn = true; |
||
875 | $checked = ''; |
||
876 | $classname = 'tracker_report_table_'; |
||
877 | if ($extracolumn === self::EXTRACOLUMN_MASSCHANGE && $this->report->getTracker()->userIsAdmin($current_user)) { |
||
878 | $classname .= 'masschange'; |
||
879 | $name = 'masschange_aids'; |
||
880 | } else if ($extracolumn === self::EXTRACOLUMN_LINK) { |
||
881 | $classname .= 'link'; |
||
882 | $name = 'link-artifact[search]'; |
||
883 | } else if ($extracolumn === self::EXTRACOLUMN_UNLINK) { |
||
884 | $classname .= 'unlink'; |
||
885 | $name = 'artifact['. (int)$artifactlink_field_id .'][removed_values]['. $row['id'] .']'; |
||
886 | if (isset($prefill_removed_values[$row['id']])) { |
||
887 | $checked = 'checked="checked"'; |
||
888 | } |
||
889 | } else { |
||
890 | $display_extracolumn = false; |
||
891 | } |
||
892 | |||
893 | if ($display_extracolumn) { |
||
894 | $html .= '<td class="'. $classname .'" width="1">'; |
||
895 | $html .= '<span><input type="checkbox" name="'. $name .'[]" value="'. $row['id'] .'" '. $checked .' /></span>'; |
||
896 | $html .= '</td>'; |
||
897 | } |
||
898 | } |
||
899 | if (!$only_one_column) { |
||
900 | $params = array( |
||
901 | 'aid' => $row['id'] |
||
902 | ); |
||
903 | if ($from_aid != null) { |
||
904 | $params['from_aid'] = $from_aid; |
||
905 | } |
||
906 | $url = TRACKER_BASE_URL .'/?'. http_build_query($params); |
||
907 | |||
908 | $html .= '<td>'; |
||
909 | $html .= '<a |
||
910 | class="direct-link-to-artifact" |
||
911 | href="'. $url .'" |
||
912 | title="'. $GLOBALS['Language']->getText('plugin_tracker_include_report','show') .' artifact #'. $row['id'] .'">'; |
||
913 | $html .= '<i class="icon-edit"></i>'; |
||
914 | $html .= '</td>'; |
||
915 | } |
||
916 | foreach($columns as $column) { |
||
917 | if($column['field']->isUsed()) { |
||
918 | $field_name = $column['field']->name; |
||
919 | $value = isset($row[$field_name]) ? $row[$field_name] : null; |
||
920 | $html .= '<td class="tracker_report_table_column_'. $column['field']->id .'">'; |
||
921 | $html .= $column['field']->fetchChangesetValue($row['id'], $row['changeset_id'], $value, $this->report, $from_aid); |
||
922 | $html .= '</td>'; |
||
923 | } |
||
924 | } |
||
925 | $html .= '</tr>'; |
||
926 | } |
||
927 | if (!$only_rows) { |
||
928 | $html .= $this->fetchAggregates($matching_ids, $extracolumn, $only_one_column, $columns, $extracted_fields, $use_data_from_db, $read_only); |
||
929 | } |
||
930 | } |
||
931 | } else { |
||
932 | $html .= '<tr class="tracker_report_table_no_result"><td colspan="'. (count($this->getColumns())+2) .'" align="center">'. 'No results' .'</td></tr>'; |
||
933 | } |
||
934 | if (!$only_rows) { |
||
935 | $html .= '</tbody>'; |
||
936 | $html .= '</table>'; |
||
937 | } |
||
938 | return $html; |
||
939 | } |
||
940 | |||
941 | public function fetchAggregates($matching_ids, $extracolumn, $only_one_column, $columns, $extracted_fields, $use_data_from_db, $read_only) { |
||
942 | $html = ''; |
||
943 | |||
944 | //We presume that if EXTRACOLUMN_LINK then it means that we are in the ArtifactLink selector so we force read only mode |
||
945 | if ($extracolumn === self::EXTRACOLUMN_LINK) { |
||
946 | $read_only = true; |
||
947 | } |
||
948 | |||
949 | $current_user = UserManager::instance()->getCurrentUser(); |
||
950 | //Insert function aggregates |
||
951 | if ($use_data_from_db) { |
||
952 | $aggregate_functions_raw = array($this->getAggregatesDao()->searchByRendererId($this->getId())); |
||
953 | } else { |
||
954 | $aggregate_functions_raw = $this->getAggregates(); |
||
955 | } |
||
956 | $aggregates = array(); |
||
957 | foreach ($aggregate_functions_raw as $rows) { |
||
958 | if ($rows) { |
||
959 | foreach ($rows as $row) { |
||
960 | //is the field used as a column? |
||
961 | if (isset($extracted_fields[$row['field_id']])) { |
||
962 | if (!isset($aggregates[$row['field_id']])) { |
||
963 | $aggregates[$row['field_id']] = array(); |
||
964 | } |
||
965 | $aggregates[$row['field_id']][] = $row['aggregate']; |
||
966 | } |
||
967 | } |
||
968 | } |
||
969 | } |
||
970 | $queries = $this->buildOrderedQuery($matching_ids, $extracted_fields, $aggregates, '', false); |
||
971 | $dao = new DataAccessObject(); |
||
972 | $results = array(); |
||
973 | foreach ($queries as $key => $sql) { |
||
974 | if ($key === 'aggregates_group_by') { |
||
975 | foreach ($sql as $k => $s) { |
||
976 | $results[$k] = $dao->retrieve($s); |
||
977 | } |
||
978 | } else { |
||
979 | if ($dar = $dao->retrieve($sql)) { |
||
980 | $results = array_merge($results, $dar->getRow()); |
||
981 | } |
||
982 | } |
||
983 | } |
||
984 | |||
985 | $is_first = true; |
||
986 | $html .= '<tr valign="top" class="tracker_report_table_aggregates">'; |
||
987 | $html .= $this->fetchAggregatesExtraColumns($extracolumn, $only_one_column, $current_user); |
||
988 | foreach ($columns as $column) { |
||
989 | $field = $column['field']; |
||
990 | if (! $field->isUsed()) { |
||
991 | continue; |
||
992 | } |
||
993 | |||
994 | $html .= '<td class="tracker_report_table_column_'. $field->getId() .'">'; |
||
995 | $html .= '<table><thead><tr>'; |
||
996 | $html .= $this->fetchAddAggregatesUsedFunctionsHeader($field, $aggregates, $results); |
||
997 | $html .= '<th>'; |
||
998 | $html .= $this->fetchAddAggregatesButton($read_only, $field, $current_user, $aggregates, $is_first); |
||
999 | $html .= '</th>'; |
||
1000 | $html .= '</tr></thead><tbody><tr>'; |
||
1001 | $result = $this->fetchAddAggregatesUsedFunctionsValue($field, $aggregates, $results); |
||
1002 | if (! $result) { |
||
1003 | $html .= '<td></td>'; |
||
1004 | } |
||
1005 | $html .= $result; |
||
1006 | $html .= '</tr></tbody></table>'; |
||
1007 | $html .= '</td>'; |
||
1008 | |||
1009 | $is_first = false; |
||
1010 | } |
||
1011 | $html .= '</tr>'; |
||
1012 | |||
1013 | return $html; |
||
1014 | } |
||
1015 | |||
1016 | private function fetchAddAggregatesUsedFunctionsHeader( |
||
1017 | Tracker_FormElement_Field $field, |
||
1018 | array $used_aggregates, |
||
1019 | array $results |
||
1020 | ) { |
||
1021 | if (! isset($used_aggregates[$field->getId()])) { |
||
1022 | return ''; |
||
1023 | } |
||
1024 | |||
1025 | $html = ''; |
||
1026 | foreach ($used_aggregates[$field->getId()] as $function) { |
||
1027 | if (! isset($results[$field->getName() . '_' . $function])) { |
||
1028 | continue; |
||
1029 | } |
||
1030 | |||
1031 | $html .= '<th>'; |
||
1032 | $html .= $GLOBALS['Language']->getText('plugin_tracker_aggregate', $function); |
||
1033 | $html .= '</th>'; |
||
1034 | } |
||
1035 | |||
1036 | return $html; |
||
1037 | } |
||
1038 | |||
1039 | private function fetchAddAggregatesUsedFunctionsValue( |
||
1089 | |||
1090 | private function fetchAddAggregatesButton( |
||
1091 | $read_only, |
||
1092 | Tracker_FormElement_Field $field, |
||
1093 | PFUser $current_user, |
||
1094 | array $used_aggregates, |
||
1095 | $is_first |
||
1096 | ) { |
||
1097 | $aggregate_functions = $field->getAggregateFunctions(); |
||
1098 | |||
1099 | if ($read_only || $current_user->isAnonymous()) { |
||
1100 | return; |
||
1101 | } |
||
1102 | |||
1103 | if (! $aggregate_functions) { |
||
1104 | return; |
||
1105 | } |
||
1106 | |||
1107 | $html = ''; |
||
1108 | $html .= '<div class="btn-group">'; |
||
1109 | $html .= '<a href="#" |
||
1110 | class="btn btn-mini dropdown-toggle" |
||
1111 | title="'. $GLOBALS['Language']->getText('plugin_tracker_aggregate', 'toggle') .'" |
||
1112 | data-toggle="dropdown">'; |
||
1113 | $html .= '<i class="icon-plus"></i> '; |
||
1114 | $html .= '<span class="caret"></span>'; |
||
1115 | $html .= '</a>'; |
||
1116 | $html .= '<ul class="dropdown-menu '. ($is_first ? '' : 'pull-right') .'">'; |
||
1117 | foreach ($aggregate_functions as $function) { |
||
1118 | $is_used = isset($used_aggregates[$field->getId()]) && in_array($function, $used_aggregates[$field->getId()]); |
||
1119 | $url = $this->getAggregateURL($field, $function); |
||
1120 | $html .= '<li>'; |
||
1121 | $html .= '<a href="'. $url .'">'; |
||
1122 | if ($is_used) { |
||
1123 | $html .= '<i class="icon-ok"></i> '; |
||
1124 | } |
||
1125 | $html .= $GLOBALS['Language']->getText('plugin_tracker_aggregate', $function); |
||
1126 | $html .= '</a>'; |
||
1127 | $html .= '</li>'; |
||
1128 | } |
||
1129 | $html .= '</ul>'; |
||
1130 | $html .= '</div>'; |
||
1131 | |||
1132 | return $html; |
||
1133 | } |
||
1134 | |||
1135 | private function getAggregateURL($field, $function) { |
||
1136 | $field_id = $field->getId(); |
||
1137 | $params = array( |
||
1138 | 'func' => 'renderer', |
||
1139 | 'report' => $this->report->getId(), |
||
1140 | 'renderer' => $this->getId(), |
||
1141 | 'renderer_table' => array( |
||
1142 | 'add_aggregate' => array( |
||
1143 | $field_id => $function |
||
1144 | ) |
||
1145 | ) |
||
1146 | ); |
||
1147 | return TRACKER_BASE_URL .'/?'. http_build_query($params); |
||
1148 | } |
||
1149 | |||
1150 | private function fetchAggregatesExtraColumns($extracolumn, $only_one_column, PFUser $current_user) { |
||
1151 | $html = ''; |
||
1152 | $inner_table = '<table><thead><tr><th></th></tr></thead></table>'; |
||
1153 | if ($extracolumn) { |
||
1154 | $display_extracolumn = true; |
||
1155 | $classname = 'tracker_report_table_'; |
||
1156 | if ($extracolumn === self::EXTRACOLUMN_MASSCHANGE && $this->report->getTracker()->userIsAdmin($current_user)) { |
||
1157 | $classname .= 'masschange'; |
||
1158 | } else if ($extracolumn === self::EXTRACOLUMN_LINK) { |
||
1159 | $classname .= 'link'; |
||
1160 | } else if ($extracolumn === self::EXTRACOLUMN_UNLINK) { |
||
1161 | $classname .= 'unlink'; |
||
1162 | } else { |
||
1163 | $display_extracolumn = false; |
||
1164 | } |
||
1165 | |||
1166 | if ($display_extracolumn) { |
||
1167 | $html .= '<td class="' . $classname . '" width="1">'; |
||
1168 | $html .= $inner_table; |
||
1169 | $html .= '</td>'; |
||
1170 | } |
||
1171 | } |
||
1172 | if (! $only_one_column) { |
||
1173 | $html .= '<td>'. $inner_table .'</td>'; |
||
1174 | } |
||
1175 | |||
1176 | return $html; |
||
1177 | } |
||
1178 | |||
1179 | protected function formatAggregateResult($value) { |
||
1180 | if (is_numeric($value)) { |
||
1181 | $decimals = 2; |
||
1182 | if (round($value) == $value) { |
||
1183 | $decimals = 0; |
||
1184 | } |
||
1185 | $value = round($value, $decimals); |
||
1186 | } else { |
||
1187 | $value = Codendi_HTMLPurifier::instance()->purify($value); |
||
1188 | } |
||
1189 | |||
1190 | return '<span class="tracker_report_table_aggregates_value">'. $value .'</span>'; |
||
1191 | } |
||
1192 | |||
1193 | /** |
||
1194 | * Extract the fields from columns: |
||
1195 | * |
||
1196 | * @param array $columns [ 0 => { 'field' => F1, 'width' => 40 }, 1 => { 'field' => F2, 'width' => 40 } ] |
||
1197 | * |
||
1198 | * @return array [ F1, F2 ] |
||
1199 | */ |
||
1200 | public function extractFieldsFromColumns($columns) { |
||
1201 | $fields = array(); |
||
1202 | $f = create_function('$v, $i, $t', '$t["fields"][$v["field"]->getId()] = $v["field"];'); |
||
1203 | array_walk($columns, $f, array('fields' => &$fields)); |
||
1204 | return $fields; |
||
1205 | } |
||
1206 | |||
1207 | /** |
||
1208 | * Build oredered query |
||
1209 | * |
||
1210 | * @param array $matching_ids The artifact to display |
||
1211 | * @param Tracker_FormElement_Field[] $fields The fields to display |
||
1212 | * |
||
1213 | * @return array of sql queries |
||
1214 | */ |
||
1215 | protected function buildOrderedQuery($matching_ids, $fields, $aggregates = false, $store_in_session = true) { |
||
1331 | |||
1332 | private function fetchMassChange($matching_ids, $total_rows, $offset) { |
||
1333 | $html = ''; |
||
1334 | $tracker = $this->report->getTracker(); |
||
1335 | if ($tracker->userIsAdmin()) { |
||
1336 | $nb_art = $matching_ids['id'] ? substr_count($matching_ids['id'], ',') + 1 : 0; |
||
1337 | $first_row = ($nb_art / $this->chunksz) + $offset; |
||
1338 | $last_row = $first_row + $this->chunksz; |
||
1339 | $html .= '<form method="POST" action="" id="tracker_report_table_masschange_form">'; |
||
1340 | $html .= '<input type="hidden" name="func" value="display-masschange-form" />'; |
||
1341 | //build input for masschange all searched art ids |
||
1342 | foreach ( explode(',', $matching_ids['id']) as $id ) { |
||
1343 | $html .= '<input type="hidden" name="masschange_aids_all[]" value="'. $id .'"/>'; |
||
1344 | } |
||
1345 | $html .= '<div id="tracker_report_table_masschange_panel">'; |
||
1346 | $html .= '<input id="masschange_btn_checked" type="submit" class="btn" name="renderer_table[masschange_checked]" value="'.$GLOBALS['Language']->getText('plugin_tracker_include_report', 'mass_change_checked', $first_row, $last_row) .'" /> '; |
||
1347 | $html .= '<input id="masschange_btn_all" type="submit" class="btn" name="renderer_table[masschange_all]" value="'.$GLOBALS['Language']->getText('plugin_tracker_include_report', 'mass_change_all', $total_rows) .'" />'; |
||
1348 | $html .= '</div>'; |
||
1349 | $html .= '</form>'; |
||
1350 | } |
||
1351 | return $html; |
||
1352 | } |
||
1353 | |||
1354 | protected function getFieldFactory() { |
||
1357 | |||
1358 | /** |
||
1359 | * Duplicate the renderer |
||
1360 | */ |
||
1361 | public function duplicate($from_renderer, $field_mapping) { |
||
1362 | //duplicate sort |
||
1363 | $this->getSortDao()->duplicate($from_renderer->id, $this->id, $field_mapping); |
||
1364 | //duplicate columns |
||
1365 | $this->getColumnsDao()->duplicate($from_renderer->id, $this->id, $field_mapping); |
||
1366 | //duplicate aggregates |
||
1367 | $this->getAggregatesDao()->duplicate($from_renderer->id, $this->id, $field_mapping); |
||
1368 | } |
||
1369 | |||
1370 | public function getType() { |
||
1373 | |||
1374 | /** |
||
1375 | * Process the request |
||
1376 | * @param Request $request |
||
1377 | */ |
||
1378 | public function processRequest(TrackerManager $tracker_manager, $request, $current_user) { |
||
1617 | |||
1618 | /** |
||
1619 | * Transforms Tracker_Renderer into a SimpleXMLElement |
||
1620 | * |
||
1621 | * @param SimpleXMLElement $root the node to which the renderer is attached (passed by reference) |
||
1622 | */ |
||
1623 | public function exportToXml(SimpleXMLElement $root, $xmlMapping) { |
||
1624 | parent::exportToXML($root, $xmlMapping); |
||
1625 | $root->addAttribute('chunksz', $this->chunksz); |
||
1626 | if ($this->multisort) { |
||
1627 | $root->addAttribute('multisort', $this->multisort); |
||
1628 | } |
||
1629 | $child = $root->addChild('columns'); |
||
1630 | foreach ($this->getColumns() as $key => $col) { |
||
1631 | |||
1632 | $child->addChild('field')->addAttribute('REF', array_search($key, $xmlMapping)); |
||
1633 | } |
||
1634 | //TODO : add aggregates in XML export |
||
1635 | /*if ($this->getAggregates()) { |
||
1636 | $child = $root->addChild('aggregates'); |
||
1637 | foreach ($this->getAggregates() as $field_id => $aggregates) { |
||
1638 | foreach ($aggregates as $aggregate) { |
||
1639 | $child->addChild('aggregate')->addAttribute('REF', array_search($field_id, $xmlMapping)) |
||
1640 | ->addAttribute('function', $aggregate); |
||
1641 | } |
||
1642 | } |
||
1643 | }*/ |
||
1644 | if ($this->getSort()) { |
||
1645 | $child = $root->addChild('sort'); |
||
1646 | foreach ($this->getSort() as $key => $sort) { |
||
1647 | $child->addChild('field')->addAttribute('REF', array_search($key, $xmlMapping)); |
||
1648 | } |
||
1649 | } |
||
1650 | } |
||
1651 | |||
1652 | /** |
||
1653 | * Export results to csv |
||
1654 | * |
||
1655 | * @param bool $only_columns True if we need to export only the displayed columns. False for all the fields. |
||
1656 | * |
||
1657 | * @return void |
||
1658 | */ |
||
1659 | protected function exportToCSV($only_columns) { |
||
1660 | $matching_ids = $this->report->getMatchingIds(); |
||
1661 | $total_rows = $matching_ids['id'] ? substr_count($matching_ids['id'], ',') + 1 : 0; |
||
1662 | |||
1663 | if ($only_columns) { |
||
1664 | $fields = $this->extractFieldsFromColumns($this->reorderColumnsByRank($this->getColumns())); |
||
1665 | } else { |
||
1666 | $fields = Tracker_FormElementFactory::instance()->getUsedFields($this->report->getTracker()); |
||
1667 | } |
||
1668 | |||
1669 | $lines = array(); |
||
1670 | $head = array('aid'); |
||
1671 | foreach ($fields as $field) { |
||
1672 | if ($this->canFieldBeExportedToCSV($field)) { |
||
1673 | $head[] = $field->getName(); |
||
1674 | } |
||
1675 | } |
||
1676 | $lines[] = $head; |
||
1677 | |||
1678 | $queries = $this->buildOrderedQuery($matching_ids, $fields); |
||
1679 | $dao = new DataAccessObject(); |
||
1680 | $results = array(); |
||
1681 | foreach($queries as $sql) { |
||
1682 | $results[] = $dao->retrieve($sql); |
||
1683 | } |
||
1684 | |||
1685 | |||
1686 | if (!empty($results[0])) { |
||
1687 | $i = 0; |
||
1688 | //extract the first results |
||
1689 | $first_result = array_shift($results); |
||
1690 | |||
1691 | //loop through it |
||
1692 | foreach ($first_result as $row) { //id, f1, f2 |
||
1693 | |||
1694 | //merge the row with the other results |
||
1695 | foreach ($results as $result) { |
||
1696 | //[id, f1, f2] + [id, f3, f4] |
||
1697 | $row = array_merge($row, $result->getRow()); |
||
1698 | //row == id, f1, f2, f3, f4... |
||
1699 | } |
||
1700 | |||
1701 | //build the csv line |
||
1702 | $line = array(); |
||
1703 | $line[] = $row['id']; |
||
1704 | foreach($fields as $field) { |
||
1705 | if($this->canFieldBeExportedToCSV($field)) { |
||
1706 | $value = isset($row[$field->getName()]) ? $row[$field->getName()] : null; |
||
1707 | $line[] = $field->fetchCSVChangesetValue($row['id'], $row['changeset_id'], $value, $this->report); |
||
1708 | } |
||
1709 | } |
||
1710 | $lines[] = $line; |
||
1711 | } |
||
1712 | |||
1713 | $separator = ","; // by default, comma. |
||
1714 | $user = UserManager::instance()->getCurrentUser(); |
||
1715 | $separator_csv_export_pref = $user->getPreference('user_csv_separator'); |
||
1716 | switch ($separator_csv_export_pref) { |
||
1717 | case "comma": |
||
1718 | $separator = ','; |
||
1719 | break; |
||
1720 | case "semicolon": |
||
1721 | $separator = ';'; |
||
1722 | break; |
||
1723 | case "tab": |
||
1724 | $separator = chr(9); |
||
1725 | break; |
||
1726 | } |
||
1727 | |||
1728 | $http = Codendi_HTTPPurifier::instance(); |
||
1729 | $file_name = str_replace(' ', '_', 'artifact_' . $this->report->getTracker()->getItemName()); |
||
1730 | header('Content-Disposition: filename='. $http->purify($file_name) .'_'. $this->report->getTracker()->getProject()->getUnixName(). '.csv'); |
||
1731 | header('Content-type: text/csv'); |
||
1732 | foreach($lines as $line) { |
||
1733 | fputcsv(fopen("php://output", "a"), $line, $separator, '"'); |
||
1734 | } |
||
1735 | die(); |
||
1736 | } else { |
||
1737 | $GLOBALS['Response']->addFeedback('error', 'Unable to export (too many fields?)'); |
||
1738 | } |
||
1739 | } |
||
1740 | |||
1741 | private function canFieldBeExportedToCSV(Tracker_FormElement_Field $field) { |
||
1748 | |||
1749 | /** |
||
1750 | * Save columns in db |
||
1751 | * |
||
1752 | * @param int $renderer_id the id of the renderer |
||
1753 | */ |
||
1754 | protected function saveColumnsRenderer($renderer_id) { |
||
1766 | |||
1767 | /** |
||
1768 | * Save aggregates in db |
||
1769 | * |
||
1770 | * @param int $renderer_id the id of the renderer |
||
1771 | */ |
||
1772 | protected function saveAggregatesRenderer($renderer_id) { |
||
1773 | $aggregates = $this->getAggregates(); |
||
1774 | $ff = $this->getFieldFactory(); |
||
1775 | //Add columns in db |
||
1776 | if (is_array($aggregates)) { |
||
1777 | $dao = $this->getAggregatesDao(); |
||
1778 | foreach($aggregates as $field_id => $aggs) { |
||
1779 | if ($field = $ff->getUsedFormElementById($field_id)) { |
||
1780 | foreach ($aggs as $agg) { |
||
1781 | $dao->create($renderer_id, $field_id, $agg['aggregate']); |
||
1782 | } |
||
1783 | } |
||
1784 | } |
||
1785 | } |
||
1786 | } |
||
1787 | |||
1788 | /** |
||
1789 | * Save multisort/chunksz in db |
||
1790 | * |
||
1791 | * @param int $renderer_id the id of the renderer |
||
1792 | */ |
||
1793 | protected function saveRendererProperties ($renderer_id) { |
||
1794 | $dao = new Tracker_Report_Renderer_TableDao(); |
||
1795 | if (!$dao->searchByRendererId($renderer_id)->getRow()) { |
||
1796 | $dao->create($renderer_id, $this->chunksz); |
||
1797 | } |
||
1798 | $dao->save($renderer_id, $this->chunksz, $this->multisort); |
||
1799 | } |
||
1800 | |||
1801 | /** |
||
1802 | * Save sort in db |
||
1803 | * |
||
1804 | * @param int $renderer_id the id of the renderer |
||
1805 | */ |
||
1806 | protected function saveSortRenderer($renderer_id) { |
||
1807 | $sort = $this->getSort(); |
||
1808 | if (is_array($sort)) { |
||
1809 | foreach ($sort as $field_id => $properties) { |
||
1810 | $this->getSortDao()->create($renderer_id, $field_id, $properties['is_desc'], $properties['rank']); |
||
1811 | } |
||
1812 | } |
||
1813 | } |
||
1814 | |||
1815 | /** |
||
1816 | * Create a renderer - add in db |
||
1817 | * |
||
1818 | * @return bool true if success, false if failure |
||
1819 | */ |
||
1820 | public function create() { |
||
1821 | $success = true; |
||
1822 | $rrf = Tracker_Report_RendererFactory::instance(); |
||
1823 | |||
1824 | if ($renderer_id = $rrf->saveRenderer($this->report, $this->name, $this->description, $this->getType())) { |
||
1825 | //columns |
||
1826 | $this->saveColumnsRenderer($renderer_id); |
||
1827 | |||
1828 | //aggregates |
||
1829 | $this->saveAggregatesRenderer($renderer_id); |
||
1830 | |||
1831 | //MultiSort/Chunksz |
||
1832 | $this->saveRendererProperties($renderer_id); |
||
1833 | |||
1834 | //Sort |
||
1835 | $this->saveSortRenderer($renderer_id); |
||
1836 | } |
||
1837 | return $success; |
||
1838 | } |
||
1839 | |||
1840 | |||
1841 | /** |
||
1842 | * Update the renderer |
||
1843 | * |
||
1844 | * @return bool true if success, false if failure |
||
1845 | */ |
||
1846 | public function update() { |
||
1847 | $success = true; |
||
1848 | if ($this->id > 0) { |
||
1849 | //first delete existing columns and sort |
||
1850 | $this->getSortDao()->delete($this->id); |
||
1851 | $this->getColumnsDao()->delete($this->id); |
||
1852 | $this->getAggregatesDao()->deleteByRendererId($this->id); |
||
1853 | |||
1854 | //columns |
||
1855 | $this->saveColumnsRenderer($this->id); |
||
1856 | |||
1857 | //aggregates |
||
1858 | $this->saveAggregatesRenderer($this->id); |
||
1859 | |||
1860 | //MultiSort/Chunksz |
||
1861 | $this->saveRendererProperties($this->id); |
||
1862 | |||
1863 | //Sort |
||
1864 | $this->saveSortRenderer($this->id); |
||
1865 | |||
1866 | } |
||
1867 | return $success; |
||
1868 | } |
||
1869 | |||
1870 | /** |
||
1871 | * Set the session |
||
1872 | * |
||
1873 | */ |
||
1874 | public function setSession($renderer_id = null) { |
||
1875 | if(!$renderer_id) { |
||
1876 | $renderer_id = $this->id; |
||
1877 | } |
||
1878 | $this->report_session->set("{$this->id}.name", $this->name); |
||
1879 | $this->report_session->set("{$this->id}.description", $this->description); |
||
1880 | $this->report_session->set("{$this->id}.chunksz", $this->chunksz); |
||
1881 | $this->report_session->set("{$this->id}.multisort", $this->multisort); |
||
1882 | $this->report_session->set("{$this->id}.rank", $this->rank); |
||
1883 | } |
||
1884 | |||
1885 | /** |
||
1886 | * Finnish saving renderer to database by creating colunms |
||
1887 | * |
||
1888 | * @param Tracker_Report_Renderer $renderer containing the columns |
||
1889 | */ |
||
1890 | public function afterSaveObject($renderer) { |
||
1891 | $renderer->injectUnsavedColumnsInRendererDB($this); |
||
1892 | $this->saveAggregates($renderer->getAggregates()); |
||
1893 | $this->saveSort($renderer->getSort()); |
||
1894 | } |
||
1895 | |||
1896 | public function injectUnsavedColumnsInRendererDB(Tracker_Report_Renderer_Table $renderer) { |
||
1899 | |||
1900 | /** |
||
1901 | *Test if sort contains at least one used field |
||
1902 | * |
||
1903 | * @return bool, true f sort has at least one used field |
||
1904 | */ |
||
1905 | public function sortHasUsedField($store_in_session = true) { |
||
1906 | $sort = $this->getSort($store_in_session); |
||
1907 | foreach($sort as $s) { |
||
1908 | if ($s['field']->isUsed()) { |
||
1909 | return true; |
||
1910 | } |
||
1911 | } |
||
1912 | return false; |
||
1913 | } |
||
1914 | |||
1915 | /** |
||
1916 | *Test if multisort does not contain unused fields |
||
1917 | * |
||
1918 | *@return bool true if still multisort |
||
1919 | */ |
||
1920 | public function isMultisort(){ |
||
1934 | |||
1935 | private function getSortIcon($is_desc) { |
||
1938 | |||
1939 | private function canFieldBeUsedToSort(Tracker_FormElement_Field $field, PFUser $current_user) { |
||
1940 | $field = $this->getFieldFactory()->getComputedValueFieldByNameForUser( |
||
1941 | $field->tracker_id, |
||
1942 | $field->name, |
||
1943 | $current_user |
||
1944 | ); |
||
1945 | |||
1946 | return $field === null; |
||
1947 | } |
||
1948 | |||
1949 | public function getIcon() { |
||
1952 | |||
1953 | private function fetchViewButtons($report_can_be_modified, PFUser $current_user) { |
||
1954 | $html = ''; |
||
1955 | $html .= '<div id="tracker_report_renderer_view_controls">'; |
||
1956 | if ($this->sortHasUsedField()) { |
||
1957 | //reset sort |
||
1958 | $reset_sort_params = array( |
||
1959 | 'report' => $this->report->id, |
||
1960 | 'renderer' => $this->id, |
||
1961 | 'func' => 'renderer', |
||
1962 | 'renderer_table[resetsort]' => 1 |
||
1963 | ); |
||
1964 | $html .= '<div class="btn-group"><a class="btn btn-mini" href="?' . http_build_query($reset_sort_params) .'">' |
||
1965 | . '<i class="icon-reply"></i> ' |
||
1966 | . $GLOBALS['Language']->getText('plugin_tracker_report','reset_sort') |
||
1967 | . '</a></div> '; |
||
1968 | |||
1969 | //toggle multisort |
||
1970 | $multisort_params = array( |
||
1971 | 'report' => $this->report->id, |
||
1972 | 'renderer' => $this->id, |
||
1973 | 'func' => 'renderer', |
||
1974 | 'renderer_table[multisort]' => 1 |
||
1975 | ); |
||
1976 | $multisort_label = $GLOBALS['Language']->getText('plugin_tracker_report','enable_multisort'); |
||
1977 | if ($this->multisort) { |
||
1978 | $multisort_label = $GLOBALS['Language']->getText('plugin_tracker_report','disable_multisort'); |
||
1979 | } |
||
1980 | $html .= '<div class="btn-group"><a class="btn btn-mini" href="?' . http_build_query($multisort_params) .'">' |
||
1981 | . '<i class="icon-sort"></i> ' |
||
1982 | . $multisort_label |
||
1983 | . '</a></div> '; |
||
1984 | |||
1985 | } |
||
1986 | |||
1987 | if ($report_can_be_modified && ! $current_user->isAnonymous()) { |
||
1988 | $html .= $this->fetchAddColumn(); |
||
1989 | } |
||
1990 | $html .= '</div>'; |
||
1991 | |||
1992 | return $html; |
||
1993 | } |
||
1994 | } |
||
1995 |
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italy
is not defined by the methodfinale(...)
.The most likely cause is that the parameter was removed, but the annotation was not.