Complex classes like Tracker_FormElement_Field_ArtifactLink 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_FormElement_Field_ArtifactLink, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
22 | class Tracker_FormElement_Field_ArtifactLink extends Tracker_FormElement_Field { |
||
23 | |||
24 | const CREATE_NEW_PARENT_VALUE = -1; |
||
25 | const NEW_VALUES_KEY = 'new_values'; |
||
26 | |||
27 | /** |
||
28 | * @var Tracker_ArtifactFactory |
||
29 | */ |
||
30 | private $artifact_factory; |
||
31 | |||
32 | /** |
||
33 | * @var Tracker_Artifact|null |
||
34 | */ |
||
35 | private $source_of_association = array(); |
||
36 | |||
37 | /** |
||
38 | * Display the html form in the admin ui |
||
39 | * |
||
40 | * @return string html |
||
41 | */ |
||
42 | protected function fetchAdminFormElement() { |
||
43 | $hp = Codendi_HTMLPurifier::instance(); |
||
44 | $html = ''; |
||
45 | $value = ''; |
||
46 | if ($this->hasDefaultValue()) { |
||
47 | $value = $this->getDefaultValue(); |
||
48 | } |
||
49 | $html .= '<input type="text" |
||
50 | value="'. $hp->purify($value, CODENDI_PURIFIER_CONVERT_HTML) .'" autocomplete="off" />'; |
||
51 | $html .= '<br />'; |
||
52 | $html .= '<a href="#">bug #123</a><br />'; |
||
53 | $html .= '<a href="#">bug #321</a><br />'; |
||
54 | $html .= '<a href="#">story #10234</a>'; |
||
55 | return $html; |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * Display the field value as a criteria |
||
60 | * |
||
61 | * @param Tracker_ReportCriteria $criteria |
||
62 | * |
||
63 | * @return string |
||
64 | */ |
||
65 | public function fetchCriteriaValue($criteria) { |
||
66 | $html = '<input type="text" name="criteria['. $this->id .']" id="tracker_report_criteria_'. $this->id .'" value="'; |
||
67 | if ($criteria_value = $this->getCriteriaValue($criteria)) { |
||
68 | $hp = Codendi_HTMLPurifier::instance(); |
||
69 | $html .= $hp->purify($criteria_value, CODENDI_PURIFIER_CONVERT_HTML); |
||
70 | } |
||
71 | $html .= '" />'; |
||
72 | return $html; |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Display the field as a Changeset value. |
||
77 | * Used in report table |
||
78 | * |
||
79 | * @param int $artifact_id the corresponding artifact id |
||
80 | * @param int $changeset_id the corresponding changeset |
||
81 | * @param mixed $value the value of the field |
||
82 | * |
||
83 | * @return string |
||
84 | */ |
||
85 | public function fetchChangesetValue($artifact_id, $changeset_id, $value, $report=null, $from_aid = null) { |
||
86 | $arr = array(); |
||
87 | $values = $this->getChangesetValues($changeset_id); |
||
88 | foreach ($values as $artifact_link_info) { |
||
89 | $arr[] = $artifact_link_info->getUrl(); |
||
90 | } |
||
91 | $html = implode(', ', $arr); |
||
92 | return $html; |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Display the field as a Changeset value. |
||
97 | * Used in CSV data export. |
||
98 | * |
||
99 | * @param int $artifact_id the corresponding artifact id |
||
100 | * @param int $changeset_id the corresponding changeset |
||
101 | * @param mixed $value the value of the field |
||
102 | * |
||
103 | * @return string |
||
104 | */ |
||
105 | public function fetchCSVChangesetValue($artifact_id, $changeset_id, $value, $report) { |
||
106 | $arr = array(); |
||
107 | $values = $this->getChangesetValues($changeset_id); |
||
108 | foreach ($values as $artifact_link_info) { |
||
109 | $arr[] = $artifact_link_info->getArtifactId(); |
||
110 | } |
||
111 | $html = implode(',', $arr); |
||
112 | return $html; |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Fetch the value |
||
117 | * @param mixed $value the value of the field |
||
118 | * @return string |
||
119 | */ |
||
120 | public function fetchRawValue($value) { |
||
124 | |||
125 | /** |
||
126 | * Get available values of this field for SOAP usage |
||
127 | * Fields like int, float, date, string don't have available values |
||
128 | * |
||
129 | * @return mixed The values or null if there are no specific available values |
||
130 | */ |
||
131 | public function getSoapAvailableValues() { |
||
134 | |||
135 | /** |
||
136 | * Return data that can be proceced by createArtifact or updateArtifact based on SOAP request |
||
137 | * |
||
138 | * @param stdClass $soap_value |
||
139 | * @param Tracker_Artifact $artifact |
||
140 | * |
||
141 | * @return array |
||
142 | */ |
||
143 | public function getFieldDataFromSoapValue(stdClass $soap_value, Tracker_Artifact $artifact = null) { |
||
146 | |||
147 | |||
148 | /** |
||
149 | * @see Tracker_FormElement_Field::getFieldDataFromRESTValue() |
||
150 | * @param array $value |
||
151 | * @param Tracker_Artifact $artifact |
||
152 | * @return array |
||
153 | * @throws Exception |
||
154 | */ |
||
155 | public function getFieldDataFromRESTValue(array $value, Tracker_Artifact $artifact = null) { |
||
156 | if (array_key_exists('links', $value) && is_array($value['links'])){ |
||
157 | $link_ids = array(); |
||
158 | foreach ($value['links'] as $link) { |
||
159 | if (array_key_exists('id', $link)) { |
||
160 | $link_ids[] = $link['id']; |
||
161 | } |
||
162 | } |
||
163 | return $this->getFieldData(implode(',', $link_ids), $artifact); |
||
164 | } |
||
165 | throw new Tracker_FormElement_InvalidFieldValueException('Value should be \'links\' and an array of {"id": integer}'); |
||
166 | } |
||
167 | |||
168 | public function getFieldDataFromRESTValueByField($value, Tracker_Artifact $artifact = null) { |
||
171 | |||
172 | /** |
||
173 | * Get the field data (SOAP or CSV) for artifact submission |
||
174 | * |
||
175 | * @param string $string_value The soap field value |
||
176 | * @param Tracker_Artifact $artifact The artifact the value is to be added/removed |
||
177 | * |
||
178 | * @return array |
||
179 | */ |
||
180 | public function getFieldData($string_value, Tracker_Artifact $artifact = null) { |
||
181 | $existing_links = $this->getArtifactLinkIdsOfLastChangeset($artifact); |
||
182 | $submitted_values = $this->getArrayOfIdsFromString($string_value); |
||
183 | $new_values = array_diff($submitted_values, $existing_links); |
||
184 | $removed_values = array_diff($existing_links, $submitted_values); |
||
185 | return $this->getDataLikeWebUI($new_values, $removed_values); |
||
186 | } |
||
187 | |||
188 | public function fetchArtifactForOverlay(Tracker_Artifact $artifact) { |
||
189 | $user_manager = UserManager::instance(); |
||
190 | $user = $user_manager->getCurrentUser(); |
||
191 | $parent_tracker = $this->getTracker()->getParent(); |
||
192 | |||
193 | if ($artifact->getParent($user) || ! $parent_tracker) { |
||
194 | return ''; |
||
195 | } |
||
196 | |||
197 | $prefill_parent = ''; |
||
198 | $name = 'artifact['. $this->id .']'; |
||
199 | $current_user = $this->getCurrentUser(); |
||
200 | $can_create = false; |
||
201 | |||
202 | return $this->fetchParentSelector($prefill_parent, $name, $parent_tracker, $current_user, $can_create); |
||
203 | } |
||
204 | |||
205 | public function fetchSubmitForOverlay($submitted_values) { |
||
222 | |||
223 | private function getArtifactLinkIdsOfLastChangeset(Tracker_Artifact $artifact = null) { |
||
224 | if ($artifact) { |
||
225 | return array_map(array($this, 'getArtifactLinkId'), $this->getChangesetValues($artifact->getLastChangeset()->getId())); |
||
226 | } |
||
227 | return array(); |
||
228 | } |
||
229 | |||
230 | private function getArtifactLinkId(Tracker_ArtifactLinkInfo $link_info) { |
||
233 | |||
234 | private function getArrayOfIdsFromString($value) { |
||
237 | |||
238 | private function getDataLikeWebUI(array $new_values, array $removed_values) { |
||
239 | return array( |
||
240 | 'new_values' => $this->formatNewValuesLikeWebUI($new_values), |
||
241 | 'removed_values' => $this->formatRemovedValuesLikeWebUI($removed_values) |
||
242 | ); |
||
243 | } |
||
244 | |||
245 | private function formatNewValuesLikeWebUI(array $new_values) { |
||
248 | |||
249 | private function formatRemovedValuesLikeWebUI(array $removed_values) { |
||
250 | $values = array(); |
||
251 | foreach ($removed_values as $value) { |
||
252 | $values[$value] = array($value); |
||
253 | } |
||
254 | return $values; |
||
255 | } |
||
256 | |||
257 | /** |
||
258 | * Get the "from" statement to allow search with this field |
||
259 | * You can join on 'c' which is a pseudo table used to retrieve |
||
260 | * the last changeset of all artifacts. |
||
261 | * |
||
262 | * @param Tracker_ReportCriteria $criteria |
||
263 | * |
||
264 | * @return string |
||
265 | */ |
||
266 | public function getCriteriaFrom($criteria) { |
||
267 | //Only filter query if field is used |
||
268 | if($this->isUsed()) { |
||
269 | //Only filter query if criteria is valuated |
||
270 | if ($criteria_value = $this->getCriteriaValue($criteria)) { |
||
271 | $a = 'A_'. $this->id; |
||
272 | $b = 'B_'. $this->id; |
||
273 | return " INNER JOIN tracker_changeset_value AS $a ON ($a.changeset_id = c.id AND $a.field_id = $this->id ) |
||
274 | INNER JOIN tracker_changeset_value_artifactlink AS $b ON ( |
||
275 | $b.changeset_value_id = $a.id |
||
276 | AND ". $this->buildMatchExpression("$b.artifact_id", $criteria_value) ." |
||
277 | ) "; |
||
278 | } |
||
279 | } |
||
280 | return ''; |
||
281 | } |
||
282 | protected $pattern = '[+\-]*[0-9]+'; |
||
283 | protected function cast($value) { |
||
286 | protected function buildMatchExpression($field_name, $criteria_value) { |
||
287 | $expr = ''; |
||
288 | $matches = array(); |
||
289 | if (preg_match('/\/(.*)\//', $criteria_value, $matches)) { |
||
290 | |||
291 | // If it is sourrounded by /.../ then assume a regexp |
||
292 | $expr = $field_name." RLIKE ".$this->getCriteriaDao()->da->quoteSmart($matches[1]); |
||
293 | } |
||
294 | if (!$expr) { |
||
295 | $matches = array(); |
||
296 | if (preg_match("/^(<|>|>=|<=)\s*($this->pattern)\$/", $criteria_value, $matches)) { |
||
297 | // It's < or >, = and a number then use as is |
||
298 | $matches[2] = (string)($this->cast($matches[2])); |
||
299 | $expr = $field_name.' '.$matches[1].' '.$matches[2]; |
||
300 | |||
301 | } else if (preg_match("/^($this->pattern)\$/", $criteria_value, $matches)) { |
||
302 | // It's a number so use equality |
||
303 | $matches[1] = $this->cast($matches[1]); |
||
304 | $expr = $field_name.' = '.$matches[1]; |
||
305 | |||
306 | } else if (preg_match("/^($this->pattern)\s*-\s*($this->pattern)\$/", $criteria_value, $matches)) { |
||
307 | // it's a range number1-number2 |
||
308 | $matches[1] = (string)($this->cast($matches[1])); |
||
309 | $matches[2] = (string)($this->cast($matches[2])); |
||
310 | $expr = $field_name.' >= '.$matches[1].' AND '.$field_name.' <= '. $matches[2]; |
||
311 | |||
312 | } else { |
||
313 | // Invalid syntax - no condition |
||
314 | $expr = '1'; |
||
315 | } |
||
316 | } |
||
317 | return $expr; |
||
318 | } |
||
319 | |||
320 | /** |
||
321 | * Get the "where" statement to allow search with this field |
||
322 | * |
||
323 | * @param Tracker_ReportCriteria $criteria |
||
324 | * |
||
325 | * @return string |
||
326 | */ |
||
327 | public function getCriteriaWhere($criteria) { |
||
330 | |||
331 | public function getQuerySelect() { |
||
332 | $R1 = 'R1_'. $this->id; |
||
333 | $R2 = 'R2_'. $this->id; |
||
334 | return "$R2.artifact_id AS `". $this->name . "`"; |
||
335 | } |
||
336 | |||
337 | public function getQueryFrom() { |
||
338 | $R1 = 'R1_'. $this->id; |
||
339 | $R2 = 'R2_'. $this->id; |
||
340 | |||
341 | return "LEFT JOIN ( tracker_changeset_value AS $R1 |
||
342 | INNER JOIN tracker_changeset_value_artifactlink AS $R2 ON ($R2.changeset_value_id = $R1.id) |
||
343 | ) ON ($R1.changeset_id = c.id AND $R1.field_id = ". $this->id ." )"; |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * Return the dao of the criteria value used with this field. |
||
348 | * @return DataAccessObject |
||
349 | */ |
||
350 | protected function getCriteriaDao() { |
||
353 | |||
354 | private function fetchParentSelector($prefill_parent, $name, Tracker $parent_tracker, PFUser $user, $can_create) { |
||
355 | $purifier = Codendi_HTMLPurifier::instance(); |
||
356 | $possible_parents_getr = new Tracker_Artifact_PossibleParentsRetriever($this->getArtifactFactory()); |
||
357 | $html = ''; |
||
358 | $html .= '<p>'; |
||
359 | list($label, $paginated_possible_parents, $display_selector) = $possible_parents_getr->getPossibleArtifactParents($parent_tracker, $user, 0, 0); |
||
360 | $possible_parents = $paginated_possible_parents->getArtifacts(); |
||
361 | if ($display_selector) { |
||
362 | $html .= '<label>'; |
||
363 | $html .= $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_artifactlink_choose_parent', $purifier->purify($parent_tracker->getItemName())); |
||
364 | $html .= '<select name="'. $purifier->purify($name) .'[parent]">'; |
||
365 | $html .= '<option value="">'. $GLOBALS['Language']->getText('global', 'please_choose_dashed') .'</option>'; |
||
366 | if ($can_create) { |
||
367 | $html .= '<option value="'.self::CREATE_NEW_PARENT_VALUE.'">'. $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_artifactlink_create_new_parent') .'</option>'; |
||
368 | } |
||
369 | $html .= $this->fetchArtifactParentsOptions($prefill_parent, $label, $possible_parents); |
||
370 | $html .= '</select>'; |
||
371 | $html .= '</label>'; |
||
372 | } elseif (count($possible_parents) > 0) { |
||
373 | $html .= $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_artifactlink_will_have_as_parent', array($possible_parents[0]->fetchDirectLinkToArtifactWithTitle())); |
||
374 | } |
||
375 | $html .= '</p>'; |
||
376 | return $html; |
||
377 | } |
||
378 | |||
379 | private function fetchArtifactParentsOptions($prefill_parent, $label, array $possible_parents) { |
||
395 | |||
396 | /** |
||
397 | * Fetch the html widget for the field |
||
398 | * |
||
399 | * @param Tracker_Artifact $artifact Artifact on which we operate |
||
400 | * @param string $name The name, if any |
||
401 | * @param array $artifact_links The current artifact links |
||
402 | * @param string $prefill_new_values Prefill new values field (what the user has submitted, if any) |
||
403 | * @param array $prefill_removed_values Pre-remove values (what the user has submitted, if any) |
||
404 | * @param string $prefill_parent Prefilled parent (what the user has submitted, if any) - Only valid on submit |
||
405 | * @param bool $read_only True if the user can't add or remove links |
||
406 | * |
||
407 | * @return string html |
||
408 | */ |
||
409 | protected function fetchHtmlWidget( |
||
410 | Tracker_Artifact $artifact, |
||
411 | $name, |
||
412 | $artifact_links, |
||
413 | $prefill_new_values, |
||
414 | $prefill_removed_values, |
||
415 | $prefill_parent, |
||
416 | $read_only, |
||
417 | $from_aid = null, |
||
418 | $reverse_artifact_links = false |
||
419 | ) { |
||
420 | $current_user = $this->getCurrentUser(); |
||
421 | $html = ''; |
||
422 | |||
423 | if ($reverse_artifact_links) { |
||
424 | $html .= '<div class="artifact-link-value-reverse">'; |
||
425 | $html .= '<a href="" class="btn" id="display-tracker-form-element-artifactlink-reverse">' . $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_artifactlink_display_reverse') . '</a>'; |
||
426 | $html .= '<div id="tracker-form-element-artifactlink-reverse" style="display: none">'; |
||
427 | } else { |
||
428 | $html .= '<div class="artifact-link-value">'; |
||
429 | } |
||
430 | |||
431 | $html .= '<h5 class="artifack_link_subtitle">'.$this->getWidgetTitle($reverse_artifact_links).'</h5>'; |
||
432 | |||
433 | $html_name_new = ''; |
||
434 | $html_name_del = ''; |
||
435 | |||
436 | if ($name) { |
||
437 | $html_name_new = 'name="'. $name .'[new_values]"'; |
||
438 | $html_name_del = 'name="'. $name .'[removed_values]'; |
||
439 | } |
||
440 | |||
441 | $hp = Codendi_HTMLPurifier::instance(); |
||
442 | $read_only_class = 'read-only'; |
||
443 | |||
444 | if (! $read_only) { |
||
445 | $read_only_class = ''; |
||
446 | $html .= '<div><span class="input-append" style="display:inline;"><input type="text" |
||
447 | '. $html_name_new .' |
||
448 | class="tracker-form-element-artifactlink-new" |
||
449 | size="40" |
||
450 | value="'. $hp->purify($prefill_new_values, CODENDI_PURIFIER_CONVERT_HTML) .'" |
||
451 | title="' . $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_artifactlink_help') . '" />'; |
||
452 | $html .= '</span></div>'; |
||
453 | |||
454 | $parent_tracker = $this->getTracker()->getParent(); |
||
455 | $is_submit = $artifact->getId() == -1; |
||
456 | if ($parent_tracker && $is_submit) { |
||
457 | $can_create = true; |
||
458 | $html .= $this->fetchParentSelector($prefill_parent, $name, $parent_tracker, $current_user, $can_create); |
||
459 | } |
||
460 | } |
||
461 | |||
462 | $html .= '<div class="tracker-form-element-artifactlink-list '.$read_only_class.'">'; |
||
463 | if ($artifact_links) { |
||
464 | $ids = array(); |
||
465 | // build an array of artifact_id / last_changeset_id for fetch renderer method |
||
466 | foreach ($artifact_links as $artifact_link) { |
||
467 | if ($artifact_link->getTracker()->isActive() && $artifact_link->userCanView($current_user)) { |
||
468 | if (!isset($ids[$artifact_link->getTrackerId()])) { |
||
469 | $ids[$artifact_link->getTrackerId()] = array( |
||
470 | 'id' => '', |
||
471 | 'last_changeset_id' => '', |
||
472 | ); |
||
473 | } |
||
474 | $ids[$artifact_link->getTrackerId()]['id'] .= $artifact_link->getArtifactId() .','; |
||
475 | $ids[$artifact_link->getTrackerId()]['last_changeset_id'] .= $artifact_link->getLastChangesetId() .','; |
||
476 | } |
||
477 | } |
||
478 | |||
479 | $projects = array(); |
||
480 | $this_project_id = $this->getTracker()->getProject()->getGroupId(); |
||
481 | foreach ($ids as $tracker_id => $matching_ids) { |
||
482 | //remove last coma |
||
483 | $matching_ids['id'] = substr($matching_ids['id'], 0, -1); |
||
484 | $matching_ids['last_changeset_id'] = substr($matching_ids['last_changeset_id'], 0, -1); |
||
485 | |||
486 | $tracker = $this->getTrackerFactory()->getTrackerById($tracker_id); |
||
487 | $project = $tracker->getProject(); |
||
488 | if ($tracker->userCanView()) { |
||
489 | $trf = Tracker_ReportFactory::instance(); |
||
490 | $report = $trf->getDefaultReportsByTrackerId($tracker->getId()); |
||
491 | if ($report) { |
||
492 | $renderers = $report->getRenderers(); |
||
493 | $renderer_table_found = false; |
||
494 | // looking for the first table renderer |
||
495 | foreach ($renderers as $renderer) { |
||
496 | if ($renderer->getType() === Tracker_Report_Renderer::TABLE) { |
||
497 | $projects[$project->getGroupId()][$tracker_id] = array( |
||
498 | 'project' => $project, |
||
499 | 'tracker' => $tracker, |
||
500 | 'report' => $report, |
||
501 | 'renderer' => $renderer, |
||
502 | 'matching_ids' => $matching_ids, |
||
503 | ); |
||
504 | $renderer_table_found = true; |
||
505 | break; |
||
506 | } |
||
507 | } |
||
508 | if ( ! $renderer_table_found) { |
||
509 | $html .= $GLOBALS['Language']->getText('plugin_tracker', 'no_reports_available'); |
||
510 | } |
||
511 | } else { |
||
512 | $html .= $GLOBALS['Language']->getText('plugin_tracker', 'no_reports_available'); |
||
513 | } |
||
514 | } |
||
515 | } |
||
516 | |||
517 | foreach ($projects as $trackers) { |
||
518 | foreach ($trackers as $t) { |
||
519 | extract($t); |
||
520 | |||
521 | $html .= '<div class="tracker-form-element-artifactlink-trackerpanel">'; |
||
522 | |||
523 | $project_name = ''; |
||
524 | if ($project->getGroupId() != $this_project_id) { |
||
525 | $project_name = ' (<abbr title="'. $hp->purify($project->getPublicName(), CODENDI_PURIFIER_CONVERT_HTML) .'">'; |
||
526 | $project_name .= $hp->purify($project->getUnixName(), CODENDI_PURIFIER_CONVERT_HTML); |
||
527 | $project_name .= '</abbr>)'; |
||
528 | } |
||
529 | $html .= '<h2 class="tracker-form-element-artifactlink-tracker_'. $tracker->getId() .'">'; |
||
530 | $html .= $hp->purify($tracker->getName(), CODENDI_PURIFIER_CONVERT_HTML) . $project_name; |
||
531 | $html .= '</h2>'; |
||
532 | if ($from_aid == null) { |
||
533 | $html .= $renderer->fetchAsArtifactLink($matching_ids, $this->getId(), $read_only, $prefill_removed_values, false); |
||
534 | } else { |
||
535 | $html .= $renderer->fetchAsArtifactLink($matching_ids, $this->getId(), $read_only, $prefill_removed_values, false, $from_aid); |
||
536 | } |
||
537 | $html .= '</div>'; |
||
538 | } |
||
539 | } |
||
540 | } else { |
||
541 | $html .= $this->getNoValueLabel(); |
||
542 | } |
||
543 | $html .= '</div>'; |
||
544 | |||
545 | if ($reverse_artifact_links) { |
||
546 | $html .= '</div>'; |
||
547 | } |
||
548 | $html .= '</div>'; |
||
549 | |||
550 | return $html; |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * |
||
555 | * @param boolean $reverse_artifact_links |
||
556 | */ |
||
557 | private function getWidgetTitle($reverse_artifact_links) { |
||
558 | if ($reverse_artifact_links) { |
||
559 | return $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_artifactlink_reverse_title'); |
||
560 | } |
||
561 | |||
562 | return $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_artifactlink_title'); |
||
563 | |||
564 | } |
||
565 | |||
566 | /** |
||
567 | * Process the request |
||
568 | * |
||
569 | * @param Tracker_IDisplayTrackerLayout $layout Displays the page header and footer |
||
570 | * @param Codendi_Request $request The data coming from the user |
||
571 | * @param PFUser $current_user The user who mades the request |
||
572 | * |
||
573 | * @return void |
||
574 | */ |
||
575 | public function process(Tracker_IDisplayTrackerLayout $layout, $request, $current_user) { |
||
576 | switch ($request->get('func')) { |
||
577 | case 'fetch-artifacts': |
||
578 | $read_only = false; |
||
579 | $prefill_removed_values = array(); |
||
580 | $only_rows = true; |
||
581 | |||
582 | $this_project_id = $this->getTracker()->getProject()->getGroupId(); |
||
583 | $hp = Codendi_HTMLPurifier::instance(); |
||
584 | |||
585 | $ugroups = $current_user->getUgroups($this_project_id, array()); |
||
586 | |||
587 | $ids = $request->get('ids'); //2, 14, 15 |
||
588 | $tracker = array(); |
||
589 | $result = array(); |
||
590 | //We must retrieve the last changeset ids of each artifact id. |
||
591 | $dao = new Tracker_ArtifactDao(); |
||
592 | foreach($dao->searchLastChangesetIds($ids, $ugroups, $current_user->isSuperUser()) as $matching_ids) { |
||
593 | $tracker_id = $matching_ids['tracker_id']; |
||
594 | $tracker = $this->getTrackerFactory()->getTrackerById($tracker_id); |
||
595 | $project = $tracker->getProject(); |
||
596 | |||
597 | if ($tracker->userCanView()) { |
||
598 | $trf = Tracker_ReportFactory::instance(); |
||
599 | $report = $trf->getDefaultReportsByTrackerId($tracker->getId()); |
||
600 | if ($report) { |
||
601 | $renderers = $report->getRenderers(); |
||
602 | // looking for the first table renderer |
||
603 | foreach ($renderers as $renderer) { |
||
604 | if ($renderer->getType() === Tracker_Report_Renderer::TABLE) { |
||
605 | $key = $this->id .'_'. $report->id .'_'. $renderer->getId(); |
||
606 | $result[$key] = $renderer->fetchAsArtifactLink($matching_ids, $this->getId(), $read_only, $prefill_removed_values, $only_rows); |
||
607 | $head = '<div class="tracker-form-element-artifactlink-trackerpanel">'; |
||
608 | |||
609 | $project_name = ''; |
||
610 | if ($project->getGroupId() != $this_project_id) { |
||
611 | $project_name = ' (<abbr title="'. $hp->purify($project->getPublicName(), CODENDI_PURIFIER_CONVERT_HTML) .'">'; |
||
612 | $project_name .= $hp->purify($project->getUnixName(), CODENDI_PURIFIER_CONVERT_HTML); |
||
613 | $project_name .= '</abbr>)'; |
||
614 | } |
||
615 | $head .= '<h2 class="tracker-form-element-artifactlink-tracker_'. $tracker->getId() .'">'; |
||
616 | $head .= $hp->purify($tracker->getName(), CODENDI_PURIFIER_CONVERT_HTML) . $project_name; |
||
617 | $head .= '</h2>'; |
||
618 | //if ($artifact) { |
||
619 | // $title = $hp->purify('link a '. $tracker->getItemName(), CODENDI_PURIFIER_CONVERT_HTML); |
||
620 | // $head .= '<a href="'.TRACKER_BASE_URL.'/?tracker='.$tracker_id.'&func=new-artifact-link&id='.$artifact->getId().'" class="tracker-form-element-artifactlink-link-new-artifact">'. 'create a new '.$hp->purify($tracker->getItemName(), CODENDI_PURIFIER_CONVERT_HTML) .'</a>'; |
||
621 | //} |
||
622 | $result[$key]['head'] = $head . $result[$key]['head']; |
||
623 | break; |
||
624 | } |
||
625 | } |
||
626 | } |
||
627 | } |
||
628 | } |
||
629 | if ($result) { |
||
630 | $head = array(); |
||
631 | $rows = array(); |
||
632 | foreach($result as $key => $value) { |
||
633 | $head[$key] = $value["head"]; |
||
634 | $rows[$key] = $value["rows"]; |
||
635 | } |
||
636 | header('Content-type: application/json'); |
||
637 | echo json_encode(array('head' => $head, 'rows' => $rows)); |
||
638 | } |
||
639 | exit(); |
||
640 | break; |
||
641 | case 'fetch-aggregates': |
||
642 | $read_only = false; |
||
643 | $prefill_removed_values = array(); |
||
644 | $only_rows = true; |
||
645 | $only_one_column = false; |
||
646 | $extracolumn = Tracker_Report_Renderer_Table::EXTRACOLUMN_UNLINK; |
||
647 | $read_only = true; |
||
648 | $use_data_from_db = false; |
||
649 | |||
650 | $ugroups = $current_user->getUgroups($this->getTracker()->getGroupId(), array()); |
||
651 | $ids = $request->get('ids'); //2, 14, 15 |
||
652 | $tracker = array(); |
||
653 | $json = array('tabs' => array()); |
||
654 | $dao = new Tracker_ArtifactDao(); |
||
655 | foreach ($dao->searchLastChangesetIds($ids, $ugroups, $current_user->isSuperUser()) as $matching_ids) { |
||
656 | $tracker_id = $matching_ids['tracker_id']; |
||
657 | $tracker = $this->getTrackerFactory()->getTrackerById($tracker_id); |
||
658 | $project = $tracker->getProject(); |
||
659 | if ($tracker->userCanView()) { |
||
660 | $trf = Tracker_ReportFactory::instance(); |
||
661 | $report = $trf->getDefaultReportsByTrackerId($tracker->getId()); |
||
662 | if ($report) { |
||
663 | $renderers = $report->getRenderers(); |
||
664 | // looking for the first table renderer |
||
665 | foreach ($renderers as $renderer) { |
||
666 | if ($renderer->getType() === Tracker_Report_Renderer::TABLE) { |
||
667 | $key = $this->id . '_' . $report->id . '_' . $renderer->getId(); |
||
668 | $columns = $renderer->getTableColumns($only_one_column, $use_data_from_db); |
||
669 | $extracted_fields = $renderer->extractFieldsFromColumns($columns); |
||
670 | $json['tabs'][] = array( |
||
671 | 'key' => $key, |
||
672 | 'src' => $renderer->fetchAggregates($matching_ids, $extracolumn, $only_one_column,$columns, $extracted_fields, $use_data_from_db, $read_only), |
||
673 | ); |
||
674 | break; |
||
675 | } |
||
676 | } |
||
677 | } |
||
678 | } |
||
679 | } |
||
680 | header('Content-type: application/json'); |
||
681 | echo json_encode($json); |
||
682 | exit(); |
||
683 | break; |
||
684 | default: |
||
685 | parent::process($layout, $request, $current_user); |
||
686 | break; |
||
687 | } |
||
688 | } |
||
689 | |||
690 | /** |
||
691 | * Fetch the html widget for the field |
||
692 | * |
||
693 | * @param string $name The name, if any |
||
694 | * @param array $artifact_links The current artifact links |
||
695 | * @param string $prefill_new_values Prefill new values field (what the user has submitted, if any) |
||
696 | * @param bool $read_only True if the user can't add or remove links |
||
697 | * |
||
698 | * @return string html |
||
699 | */ |
||
700 | protected function fetchHtmlWidgetMasschange($name, $artifact_links, $prefill_new_values, $read_only) { |
||
701 | $html = ''; |
||
702 | $html_name_new = ''; |
||
703 | if ($name) { |
||
704 | $html_name_new = 'name="'. $name .'[new_values]"'; |
||
705 | } |
||
706 | $hp = Codendi_HTMLPurifier::instance(); |
||
707 | if (!$read_only) { |
||
708 | $html .= '<input type="text" |
||
709 | '. $html_name_new .' |
||
710 | value="'. $hp->purify($prefill_new_values, CODENDI_PURIFIER_CONVERT_HTML) .'" |
||
711 | title="' . $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_artifactlink_help') . '" />'; |
||
712 | $html .= '<br />'; |
||
713 | } |
||
714 | if ($artifact_links) { |
||
715 | $html .= '<ul class="tracker-form-element-artifactlink-list">'; |
||
716 | foreach ($artifact_links as $artifact_link_info) { |
||
717 | $html .= '<li>'; |
||
718 | $html .= $artifact_link_info->getUrl(); |
||
719 | $html .= '</li>'; |
||
720 | } |
||
721 | $html .= '</ul>'; |
||
722 | } |
||
723 | return $html; |
||
724 | } |
||
725 | |||
726 | /** |
||
727 | * Fetch the html code to display the field value in artifact |
||
728 | * |
||
729 | * @param Tracker_Artifact $artifact The artifact |
||
730 | * @param Tracker_Artifact_ChangesetValue $value The actual value of the field |
||
731 | * @param array $submitted_values The value already submitted by the user |
||
732 | * |
||
733 | * @return string |
||
734 | */ |
||
735 | protected function fetchArtifactValue(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null, $submitted_values = array()) { |
||
736 | $links_tab = $this->fetchLinks($artifact, $value, $submitted_values); |
||
737 | $reverse_links_tab = $this->fetchReverseLinks($artifact); |
||
738 | |||
739 | return $links_tab . $reverse_links_tab; |
||
740 | } |
||
741 | |||
742 | private function fetchLinks(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null, $submitted_values = array()) { |
||
743 | $artifact_links = array(); |
||
744 | if ($value != null) { |
||
745 | $artifact_links = $value->getValue(); |
||
746 | } |
||
747 | |||
748 | if (! empty($submitted_values) && isset($submitted_values[0]) && is_array($submitted_values[0]) && isset($submitted_values[0][$this->getId()])) { |
||
749 | $submitted_value = $submitted_values[0][$this->getId()]; |
||
750 | } |
||
751 | |||
752 | $prefill_new_values = ''; |
||
753 | if (isset($submitted_value['new_values'])) { |
||
754 | $prefill_new_values = $submitted_value['new_values']; |
||
755 | } |
||
756 | |||
757 | $prefill_removed_values = array(); |
||
758 | if (isset($submitted_value['removed_values'])) { |
||
759 | $prefill_removed_values = $submitted_value['removed_values']; |
||
760 | } |
||
761 | |||
762 | $read_only = false; |
||
763 | $name = 'artifact['. $this->id .']'; |
||
764 | $from_aid = $artifact->getId(); |
||
765 | $prefill_parent = ''; |
||
766 | |||
767 | return $this->fetchHtmlWidget( |
||
768 | $artifact, |
||
769 | $name, |
||
770 | $artifact_links, |
||
771 | $prefill_new_values, |
||
772 | $prefill_removed_values, |
||
773 | $read_only, |
||
774 | $prefill_parent, |
||
775 | $from_aid |
||
776 | ); |
||
777 | } |
||
778 | |||
779 | private function fetchReverseLinks(Tracker_Artifact $artifact) { |
||
780 | $reverse_links = $this->getReverseLinks($artifact->getId()); |
||
781 | |||
782 | return $this->fetchHtmlWidget( |
||
783 | $artifact, |
||
784 | '', |
||
785 | $reverse_links, |
||
786 | '', |
||
787 | '', |
||
788 | '', |
||
789 | true, |
||
790 | null, |
||
791 | true |
||
792 | ); |
||
793 | } |
||
794 | |||
795 | /** |
||
796 | * Fetch the html code to display the field value in artifact in read only mode |
||
797 | * |
||
798 | * @param Tracker_Artifact $artifact The artifact |
||
799 | * @param Tracker_Artifact_ChangesetValue $value The actual value of the field |
||
800 | * |
||
801 | * @return string |
||
802 | */ |
||
803 | public function fetchArtifactValueReadOnly(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null) { |
||
804 | $links_tab_read_only = $this->fetchLinksReadOnly($artifact, $value); |
||
805 | $reverse_links_tab = $this->fetchReverseLinks($artifact); |
||
806 | |||
807 | return $links_tab_read_only . $reverse_links_tab; |
||
808 | } |
||
809 | |||
810 | public function fetchArtifactCopyMode(Tracker_Artifact $artifact, $submitted_values = array()) { |
||
813 | |||
814 | public function fetchArtifactValueWithEditionFormIfEditable(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null, $submitted_values = array()) { |
||
817 | |||
818 | public function getHiddenArtifactValueForEdition(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null) { |
||
821 | |||
822 | private function fetchLinksReadOnly(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null) { |
||
823 | $artifact_links = array(); |
||
824 | |||
825 | if ($value != null) { |
||
826 | $artifact_links = $value->getValue(); |
||
827 | } |
||
828 | |||
829 | $read_only = true; |
||
830 | $name = ''; |
||
831 | $prefill_new_values = ''; |
||
832 | $prefill_removed_values = array(); |
||
833 | $prefill_parent = ''; |
||
834 | |||
835 | return $this->fetchHtmlWidget( |
||
836 | $artifact, |
||
837 | $name, |
||
838 | $artifact_links, |
||
839 | $prefill_new_values, |
||
840 | $prefill_removed_values, |
||
841 | $prefill_parent, |
||
842 | $read_only |
||
843 | ); |
||
844 | } |
||
845 | |||
846 | /** |
||
847 | * Fetch the html code to display the field value in new artifact submission form |
||
848 | * |
||
849 | * @param array $submitted_values the values already submitted |
||
850 | * |
||
851 | * @return string html |
||
852 | */ |
||
853 | protected function fetchSubmitValue($submitted_values = array()) { |
||
854 | $html = ''; |
||
855 | $prefill_new_values = ''; |
||
856 | if (isset($submitted_values[$this->getId()]['new_values'])) { |
||
857 | $prefill_new_values = $submitted_values[$this->getId()]['new_values']; |
||
858 | } else if ($this->hasDefaultValue()) { |
||
859 | $prefill_new_values = $this->getDefaultValue(); |
||
860 | } |
||
861 | $prefill_parent = ''; |
||
862 | if (isset($submitted_values[$this->getId()]['parent'])) { |
||
863 | $prefill_parent = $submitted_values[$this->getId()]['parent']; |
||
864 | } |
||
865 | $read_only = false; |
||
866 | $name = 'artifact['. $this->id .']'; |
||
867 | $prefill_removed_values = array(); |
||
868 | $artifact_links = array(); |
||
869 | |||
870 | // Well, shouldn't be here but API doesn't provide a Null Artifact on creation yet |
||
871 | // Here to avoid having to pass null arg for fetchHtmlWidget |
||
872 | $artifact = new Tracker_Artifact(-1, $this->tracker_id, $this->getCurrentUser()->getId(), 0, false); |
||
873 | |||
874 | return $this->fetchHtmlWidget($artifact, $name, $artifact_links, $prefill_new_values, $prefill_removed_values, $prefill_parent, $read_only); |
||
875 | } |
||
876 | |||
877 | /** |
||
878 | * Fetch the html code to display the field value in masschange submission form |
||
879 | * |
||
880 | * @param array $submitted_values the values already submitted |
||
881 | * |
||
882 | * @return string html |
||
883 | */ |
||
884 | protected function fetchSubmitValueMasschange() { |
||
885 | $html = ''; |
||
886 | $prefill_new_values = $GLOBALS['Language']->getText('global','unchanged'); |
||
887 | $read_only = false; |
||
888 | $name = 'artifact['. $this->id .']'; |
||
889 | $artifact_links = array(); |
||
890 | |||
891 | return $this->fetchHtmlWidgetMasschange($name, $artifact_links, $prefill_new_values, $read_only); |
||
892 | } |
||
893 | |||
894 | |||
895 | /** |
||
896 | * Fetch the html code to display the field value in tooltip |
||
897 | * |
||
898 | * @param Tracker_Artifact $artifact |
||
899 | * @param Tracker_Artifact_ChangesetValue $value The changeset value of the field |
||
900 | * |
||
901 | * @return string |
||
902 | */ |
||
903 | protected function fetchTooltipValue(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null) { |
||
904 | $html = ''; |
||
905 | if ($value != null) { |
||
906 | $html = '<ul>'; |
||
907 | $artifact_links = $value->getValue(); |
||
908 | foreach($artifact_links as $artifact_link_info) { |
||
909 | $html .= '<li>' . $artifact_link_info->getLabel() . '</li>'; |
||
910 | } |
||
911 | $html .= '</ul>'; |
||
912 | } |
||
913 | return $html; |
||
914 | } |
||
915 | |||
916 | /** |
||
917 | * @return Tracker_FormElement_Field_Value_ArtifactLinkDao |
||
918 | */ |
||
919 | protected function getValueDao() { |
||
922 | |||
923 | /** |
||
924 | * Fetch the html code to display the field value in artifact |
||
925 | * |
||
926 | * @param Tracker_Artifact $artifact The artifact |
||
927 | * @param PFUser $user The user who will receive the email |
||
928 | * @param Tracker_Artifact_ChangesetValue $value The actual value of the field |
||
929 | * @param array $submitted_values The value already submitted by the user |
||
930 | * |
||
931 | * @return string |
||
932 | */ |
||
933 | public function fetchMailArtifactValue( |
||
934 | Tracker_Artifact $artifact, |
||
935 | PFUser $user, |
||
936 | Tracker_Artifact_ChangesetValue $value = null, |
||
937 | $format='text' |
||
938 | ) { |
||
939 | if ( empty($value) || !$value->getValue()) { |
||
940 | return '-'; |
||
941 | } |
||
942 | $output = ''; |
||
943 | switch($format) { |
||
944 | case 'html': |
||
945 | $artifactlink_infos = $value->getValue(); |
||
946 | $url = array(); |
||
947 | foreach ($artifactlink_infos as $artifactlink_info) { |
||
948 | if ($artifactlink_info->userCanView($user)) { |
||
949 | $url[] = $artifactlink_info->getUrl(); |
||
950 | } |
||
951 | } |
||
952 | return implode(' , ', $url); |
||
953 | default: |
||
954 | $output = PHP_EOL; |
||
955 | $artifactlink_infos = $value->getValue(); |
||
956 | foreach ($artifactlink_infos as $artifactlink_info) { |
||
957 | if ($artifactlink_info->userCanView($user)) { |
||
958 | $output .= $artifactlink_info->getLabel(); |
||
959 | $output .= PHP_EOL; |
||
960 | } |
||
961 | } |
||
962 | break; |
||
963 | } |
||
964 | return $output; |
||
965 | } |
||
966 | |||
967 | /** |
||
968 | * Fetch the value to display changes in followups |
||
969 | * |
||
970 | * @param Tracker_ $artifact |
||
971 | * @param array $from the value(s) *before* |
||
972 | * @param array $to the value(s) *after* |
||
973 | * |
||
974 | * @return string |
||
975 | */ |
||
976 | public function fetchFollowUp($artifact, $from, $to) { |
||
979 | |||
980 | /** |
||
981 | * Fetch the value in a specific changeset |
||
982 | * |
||
983 | * @param Tracker_Artifact_Changeset $changeset |
||
984 | * |
||
985 | * @return string |
||
986 | */ |
||
987 | public function fetchRawValueFromChangeset($changeset) { |
||
990 | |||
991 | /** |
||
992 | * Get the value of this field |
||
993 | * |
||
994 | * @param Tracker_Artifact_Changeset $changeset The changeset (needed in only few cases like 'lud' field) |
||
995 | * @param int $value_id The id of the value |
||
996 | * @param boolean $has_changed If the changeset value has changed from the rpevious one |
||
997 | * |
||
998 | * @return Tracker_Artifact_ChangesetValue or null if not found |
||
999 | */ |
||
1000 | public function getChangesetValue($changeset, $value_id, $has_changed) { |
||
1001 | $rows = $this->getValueDao()->searchById($value_id, $this->id); |
||
1002 | $artifact_links = $this->getArtifactLinkInfos($rows); |
||
1003 | $reverse_artifact_links = array(); |
||
1004 | |||
1005 | if ($changeset) { |
||
1006 | $reverse_artifact_links = $this->getReverseLinks($changeset->getArtifact()->getId()); |
||
1007 | } |
||
1008 | |||
1009 | return new Tracker_Artifact_ChangesetValue_ArtifactLink($value_id, $this, $has_changed, $artifact_links, $reverse_artifact_links); |
||
1010 | } |
||
1011 | |||
1012 | |||
1013 | private function getReverseLinks($artifact_id) { |
||
1014 | $links_data = $this->getValueDao()->searchReverseLinksById($artifact_id); |
||
1015 | |||
1016 | return $this->getArtifactLinkInfos($links_data); |
||
1017 | } |
||
1018 | |||
1019 | private function getArtifactLinkInfos($data) { |
||
1020 | $artifact_links = array(); |
||
1021 | while ($row = $data->getRow()) { |
||
1022 | $artifact_links[$row['artifact_id']] = new Tracker_ArtifactLinkInfo($row['artifact_id'], $row['keyword'], $row['group_id'], $row['tracker_id'], $row['last_changeset_id']); |
||
1023 | } |
||
1024 | |||
1025 | return $artifact_links; |
||
1026 | } |
||
1027 | |||
1028 | /** |
||
1029 | * @return array |
||
1030 | */ |
||
1031 | protected $artifact_links_by_changeset = array(); |
||
1032 | |||
1033 | /** |
||
1034 | * |
||
1035 | * @param Integer $changeset_id |
||
1036 | * |
||
1037 | * @return Tracker_ArtifactLinkInfo[] |
||
1038 | */ |
||
1039 | protected function getChangesetValues($changeset_id) { |
||
1040 | if (!isset($this->artifact_links_by_changeset[$changeset_id])) { |
||
1041 | $this->artifact_links_by_changeset[$changeset_id] = array(); |
||
1042 | |||
1043 | $da = CodendiDataAccess::instance(); |
||
1044 | $field_id = $da->escapeInt($this->id); |
||
1045 | $changeset_id = $da->escapeInt($changeset_id); |
||
1046 | $sql = "SELECT cv.changeset_id, cv.has_changed, val.*, a.tracker_id, a.last_changeset_id |
||
1047 | FROM tracker_changeset_value_artifactlink AS val |
||
1048 | INNER JOIN tracker_artifact AS a ON(a.id = val.artifact_id) |
||
1049 | INNER JOIN tracker_changeset_value AS cv |
||
1050 | ON ( val.changeset_value_id = cv.id |
||
1051 | AND cv.field_id = $field_id |
||
1052 | AND cv.changeset_id = $changeset_id |
||
1053 | ) |
||
1054 | ORDER BY val.artifact_id"; |
||
1055 | $dao = new DataAccessObject(); |
||
1056 | foreach ($dao->retrieve($sql) as $row) { |
||
1057 | $this->artifact_links_by_changeset[$row['changeset_id']][] = new Tracker_ArtifactLinkInfo( |
||
1058 | $row['artifact_id'], |
||
1059 | $row['keyword'], |
||
1060 | $row['group_id'], |
||
1061 | $row['tracker_id'], |
||
1062 | $row['last_changeset_id'] |
||
1063 | ); |
||
1064 | } |
||
1065 | } |
||
1066 | return $this->artifact_links_by_changeset[$changeset_id]; |
||
1067 | } |
||
1068 | |||
1069 | /** |
||
1070 | * Check if there are changes between old and new value for this field |
||
1071 | * |
||
1072 | * @param Tracker_Artifact_ChangesetValue $old_value The data stored in the db |
||
1073 | * @param array $new_value array of artifact ids |
||
1074 | * |
||
1075 | * @return bool true if there are differences |
||
1076 | */ |
||
1077 | public function hasChanges(Tracker_Artifact_ChangesetValue_ArtifactLink $old_value, $new_value) { |
||
1080 | |||
1081 | /** |
||
1082 | * @return the label of the field (mainly used in admin part) |
||
1083 | */ |
||
1084 | public static function getFactoryLabel() { |
||
1087 | |||
1088 | /** |
||
1089 | * @return the description of the field (mainly used in admin part) |
||
1090 | */ |
||
1091 | public static function getFactoryDescription() { |
||
1094 | |||
1095 | /** |
||
1096 | * @return the path to the icon |
||
1097 | */ |
||
1098 | public static function getFactoryIconUseIt() { |
||
1101 | |||
1102 | /** |
||
1103 | * @return the path to the icon |
||
1104 | */ |
||
1105 | public static function getFactoryIconCreate() { |
||
1108 | |||
1109 | /** |
||
1110 | * @return bool say if the field is a unique one |
||
1111 | */ |
||
1112 | public static function getFactoryUniqueField() { |
||
1115 | |||
1116 | /** |
||
1117 | * Say if the value is valid. If not valid set the internal has_error to true. |
||
1118 | * |
||
1119 | * @param Tracker_Artifact $artifact The artifact |
||
1120 | * @param array $value data coming from the request. |
||
1121 | * |
||
1122 | * @return bool true if the value is considered ok |
||
1123 | */ |
||
1124 | public function isValid(Tracker_Artifact $artifact, $value) { |
||
1125 | $this->has_errors = ! $this->validate($artifact, $value); |
||
1126 | |||
1127 | return ! $this->has_errors; |
||
1128 | } |
||
1129 | |||
1130 | /** |
||
1131 | * Validate a required field |
||
1132 | * |
||
1133 | * @param Tracker_Artifact $artifact The artifact to check |
||
1134 | * @param mixed $value The submitted value |
||
1135 | * |
||
1136 | * @return boolean true on success or false on failure |
||
1137 | */ |
||
1138 | public function isValidRegardingRequiredProperty(Tracker_Artifact $artifact, $value) { |
||
1139 | if ( (! is_array($value) || empty($value['new_values'])) && $this->isRequired()) { |
||
1140 | if ( ! $this->isEmpty($value, $artifact)) { |
||
1141 | // Field is required but there are values, so field is valid |
||
1142 | $this->has_errors = false; |
||
1143 | } else { |
||
1144 | $this->addRequiredError(); |
||
1145 | return false; |
||
1146 | } |
||
1147 | } |
||
1148 | |||
1149 | return true; |
||
1150 | } |
||
1151 | |||
1152 | /** |
||
1153 | * @return Array the ids |
||
1154 | */ |
||
1155 | private function getLastChangesetArtifactIds(Tracker_Artifact $artifact) { |
||
1156 | $lastChangeset = $artifact->getLastChangeset(); |
||
1157 | $ids = array(); |
||
1158 | if($lastChangeset) { |
||
1159 | $ids = $lastChangeset->getValue($this)->getArtifactIds(); |
||
1160 | } |
||
1161 | return $ids; |
||
1162 | } |
||
1163 | |||
1164 | /** |
||
1165 | * Say if the submitted value is empty |
||
1166 | * if no last changeset values and empty submitted values : empty |
||
1167 | * if not empty last changeset values and empty submitted values : not empty |
||
1168 | * if empty new values and not empty last changeset values and not empty removed values have the same size: empty |
||
1169 | * |
||
1170 | * @param array $submitted_value |
||
1171 | * @param Tracker_Artifact $artifact |
||
1172 | * |
||
1173 | * @return bool true if the submitted value is empty |
||
1174 | */ |
||
1175 | public function isEmpty($submitted_value, Tracker_Artifact $artifact) { |
||
1176 | $hasNoNewValues = empty($submitted_value['new_values']); |
||
1177 | $hasNoLastChangesetValues = true; |
||
1178 | $last_changeset_values = array(); |
||
1179 | $last_changeset = $this->getLastChangesetValue($artifact); |
||
1180 | |||
1181 | if ($last_changeset) { |
||
1182 | $last_changeset_values = $last_changeset->getArtifactIds(); |
||
1183 | $hasNoLastChangesetValues = empty($last_changeset_values); |
||
1184 | } |
||
1185 | |||
1186 | $hasLastChangesetValues = !$hasNoLastChangesetValues; |
||
1187 | |||
1188 | if (($hasNoLastChangesetValues && $hasNoNewValues) || |
||
1189 | ($hasLastChangesetValues && $hasNoNewValues |
||
1190 | && $this->allLastChangesetValuesRemoved($last_changeset_values, $submitted_value))) { |
||
1191 | return true; |
||
1192 | } |
||
1193 | return false; |
||
1194 | } |
||
1195 | |||
1196 | /** |
||
1197 | * Say if all values of the changeset have been removed |
||
1198 | * |
||
1199 | * @param array $last_changeset_values |
||
1200 | * @param array $submitted_value |
||
1201 | * |
||
1202 | * @return bool true if all values have been removed |
||
1203 | */ |
||
1204 | private function allLastChangesetValuesRemoved($last_changeset_values, $submitted_value) { |
||
1208 | |||
1209 | /** |
||
1210 | * Validate a value |
||
1211 | * |
||
1212 | * @param Tracker_Artifact $artifact The artifact |
||
1213 | * @param string $value data coming from the request. Should be artifact id separated by comma |
||
1214 | * |
||
1215 | * @return bool true if the value is considered ok |
||
1216 | */ |
||
1217 | protected function validate(Tracker_Artifact $artifact, $value) { |
||
1218 | $is_valid = true; |
||
1219 | if (! isset($value['new_values'])) { |
||
1220 | return $is_valid; |
||
1221 | } |
||
1222 | $new_values = $value['new_values']; |
||
1223 | if (trim($new_values) != '') { |
||
1224 | $r = $this->getRuleArtifactId(); |
||
1225 | $art_id_array = explode(',', $new_values); |
||
1226 | foreach ($art_id_array as $artifact_id) { |
||
1227 | $artifact_id = trim ($artifact_id); |
||
1228 | if ( ! $r->isValid($artifact_id)) { |
||
1229 | $is_valid = false; |
||
1230 | $GLOBALS['Response']->addFeedback('error', $GLOBALS['Language']->getText('plugin_tracker_common_artifact', 'error_artifactlink_value', array($this->getLabel(), $artifact_id))); |
||
1231 | } |
||
1232 | } |
||
1233 | } |
||
1234 | |||
1235 | return $is_valid; |
||
1236 | } |
||
1237 | |||
1238 | public function getRuleArtifactId() { |
||
1241 | |||
1242 | public function setArtifactFactory(Tracker_ArtifactFactory $artifact_factory) { |
||
1245 | |||
1246 | /** |
||
1247 | * @return Tracker_ArtifactFactory |
||
1248 | */ |
||
1249 | public function getArtifactFactory() { |
||
1250 | if (!$this->artifact_factory) { |
||
1251 | $this->artifact_factory = Tracker_ArtifactFactory::instance(); |
||
1252 | } |
||
1253 | return $this->artifact_factory; |
||
1254 | } |
||
1255 | |||
1256 | public function getTrackerFactory() { |
||
1259 | |||
1260 | protected function getTrackerChildrenFromHierarchy(Tracker $tracker) { |
||
1263 | |||
1264 | /** |
||
1265 | * @return Tracker_HierarchyFactory |
||
1266 | */ |
||
1267 | protected function getHierarchyFactory() { |
||
1270 | |||
1271 | /** |
||
1272 | * @see Tracker_FormElement_Field::postSaveNewChangeset() |
||
1273 | */ |
||
1274 | public function postSaveNewChangeset( |
||
1275 | Tracker_Artifact $artifact, |
||
1276 | PFUser $submitter, |
||
1277 | Tracker_Artifact_Changeset $new_changeset, |
||
1278 | Tracker_Artifact_Changeset $previous_changeset = null |
||
1279 | ) { |
||
1280 | $queue = new Tracker_FormElement_Field_ArtifactLink_PostSaveNewChangesetQueue(); |
||
1281 | $queue->add($this->getUpdateLinkingDirectionCommand()); |
||
1282 | $queue->add($this->getProcessChildrenTriggersCommand()); |
||
1283 | $queue->execute($artifact, $submitter, $new_changeset, $previous_changeset); |
||
1284 | } |
||
1285 | |||
1286 | /** |
||
1287 | * @protected for testing purpose |
||
1288 | */ |
||
1289 | protected function getProcessChildrenTriggersCommand() { |
||
1290 | return new Tracker_FormElement_Field_ArtifactLink_ProcessChildrenTriggersCommand( |
||
1291 | $this, |
||
1292 | $this->getWorkflowFactory()->getTriggerRulesManager() |
||
1293 | ); |
||
1294 | } |
||
1295 | |||
1296 | private function getUpdateLinkingDirectionCommand() { |
||
1299 | |||
1300 | /** |
||
1301 | * Return true if $artifact_to_check is "parent of" $artifact_reference |
||
1302 | * |
||
1303 | * @todo: take planning into account |
||
1304 | * |
||
1305 | * When $artifact_to_check is a Release |
||
1306 | * And $artifact_reference is a Sprint |
||
1307 | * And Release -> Sprint (in tracker hierarchy) |
||
1308 | * Then return True |
||
1309 | * |
||
1310 | * @param Tracker_Artifact $artifact_to_check |
||
1311 | * @param Tracker_Artifact $artifact_reference |
||
1312 | * |
||
1313 | * @return Boolean |
||
1314 | */ |
||
1315 | protected function isSourceOfAssociation(Tracker_Artifact $artifact_to_check, Tracker_Artifact $artifact_reference) { |
||
1319 | |||
1320 | /** |
||
1321 | * Save the value submitted by the user in the new changeset |
||
1322 | * |
||
1323 | * @param Tracker_Artifact $artifact The artifact |
||
1324 | * @param Tracker_Artifact_Changeset $old_changeset The old changeset. null if it is the first one |
||
1325 | * @param int $new_changeset_id The id of the new changeset |
||
1326 | * @param mixed $submitted_value The value submitted by the user |
||
1327 | * @param boolean $is_submission true if artifact submission, false if artifact update |
||
1328 | * |
||
1329 | * @return bool true if success |
||
1330 | */ |
||
1331 | public function saveNewChangeset(Tracker_Artifact $artifact, $old_changeset, $new_changeset_id, $submitted_value, PFUser $submitter, $is_submission = false, $bypass_permissions = false) { |
||
1335 | |||
1336 | /** |
||
1337 | * Verify (and update if needed) that the link between what submitted the user ($submitted_values) and |
||
1338 | * the current artifact is correct resp. the association definition. |
||
1339 | * |
||
1340 | * Given I defined following hierarchy: |
||
1341 | * Release |
||
1342 | * `-- Sprint |
||
1343 | * |
||
1344 | * If $artifact is a Sprint and I try to link a Release, this method detect |
||
1345 | * it and update the corresponding Release with a link toward current sprint |
||
1346 | * |
||
1347 | * @param Tracker_Artifact $artifact |
||
1348 | * @param Tracker_Artifact_Changeset $old_changeset |
||
1349 | * @param mixed $submitted_value |
||
1350 | * @param PFUser $submitter |
||
1351 | * |
||
1352 | * @return mixed The submitted value expurged from updated links |
||
1353 | */ |
||
1354 | protected function updateLinkingDirection(Tracker_Artifact $artifact, $old_changeset, $submitted_value, PFUser $submitter) { |
||
1355 | $previous_changesetvalue = $this->getPreviousChangesetValue($old_changeset); |
||
1356 | $artifacts = $this->getArtifactsFromChangesetValue($submitted_value, $previous_changesetvalue); |
||
1357 | $artifact_id_already_linked = array(); |
||
1358 | foreach ($artifacts as $artifact_to_add) { |
||
1359 | if ($this->isSourceOfAssociation($artifact_to_add, $artifact)) { |
||
1360 | $this->source_of_association[] = $artifact_to_add; |
||
1361 | $artifact_id_already_linked[] = $artifact_to_add->getId(); |
||
1362 | } |
||
1363 | } |
||
1364 | |||
1365 | return $this->removeArtifactsFromSubmittedValue($submitted_value, $artifact_id_already_linked); |
||
1366 | } |
||
1367 | |||
1368 | /** |
||
1369 | * Remove from user submitted artifact links the artifact ids that where already |
||
1370 | * linked after the direction checking |
||
1371 | * |
||
1372 | * Should be private to the class but almost impossible to test in the context |
||
1373 | * of saveNewChangeset. |
||
1374 | * |
||
1375 | * @param Array $submitted_value |
||
1376 | * @param Array $artifact_id_already_linked |
||
1377 | * |
||
1378 | * @return Array |
||
1379 | */ |
||
1380 | public function removeArtifactsFromSubmittedValue($submitted_value, array $artifact_id_already_linked) { |
||
1381 | $new_values = explode(',', $submitted_value['new_values']); |
||
1382 | $new_values = array_map('trim', $new_values); |
||
1383 | $new_values = array_diff($new_values, $artifact_id_already_linked); |
||
1384 | $submitted_value['new_values'] = implode(',', $new_values); |
||
1385 | return $submitted_value; |
||
1386 | } |
||
1387 | |||
1388 | protected function getArtifactsFromChangesetValue($value, $previous_changesetvalue = null) { |
||
1389 | $new_values = (string)$value['new_values']; |
||
1390 | $removed_values = isset($value['removed_values']) ? $value['removed_values'] : array(); |
||
1391 | // this array will be the one to save in the new changeset |
||
1392 | $artifact_ids = array(); |
||
1393 | if ($previous_changesetvalue != null) { |
||
1394 | $artifact_ids = $previous_changesetvalue->getArtifactIds(); |
||
1395 | // We remove artifact links that user wants to remove |
||
1396 | if (is_array($removed_values) && ! empty($removed_values)) { |
||
1397 | $artifact_ids = array_diff($artifact_ids, array_keys($removed_values)); |
||
1398 | } |
||
1399 | } |
||
1400 | |||
1401 | if (trim($new_values) != '') { |
||
1402 | $new_artifact_ids = array_diff(explode(',', $new_values), array_keys($removed_values)); |
||
1403 | // We add new links to existing ones |
||
1404 | foreach ($new_artifact_ids as $new_artifact_id) { |
||
1405 | if ( ! in_array($new_artifact_id, $artifact_ids)) { |
||
1406 | $artifact_ids[] = (int)$new_artifact_id; |
||
1407 | } |
||
1408 | } |
||
1409 | } |
||
1410 | |||
1411 | return $this->getArtifactFactory()->getArtifactsByArtifactIdList($artifact_ids); |
||
1412 | } |
||
1413 | |||
1414 | /** |
||
1415 | * Save the value and return the id |
||
1416 | * |
||
1417 | * @param Tracker_Artifact $artifact The artifact |
||
1418 | * @param int $changeset_value_id The id of the changeset_value |
||
1419 | * @param mixed $value The value submitted by the user |
||
1420 | * @param Tracker_Artifact_ChangesetValue $previous_changesetvalue The data previously stored in the db |
||
1421 | * |
||
1422 | */ |
||
1423 | protected function saveValue($artifact, $changeset_value_id, $value, Tracker_Artifact_ChangesetValue $previous_changesetvalue = null) { |
||
1424 | $dao = $this->getValueDao(); |
||
1425 | |||
1426 | foreach ($this->getArtifactIdsToLink($artifact, $value, $previous_changesetvalue) as $artifact_to_be_linked_by_tracker) { |
||
1427 | $tracker = $artifact_to_be_linked_by_tracker['tracker']; |
||
1428 | $dao->create( |
||
1429 | $changeset_value_id, |
||
1430 | $artifact_to_be_linked_by_tracker['ids'], |
||
1431 | $tracker->getItemName(), |
||
1432 | $tracker->getGroupId() |
||
1433 | ); |
||
1434 | } |
||
1435 | |||
1436 | return $this->updateCrossReferences($artifact, $value); |
||
1437 | } |
||
1438 | |||
1439 | /** @return array */ |
||
1440 | private function getArtifactIdsToLink(Tracker_Artifact $artifact, $value, Tracker_Artifact_ChangesetValue $previous_changesetvalue = null) { |
||
1441 | $all_artifacts_to_link = $this->getArtifactsFromChangesetValue( |
||
1442 | $value, |
||
1443 | $previous_changesetvalue |
||
1444 | ); |
||
1445 | |||
1446 | $all_artifact_to_be_linked = array(); |
||
1447 | foreach ($all_artifacts_to_link as $artifact_to_link) { |
||
1448 | if ($this->canLinkArtifacts($artifact, $artifact_to_link)) { |
||
1449 | $tracker = $artifact_to_link->getTracker(); |
||
1450 | |||
1451 | if (! isset($all_artifact_to_be_linked[$tracker->getId()])) { |
||
1452 | $all_artifact_to_be_linked[$tracker->getId()] = array( |
||
1453 | 'tracker' => $tracker, |
||
1454 | 'ids' => array() |
||
1455 | ); |
||
1456 | } |
||
1457 | |||
1458 | $all_artifact_to_be_linked[$tracker->getId()]['ids'][] = $artifact_to_link->getId(); |
||
1459 | } |
||
1460 | } |
||
1461 | |||
1462 | return $all_artifact_to_be_linked; |
||
1463 | } |
||
1464 | |||
1465 | private function canLinkArtifacts(Tracker_Artifact $src_artifact, Tracker_Artifact $artifact_to_link) { |
||
1468 | |||
1469 | /** |
||
1470 | * Update cross references of this field |
||
1471 | * |
||
1472 | * @param Tracker_Artifact $artifact the artifact that is currently updated |
||
1473 | * @param array $values the array of added and removed artifact links ($values['added_values'] is a string and $values['removed_values'] is an array of artifact ids |
||
1474 | * |
||
1475 | * @return boolean |
||
1476 | */ |
||
1477 | protected function updateCrossReferences(Tracker_Artifact $artifact, $values) { |
||
1478 | $update_ok = true; |
||
1479 | |||
1480 | foreach ($this->getAddedArtifactIds($values) as $added_artifact_id) { |
||
1481 | $update_ok = $update_ok && $this->insertCrossReference($artifact, $added_artifact_id); |
||
1482 | } |
||
1483 | foreach ($this->getRemovedArtifactIds($values) as $removed_artifact_id) { |
||
1484 | $update_ok = $update_ok && $this->removeCrossReference($artifact, $removed_artifact_id); |
||
1485 | } |
||
1486 | |||
1487 | return $update_ok; |
||
1488 | } |
||
1489 | |||
1490 | private function getAddedArtifactIds(array $values) { |
||
1491 | if (array_key_exists('new_values', $values)) { |
||
1492 | if (trim($values['new_values']) != '') { |
||
1493 | return array_map('intval', explode(',', $values['new_values'])); |
||
1494 | } |
||
1495 | } |
||
1496 | return array(); |
||
1497 | } |
||
1498 | |||
1499 | private function getRemovedArtifactIds(array $values) { |
||
1500 | if (array_key_exists('removed_values', $values)) { |
||
1501 | return array_map('intval', array_keys($values['removed_values'])); |
||
1502 | } |
||
1503 | return array(); |
||
1504 | } |
||
1505 | |||
1506 | private function insertCrossReference(Tracker_Artifact $source_artifact, $target_artifact_id) { |
||
1507 | return $this->getTrackerReferenceManager()->insertBetweenTwoArtifacts( |
||
1508 | $source_artifact, |
||
1509 | $this->getArtifactFactory()->getArtifactById($target_artifact_id), |
||
1510 | $this->getCurrentUser() |
||
1511 | ); |
||
1512 | } |
||
1513 | |||
1514 | private function removeCrossReference(Tracker_Artifact $source_artifact, $target_artifact_id) { |
||
1515 | return $this->getTrackerReferenceManager()->removeBetweenTwoArtifacts( |
||
1516 | $source_artifact, |
||
1517 | $this->getArtifactFactory()->getArtifactById($target_artifact_id), |
||
1518 | $this->getCurrentUser() |
||
1519 | ); |
||
1520 | } |
||
1521 | |||
1522 | protected function getTrackerReferenceManager() { |
||
1523 | return new Tracker_ReferenceManager( |
||
1524 | ReferenceManager::instance(), |
||
1525 | Tracker_ArtifactFactory::instance() |
||
1526 | ); |
||
1527 | } |
||
1528 | |||
1529 | /** |
||
1530 | * Retrieve linked artifacts according to user's permissions |
||
1531 | * |
||
1532 | * @param Tracker_Artifact_Changeset $changeset The changeset you want to retrieve artifact from |
||
1533 | * @param PFUser $user The user who will see the artifacts |
||
1534 | * |
||
1535 | * @return Tracker_Artifact[] |
||
1536 | */ |
||
1537 | public function getLinkedArtifacts(Tracker_Artifact_Changeset $changeset, PFUser $user) { |
||
1538 | $artifacts = array(); |
||
1539 | $changeset_value = $changeset->getValue($this); |
||
1540 | if ($changeset_value) { |
||
1541 | foreach ($changeset_value->getArtifactIds() as $id) { |
||
1542 | $this->addArtifactUserCanViewFromId($artifacts, $id, $user); |
||
1543 | } |
||
1544 | } |
||
1545 | return $artifacts; |
||
1546 | } |
||
1547 | |||
1548 | |||
1549 | |||
1550 | |||
1551 | /** |
||
1552 | * Retrieve sliced linked artifacts according to user's permissions |
||
1553 | * |
||
1554 | * This is nearly the same as a paginated list however, for performance |
||
1555 | * reasons, the total size may be different than the sum of total paginated |
||
1556 | * artifacts. |
||
1557 | * |
||
1558 | * Example to illustrate the difference between paginated and sliced: |
||
1559 | * |
||
1560 | * Given that artifact links are [12, 13, 24, 39, 65, 69] |
||
1561 | * And that the user cannot see artifact #39 |
||
1562 | * When I request linked artifacts by bunchs of 2 |
||
1563 | * Then I get [[12, 13], [24], [65, 69]] # instead of [[12, 13], [24, 65], [69]] |
||
1564 | * And total size will be 6 # instead of 5 |
||
1565 | * |
||
1566 | * @param Tracker_Artifact_Changeset $changeset The changeset you want to retrieve artifact from |
||
1567 | * @param PFUser $user The user who will see the artifacts |
||
1568 | * @param int $limit The number of artifact to fetch |
||
1569 | * @param int $offset The offset |
||
1570 | * |
||
1571 | * @return Tracker_Artifact_PaginatedArtifacts |
||
1572 | */ |
||
1573 | public function getSlicedLinkedArtifacts(Tracker_Artifact_Changeset $changeset, PFUser $user, $limit, $offset) { |
||
1574 | $changeset_value = $changeset->getValue($this); |
||
1575 | if (! $changeset_value) { |
||
1576 | return new Tracker_Artifact_PaginatedArtifacts(array(), 0); |
||
1577 | } |
||
1578 | |||
1579 | $artifact_ids = $changeset_value->getArtifactIds(); |
||
1580 | $size = count($artifact_ids); |
||
1581 | |||
1582 | $artifacts = array(); |
||
1583 | foreach (array_slice($artifact_ids, $offset, $limit) as $id) { |
||
1584 | $this->addArtifactUserCanViewFromId($artifacts, $id, $user); |
||
1585 | } |
||
1586 | |||
1587 | return new Tracker_Artifact_PaginatedArtifacts($artifacts, $size); |
||
1588 | } |
||
1589 | |||
1590 | /** @return Tracker_Artifact|null */ |
||
1591 | private function addArtifactUserCanViewFromId(array &$artifacts, $id, PFUser $user) { |
||
1592 | $artifact = $this->getArtifactFactory()->getArtifactById($id); |
||
1593 | if ($artifact && $artifact->userCanView($user)) { |
||
1594 | $artifacts[] = $artifact; |
||
1595 | } |
||
1596 | } |
||
1597 | |||
1598 | /** |
||
1599 | * If request come with a 'parent', it should be automagically transformed as |
||
1600 | * 'new_values'. |
||
1601 | * Please note that it only work on artifact creation. |
||
1602 | * |
||
1603 | * @param type $fields_data |
||
1604 | */ |
||
1605 | public function augmentDataFromRequest(&$fields_data) { |
||
1621 | |||
1622 | public function accept(Tracker_FormElement_FieldVisitor $visitor) { |
||
1625 | } |
||
1626 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.