GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Tracker_Artifact   F
last analyzed

Complexity

Total Complexity 310

Size/Duplication

Total Lines 1987
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 61
Metric Value
wmc 310
lcom 1
cbo 61
dl 0
loc 1987
rs 0.5217

148 Methods

Rating   Name   Duplication   Size   Complexity  
A getEventManager() 0 3 1
A equals() 0 3 2
A setUseArtifactPermissions() 0 3 1
A useArtifactPermissions() 0 3 1
A setUserCanView() 0 3 1
A hasChildren() 0 3 1
A fetchDirectLinkToArtifact() 0 3 1
A fetchDirectLinkToArtifactWithTitle() 0 3 1
A fetchDirectLinkToArtifactWithoutXRef() 0 4 1
A getRestUri() 0 3 1
A getUri() 0 3 1
A getXRef() 0 3 1
A setFormElementFactory() 0 3 1
A setArtifactFactory() 0 3 1
A getReferenceManager() 0 3 1
A getTrackerId() 0 3 1
A setTracker() 0 4 1
A getVersionIdentifier() 0 3 1
A __construct() 0 8 1
B userCanView() 0 21 6
A userCanUpdate() 0 6 3
A permission_db_authorized_ugroups() 0 13 3
A getAuthorizedUGroups() 0 13 3
A setAuthorizedUGroups() 0 3 1
B fetchMail() 0 32 3
B fetchMailFormElements() 0 18 5
A prepareElementsForDisplay() 0 5 2
C fetchMailFollowUp() 0 49 7
A fetchTooltip() 0 16 4
A fetchWidget() 0 15 2
A fetchTitleWithoutUnsubscribeButton() 0 3 1
A fetchTitle() 0 3 1
A fetchTitleContent() 0 12 2
A fetchEmailActionButtons() 0 8 1
A fetchNotificationButton() 0 9 1
A getUnsubscribeButtonLabel() 0 9 2
A fetchIncomingMailButton() 0 20 3
A getUnsubscribeButtonAlternateText() 0 9 2
A doesUserHaveUnsubscribedFromNotification() 0 3 1
A fetchHiddenTrackerId() 0 3 1
A getXRefAndTitle() 0 8 1
A fetchColoredXRef() 0 3 1
B getTitle() 0 15 6
A getCachedTitle() 0 3 1
B getAssignedTo() 0 19 6
A setTitle() 0 3 1
A getStatus() 0 10 3
A getStatusForChangeset() 0 11 3
A setStatus() 0 3 1
A getSemanticStatusValue() 0 3 2
A isOpen() 0 3 1
B fetchMailTitle() 0 13 6
A fetchHistory() 0 7 1
A validateCommentFormat() 0 4 1
D process() 0 162 37
A sendUserDoesNotHavePermissionsErrorCode() 0 4 1
A userHasRankingPermissions() 0 15 1
A getProjectId() 0 3 1
A getPriorityManager() 0 8 1
A getChildrenForUser() 0 9 3
A getChildPresenterCollection() 0 10 2
A getCardAccentColor() 0 12 2
A fetchXRefLink() 0 7 1
A getSubmitNewArtifactLinkedToMeUri() 0 8 1
A getFormElementFactory() 0 6 2
A getArtifactFactory() 0 6 2
A createInitialChangeset() 0 11 1
A getErrors() 0 11 3
A createNewChangeset() 0 14 1
A getTracker() 0 6 2
A getLastUpdateDate() 0 7 2
A wasLastModifiedByAnonymous() 0 10 3
A getLastModifiedBy() 0 10 3
A getLastChangeset() 0 8 2
A getLastChangesetWithFieldValue() 0 3 1
A getFirstChangeset() 0 6 1
A isFirstChangeset() 0 4 1
A getPriorityHistory() 0 3 1
A cmpFollowups() 0 3 2
A getFollowupsContent() 0 8 1
A getChangesets() 0 6 2
A forceFetchAllChangesets() 0 3 1
A setChangesets() 0 3 1
A clearChangesets() 0 3 1
A addChangeset() 0 3 1
B getCommentators() 0 13 5
A getChangesetDao() 0 3 1
A getChangesetFactory() 0 8 1
A getMustacheRenderer() 0 3 1
A getChangesetCommentDao() 0 3 1
A getChangeset() 0 6 2
A getPreviousChangeset() 0 9 3
A exportCommentsToSOAP() 0 10 3
A exportHistoryToSOAP() 0 7 2
A getId() 0 3 1
A setId() 0 4 1
A getValue() 0 9 3
A getSubmittedOn() 0 3 1
A getSubmittedBy() 0 3 1
A getSubmittedByUser() 0 6 2
A setSubmittedByUser() 0 4 1
A getPerTrackerArtifactId() 0 6 2
A getUnsubscribersIds() 0 7 2
A getWorkflow() 0 5 1
A getUserManager() 0 3 1
A getCurrentUser() 0 3 1
A getProjectManager() 0 3 1
B linkArtifact() 0 22 4
A linkArtifacts() 0 5 1
A unlinkArtifact() 0 15 3
A getLinkedArtifacts() 0 8 2
A getSlicedLinkedArtifacts() 0 8 2
A getLinkedArtifactsOfHierarchy() 0 12 3
A getAllowedChildrenTypes() 0 3 1
A getAllowedChildrenTypesForUser() 0 9 3
A getUniqueLinkedArtifacts() 0 9 2
A __toString() 0 3 1
A getAllAncestors() 0 6 2
A setAllAncestors() 0 3 1
A getParent() 0 3 1
B getParentWithoutPermissionChecking() 0 14 6
A setParentWithoutPermissionChecking() 0 3 1
A getSiblings() 0 11 4
A setSiblings() 0 3 1
A getSiblingsWithoutPermissionChecking() 0 6 2
A setSiblingsWithoutPermissionChecking() 0 3 1
A getHierarchyFactory() 0 6 2
A setHierarchyFactory() 0 3 1
A getChildTrackersIds() 0 8 2
A getAnArtifactLinkField() 0 3 1
A getABurndownField() 0 3 1
A summonArtifactRedirectors() 0 10 1
A summonArtifactAssociators() 0 12 1
A delete() 0 20 2
D getAuthorisedUgroups() 0 27 10
A exportPermissions() 0 6 1
A getDao() 0 3 1
A getPermissionsManager() 0 3 1
A getCrossReferenceManager() 0 3 1
A getCrossReferenceFactory() 0 3 1
A getCrossReferencesSOAPValues() 0 16 3
B getSoapValue() 0 22 4
A exportToXML() 0 21 1
A getArtifactAttachmentExporter() 0 3 1
A getArtifactXMLExporter() 0 10 1
A getTokenBasedEmailAddress() 0 3 1
A getInsecureEmailAddress() 0 3 1
A getEmailDomain() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like Tracker_Artifact 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_Artifact, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Copyright (c) Xerox Corporation, Codendi Team, 2001-2009. All rights reserved
4
 *
5
 * This file is a part of Codendi.
6
 *
7
 * Codendi is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * Codendi is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with Codendi. If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
require_once(dirname(__FILE__).'/../../constants.php');
22
23
class Tracker_Artifact implements Recent_Element_Interface, Tracker_Dispatchable_Interface {
24
    const REST_ROUTE        = 'artifacts';
25
    const NO_PARENT         = -1;
26
    const PERMISSION_ACCESS = 'PLUGIN_TRACKER_ARTIFACT_ACCESS';
27
    const REFERENCE_NATURE  = 'plugin_tracker_artifact';
28
    const STATUS_OPEN       = 'open';
29
    const STATUS_CLOSED     = 'closed';
30
31
    public $id;
32
    public $tracker_id;
33
    public $use_artifact_permissions;
34
    protected $per_tracker_id;
35
    protected $submitted_by;
36
    protected $submitted_on;
37
38
    protected $changesets;
39
40
    /**
41
     * @var array of Tracker_Artifact
42
     */
43
    private $ancestors;
44
45
    /**
46
     * @var Tracker
47
     */
48
    private $tracker;
49
50
    /**
51
     * @var Tracker_FormElementFactory
52
     */
53
    private $formElementFactory;
54
55
    /**
56
     * @var Tracker_HierarchyFactory
57
     */
58
    private $hierarchy_factory;
59
60
    /**
61
     * @var String
62
     */
63
    private $title;
64
65
    /**
66
     * @var String
67
     */
68
    private $status;
69
70
    /** @var Tracker_ArtifactFactory */
71
    private $artifact_factory;
72
73
    /** @var Tracker_Artifact[] */
74
    private $siblings;
75
76
    /** @var Tracker_Artifact[] */
77
    private $siblings_without_permission_checking;
78
79
    /** @var Tracker_Artifact */
80
    private $parent_without_permission_checking;
81
82
    /** @var Boolean[] */
83
    private $can_view_cache = array();
84
85
    /** @var PFUser*/
86
    private $submitted_by_user;
87
88
    /** @var array */
89
    private $authorized_ugroups;
90
91
    /**
92
     * Constructor
93
     *
94
     * @param int     $id                       The Id of the artifact
95
     * @param int     $tracker_id               The tracker Id the artifact belongs to
96
     * @param int     $submitted_by             The id of the user who's submitted the artifact
97
     * @param int     $submitted_on             The timestamp of artifact submission
98
     *
99
     * @param boolean $use_artifact_permissions True if this artifact uses permission, false otherwise
100
     */
101
    public function __construct($id, $tracker_id, $submitted_by, $submitted_on, $use_artifact_permissions) {
102
        $this->id                       = $id;
103
        $this->tracker_id               = $tracker_id;
104
        $this->submitted_by             = $submitted_by;
105
        $this->submitted_on             = $submitted_on;
106
        $this->use_artifact_permissions = $use_artifact_permissions;
107
        $this->per_tracker_id           = null;
108
    }
109
110
    /**
111
     * Obtain event manager instance
112
     *
113
     * @return EventManager
114
     */
115
    private function getEventManager() {
116
        return EventManager::instance();
117
    }
118
119
    /**
120
     * Return true if given given artifact refer to the same DB object (basically same id).
121
     *
122
     * @param Tracker_Artifact $artifact
123
     *
124
     * @return Boolean
125
     */
126
    public function equals(Tracker_Artifact $artifact = null) {
127
        return $artifact && $this->id == $artifact->getId();
128
    }
129
130
    /**
131
    * Set the value of use_artifact_permissions
132
    *
133
    * @param bool $use_artifact_permissions
134
    *
135
    * @return bool true if the artifact has individual permissions set
136
    */
137
    public function setUseArtifactPermissions($use_artifact_permissions) {
138
        $this->use_artifact_permissions = $use_artifact_permissions;
139
    }
140
141
    /**
142
     * useArtifactPermissions
143
     * @return bool true if the artifact has individual permissions set
144
     */
145
    public function useArtifactPermissions() {
146
        return $this->use_artifact_permissions;
147
    }
148
149
    /**
150
     * userCanView - determine if the user can view this artifact.
151
     *
152
     * @param PFUser $user if not specified, use the current user
153
     *
154
     * @return boolean user can view the artifact
155
     */
156
    public function userCanView(PFUser $user = null) {
157
        $um = $this->getUserManager();
158
        $project_manager = $this->getProjectManager();
159
160
        if (! $user) {
161
            $user = $um->getCurrentUser();
162
        }
163
        if ($user instanceof Tracker_UserWithReadAllPermission) {
164
            return true;
165
        }
166
167
        if (! isset($this->can_view_cache[$user->getId()])) {
168
            if ($this->getTracker()->userIsAdmin() || $user->isSuperUser()) {
169
                $this->setUserCanView($user, true);
170
            } else {
171
                $permission_checker = new Tracker_Permission_PermissionChecker($um, $project_manager);
172
                $this->setUserCanView($user, $permission_checker->userCanView($user, $this));
173
            }
174
        }
175
        return $this->can_view_cache[$user->getId()];
176
    }
177
178
    public function setUserCanView(PFUser $user, $can_view) {
179
        $this->can_view_cache[$user->getId()] = $can_view;
180
    }
181
182
    public function userCanUpdate(PFUser $user) {
183
        if ($user->isAnonymous() || !$this->userCanView($user)) {
184
            return false;
185
        }
186
        return true;
187
    }
188
189
    /**
190
     * @deprecated
191
     */
192
    public function permission_db_authorized_ugroups( $permission_type ) {
193
        include_once 'www/project/admin/permissions.php';
194
        $result = array();
195
        $res    = permission_db_authorized_ugroups($permission_type, $this->getId());
196
        if ( db_numrows($res) > 0 ) {
197
            while ( $row = db_fetch_array($res) ) {
198
                $result[] = $row;
199
            }
200
            return $result;
201
        } else {
202
            return false;
203
        }
204
    }
205
206
    public function getAuthorizedUGroups() {
207
        if (! isset($this->authorized_ugroups)) {
208
            $this->authorized_ugroups = array();
209
            if ($this->useArtifactPermissions()) {
210
                $this->authorized_ugroups = PermissionsManager::instance()->getAuthorizedUgroupIds(
211
                    $this->id,
212
                    self::PERMISSION_ACCESS
213
                );
214
            }
215
        }
216
217
        return $this->authorized_ugroups;
218
    }
219
220
    public function setAuthorizedUGroups(array $ugroups) {
221
        $this->authorized_ugroups = $ugroups;
222
    }
223
224
    /**
225
     * This method returns the artifact mail rendering
226
     *
227
     * @param array  $recipient
228
     * @param string $format, the mail format text or html
0 ignored issues
show
Documentation introduced by
There is no parameter named $format,. Did you maybe mean $format?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
229
     * @param bool   $ignore_perms, indicates if we ignore various permissions
0 ignored issues
show
Documentation introduced by
There is no parameter named $ignore_perms,. Did you maybe mean $ignore_perms?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
230
     *
231
     * @return string
232
     */
233
    public function fetchMail($recipient, $format, $ignore_perms=false) {
234
        $output = '';
235
        switch($format) {
236
            case 'html':
237
                $content = $this->fetchMailFormElements($recipient, $format, $ignore_perms);
238
                if ($content) {
239
                    $output .=
240
                    '<table style="width:100%">
241
                        <tr>
242
                            <td colspan="3" align="left">
243
                                <h2>'.
244
                                    $GLOBALS['Language']->getText('plugin_tracker_artifact_changeset', 'header_html_snapshot').'
245
                                </h2>
246
                            </td>
247
                        </tr>
248
                    </table>';
249
                    $output .= $content;
250
                }
251
                $output .=
252
                '<table style="width:100%">'.
253
                        $this->fetchMailFollowUp($recipient, $format, $ignore_perms).
254
                '</table>';
255
                break;
256
            default:
257
                $output .= PHP_EOL;
258
                //fields formelements
259
                $output .= $this->fetchMailFormElements($recipient, $format, $ignore_perms);
260
                $output .= $this->fetchMailFollowUp($recipient, $format, $ignore_perms);
261
                break;
262
        }
263
        return $output;
264
    }
265
266
    /**
267
     * Returns the artifact field for mail rendering
268
     *
269
     * @param array  $recipient
270
     * @param string $format, the mail format text or html
0 ignored issues
show
Documentation introduced by
There is no parameter named $format,. Did you maybe mean $format?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
271
     * @param bool   $ignore_perms, indicates if we ignore various permissions
0 ignored issues
show
Documentation introduced by
There is no parameter named $ignore_perms,. Did you maybe mean $ignore_perms?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
272
     *
273
     * @return String
274
     */
275
    public function fetchMailFormElements($recipient, $format, $ignore_perms = false) {
276
        $output = '';
277
        $toplevel_form_elements = $this->getTracker()->getFormElements();
278
        $this->prepareElementsForDisplay($toplevel_form_elements);
279
280
        foreach ($toplevel_form_elements as $formElement) {
0 ignored issues
show
Bug introduced by
The expression $toplevel_form_elements of type object<Tracker_FormElement> is not traversable.
Loading history...
281
            $output .= $formElement->fetchMailArtifact($recipient, $this, $format, $ignore_perms);
282
            if ($format == 'text' && $output) {
283
                $output .= PHP_EOL;
284
            }
285
        }
286
287
        if ($format == 'html') {
288
            $output = '<table width="100%">'.$output.'</table>';
289
        }
290
291
        return $output;
292
    }
293
294
    /** @param Tracker_FormElement[] */
295
    private function prepareElementsForDisplay($toplevel_form_elements) {
296
        foreach ($toplevel_form_elements as $formElement) {
297
            $formElement->prepareForDisplay();
298
        }
299
    }
300
301
    /**
302
     * Returns the artifact followup for mail rendering
303
     *
304
     * @param array  $recipient
305
     * @param string $format, the mail format text or html
0 ignored issues
show
Documentation introduced by
There is no parameter named $format,. Did you maybe mean $format?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
306
     * @param bool   $ignore_perms, indicates if we ignore various permissions
0 ignored issues
show
Documentation introduced by
There is no parameter named $ignore_perms,. Did you maybe mean $ignore_perms?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
307
     *
308
     * @return String
309
     */
310
    public function fetchMailFollowUp($recipient, $format, $ignore_perms=false) {
311
        $uh = UserHelper::instance();
312
        $um = UserManager::instance();
313
        $cs = $this->getChangesets();
314
        $hp = Codendi_HTMLPurifier::instance();
315
        $output = '';
316
317
        if($format == 'html'){
318
            $output .=
319
            '<tr>
320
                <td colspan="3" align="left">
321
                    <h2>'.
322
                        $GLOBALS['Language']->getText('plugin_tracker_include_artifact','follow_ups').'
323
                    </h2>
324
                </td>
325
            </tr>';
326
        }
327
328
        foreach ( $cs as $changeset ) {
329
            $comment = $changeset->getComment();
330
            /* @var $comment Tracker_Artifact_Changeset_Comment */
331
            if (empty($comment) || $comment->hasEmptyBody()) {
332
                //do not display empty comment
333
                continue;
334
            }
335
            switch ($format) {
336
                case 'html':
337
                    $followup = $comment->fetchMailFollowUp($format);
338
                    $output .=  $followup;
339
                    break;
340
                case 'text':
341
                    $user = $um->getUserById($comment->submitted_by);
342
                    $output .= PHP_EOL;
343
                    $output .= '----------------------------- ';
344
                    $output .= PHP_EOL;
345
                    $output .= $GLOBALS['Language']->getText('plugin_tracker_artifact','mail_followup_date') . util_timestamp_to_userdateformat($comment->submitted_on);
0 ignored issues
show
Deprecated Code introduced by
The function util_timestamp_to_userdateformat() has been deprecated with message: Use DateHelper::formatForLanguage() instead

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
346
                    $output .= "\t" . $GLOBALS['Language']->getText('plugin_tracker_artifact','mail_followup_by') . $uh->getDisplayNameFromUser($user);
347
                    $output .= PHP_EOL;
348
                    $output .= $comment->getPurifiedBodyForText();
349
                    $output .= PHP_EOL;
350
                    $output .= PHP_EOL;
351
                    break;
352
                default:
353
                    $output .= '<!-- TODO -->';
354
                    break;
355
            }
356
        }
357
        return $output;
358
    }
359
    /**
360
     * Fetch the tooltip displayed on an artifact reference
361
     *
362
     * @param PFUser $user The user who fetch the tooltip
363
     *
364
     * @return string html
365
     */
366
    public function fetchTooltip($user) {
367
        $tooltip = $this->getTracker()->getTooltip();
368
        $html = '';
369
        if ($this->userCanView($user)) {
370
            $fields = $tooltip->getFields();
371
            if (!empty($fields)) {
372
                $html .= '<table>';
373
                foreach ($fields as $f) {
374
                    //TODO: check field permissions
375
                    $html .= $f->fetchTooltip($this);
376
                }
377
                $html .= '</table>';
378
            }
379
        }
380
        return $html;
381
    }
382
383
    /**
384
     * Fetch the artifact for the MyArtifact widget
385
     *
386
     * @param string $item_name The short name of the tracker this artifact belongs to
387
     * @param string $title     The title of this artifact
388
     *
389
     * @return string html
390
     */
391
    public function fetchWidget($item_name, $title) {
392
        $hp = Codendi_HTMLPurifier::instance();
393
        $html = '';
394
        $html .= ' <a class="direct-link-to-artifact" href="'.TRACKER_BASE_URL.'/?aid='. $this->id .'">';
395
        $html .= $hp->purify($item_name, CODENDI_PURIFIER_CONVERT_HTML);
396
        $html .= ' #';
397
        $html .= $this->id;
398
        if ($title) {
399
            $html .= ' - ';
400
            $html .= $hp->purify($title, CODENDI_PURIFIER_CONVERT_HTML);
401
        }
402
403
        $html .= '</a>';
404
        return $html;
405
    }
406
407
    public function fetchTitleWithoutUnsubscribeButton($prefix) {
408
        return $this->fetchTitleContent($prefix, false);
409
    }
410
411
     /**
412
     * Returns HTML code to display the artifact title
413
     *
414
     * @param string $prefix The prefix to display before the title of the artifact. Default is blank.
415
     *
416
     * @return string The HTML code for artifact title
417
     */
418
    public function fetchTitle($prefix = '') {
419
        return $this->fetchTitleContent($prefix, true);
420
    }
421
422
    private function fetchTitleContent($prefix = '', $unsubscribe_button) {
423
        $html = '';
424
        $html .= $this->fetchHiddenTrackerId();
425
        $html .= '<div class="tracker_artifact_title">';
426
        $html .= $prefix;
427
        $html .= $this->getXRefAndTitle();
428
        if ($unsubscribe_button) {
429
            $html .= $this->fetchEmailActionButtons();
430
        }
431
        $html .= '</div>';
432
        return $html;
433
    }
434
435
    public function fetchEmailActionButtons() {
436
        $html = '<div class="tracker-artifact-email-actions">';
437
        $html .= $this->fetchIncomingMailButton() . ' ';
438
        $html .= $this->fetchNotificationButton();
439
        $html .= '</div>';
440
441
        return $html;
442
    }
443
444
    private function fetchNotificationButton() {
445
        $alternate_text = $this->getUnsubscribeButtonAlternateText();
446
447
        $html  = '<button class="btn btn-default tracker-artifact-notification" title="' . $alternate_text . '">';
448
        $html .= '<i class="icon-bell-alt"></i> ' . $this->getUnsubscribeButtonLabel();
449
        $html .= '</button>';
450
451
        return $html;
452
    }
453
454
    private function getUnsubscribeButtonLabel() {
455
        $user = $this->getCurrentUser();
456
457
        if ($this->doesUserHaveUnsubscribedFromNotification($user)) {
458
            return $GLOBALS['Language']->getText('plugin_tracker', 'enable_notifications');
459
        }
460
461
        return $GLOBALS['Language']->getText('plugin_tracker', 'disable_notifications');
462
    }
463
464
    private function fetchIncomingMailButton() {
465
        if (! $this->getCurrentUser()->isSuperUser()) {
466
            return '';
467
        }
468
469
        $retriever = Tracker_Artifact_Changeset_IncomingMailGoldenRetriever::instance();
470
        $raw_mail  = $retriever->getRawMailThatCreatedArtifact($this);
471
        if (! $raw_mail) {
472
            return '';
473
        }
474
475
        $raw_email_button_title = $GLOBALS['Language']->getText('plugin_tracker', 'raw_email_button_title');
476
        $raw_mail               = Codendi_HTMLPurifier::instance()->purify($raw_mail);
477
478
        $html = '<button type="button" class="btn btn-default artifact-incoming-mail-button" data-raw-email="'. $raw_mail .'">
479
                      <i class="icon-envelope"></i> '. $raw_email_button_title .'
480
                 </button>';
481
482
        return $html;
483
    }
484
485
    private function getUnsubscribeButtonAlternateText() {
486
        $user = $this->getCurrentUser();
487
488
        if ($this->doesUserHaveUnsubscribedFromNotification($user)) {
489
            return $GLOBALS['Language']->getText('plugin_tracker', 'enable_notifications_alternate_text');
490
        }
491
492
        return $GLOBALS['Language']->getText('plugin_tracker', 'disable_notifications_alternate_text');
493
    }
494
495
    private function doesUserHaveUnsubscribedFromNotification(PFUser $user) {
496
        return $this->getDao()->doesUserHaveUnsubscribedFromNotifications($this->id, $user->getId());
497
    }
498
499
    public function fetchHiddenTrackerId() {
500
        return '<input type="hidden" id="tracker_id" name="tracker_id" value="'.$this->getTrackerId().'"/>';
501
    }
502
503
    public function getXRefAndTitle() {
504
        $hp = Codendi_HTMLPurifier::instance();
505
        return '<span class="'. $this->getTracker()->getColor() .' xref-in-title">' .
506
                $this->getXRef() .
507
                '<span> -</span>'.
508
                '</span> '.
509
                $hp->purify($this->getTitle());
510
    }
511
512
    public function fetchColoredXRef() {
513
        return '<span class="colored-xref '. $this->getTracker()->getColor() .'"><a class="cross-reference" href="' . $this->getUri() . '">'. $this->getXRef() .'</a></span>';
514
    }
515
516
    /**
517
     * Get the artifact title, or null if no title defined in semantics
518
     *
519
     * @return string the title of the artifact, or null if no title defined in semantics
520
     */
521
    public function getTitle() {
522
        if ( ! isset($this->title)) {
523
            $this->title = null;
524
            if ($title_field = Tracker_Semantic_Title::load($this->getTracker())->getField()) {
525
                if ($title_field->userCanRead()) {
526
                    if ($last_changeset = $this->getLastChangeset()) {
527
                        if ($title_field_value = $last_changeset->getValue($title_field)) {
528
                            $this->title = $title_field_value->getText();
529
                        }
530
                    }
531
                }
532
            }
533
        }
534
        return $this->title;
535
    }
536
537
    public function getCachedTitle() {
538
        return $this->title;
539
    }
540
541
    /**
542
     * @return PFUser[]
543
     */
544
    public function getAssignedTo(PFUser $user) {
545
        $assigned_to_field = Tracker_Semantic_Contributor::load($this->getTracker())->getField();
546
        if ($assigned_to_field && $assigned_to_field->userCanRead($user)) {
547
            $field_value = $this->getLastChangeset()->getValue($assigned_to_field);
548
            if ($field_value) {
549
                $user_manager   = $this->getUserManager();
550
                $user_ids       = $field_value->getValue();
551
                $assigned_users = array();
552
                foreach($user_ids as $user_id) {
553
                    if ($user_id != 100) {
554
                        $assigned_user    = $user_manager->getUserById($user_id);
555
                        $assigned_users[] = $assigned_user;
556
                    }
557
                }
558
                return $assigned_users;
559
            }
560
        }
561
        return array();
562
    }
563
564
    /**
565
     * @param string $title
566
     */
567
    public function setTitle($title) {
568
        $this->title = $title;
569
    }
570
571
    /**
572
     * Get the artifact status, or null if no status defined in semantics
573
     *
574
     * @return string the status of the artifact, or null if no status defined in semantics
575
     */
576
    public function getStatus() {
577
        if ( ! isset($this->status)) {
578
            $last_changeset = $this->getLastChangeset();
579
            if ($last_changeset) {
580
                $this->status = $this->getStatusForChangeset($last_changeset);
581
            }
582
        }
583
584
        return $this->status;
585
    }
586
587
    /**
588
     * Get the artifact status, or null if no status defined in semantics
589
     *
590
     * @return string the status of the artifact, or null if no status defined in semantics
591
     */
592
    public function getStatusForChangeset(Tracker_Artifact_Changeset $changeset) {
593
        $status_field = Tracker_Semantic_Status::load($this->getTracker())->getField();
594
        if (! $status_field) {
595
            return null;
596
        }
597
        if (! $status_field->userCanRead()) {
598
            return null;
599
        }
600
601
        return $status_field->getFirstValueFor($changeset);
602
    }
603
604
605
    /**
606
     * @param String $status
607
     */
608
    public function setStatus($status) {
609
        $this->status = $status;
610
    }
611
612
    public function getSemanticStatusValue() {
613
        return $this->isOpen() ? self::STATUS_OPEN : self::STATUS_CLOSED;
614
    }
615
616
    public function isOpen() {
617
        return Tracker_Semantic_Status::load($this->getTracker())->isOpen($this);
618
    }
619
620
    /**
621
     *
622
     * @param <type> $recipient
0 ignored issues
show
Documentation introduced by
The doc-type <type> could not be parsed: Unknown type name "<" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
623
     * @param <type> $ignore_perms
0 ignored issues
show
Documentation introduced by
The doc-type <type> could not be parsed: Unknown type name "<" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
624
     * @return <type>
0 ignored issues
show
Documentation introduced by
The doc-type <type> could not be parsed: Unknown type name "<" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
625
     */
626
    public function fetchMailTitle($recipient, $format = 'text', $ignore_perms = false) {
627
        $output = '';
628
        if ( $title_field = Tracker_Semantic_Title::load($this->getTracker())->getField() ) {
629
            if ( $ignore_perms || $title_field->userCanRead($recipient) ) {
630
                if ($value = $this->getLastChangeset()->getValue($title_field)) {
631
                    if ($title = $value->getText() ) {
632
                        $output .= $title;
633
                    }
634
                }
635
            }
636
        }
637
        return $output;
638
    }
639
640
    /**
641
     * Returns HTML code to display the artifact history
642
     *
643
     * @return string The HTML code for artifact history
644
     */
645
    protected function fetchHistory() {
646
        $html = '';
647
        $html .= '<h4 class="tracker_artifact_tab">History</h4>';
648
        $h = new Tracker_History($this);
649
        $html .= $h->fetch();
650
        return $html;
651
    }
652
653
    /**
654
     * Returns HTML code to display the artifact history
655
     *
656
     * @param Codendi_Request $request The data from the user
657
     *
658
     * @return String The valid followup comment format
659
     */
660
    public function validateCommentFormat($request, $comment_format_field_name) {
661
        $comment_format = $request->get($comment_format_field_name);
662
        return Tracker_Artifact_Changeset_Comment::checkCommentFormat($comment_format);
663
    }
664
665
    /**
666
     * Process the artifact functions
667
     *
668
     * @param Tracker_IDisplayTrackerLayout  $layout          Displays the page header and footer
669
     * @param Codendi_Request                $request         The data from the user
670
     * @param PFUser                           $current_user    The current user
671
     *
672
     * @return void
673
     */
674
    public function process(Tracker_IDisplayTrackerLayout $layout, $request, $current_user) {
675
        switch ($request->get('func')) {
676
            case 'get-children':
677
                $children = $this->getChildPresenterCollection($current_user);
678
                $GLOBALS['Response']->sendJSON($children);
679
                exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method process() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
680
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
681
            case 'update-comment':
682
                if ((int)$request->get('changeset_id') && $request->exist('content')) {
683
                    if ($changeset = $this->getChangeset($request->get('changeset_id'))) {
684
                        $comment_format = $this->validateCommentFormat($request, 'comment_format');
685
                        $changeset->updateComment($request->get('content'), $current_user, $comment_format, $_SERVER['REQUEST_TIME']);
686
                        if ($request->isAjax()) {
687
                            //We assume that we can only change a comment from a followUp
688
                            echo $changeset->getComment()->fetchFollowUp();
689
                        }
690
                    }
691
                }
692
                break;
693
            case 'preview-attachment':
694
            case 'show-attachment':
695
                if ((int)$request->get('field') && (int)$request->get('attachment')) {
696
                    $ff = Tracker_FormElementFactory::instance();
697
                    //TODO: check that the user can read the field
698
                    if ($field = $ff->getFormElementByid($request->get('field'))) {
699
                        $method = explode('-', $request->get('func'));
700
                        $method = $method[0];
701
                        $method .= 'Attachment';
702
                        if (method_exists($field, $method)) {
703
                            $field->$method($request->get('attachment'));
704
                        }
705
                    }
706
                }
707
                break;
708
            case 'artifact-delete-changeset':
709
                // @see comment in Tracker_Artifact_Changeset::fetchFollowUp()
710
                //if ($changeset = $this->getChangeset($request->get('changeset'))) {
711
                //    $changeset->delete($current_user);
712
                //}
713
                $GLOBALS['Response']->redirect('?aid='. $this->id);
714
                break;
715
            case 'artifact-update':
716
                $action = new Tracker_Action_UpdateArtifact(
717
                    $this,
718
                    $this->getFormElementFactory(),
719
                    $this->getEventManager()
720
                );
721
                $action->process($layout, $request, $current_user);
722
                break;
723
            case 'check-user-can-link-and-unlink':
724
                $source_artifact      = (int)$request->get('from-artifact');
725
                $destination_artifact = (int)$request->get('to-artifact');
726
727
                if (! ($this->userHasRankingPermissions($source_artifact) && $this->userHasRankingPermissions($destination_artifact))) {
728
                    $this->sendUserDoesNotHavePermissionsErrorCode();
729
                }
730
                break;
731
            case 'unassociate-artifact-to':
732
                $artlink_fields     = $this->getFormElementFactory()->getUsedArtifactLinkFields($this->getTracker());
733
                $linked_artifact_id = $request->get('linked-artifact-id');
734
735
                if (! $this->userHasRankingPermissions($this->getId())) {
736
                    $this->sendUserDoesNotHavePermissionsErrorCode();
737
                    break;
738
                }
739
740
                if (count($artlink_fields)) {
741
                    $this->unlinkArtifact($artlink_fields, $linked_artifact_id, $current_user);
742
                    $this->summonArtifactAssociators($request, $current_user, $linked_artifact_id);
743
                } else {
744
                    $GLOBALS['Response']->addFeedback('error', $GLOBALS['Language']->getText('plugin_tracker', 'must_have_artifact_link_field'));
745
                    $GLOBALS['Response']->sendStatusCode(400);
746
                }
747
                break;
748
            case 'associate-artifact-to':
749
                $linked_artifact_id = $request->get('linked-artifact-id');
750
751
                if (! $this->userHasRankingPermissions($this->getId())) {
752
                    $this->sendUserDoesNotHavePermissionsErrorCode();
753
                    break;
754
                }
755
756
                if (!$this->linkArtifact($linked_artifact_id, $current_user)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->linkArtifact($lin...fact_id, $current_user) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
757
                    $GLOBALS['Response']->sendStatusCode(400);
758
                } else {
759
                    $this->summonArtifactAssociators($request, $current_user, $linked_artifact_id);
760
                }
761
                break;
762
            case 'higher-priority-than':
763
                $target_id    = (int)$request->get('target-id');
764
                $milestone_id = (int)$request->get('milestone-id');
765
                $project_id   = $this->getProjectId();
766
767
                $user_is_authorized = $this->userHasRankingPermissions($milestone_id);
768
769
                if (! $this->userHasRankingPermissions($milestone_id)) {
770
                    $this->sendUserDoesNotHavePermissionsErrorCode();
771
                    break;
772
                }
773
774
                $this->getPriorityManager()->moveArtifactBeforeWithHistoryChangeLogging($this->getId(), $target_id, $milestone_id, $project_id);
775
                break;
776
            case 'lesser-priority-than':
777
                $target_id    = (int)$request->get('target-id');
778
                $milestone_id = (int)$request->get('milestone-id');
779
                $project_id   = $this->getProjectId();
780
781
                if (! $this->userHasRankingPermissions($milestone_id)) {
782
                    $this->sendUserDoesNotHavePermissionsErrorCode();
783
                    break;
784
                }
785
786
                $this->getPriorityManager()->moveArtifactAfterWithHistoryChangeLogging($this->getId(), $target_id, $milestone_id, $project_id);
787
                break;
788
            case 'show-in-overlay':
789
                $renderer = new Tracker_Artifact_EditOverlayRenderer($this, $this->getEventManager());
790
                $renderer->display($request, $current_user);
791
                break;
792
            case 'get-new-changesets':
793
                $changeset_id = $request->getValidated('changeset_id', 'uint', 0);
794
                $changeset_factory = $this->getChangesetFactory();
795
                $GLOBALS['Response']->sendJSON($changeset_factory->getNewChangesetsFormattedForJson($this, $changeset_id));
796
                break;
797
            case 'edit':
798
                $GLOBALS['Response']->redirect($this->getUri());
799
                break;
800
            case 'get-edit-in-place':
801
                $renderer = new Tracker_Artifact_Renderer_EditInPlaceRenderer($this, $this->getMustacheRenderer());
802
                $renderer->display($current_user, $request);
803
                break;
804
            case 'update-in-place':
805
                $renderer = new Tracker_Artifact_Renderer_EditInPlaceRenderer($this, $this->getMustacheRenderer());
806
                $renderer->updateArtifact($request, $current_user);
807
                break;
808
            case 'copy-artifact':
809
                $art_link = $this->fetchDirectLinkToArtifact();
810
                $GLOBALS['Response']->addFeedback('info', $GLOBALS['Language']->getText('plugin_tracker_artifact', 'copy_mode_info', array($art_link)), CODENDI_PURIFIER_LIGHT);
811
812
                $renderer = new Tracker_Artifact_CopyRenderer($this->getEventManager(), $this, $this->getFormElementFactory(), $layout);
813
                $renderer->display($request, $current_user);
814
                break;
815
            case 'manage-subscription':
816
                $artifact_subscriber = new Tracker_ArtifactNotificationSubscriber($this, $this->getDao());
817
818
                if ($this->doesUserHaveUnsubscribedFromNotification($current_user)) {
819
                    $artifact_subscriber->subscribeUser($current_user, $request);
820
                    break;
821
                }
822
823
                $artifact_subscriber->unsubscribeUser($current_user, $request);
824
                break;
825
826
            default:
827
                if ($request->isAjax()) {
828
                    echo $this->fetchTooltip($current_user);
829
                } else {
830
                    $renderer = new Tracker_Artifact_ReadOnlyRenderer($this->getEventManager(), $this, $this->getFormElementFactory(), $layout);
831
                    $renderer->display($request, $current_user);
832
                }
833
                break;
834
        }
835
    }
836
837
    private function sendUserDoesNotHavePermissionsErrorCode() {
838
        $GLOBALS['Response']->addFeedback('error', $GLOBALS['Language']->getText('plugin_tracker', 'unsufficient_permissions_for_ranking'));
839
        $GLOBALS['Response']->sendStatusCode(403);
840
    }
841
842
    private function userHasRankingPermissions($milestone_id) {
843
        $user_is_authorized = true;
844
845
        $this->getEventManager()->processEvent(
846
            ITEM_PRIORITY_CHANGE,
847
            array(
848
                'user_is_authorized' => &$user_is_authorized,
849
                'group_id'           => $this->getProjectId(),
850
                'milestone_id'       => $milestone_id,
851
                'user'               => $this->getCurrentUser()
852
            )
853
        );
854
855
        return $user_is_authorized;
856
    }
857
858
    private function getProjectId() {
859
        return $this->getTracker()->getGroupId();
860
    }
861
862
    /** @return Tracker_Artifact_PriorityManager */
863
    protected function getPriorityManager() {
864
        return new Tracker_Artifact_PriorityManager(
865
            new Tracker_Artifact_PriorityDao(),
866
            new Tracker_Artifact_PriorityHistoryDao(),
867
            UserManager::instance(),
868
            Tracker_ArtifactFactory::instance()
869
        );
870
    }
871
872
    /** @return Tracker_Artifact[] */
873
    public function getChildrenForUser(PFUser $current_user) {
874
        $children = array();
875
        foreach ($this->getArtifactFactory()->getChildren($this) as $child) {
876
            if ($child->userCanView($current_user)) {
877
                $children[] = $child;
878
            }
879
        }
880
        return $children;
881
    }
882
883
    /** @return Tracker_ArtifactChildPresenter[] */
884
    private function getChildPresenterCollection(PFUser $current_user) {
885
        $presenters = array();
886
        foreach ($this->getChildrenForUser($current_user) as $child) {
887
            $tracker      = $child->getTracker();
888
            $semantics    = Tracker_Semantic_Status::load($tracker);
889
890
            $presenters[] = new Tracker_ArtifactChildPresenter($child, $this, $semantics);
891
        }
892
        return $presenters;
893
    }
894
895
    public function hasChildren() {
896
        return $this->getArtifactFactory()->hasChildren($this);
897
    }
898
899
    /**
900
     * @see Tracker_CardPresenter::getAccentColor()
901
     *
902
     * @return string
903
     */
904
    public function getCardAccentColor(PFUser $current_user) {
905
        $selectbox = $this->getFormElementFactory()->getSelectboxFieldByNameForUser(
906
            $this->getTrackerId(),
907
            Tracker::TYPE_FIELD_NAME,
908
            $current_user
909
        );
910
        if (! $selectbox) {
911
            return '';
912
        }
913
914
        return $selectbox->getCurrentDecoratorColor($this);
915
    }
916
917
    /**
918
     * @return string html
919
     */
920
    public function fetchDirectLinkToArtifact() {
921
        return '<a class="direct-link-to-artifact" href="'. $this->getUri() . '">' . $this->getXRef() . '</a>';
922
    }
923
924
    /**
925
     * @return string html
926
     */
927
    public function fetchDirectLinkToArtifactWithTitle() {
928
        return '<a class="direct-link-to-artifact" href="'. $this->getUri() . '">' . $this->getXRefAndTitle() . '</a>';
929
    }
930
931
    /**
932
     * @return string html
933
     */
934
    public function fetchDirectLinkToArtifactWithoutXRef() {
935
        $hp = Codendi_HTMLPurifier::instance();
936
        return '<a class="direct-link-to-artifact" href="'. $this->getUri() . '">' . $hp->purify($this->getTitle()) . '</a>';
937
    }
938
939
    public function getRestUri() {
940
        return self::REST_ROUTE . '/' . $this->getId();
941
    }
942
943
    /**
944
     * @return string
945
     */
946
    public function getUri() {
947
        return TRACKER_BASE_URL .'/?aid=' . $this->getId();
948
    }
949
950
    /**
951
     * @return string the cross reference text: bug #42
952
     */
953
    public function getXRef() {
954
        return $this->getTracker()->getItemName() . ' #' . $this->getId();
955
    }
956
957
    /**
958
     * Fetch the html xref link to the artifact
959
     *
960
     * @return string html
961
     */
962
    public function fetchXRefLink() {
963
        return '<a class="cross-reference" href="/goto?'. http_build_query(array(
964
            'key'      => $this->getTracker()->getItemName(),
965
            'val'      => $this->getId(),
966
            'group_id' => $this->getTracker()->getGroupId(),
967
        )) .'">'. $this->getXRef() .'</a>';
968
    }
969
970
    /**
971
     * Return the URL to use when you want to create a new artifact of $target_tracker type linked to current artifact
972
     *
973
     * @param Tracker $target_tracker
974
     * @return String
975
     */
976
    public function getSubmitNewArtifactLinkedToMeUri(Tracker $target_tracker) {
977
        return TRACKER_BASE_URL . '/?'.http_build_query(array(
978
            'tracker'   => $target_tracker->getId(),
979
            'func'      => 'new-artifact-link',
980
            'id'        => $this->getId(),
981
            'immediate' => 1,
982
        ));
983
    }
984
985
    /**
986
     * Returns a Tracker_FormElementFactory instance
987
     *
988
     * @return Tracker_FormElementFactory
989
     */
990
    protected function getFormElementFactory() {
991
        if (empty($this->formElementFactory)) {
992
            $this->formElementFactory = Tracker_FormElementFactory::instance();
993
        }
994
        return $this->formElementFactory;
995
    }
996
997
    public function setFormElementFactory(Tracker_FormElementFactory $factory) {
998
        $this->formElementFactory = $factory;
999
    }
1000
1001
    /**
1002
     * Returns a Tracker_ArtifactFactory instance
1003
     *
1004
     * @return Tracker_ArtifactFactory
1005
     */
1006
    protected function getArtifactFactory() {
1007
        if ($this->artifact_factory) {
1008
            return $this->artifact_factory;
1009
        }
1010
        return Tracker_ArtifactFactory::instance();
1011
    }
1012
1013
    public function setArtifactFactory(Tracker_ArtifactFactory $artifact_factory) {
1014
        $this->artifact_factory = $artifact_factory;
1015
    }
1016
1017
    /**
1018
     * Create the initial changeset of this artifact
1019
     *
1020
     * @param array   $fields_data The artifact fields values
1021
     * @param PFUser  $submitter   The user who did the artifact submission
1022
     * @param integer $submitted_on When the changeset is created
1023
     *
1024
     * @return int The Id of the initial changeset, or null if fields were not valid
1025
     */
1026
    public function createInitialChangeset($fields_data, $submitter, $submitted_on) {
1027
        $creator = new Tracker_Artifact_Changeset_InitialChangesetCreator(
1028
            new Tracker_Artifact_Changeset_InitialChangesetFieldsValidator($this->getFormElementFactory()),
1029
            $this->getFormElementFactory(),
1030
            $this->getChangesetDao(),
1031
            $this->getArtifactFactory(),
1032
            $this->getEventManager()
1033
        );
1034
1035
        return $creator->create($this, $fields_data, $submitter, $submitted_on);
1036
    }
1037
1038
    public function getErrors() {
1039
        $list_errors = array();
1040
        $is_valid = true;
1041
        $used_fields    = $this->getFormElementFactory()->getUsedFields($this->getTracker());
1042
        foreach ($used_fields as $field) {
1043
            if ($field->hasErrors()) {
1044
                $list_errors[] = $field->getId();
1045
            }
1046
        }
1047
        return $list_errors;
1048
    }
1049
1050
    /**
1051
     * Update an artifact (means create a new changeset)
1052
     *
1053
     * @param array   $fields_data       Artifact fields values
1054
     * @param string  $comment           The comment (follow-up) associated with the artifact update
1055
     * @param PFUser  $submitter         The user who is doing the update
1056
     * @param boolean $send_notification true if a notification must be sent, false otherwise
1057
     * @param string  $comment_format    The comment (follow-up) type ("text" | "html")
1058
     *
1059
     * @throws Tracker_Exception In the validation
1060
     * @throws Tracker_NoChangeException In the validation
1061
     * @return Tracker_Artifact_Changeset|Boolean The new changeset if update is done without error, false otherwise
1062
     */
1063
    public function createNewChangeset($fields_data, $comment, PFUser $submitter, $send_notification = true, $comment_format = Tracker_Artifact_Changeset_Comment::TEXT_COMMENT) {
1064
        $submitted_on = $_SERVER['REQUEST_TIME'];
1065
1066
        $creator = new Tracker_Artifact_Changeset_NewChangesetCreator(
1067
            new Tracker_Artifact_Changeset_NewChangesetFieldsValidator($this->getFormElementFactory()),
1068
            $this->getFormElementFactory(),
1069
            $this->getChangesetDao(),
1070
            $this->getChangesetCommentDao(),
1071
            $this->getArtifactFactory(),
1072
            $this->getEventManager(),
1073
            $this->getReferenceManager()
1074
        );
1075
        return $creator->create($this, $fields_data, $comment, $submitter, $submitted_on, $send_notification, $comment_format);
1076
    }
1077
1078
    /**
1079
     * @return ReferenceManager
1080
     */
1081
    public function getReferenceManager() {
1082
        return ReferenceManager::instance();
1083
    }
1084
1085
    /**
1086
     * Returns the tracker Id this artifact belongs to
1087
     *
1088
     * @return int The tracker Id this artifact belongs to
1089
     */
1090
    public function getTrackerId() {
1091
        return $this->tracker_id;
1092
    }
1093
1094
    /**
1095
     * Returns the tracker this artifact belongs to
1096
     *
1097
     * @return Tracker The tracker this artifact belongs to
1098
     */
1099
    public function getTracker() {
1100
        if (!isset($this->tracker)) {
1101
            $this->tracker = TrackerFactory::instance()->getTrackerByid($this->tracker_id);
1102
        }
1103
        return $this->tracker;
1104
    }
1105
1106
    public function setTracker(Tracker $tracker) {
1107
        $this->tracker = $tracker;
1108
        $this->tracker_id = $tracker->getId();
1109
    }
1110
1111
    /**
1112
     * Returns the last modified date of the artifact
1113
     *
1114
     * @return Integer The timestamp (-1 if no date)
1115
     */
1116
    public function getLastUpdateDate() {
1117
        $last_changeset = $this->getLastChangeset();
1118
        if ($last_changeset) {
1119
            return $last_changeset->getSubmittedOn();
1120
        }
1121
        return -1;
1122
    }
1123
1124
    public function wasLastModifiedByAnonymous() {
1125
        $last_changeset = $this->getLastChangeset();
1126
        if ($last_changeset) {
1127
            if ($last_changeset->getSubmittedBy()) {
1128
                return false;
1129
            }
1130
            return true;
1131
        }
1132
        return false;
1133
    }
1134
1135
    public function getLastModifiedBy() {
1136
        $last_changeset = $this->getLastChangeset();
1137
        if ($last_changeset) {
1138
            if ($last_changeset->getSubmittedBy()) {
1139
                return $last_changeset->getSubmittedBy();
1140
            }
1141
            return $last_changeset->getEmail();
1142
        }
1143
        return $this->getSubmittedBy();
1144
    }
1145
1146
    /**
1147
     * @return Integer
1148
     */
1149
    public function getVersionIdentifier() {
1150
        return $this->getLastUpdateDate();
1151
    }
1152
1153
    /**
1154
     * Returns the latest changeset of this artifact
1155
     *
1156
     * @return Tracker_Artifact_Changeset The latest changeset of this artifact, or null if no latest changeset
1157
     */
1158
    public function getLastChangeset() {
1159
        if ($this->changesets === null) {
1160
            return $this->getChangesetFactory()->getLastChangeset($this);
1161
        } else {
1162
            $changesets = $this->getChangesets();
1163
            return end($changesets);
1164
        }
1165
    }
1166
1167
    /**
1168
     * @return Tracker_Artifact_Changeset|null
1169
     */
1170
    public function getLastChangesetWithFieldValue(Tracker_FormElement_Field $field) {
1171
        return $this->getChangesetFactory()->getLastChangesetWithFieldValue($this, $field);
1172
    }
1173
1174
    /**
1175
     * Returns the first changeset of this artifact
1176
     *
1177
     * @return Tracker_Artifact_Changeset The first changeset of this artifact
1178
     */
1179
    public function getFirstChangeset() {
1180
        $changesets = $this->getChangesets();
1181
        reset($changesets);
1182
        list(,$c) = each($changesets);
1183
        return $c;
1184
    }
1185
1186
    /**
1187
     * say if the changeset is the first one for this artifact
1188
     *
1189
     * @return bool
1190
     */
1191
    public function isFirstChangeset(Tracker_Artifact_Changeset $changeset) {
1192
        $c = $this->getFirstChangeset();
1193
        return $c->getId() == $changeset->getId();
1194
    }
1195
1196
    private function getPriorityHistory() {
1197
        return $this->getPriorityManager()->getArtifactPriorityHistory($this);
1198
    }
1199
1200
    public function cmpFollowups($a, $b) {
1201
        return ($a->getFollowUpDate() < $b->getFollowUpDate()) ? -1 : 1;
1202
    }
1203
1204
    public function getFollowupsContent() {
1205
        $followups_content = $this->getChangesets();
1206
        $followups_content = array_merge($followups_content, $this->getPriorityHistory());
1207
1208
        usort($followups_content, array($this, "cmpFollowups"));
1209
1210
        return $followups_content;
1211
    }
1212
1213
    /**
1214
     * Returns all the changesets of this artifact
1215
     *
1216
     * @return Tracker_Artifact_Changeset[] The changesets of this artifact
1217
     */
1218
    public function getChangesets() {
1219
        if ($this->changesets === null) {
1220
            $this->forceFetchAllChangesets();
1221
        }
1222
        return $this->changesets;
1223
    }
1224
1225
    public function forceFetchAllChangesets() {
1226
        $this->changesets = $this->getChangesetFactory()->getChangesetsForArtifact($this);
1227
    }
1228
1229
    /**
1230
     * @param array $changesets array of Tracker_Artifact_Changeset
1231
     */
1232
    public function setChangesets(array $changesets) {
1233
        $this->changesets = $changesets;
1234
    }
1235
1236
    public function clearChangesets() {
1237
        $this->changesets = null;
1238
    }
1239
1240
    public function addChangeset(Tracker_Artifact_Changeset $changeset) {
1241
        $this->changesets[$changeset->getId()] = $changeset;
1242
    }
1243
1244
    /**
1245
     * Get all commentators of this artifact
1246
     *
1247
     * @return array of strings (username or emails)
1248
     */
1249
    public function getCommentators() {
1250
        $commentators = array();
1251
        foreach ($this->getChangesets() as $c) {
1252
            if ($submitted_by = $c->getSubmittedBy()) {
1253
                if ($user = $this->getUserManager()->getUserById($submitted_by)) {
1254
                    $commentators[] = $user->getUserName();
1255
                }
1256
            } else if ($email = $c->getEmail()) {
1257
                $commentators[] = $email;
1258
            }
1259
        }
1260
        return $commentators;
1261
    }
1262
1263
    /**
1264
     * Return the ChangesetDao
1265
     *
1266
     * @return Tracker_Artifact_ChangesetDao The Dao
1267
     */
1268
    protected function getChangesetDao() {
1269
        return new Tracker_Artifact_ChangesetDao();
1270
    }
1271
1272
    /**
1273
     * @return Tracker_Artifact_ChangesetFactory
1274
     */
1275
    protected function getChangesetFactory() {
1276
        return new Tracker_Artifact_ChangesetFactory(
1277
            $this->getChangesetDao(),
1278
            new Tracker_Artifact_ChangesetJsonFormatter(
1279
                $this->getMustacheRenderer()
1280
            )
1281
        );
1282
    }
1283
1284
    /**
1285
     * @return MustacheRenderer
1286
     */
1287
    private function getMustacheRenderer() {
1288
        return TemplateRendererFactory::build()->getRenderer(dirname(TRACKER_BASE_DIR).'/templates') ;
1289
    }
1290
1291
    /**
1292
     * Return the ChangesetCommentDao
1293
     *
1294
     * @return Tracker_Artifact_Changeset_CommentDao The Dao
1295
     */
1296
    protected function getChangesetCommentDao() {
1297
        return new Tracker_Artifact_Changeset_CommentDao();
1298
    }
1299
1300
    /**
1301
     * Returns the changeset of this artifact with Id $changeset_id, or null if not found
1302
     *
1303
     * @param int $changeset_id The Id of the changeset to retrieve
1304
     *
1305
     * @return Tracker_Artifact_Changeset The changeset, or null if not found
1306
     */
1307
    public function getChangeset($changeset_id) {
1308
        if (! isset($this->changesets[$changeset_id])) {
1309
            $this->changesets[$changeset_id] = $this->getChangesetFactory()->getChangeset($this, $changeset_id);
1310
        }
1311
        return $this->changesets[$changeset_id];
1312
    }
1313
1314
    /**
1315
     * Returns the previous changeset just before the changeset $changeset_id, or null if $changeset_id is the first one
1316
     *
1317
     * @param int $changeset_id The changeset reference
1318
     *
1319
     * @return Tracker_Artifact_Changeset The previous changeset, or null if not found
1320
     */
1321
    public function getPreviousChangeset($changeset_id) {
1322
        $previous = null;
1323
        $changesets = $this->getChangesets();
1324
        reset($changesets);
1325
        while ((list(,$changeset) = each($changesets)) && $changeset->id != $changeset_id) {
1326
            $previous = $changeset;
1327
        }
1328
        return $previous;
1329
    }
1330
1331
    public function exportCommentsToSOAP() {
1332
        $soap_comments = array();
1333
        foreach ($this->getChangesets() as $changeset) {
1334
            $changeset_comment = $changeset->exportCommentToSOAP();
1335
            if ($changeset_comment) {
1336
                $soap_comments[] = $changeset_comment;
1337
            }
1338
        }
1339
        return $soap_comments;
1340
    }
1341
1342
    public function exportHistoryToSOAP(PFUser $user) {
1343
        $soap_comments = array();
1344
        foreach ($this->getChangesets() as $changeset) {
1345
            $soap_comments[] = $changeset->getSoapValue($user);
1346
        }
1347
        return $soap_comments;
1348
    }
1349
1350
    /**
1351
     * Get the Id of this artifact
1352
     *
1353
     * @return int The Id of this artifact
1354
     */
1355
    public function getId() {
1356
        return $this->id;
1357
    }
1358
1359
    /**
1360
     * Set the Id of this artifact
1361
     *
1362
     * @param int $id the new id of the artifact
1363
     *
1364
     * @return Tracker_Artifact
1365
     */
1366
    public function setId($id) {
1367
        $this->id = $id;
1368
        return $this;
1369
    }
1370
1371
    /**
1372
     * Get the value for this field in the changeset
1373
     *
1374
     * @param Tracker_FormElement_Field  $field     The field
1375
     * @param Tracker_Artifact_Changeset $changeset The changeset. if null given take the last changeset of the artifact
1376
     *
1377
     * @return Tracker_Artifact_ChangesetValue | null
1378
     */
1379
    function getValue(Tracker_FormElement_Field $field, Tracker_Artifact_Changeset $changeset = null) {
1380
        if (!$changeset) {
1381
            $changeset = $this->getLastChangeset();
1382
        }
1383
        if ($changeset) {
1384
            return $changeset->getValue($field);
1385
        }
1386
        return null;
1387
    }
1388
1389
    /**
1390
     * Returns the date (timestamp) the artifact ha been created
1391
     *
1392
     * @return int the timestamp for the date this aetifact was created
1393
     */
1394
    function getSubmittedOn() {
1395
        return $this->submitted_on;
1396
    }
1397
1398
    /**
1399
     * Returns the user who submitted the artifact
1400
     *
1401
     * @return int the user id
1402
     */
1403
    function getSubmittedBy() {
1404
        return $this->submitted_by;
1405
    }
1406
1407
    /**
1408
     * The user who created the artifact
1409
     *
1410
     * @return PFUser
1411
     */
1412
    public function getSubmittedByUser() {
1413
        if (! isset($this->submitted_by_user)) {
1414
            $this->submitted_by_user = $this->getUserManager()->getUserById($this->submitted_by);
1415
        }
1416
        return $this->submitted_by_user;
1417
    }
1418
1419
    public function setSubmittedByUser(PFUser $user) {
1420
        $this->submitted_by_user = $user;
1421
        $this->submitted_by      = $user->getId();
1422
    }
1423
1424
    /**
1425
     * Returns the id of the artifact in this tracker
1426
     *
1427
     * @return int the artifact id
1428
     */
1429
    public function getPerTrackerArtifactId() {
1430
        if ($this->per_tracker_id == null) {
1431
            $this->per_tracker_id = $this->getDao()->getPerTrackerArtifactId($this->id);
1432
        }
1433
        return $this->per_tracker_id;
1434
    }
1435
1436
    /**
1437
     * Returns ids of user who unsubscribed to artifact notification
1438
     *
1439
     * @return array
1440
     */
1441
    public function getUnsubscribersIds() {
1442
        $unsubscribers_ids = array();
1443
        foreach ($this->getDao()->getUnsubscribersIds($this->id) as $row) {
0 ignored issues
show
Bug introduced by
The expression $this->getDao()->getUnsubscribersIds($this->id) of type false|object is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1444
            $unsubscribers_ids[] = $row['user_id'];
1445
        }
1446
        return $unsubscribers_ids;
1447
    }
1448
1449
    /**
1450
     * Return Workflow the artifact should respect
1451
     *
1452
     * @return Workflow
1453
     */
1454
    public function getWorkflow() {
1455
        $workflow = $this->getTracker()->getWorkflow();
1456
        $workflow->setArtifact($this);
1457
        return $workflow;
1458
    }
1459
1460
    /**
1461
     * Get the UserManager instance
1462
     *
1463
     * @return UserManager
1464
     */
1465
    public function getUserManager() {
1466
        return UserManager::instance();
1467
    }
1468
1469
    private function getCurrentUser() {
1470
        return $this->getUserManager()->getCurrentUser();
1471
    }
1472
1473
    /**
1474
     * Get the ProjectManager instance
1475
     *
1476
     * @return ProjectManager
1477
     */
1478
    private function getProjectManager() {
1479
        return ProjectManager::instance();
1480
    }
1481
1482
    /**
1483
     * User want to link an artifact to the current one
1484
     *
1485
     * @param int  $linked_artifact_id The id of the artifact to link
1486
     * @param PFUser $current_user       The user who made the link
1487
     *
1488
     * @return bool true if success false otherwise
1489
     */
1490
    public function linkArtifact($linked_artifact_id, PFUser $current_user) {
1491
        $artlink_fields = $this->getFormElementFactory()->getUsedArtifactLinkFields($this->getTracker());
1492
        if (count($artlink_fields)) {
1493
            $comment       = '';
1494
            $artlink_field = $artlink_fields[0];
1495
            $fields_data   = array();
1496
            $fields_data[$artlink_field->getId()]['new_values'] = $linked_artifact_id;
1497
1498
            try {
1499
                $this->createNewChangeset($fields_data, $comment, $current_user);
1500
                return true;
1501
            } catch (Tracker_NoChangeException $e) {
1502
                $GLOBALS['Response']->addFeedback('info', $e->getMessage(), CODENDI_PURIFIER_LIGHT);
1503
                return false;
1504
            } catch (Tracker_Exception $e) {
1505
                $GLOBALS['Response']->addFeedback('error', $e->getMessage());
1506
                return false;
1507
            }
1508
        } else {
1509
            $GLOBALS['Response']->addFeedback('error', $GLOBALS['Language']->getText('plugin_tracker', 'must_have_artifact_link_field'));
1510
        }
1511
    }
1512
1513
    /**
1514
     * User want to link an artifact to the current one
1515
     *
1516
     * @param array  $linked_artifact_ids The ids of the artifacts to link
1517
     * @param PFUser $current_user       The user who made the link
1518
     *
1519
     * @return bool true if success false otherwise
1520
     */
1521
    public function linkArtifacts($linked_artifact_ids, PFUser $current_user) {
1522
        $linked_artifact_ids = implode(',', $linked_artifact_ids);
1523
1524
        return $this->linkArtifact($linked_artifact_ids, $current_user);
1525
    }
1526
1527
    private function unlinkArtifact($artlink_fields, $linked_artifact_id, PFUser $current_user) {
1528
        $comment       = '';
1529
        $artlink_field = $artlink_fields[0];
1530
        $fields_data   = array();
1531
        $fields_data[$artlink_field->getId()]['new_values'] = '';
1532
        $fields_data[$artlink_field->getId()]['removed_values'] = array($linked_artifact_id => 1);
1533
1534
        try {
1535
            $this->createNewChangeset($fields_data, $comment, $current_user);
1536
        } catch (Tracker_NoChangeException $e) {
1537
            $GLOBALS['Response']->addFeedback('info', $e->getMessage(), CODENDI_PURIFIER_LIGHT);
1538
        } catch (Tracker_Exception $e) {
1539
            $GLOBALS['Response']->addFeedback('error', $e->getMessage());
1540
        }
1541
    }
1542
1543
    /**
1544
     * Get artifacts linked to the current artifact
1545
     *
1546
     * @param PFUser $user The user who should see the artifacts
1547
     *
1548
     * @return Tracker_Artifact[]
1549
     */
1550
    public function getLinkedArtifacts(PFUser $user) {
1551
        $artifact_links      = array();
1552
        $artifact_link_field = $this->getAnArtifactLinkField($user);
1553
        if ($artifact_link_field) {
1554
            $artifact_links = $artifact_link_field->getLinkedArtifacts($this->getLastChangeset(), $user);
0 ignored issues
show
Bug introduced by
It seems like $this->getLastChangeset() can be null; however, getLinkedArtifacts() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1555
        }
1556
        return $artifact_links;
1557
    }
1558
1559
    /**
1560
     * Get artifacts linked to the current artifact
1561
     *
1562
     * @see Tracker_FormElement_Field_ArtifactLink::getSlicedLinkedArtifacts()
1563
     *
1564
     * @param PFUser $user   The user who should see the artifacts
1565
     * @param int    $limit  The number of artifact to fetch
1566
     * @param int    $offset The offset
1567
     *
1568
     * @return Tracker_Artifact_PaginatedArtifacts
1569
     */
1570
    public function getSlicedLinkedArtifacts(PFUser $user, $limit, $offset) {
1571
        $artifact_link_field = $this->getAnArtifactLinkField($user);
1572
        if (! $artifact_link_field) {
1573
            return new Tracker_Artifact_PaginatedArtifacts(array(), 0);
1574
        }
1575
1576
        return $artifact_link_field->getSlicedLinkedArtifacts($this->getLastChangeset(), $user, $limit, $offset);
0 ignored issues
show
Bug introduced by
It seems like $this->getLastChangeset() can be null; however, getSlicedLinkedArtifacts() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1577
    }
1578
1579
    /**
1580
     * Get artifacts linked to the current artifact and sub artifacts
1581
     *
1582
     * @param PFUser $user The user who should see the artifacts
1583
     *
1584
     * @return Array of Tracker_Artifact
1585
     */
1586
    public function getLinkedArtifactsOfHierarchy(PFUser $user) {
1587
        $artifact_links = $this->getLinkedArtifacts($user);
1588
        $allowed_trackers = $this->getAllowedChildrenTypes();
1589
        foreach ($artifact_links as $artifact_link) {
1590
            $tracker = $artifact_link->getTracker();
1591
            if (in_array($tracker, $allowed_trackers)) {
1592
                $sub_linked_artifacts = $artifact_link->getLinkedArtifactsOfHierarchy($user);
1593
                $artifact_links       = array_merge($artifact_links, $sub_linked_artifacts);
1594
            }
1595
        }
1596
        return $artifact_links;
1597
    }
1598
1599
    /**
1600
     * @return Tracker[]
1601
     */
1602
    public function getAllowedChildrenTypes() {
1603
        return $this->getHierarchyFactory()->getChildren($this->getTrackerId());
1604
    }
1605
1606
    /**
1607
     * @return Tracker[]
1608
     */
1609
    public function getAllowedChildrenTypesForUser(PFUser $user) {
1610
        $allowed_children = array();
1611
        foreach ($this->getAllowedChildrenTypes() as $tracker) {
1612
            if ($tracker->userCanSubmitArtifact($user)) {
1613
                $allowed_children[] = $tracker;
1614
            }
1615
        }
1616
        return $allowed_children;
1617
    }
1618
1619
    /**
1620
     * Get artifacts linked to the current artifact if
1621
     * they are not in children.
1622
     *
1623
     * @param PFUser $user The user who should see the artifacts
1624
     *
1625
     * @return Array of Tracker_Artifact
1626
     */
1627
    public function getUniqueLinkedArtifacts(PFUser $user) {
1628
        $sub_artifacts = $this->getLinkedArtifacts($user);
1629
        $grandchild_artifacts = array();
1630
        foreach ($sub_artifacts as $artifact) {
1631
            $grandchild_artifacts = array_merge($grandchild_artifacts, $artifact->getLinkedArtifactsOfHierarchy($user));
1632
        }
1633
        array_filter($grandchild_artifacts);
1634
        return array_diff($sub_artifacts, $grandchild_artifacts);
1635
    }
1636
1637
    public function __toString() {
1638
        return __CLASS__." #$this->id";
1639
    }
1640
1641
    /**
1642
     * Returns all ancestors of current artifact (from direct parent to oldest ancestor)
1643
     *
1644
     * @param PFUser $user
1645
     *
1646
     * @return Tracker_Artifact[]
1647
     */
1648
    public function getAllAncestors(PFUser $user) {
1649
        if (!isset($this->ancestors)) {
1650
            $this->ancestors = $this->getHierarchyFactory()->getAllAncestors($user, $this);
1651
        }
1652
        return $this->ancestors;
1653
    }
1654
1655
    public function setAllAncestors(array $ancestors) {
1656
        $this->ancestors = $ancestors;
1657
    }
1658
1659
    /**
1660
     * Return the parent artifact of current artifact if any
1661
     *
1662
     * @param PFUser $user
1663
     *
1664
     * @return Tracker_Artifact
1665
     */
1666
    public function getParent(PFUser $user) {
1667
        return $this->getHierarchyFactory()->getParentArtifact($user, $this);
1668
    }
1669
1670
    /**
1671
     * Get parent artifact regartheless if user can access it
1672
     *
1673
     * Note: even if there are several parents, only the first one is returned
1674
     *
1675
     * @return Tracker_Artifact|null
1676
     */
1677
    public function getParentWithoutPermissionChecking() {
1678
        if ($this->parent_without_permission_checking !== self::NO_PARENT && ! isset($this->parent_without_permission_checking)) {
1679
            $dar = $this->getDao()->getParents(array($this->getId()));
1680
            if ($dar && count($dar) == 1) {
1681
                $this->parent_without_permission_checking = $this->getArtifactFactory()->getInstanceFromRow($dar->current());
1682
            } else {
1683
                $this->parent_without_permission_checking = self::NO_PARENT;
1684
            }
1685
        }
1686
        if ($this->parent_without_permission_checking === self::NO_PARENT) {
1687
            return null;
1688
        }
1689
        return $this->parent_without_permission_checking;
1690
    }
1691
1692
    public function setParentWithoutPermissionChecking($parent) {
1693
        $this->parent_without_permission_checking = $parent;
1694
    }
1695
1696
    /**
1697
     * Get artifacts that share same parent that mine (sista & bro)
1698
     *
1699
     * @param PFUser $user
1700
     *
1701
     * @return Tracker_Artifact[]
1702
     */
1703
    public function getSiblings(PFUser $user) {
1704
        if (! isset($this->siblings)) {
1705
            $this->siblings = array();
1706
            foreach ($this->getSiblingsWithoutPermissionChecking() as $artifact) {
1707
                if ($artifact->userCanView($user)) {
1708
                    $this->siblings[] = $artifact;
1709
                }
1710
            }
1711
        }
1712
        return $this->siblings;
1713
    }
1714
1715
    public function setSiblings(array $artifacts) {
1716
        $this->siblings = $artifacts;
1717
    }
1718
1719
    /**
1720
     * Get all sista & bro regartheless if user can access them
1721
     *
1722
     * @return Tracker_Artifact[]
1723
     */
1724
    public function getSiblingsWithoutPermissionChecking() {
1725
        if (! isset($this->siblings_without_permission_checking)) {
1726
            $this->siblings_without_permission_checking = $this->getDao()->getSiblings($this->getId())->instanciateWith(array($this->getArtifactFactory(), 'getInstanceFromRow'));
1727
        }
1728
        return $this->siblings_without_permission_checking;
1729
    }
1730
1731
    public function setSiblingsWithoutPermissionChecking($siblings) {
1732
        $this->siblings_without_permission_checking = $siblings;
1733
    }
1734
1735
    /**
1736
     * Returns the previously injected factory (e.g. in tests), or a new
1737
     * instance (e.g. in production).
1738
     *
1739
     * @return Tracker_HierarchyFactory
1740
     */
1741
    public function getHierarchyFactory() {
1742
        if ($this->hierarchy_factory == null) {
1743
            $this->hierarchy_factory = Tracker_HierarchyFactory::instance();
1744
        }
1745
        return $this->hierarchy_factory;
1746
    }
1747
1748
1749
    public function setHierarchyFactory($hierarchy = null) {
1750
        $this->hierarchy_factory = $hierarchy;
1751
    }
1752
1753
    /**
1754
     * Returns the ids of the children of the tracker.
1755
     *
1756
     * @return array of int
1757
     */
1758
    protected function getChildTrackersIds() {
1759
        $children_trackers_ids = array();
1760
        $children_hierarchy_tracker = $this->getHierarchyFactory()->getChildren($this->getTrackerId());
1761
        foreach ($children_hierarchy_tracker as $tracker) {
1762
            $children_trackers_ids[] = $tracker->getId();
1763
        }
1764
        return $children_trackers_ids;
1765
    }
1766
1767
    /**
1768
     * Return the first (and only one) ArtifactLink field (if any)
1769
     *
1770
     * @return Tracker_FormElement_Field_ArtifactLink
1771
     */
1772
    public function getAnArtifactLinkField(PFUser $user) {
1773
        return $this->getFormElementFactory()->getAnArtifactLinkField($user, $this->getTracker());
1774
    }
1775
1776
    /**
1777
     * Return the first BurndownField (if any)
1778
     *
1779
     * @return Tracker_FormElement_Field_Burndown
1780
     */
1781
    public function getABurndownField(PFUser $user) {
1782
        return $this->getFormElementFactory()->getABurndownField($user, $this->getTracker());
1783
    }
1784
1785
    /**
1786
     * Invoke those we don't speak of which may want to redirect to a
1787
     * specific page after an update/creation of this artifact.
1788
     * If the summoning is not strong enough (or there is no listener) then
1789
     * nothing is done. Else the client is redirected and
1790
     * the script will die in agony!
1791
     *
1792
     * @param Codendi_Request $request The request
1793
     */
1794
    public function summonArtifactRedirectors(Codendi_Request $request, Tracker_Artifact_Redirect $redirect) {
1795
        $this->getEventManager()->processEvent(
1796
            TRACKER_EVENT_REDIRECT_AFTER_ARTIFACT_CREATION_OR_UPDATE,
1797
            array(
1798
                'request'  => $request,
1799
                'artifact' => $this,
1800
                'redirect' => $redirect
1801
            )
1802
        );
1803
    }
1804
1805
    private function summonArtifactAssociators(Codendi_Request $request, PFUser $current_user, $linked_artifact_id) {
1806
        $this->getEventManager()->processEvent(
1807
            TRACKER_EVENT_ARTIFACT_ASSOCIATION_EDITED,
1808
            array(
1809
                'artifact'             => $this,
1810
                'linked-artifact-id'   => $linked_artifact_id,
1811
                'request'              => $request,
1812
                'user'                 => $current_user,
1813
                'form_element_factory' => $this->getFormElementFactory(),
1814
            )
1815
        );
1816
    }
1817
1818
    public function delete(PFUser $user) {
1819
        $this->getDao()->startTransaction();
1820
        foreach($this->getChangesets() as $changeset) {
1821
            $changeset->delete($user);
1822
        }
1823
        $this->getPermissionsManager()->clearPermission(self::PERMISSION_ACCESS, $this->getId());
1824
        $this->getCrossReferenceManager()->deleteEntity($this->getId(), self::REFERENCE_NATURE, $this->getTracker()->getGroupId());
1825
        $this->getDao()->deleteArtifactLinkReference($this->getId());
1826
        // We do not keep trace of the history change here because it doesn't have any sense
1827
        $this->getPriorityManager()->deletePriority($this);
1828
        $this->getDao()->delete($this->getId());
1829
        $this->getDao()->commit();
1830
1831
        EventManager::instance()->processEvent(
1832
            TRACKER_EVENT_ARTIFACT_DELETE,
1833
            array(
1834
                'artifact' => $this,
1835
            )
1836
        );
1837
    }
1838
1839
    /**
1840
     * Return the authorised ugroups to see the artifact
1841
     *
1842
     * @return Array
1843
     */
1844
    private function getAuthorisedUgroups () {
1845
        $ugroups = array();
1846
        //Individual artifact permission
1847
        if ($this->useArtifactPermissions()) {
1848
            $rows = $this->permission_db_authorized_ugroups('PLUGIN_TRACKER_ARTIFACT_ACCESS');
1849
            if ( $rows !== false ) {
1850
                foreach ($rows as $row) {
1851
                    $ugroups[] = $row['ugroup_id'];
1852
                }
1853
            }
1854
        } else {
1855
            $permissions = $this->getTracker()->getAuthorizedUgroupsByPermissionType();
1856
            foreach ($permissions  as $permission => $ugroups) {
1857
                switch($permission) {
1858
                    case Tracker::PERMISSION_FULL:
1859
                    case Tracker::PERMISSION_SUBMITTER:
1860
                    case Tracker::PERMISSION_ASSIGNEE:
1861
                    case Tracker::PERMISSION_SUBMITTER_ONLY:
1862
                        foreach ($ugroups as $ugroup) {
1863
                            $ugroups[] = $ugroup['ugroup_id'];
1864
                        }
1865
                    break;
1866
                }
1867
            }
1868
        }
1869
        return $ugroups;
1870
    }
1871
1872
    /**
1873
     * Returns ugroups of an artifact in a human readable format
1874
     *
1875
     * @return array
1876
     */
1877
    public function exportPermissions() {
1878
        $project     = ProjectManager::instance()->getProject($this->getTracker()->getGroupId());
1879
        $literalizer = new UGroupLiteralizer();
1880
        $ugroupsId     = $this->getAuthorisedUgroups();
1881
        return $literalizer->ugroupIdsToString($ugroupsId, $project);
1882
    }
1883
1884
    protected function getDao() {
1885
        return new Tracker_ArtifactDao();
1886
    }
1887
1888
    protected function getPermissionsManager() {
1889
        return PermissionsManager::instance();
1890
    }
1891
1892
    protected function getCrossReferenceManager() {
1893
        return new CrossReferenceManager();
1894
    }
1895
1896
    protected function getCrossReferenceFactory() {
1897
        return new CrossReferenceFactory($this->getId(), self::REFERENCE_NATURE, $this->getTracker()->getGroupId());
1898
    }
1899
1900
    /**
1901
     * Get the cross references from/to this artifact.
1902
     *
1903
     * Note: the direction of cross references is not returned
1904
     *
1905
     * @return array of references info to be sent in soap format: array('ref' => ..., 'url' => ...)
1906
     */
1907
    public function getCrossReferencesSOAPValues() {
1908
         $soap_value = array();
1909
         $cross_reference_factory = $this->getCrossReferenceFactory();
1910
         $cross_reference_factory->fetchDatas();
1911
1912
         $cross_references = $cross_reference_factory->getFormattedCrossReferences();
1913
         foreach ($cross_references as $array_of_references_by_direction) {
1914
             foreach ($array_of_references_by_direction as $reference) {
1915
                $soap_value[] = array(
1916
                    'ref' => $reference['ref'],
1917
                    'url' => $reference['url'],
1918
                );
1919
             }
1920
         }
1921
         return $soap_value;
1922
    }
1923
1924
    public function getSoapValue(PFUser $user) {
1925
        $soap_artifact = array();
1926
        if ($this->userCanView($user)) {
1927
            $last_changeset = $this->getLastChangeset();
1928
1929
            $soap_artifact['artifact_id']      = $this->getId();
1930
            $soap_artifact['tracker_id']       = $this->getTrackerId();
1931
            $soap_artifact['submitted_by']     = $this->getSubmittedBy();
1932
            $soap_artifact['submitted_on']     = $this->getSubmittedOn();
1933
            $soap_artifact['cross_references'] = $this->getCrossReferencesSOAPValues();
1934
            $soap_artifact['last_update_date'] = $last_changeset->getSubmittedOn();
1935
1936
            $soap_artifact['value'] = array();
1937
            foreach ($this->getFormElementFactory()->getUsedFieldsForSoap($this->getTracker()) as $field) {
1938
                $value = $field->getSoapValue($user, $last_changeset);
0 ignored issues
show
Bug introduced by
It seems like $last_changeset defined by $this->getLastChangeset() on line 1927 can be null; however, Tracker_FormElement_Field::getSoapValue() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1939
                if ($value !== null) {
1940
                    $soap_artifact['value'][] = $value;
1941
                }
1942
            }
1943
        }
1944
        return $soap_artifact;
1945
    }
1946
1947
    /**
1948
     * Adds to $artifacts_node the xml export of the artifact.
1949
     */
1950
    public function exportToXML(
1951
        SimpleXMLElement $artifacts_node,
1952
        PFUser $user,
1953
        Tuleap\Project\XML\Export\ArchiveInterface $archive,
1954
        UserXMLExporter $user_xml_exporter
1955
    ) {
1956
        $children_collector     = new Tracker_XML_Exporter_NullChildrenCollector();
1957
        $file_path_xml_exporter = new Tracker_XML_Exporter_InArchiveFilePathXMLExporter();
1958
1959
        $artifact_xml_exporter = $this->getArtifactXMLExporter(
1960
            $children_collector,
1961
            $file_path_xml_exporter,
1962
            $user,
1963
            $user_xml_exporter
1964
        );
1965
1966
        $artifact_xml_exporter->exportFullHistory($artifacts_node, $this);
1967
1968
        $attachment_exporter = $this->getArtifactAttachmentExporter();
1969
        $attachment_exporter->exportAttachmentsInArchive($this, $archive);
1970
    }
1971
1972
    /**
1973
     * @return Tracker_XML_Exporter_ArtifactAttachmentExporter
1974
     */
1975
    private function getArtifactAttachmentExporter() {
1976
        return new Tracker_XML_Exporter_ArtifactAttachmentExporter($this->getFormElementFactory());
1977
    }
1978
1979
    private function getArtifactXMLExporter(
1980
        Tracker_XML_ChildrenCollector $children_collector,
1981
        Tracker_XML_Exporter_FilePathXMLExporter $file_path_xml_exporter,
1982
        PFUser $current_user,
1983
        UserXMLExporter $user_xml_exporter
1984
    ) {
1985
        $builder = new Tracker_XML_Exporter_ArtifactXMLExporterBuilder();
1986
1987
        return $builder->build($children_collector, $file_path_xml_exporter, $current_user, $user_xml_exporter);
1988
    }
1989
1990
    /** @return string */
1991
    public function getTokenBasedEmailAddress() {
1992
        return trackerPlugin::EMAILGATEWAY_TOKEN_ARTIFACT_UPDATE .'@' . $this->getEmailDomain();
1993
    }
1994
1995
    /** @return string */
1996
    public function getInsecureEmailAddress() {
1997
        return trackerPlugin::EMAILGATEWAY_INSECURE_ARTIFACT_UPDATE .'+'. $this->getId() .'@' . $this->getEmailDomain();
1998
    }
1999
2000
    private function getEmailDomain() {
2001
        $email_domain = ForgeConfig::get('sys_default_mail_domain');
2002
2003
        if (! $email_domain) {
2004
            $email_domain = ForgeConfig::get('sys_default_domain');
2005
        }
2006
2007
        return $email_domain;
2008
    }
2009
}
2010