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.

fetchMailArtifactValue()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4285
cc 3
eloc 5
nc 2
nop 4
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('common/valid/Rule.class.php');
22
require_once('common/include/Codendi_HTTPPurifier.class.php');
23
24
class Tracker_FormElement_Field_File extends Tracker_FormElement_Field {
25
    const SOAP_FAKE_FILE = 'soapfakefile';
26
    const SOAP_FAULT_INVALID_REQUEST_FORMAT = '3029';
27
28
    public function getCriteriaFrom($criteria) {
29
        //Only filter query if field  is used
30
        if($this->isUsed()) {
31
            //Only filter query if criteria is valuated
32
            if ($criteria_value = $this->getCriteriaValue($criteria)) {
33
                $a = 'A_'. $this->id;
34
                $b = 'B_'. $this->id;
35
                $c = 'C_'. $this->id;
36
37
                $da             = CodendiDataAccess::instance();
38
                $criteria_value = $da->quoteSmart("%$criteria_value%");
39
40
                return " INNER JOIN tracker_changeset_value AS $a ON ($a.changeset_id = c.id AND $a.field_id = $this->id )
41
                         INNER JOIN tracker_changeset_value_file AS $b ON ($b.changeset_value_id = $a.id)
42
                         INNER JOIN tracker_fileinfo AS $c ON (
43
                            $c.id = $b.fileinfo_id
44
                            AND (
45
                                $c.description LIKE ". $criteria_value ."
46
                                OR
47
                                $c.filename LIKE ". $criteria_value ."
48
                            )
49
                         ) ";
50
            }
51
        }
52
        return '';
53
    }
54
55
    public function getCriteriaWhere($criteria) {
56
        return '';
57
    }
58
59
    public function getQuerySelect() {
60
        $R1 = 'R1_'. $this->id;
61
        $R2 = 'R2_'. $this->id;
62
        return "$R2.fileinfo_id AS `". $this->name ."`";
63
    }
64
65
    public function getQueryFrom() {
66
        $R1 = 'R1_'. $this->id;
67
        $R2 = 'R2_'. $this->id;
68
69
        return "LEFT JOIN ( tracker_changeset_value AS $R1
70
                    INNER JOIN tracker_changeset_value_file AS $R2 ON ($R2.changeset_value_id = $R1.id)
71
                ) ON ($R1.changeset_id = c.id AND $R1.field_id = ". $this->id ." )";
72
    }
73
    /**
74
     * Get the "group by" statement to retrieve field values
75
     */
76
    public function getQueryGroupby() {
77
        $R1 = 'R1_'. $this->id;
78
        $R2 = 'R2_'. $this->id;
79
        return "$R2.fileinfo_id";
80
    }
81
82
    protected function getCriteriaDao() {
83
        return new Tracker_Report_Criteria_File_ValueDao();
84
    }
85
86
    public function fetchChangesetValue($artifact_id, $changeset_id, $value, $report=null, $from_aid = null) {
87
        $html = '';
88
        $submitter_needed = true;
89
        $html .= $this->fetchAllAttachment($artifact_id, $this->getChangesetValues($changeset_id), $submitter_needed, array());
90
        return $html;
91
    }
92
93
    /**
94
     * Display the field as a Changeset value.
95
     * Used in CSV data export.
96
     *
97
     * @param int $artifact_id the corresponding artifact id
98
     * @param int $changeset_id the corresponding changeset
99
     * @param mixed $value the value of the field
100
     *
101
     * @return string
102
     */
103
    public function fetchCSVChangesetValue($artifact_id, $changeset_id, $value, $report) {
104
        return $this->fetchAllAttachmentForCSV($artifact_id, $this->getChangesetValues($changeset_id));
105
    }
106
107
    public function fetchCriteriaValue($criteria) {
108
        $html = '<input type="text" name="criteria['. $this->id .']" id="tracker_report_criteria_'. $this->id .'" value="';
109
        if ($criteria_value = $this->getCriteriaValue($criteria)) {
110
            $hp = Codendi_HTMLPurifier::instance();
111
            $html .= $hp->purify($criteria_value, CODENDI_PURIFIER_CONVERT_HTML);
112
        }
113
        $html .= '" />';
114
        return $html;
115
    }
116
117
    /**
118
     * Fetch the value
119
     * @param mixed $value the value of the field
120
     * @return string
121
     */
122
    public function fetchRawValue($value) {
123
        return $value;
124
    }
125
126
    /**
127
     * Fetch the value in a specific changeset
128
     * @param Tracker_Artifact_Changeset $changeset
129
     * @return string
130
     */
131
    public function fetchRawValueFromChangeset($changeset) {
132
        $value = '';
133
        if ($v = $changeset->getValue($this)) {
134
            if (isset($v['value_id'])) {
135
                $v = array($v);
136
            }
137
            foreach ($v as $val) {
138
                if ($val['value_id'] != 100) {
139
                    if ($row = $this->getValueDao()->searchById($val['value_id'], $this->id)->getRow()) {
140
                        if ($value) {
141
                            $value .= ', ';
142
                        }
143
                        $value .= $row['filename'];
144
                    }
145
                }
146
            }
147
        }
148
        return $value;
149
    }
150
151
    protected function getValueDao() {
152
        return new Tracker_FormElement_Field_Value_FileDao();
153
    }
154
155
156
    /**
157
     * Fetch the html code to display the field value in artifact
158
     *
159
     * @param Tracker_Artifact                $artifact         The artifact
160
     * @param Tracker_Artifact_ChangesetValue $value            The actual value of the field
161
     * @param array                           $submitted_values The value already submitted by the user
162
     *
163
     * @return string
164
     */
165
    protected function fetchArtifactValue(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null, $submitted_values = array()) {
166
        $html             = '';
167
        $submitter_needed = true;
168
        $read_only        = false;
169
        $html .= $this->fetchAllAttachment($artifact->id, $value, $submitter_needed, $submitted_values, $read_only);
170
        $html .= $this->fetchSubmitValue();
171
        return $html;
172
    }
173
174
    public function fetchArtifactForOverlay(Tracker_Artifact $artifact) {
175
        return $this->fetchArtifactReadOnly($artifact);
176
    }
177
178
    public function fetchSubmitForOverlay($submitted_values) {
179
        return '';
180
    }
181
182
    public function fetchArtifactCopyMode(Tracker_Artifact $artifact) {
183
        $last_changeset = $artifact->getLastChangeset();
184
        if ($last_changeset) {
185
            $value = $last_changeset->getValue($this);
186
            return $this->fetchAllAttachmentTitleAndDescription($value);
187
        }
188
        return '';
189
    }
190
191
    /**
192
     * Fetch the html code to display the field value in Mail
193
     *
194
     * @param Tracker_Artifact                $artifact         The artifact
195
     * @param PFUser                          $user             The user who will receive the email
196
     * @param Tracker_Artifact_ChangesetValue $value            The actual value of the field
197
     * @param array                           $submitted_values The value already submitted by the user
0 ignored issues
show
Bug introduced by
There is no parameter named $submitted_values. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
198
     *
199
     * @return string
200
     */
201
    public function fetchMailArtifactValue(Tracker_Artifact $artifact, PFUser $user, Tracker_Artifact_ChangesetValue $value = null, $format='text') {
202
        if ( empty($value) || ! $value->getFiles()) {
203
            return '-';
204
        }
205
        $output = '';
206
        return $this->fetchMailAllAttachment($artifact->id, $value, $format);
207
    }
208
209
    /**
210
     * Fetch the html code to display the field value in artifact in read only mode
211
     *
212
     * @param Tracker_Artifact                $artifact The artifact
213
     * @param Tracker_Artifact_ChangesetValue $value    The actual value of the field
214
     *
215
     * @return string
216
     */
217
    public function fetchArtifactValueReadOnly(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null) {
218
        $html = '';
219
        $submitter_needed = true;
220
        $html .= $this->fetchAllAttachment($artifact->id, $value, $submitter_needed, array());
221
        return $html;
222
    }
223
224
    public function fetchArtifactValueWithEditionFormIfEditable(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null, $submitted_values = array()) {
225
        return $this->fetchArtifactValueReadOnly($artifact, $value) . $this->getHiddenArtifactValueForEdition($artifact, $value, $submitted_values);
226
    }
227
    /**
228
     * Fetch the html code to display the field value in new artifact submission form
229
     *
230
     * @return string html
231
     */
232
    protected function fetchSubmitValue() {
233
        $html = '';
234
        $html .= '<div class="add-attachement">';
235
        $html .= '<p>'. $GLOBALS['Language']->getText('plugin_tracker_formelement_admin','add_new_file') .'</p>';
236
        $html .= '<div class="tracker_artifact_add_attachment">';
237
        $html .= '<p>';
238
        $html .= '<input type="file" id="tracker_field_'. $this->id .'" name="artifact['. $this->id .'][][file]" />';
239
        $html .= '<label>'. $GLOBALS['Language']->getText('plugin_tracker_formelement_admin','add_new_file_description');
240
        $html .= '</label>';
241
        $html .= ' <input type="text" id="tracker_field_'. $this->id .'" name="artifact['. $this->id .'][][description]" />';
242
        $html .= '</p>';
243
        $html .= '</div>';
244
        $html .= '</div>';
245
        return $html;
246
    }
247
248
    /**
249
     * Fetch the html code to display the field value in new artifact submission form
250
     *
251
     * @return string html
252
     */
253
    protected function fetchSubmitValueMasschange() {
254
        return '';  // deactivate mass change for file fields (see issue described in rev #15855)
255
    }
256
257
    /**
258
     * Fetch the changes that has been made to this field in a followup
259
     *
260
     * @param Tracker_Artifact $artifact The artifact
261
     * @param array            $from     the value(s) *before*
262
     * @param array            $to       the value(s) *after*
263
     *
264
     * @return string html
265
     */
266
    public function fetchFollowUp($artifact, $from, $to) {
267
        $html = '';
268
        //Retrieve all the values for the changeset
269
        $to_values = $this->getChangesetValues($to['changeset_id']);
270
        foreach ($to_values as $key => $v) {
271
            if (!$v['has_changed']) {
272
                unset($to_values[$key]);
273
            }
274
        }
275
        if (count($to_values)) {
276
            $submitter_needed = false;
277
            $html .= 'Added: '. $this->fetchAllAttachment($artifact->id, $to_values, $submitter_needed, array());
278
        }
279
        return $html;
280
    }
281
282
    protected function fetchAllAttachment($artifact_id, $values, $submitter_needed, $submitted_values, $read_only = true) {
283
        $html = '';
284
        if (count($values)) {
285
            $hp = Codendi_HTMLPurifier::instance();
286
            $uh = UserHelper::instance();
287
            $added = array();
288
            foreach ($values as $fileinfo) {
289
                $query_link = $this->getFileHTMLUrl($fileinfo);
290
                $sanitized_description = $hp->purify($fileinfo->getDescription(), CODENDI_PURIFIER_CONVERT_HTML);
291
               
292
                $link_show = '<a href="'. $query_link .'"'.
293
                                 $this->getVisioningAttributeForLink($fileinfo, $read_only) .'
294
                                 title="'. $sanitized_description .'">';
295
296
                $add = '<div class="tracker_artifact_attachment">';
297
                if (!$read_only) {
298
                    $add .= $this->fetchDeleteCheckbox($fileinfo, $submitted_values);
299
                }
300
301
302
                $add .= '<div class="tracker_artifact_preview_attachment_hover">';
303
                if ($submitter_needed) {
304
                    $add .= '<div class="tracker_artifact_attachment_submitter">'. 'By '. $uh->getLinkOnUserFromUserId($fileinfo->getSubmittedBy()) .'</div>';
305
                }
306
                $add .= '<div class="tracker_artifact_attachment_size">('. $fileinfo->getHumanReadableFilesize() .')</div>';
307
                $add .= '<div>';
308
                $add .= $link_show . '<i class="icon-eye-open"></i></a>';
309
                $add .= '<a href="'. $query_link .'" download><i class="icon-download-alt"></i></a>';
310
                $add .= '</div>';
311
                $add .= '</div>';
312
313
                if ($fileinfo->isImage()) {
314
                    $query_add = $this->getFileHTMLPreviewUrl($fileinfo);
315
316
                    $add .= '<div class="tracker_artifact_preview_attachment image">';
317
                    $add .= '<div style="background-image: url(\''. $query_add .'\')"></div>';
318
                    $add .= '</div>';
319
                } else {
320
                    $add .= '<div class="tracker_artifact_preview_attachment"></div>';
321
                }
322
323
324
                $link_goto = '<a href="'. $query_link .'"'.
325
                                 'title="'. $sanitized_description .'">';
326
                $add .= '<div class="tracker_artifact_attachment_name">' . $link_goto . $hp->purify($fileinfo->getFilename(), CODENDI_PURIFIER_CONVERT_HTML) .'</a></div>';
327
328
                if ($sanitized_description) {
329
                    $add .= '<div class="tracker_artifact_attachment_description">' . $sanitized_description . '</div>';
330
                }
331
332
                $add .= '</div>';
333
                $added[] = $add;
334
            }
335
            $html .= implode('', $added);
336
        }
337
338
        if ($read_only && ! count($values)) {
339
            $html .= $this->getNoValueLabel();
340
        }
341
342
        return $html;
343
    }
344
345
    public function getFileHTMLUrl(Tracker_FileInfo $file_info) {
346
        $artifact = $this->getFileInfoFactory()->getArtifactByFileInfoId($file_info->getId());
0 ignored issues
show
Bug introduced by
It seems like $file_info->getId() targeting Tracker_FileInfo::getId() can also be of type boolean; however, Tracker_FileInfoFactory::getArtifactByFileInfoId() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
347
        if (!$artifact) {
348
            return;
349
        }
350
351
        return TRACKER_BASE_URL . '/?' . http_build_query(
352
            array(
353
                'aid'   => $artifact->getId(),
354
                'field' => $this->id,
355
                'func'  => 'show-attachment',
356
                'attachment' => $file_info->getId()
357
            )
358
        );
359
    }
360
361
    public function getFileHTMLPreviewUrl(Tracker_FileInfo $file_info) {
362
        if (! $file_info->isImage()) {
363
            return;
364
        }
365
366
        $artifact = $this->getFileInfoFactory()->getArtifactByFileInfoId($file_info->getId());
0 ignored issues
show
Bug introduced by
It seems like $file_info->getId() targeting Tracker_FileInfo::getId() can also be of type boolean; however, Tracker_FileInfoFactory::getArtifactByFileInfoId() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
367
368
        if (!$artifact) {
369
            return;
370
        }
371
372
        return TRACKER_BASE_URL . '/?' . http_build_query(
373
            array(
374
                'aid'   => $artifact->getId(),
375
                'field' => $this->id,
376
                'func'  => 'preview-attachment',
377
                'attachment' => $file_info->getId()
378
            )
379
        );
380
    }
381
382
    private function getVisioningAttributeForLink($fileinfo, $read_only) {
383
        if (! $fileinfo->isImage()) {
384
            return '';
385
        }
386
387
        if ($read_only) {
388
            return 'rel="lytebox['. $this->getId() .']"';
389
        }
390
391
        return 'data-rel="lytebox['. $this->getId() .']"';
392
    }
393
394
    private function fetchDeleteCheckbox(Tracker_FileInfo $fileinfo, $submitted_values) {
395
        $html = '';
396
        $html .= '<label class="pc_checkbox tracker_artifact_attachment_delete">';
397
        $checked = '';
398
        if (isset($submitted_values[0][$this->id]) && ! empty($submitted_values[0][$this->id]['delete']) && in_array($fileinfo->getId(), $submitted_values[0][$this->id]['delete'])) {
399
            $checked = 'checked="checked"';
400
        }
401
        $html .= '<input type="checkbox" name="artifact['. $this->id .'][delete][]" value="'. $fileinfo->getId() .'" title="delete" '. $checked .' />&nbsp;';
402
        $html .= '</label>';
403
        return $html;
404
    }
405
406
    protected function fetchAllAttachmentForCSV($artifact_id, $values) {
407
        $txt = '';
408
        if (count($values)) {
409
            $filenames = array();
410
            foreach ($values as $fileinfo) {
411
                $filenames[] = $fileinfo->getFilename();
412
            }
413
            $txt .= implode(',', $filenames);
414
        }
415
        return $txt;
416
    }
417
418
    protected function fetchAllAttachmentTitleAndDescription($values) {
419
        $html = '';
420
        if($values) {
421
            $purifier = Codendi_HTMLPurifier::instance();
422
            $html    .= '<div class="tracker-artifact-attachement-title-list tracker_artifact_field">';
423
            $html    .= '<div class="disabled_field">' . $GLOBALS['Language']->getText('plugin_tracker_artifact', 'formelement_attachment_copy') . '</div>';
424
            $html    .= '<ul>';
425
            foreach($values as $value) {
426
                $description = $value->getDescription();
427
428
                $html .= '<li>';
429
                $html .= '<span class="file-title">';
430
                $html .= $purifier->purify($value->getFileName());
431
                $html .= '</span>';
432
433
                if($description) {
434
                    $html .= '<span class="file-description">';
435
                    $html .= ' - ' . $purifier->purify($description);
436
                    $html .= '</span>';
437
                }
438
                $html .= '</li>';
439
            }
440
            $html .= '</ul>';
441
            $html .= '</div>';
442
        }
443
        return $html;
444
    }
445
446
    /**
447
     * Fetch all attachements for Mail output
448
     *
449
     * @param Integer $artifact_id The artifact Id
450
     * @param Array            $values     The actual value of the field
451
     * @param String            $format       The mail format
452
     *
453
     * @return String
454
     */
455
    protected function fetchMailAllAttachment($artifact_id, $values, $format) {
456
        $output = '';
457
        if (!count($values) ) {
458
            return '';
459
        }
460
461
        $uh = UserHelper::instance();
462
463
        $proto = ($GLOBALS['sys_force_ssl']) ? 'https' : 'http';
464
        $url = $proto .'://'. $GLOBALS['sys_default_domain'];
465
466
        if ($format == 'text') {
467
            foreach ($values as $fileinfo) {
468
                $query_link = $this->getFileHTMLUrl($fileinfo);
469
470
                $link = '<'.$url.$query_link.'>';
471
                $output .= $fileinfo->getDescription();
472
                $output .= ' | ';
473
                $output .= $fileinfo->getFilename();
474
                $output .= ' | ';
475
                $output .= $fileinfo->getHumanReadableFilesize();
476
                $output .= ' | ';
477
                $output .= $uh->getDisplayNameFromUserId( $fileinfo->getSubmittedBy() );
478
                $output .= PHP_EOL;
479
                $output .= $link;
480
                $output .= PHP_EOL;
481
            }
482
        } else {
483
            $hp = Codendi_HTMLPurifier::instance();
484
            $added = array();
485
            foreach ($values as $fileinfo) {
486
                $query_link = $this->getFileHTMLUrl($fileinfo);
487
                $sanitized_description = $hp->purify($fileinfo->getDescription(), CODENDI_PURIFIER_CONVERT_HTML);
488
                $link_show = '<a href="'.$url.$query_link .'"
489
                                 title="'. $sanitized_description .'">';
490
491
                $info = $link_show . $hp->purify($fileinfo->getFilename(), CODENDI_PURIFIER_CONVERT_HTML) .'</a>';
492
                $info .= ' ('. $fileinfo->getHumanReadableFilesize() .')';
493
494
                $add = '<div class="tracker_artifact_attachment">';
495
                $add .= '<table><tr><td>';
496
                $add .= $info;
497
                $add .= '</td></tr></table>';
498
                $add .= '</div>';
499
                $added[] = $add;
500
            }
501
            $output .= implode('', $added);
502
        }
503
        return $output;
504
    }
505
506
    protected $file_values_by_changeset;
507
508
    /**
509
     * @return array
510
     */
511
    protected function getChangesetValues($changeset_id) {
512
        $da = CodendiDataAccess::instance();
513
        if (!$this->file_values_by_changeset) {
514
            $this->file_values_by_changeset = array();
515
            $field_id     = $da->escapeInt($this->id);
516
            $sql = "SELECT c.changeset_id, c.has_changed, f.*
517
                    FROM tracker_fileinfo as f
518
                         INNER JOIN tracker_changeset_value_file AS vf on (f.id = vf.fileinfo_id)
519
                         INNER JOIN tracker_changeset_value AS c
520
                         ON ( vf.changeset_value_id = c.id
521
                          AND c.field_id = $field_id
522
                         )
523
                    ORDER BY f.id";
524
            $dao = new DataAccessObject();
525
            $values = array();
526
            foreach ($dao->retrieve($sql) as $row) {
0 ignored issues
show
Bug introduced by
The expression $dao->retrieve($sql) 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...
527
                $this->file_values_by_changeset[$row['changeset_id']][] = $this->getFileInfo($row['id'], $row);
528
            }
529
        }
530
        return isset($this->file_values_by_changeset[$changeset_id]) ? $this->file_values_by_changeset[$changeset_id] : array();
531
    }
532
533
    public function previewAttachment($attachment_id) {
534
        if ($fileinfo = Tracker_FileInfo::instance($this, $attachment_id)) {
535
            if ($fileinfo->isImage() && file_exists($fileinfo->getThumbnailPath())) {
536
                header('Content-type: '. $fileinfo->getFiletype());
537
                readfile($fileinfo->getThumbnailPath());
538
            }
539
        }
540
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method previewAttachment() 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...
541
    }
542
543
    public function showAttachment($attachment_id) {
544
        if ($fileinfo = Tracker_FileInfo::instance($this, $attachment_id)) {
545
            if ($fileinfo->fileExists()) {
546
                $http = Codendi_HTTPPurifier::instance();
547
                header('X-Content-Type-Options: nosniff');
548
                header('Content-Type: '.$http->purify($fileinfo->getFiletype()));
549
                header('Content-Length: '.$http->purify($fileinfo->getFilesize()));
550
                header('Content-Disposition: attachment; filename="'.$http->purify($fileinfo->getFilename()).'"');
551
                header('Content-Description: '. $http->purify($fileinfo->getDescription()));
552
                readfile($fileinfo->getPath());
553
            }
554
        }
555
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method showAttachment() 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...
556
    }
557
558
    public function getRootPath() {
559
        return ForgeConfig::get('sys_data_dir') .'/tracker/'. $this->getId();
560
    }
561
562
    /**
563
     * Display the html field in the admin ui
564
     *
565
     * @return string html
566
     */
567
    protected function fetchAdminFormElement() {
568
        $html = '';
569
        $html .= '<div>';
570
        $html .= '<p>'.$GLOBALS['Language']->getText('plugin_tracker_formelement_admin','add_new_file').'</p>';
571
        $html .= '<table class="tracker_artifact_add_attachment">';
572
        $html .= '<tr><td><label>'.$GLOBALS['Language']->getText('plugin_tracker_formelement_admin','add_new_file_description').'</label></td><td><label>'.$GLOBALS['Language']->getText('plugin_tracker_formelement_admin','add_new_file_file').'</label></td></tr>';
573
        $html .= '<tr><td><input type="text" id="tracker_field_'. $this->id .'" /></td>';
574
        $html .= '<td><input type="file" id="tracker_field_'. $this->id .'" /></td></tr>';
575
        $html .= '</table>';
576
        $html .= '</div>';
577
        return $html;
578
    }
579
580
    /**
581
     * @return the label of the field (mainly used in admin part)
582
     */
583
    public static function getFactoryLabel() {
584
        return $GLOBALS['Language']->getText('plugin_tracker_formelement_admin','file');
585
    }
586
587
    /**
588
     * @return the description of the field (mainly used in admin part)
589
     */
590
    public static function getFactoryDescription() {
591
        return $GLOBALS['Language']->getText('plugin_tracker_formelement_admin','file_description');
592
    }
593
594
    /**
595
     * @return the path to the icon
596
     */
597
    public static function getFactoryIconUseIt() {
598
        return $GLOBALS['HTML']->getImagePath('ic/attach.png');
599
    }
600
601
    /**
602
     * @return the path to the icon
603
     */
604
    public static function getFactoryIconCreate() {
605
        return $GLOBALS['HTML']->getImagePath('ic/attach--plus.png');
606
    }
607
608
    /**
609
     * Fetch the html code to display the field value in tooltip
610
     *
611
     * @param Tracker_Artifact            $artifact The artifact
612
     * @param Tracker_ChangesetValue_File $value    The changeset value of this field
613
     *
614
     * @return string The html code to display the field value in tooltip
615
     */
616
    protected function fetchTooltipValue(Tracker_Artifact $artifact, Tracker_Artifact_ChangesetValue $value = null) {
617
        $html = '';
618
        if ($value) {
619
            $files_info = $value->getFiles();
620
            if (count($files_info)) {
621
                $hp = Codendi_HTMLPurifier::instance();
622
623
                $added = array();
624
                foreach ($files_info as $file_info) {
625
                    $add = '';
626
627
                    if ($file_info->isImage()) {
628
                        $query = $this->getFileHTMLPreviewUrl($file_info);
629
                        $add .= '<img src="'.$query .'"
630
                                      alt="'.  $hp->purify($file_info->getDescription(), CODENDI_PURIFIER_CONVERT_HTML)  .'"
631
                                      style="vertical-align:middle;" />';
632
                    } else if ($file_info->getDescription()) {
633
                        $add .= $hp->purify($file_info->getDescription(), CODENDI_PURIFIER_CONVERT_HTML);
634
                    } else {
635
                        $add .= $hp->purify($file_info->getFilename(), CODENDI_PURIFIER_CONVERT_HTML);
636
                    }
637
                    $added[] = $add;
638
                }
639
                $html .= implode('<br />', $added);
640
            }
641
        }
642
        return $html;
643
    }
644
645
    /**
646
     * Validate a value
647
     *
648
     * @param Tracker_Artifact $artifact The artifact
649
     * @param mixed            $value    data coming from the request.
650
     *
651
     * @return bool true if the value is considered ok
652
     */
653
    protected function validate(Tracker_Artifact $artifact, $value) {
654
        return true;
655
    }
656
657
    /**
658
     * Say if the value is valid. If not valid set the internal has_error to true.
659
     *
660
     * @param Tracker_Artifact $artifact The artifact
661
     * @param mixed            $value    data coming from the request. May be string or array.
662
     *
663
     * @return bool true if the value is considered ok
664
     */
665
    public function isValid(Tracker_Artifact $artifact, $value) {
666
        $this->has_errors = false;
667
668
        if (is_array($value)) {
669
            $this->checkAllFilesHaveBeenSuccessfullyUploaded($value);
670
        }
671
672
        return !$this->has_errors;
673
    }
674
675
    private function checkAllFilesHaveBeenSuccessfullyUploaded($value) {
676
        $r = new Rule_File();
677
        foreach($value as $i => $attachment) {
678
            //is delete or no description and no file uploaded => ignore it
679
            if ( "$i" != 'delete' && ((!empty($attachment['error']) && $attachment['error'] != UPLOAD_ERR_NO_FILE) || trim($attachment['description']))) {
680
                if (!$r->isValid($attachment)) {
681
                    $this->has_errors = true;
682
                    $GLOBALS['Response']->addFeedback('error', $this->getLabel() .' #'. $i .' has error: '. $r->getErrorMessage());
683
                }
684
            }
685
        }
686
    }
687
688
    /**
689
     * Validate a required field
690
     *
691
     * @param Tracker_Artifact                $artifact             The artifact to check
692
     * @param mixed                           $value      The submitted value
693
     *
694
     * @return boolean true on success or false on failure
695
     */
696
    public function isValidRegardingRequiredProperty(Tracker_Artifact $artifact, $value) {
697
        $this->has_errors = false;
698
699
        if (is_array($value) &&
700
            $this->isRequired() &&
701
            ! $this->checkThatAtLeastOneFileIsUploaded($value) &&
702
            $this->isPreviousChangesetEmpty($artifact, $value)) {
703
            $this->addRequiredError();
704
        }
705
706
        return ! $this->has_errors;
707
    }
708
709
    /**
710
     * Check that at least one file is sent
711
     *
712
     * @param array $files the files
713
     *
714
     * @return bool true if success
715
     */
716
    public function checkThatAtLeastOneFileIsUploaded($files) {
717
        $r = new Rule_File();
718
        $a_file_is_sent = false;
719
        reset($files);
720
        while (!$a_file_is_sent && (list($action, $attachment) = each($files))) {
721
            if ("$action" != 'delete') {
722
                $a_file_is_sent = $r->isValid($attachment);
723
            }
724
        }
725
        return $a_file_is_sent;
726
    }
727
728
    /**
729
     * Extract data from request
730
     * Some fields like files doesn't have their value submitted in POST or GET
731
     * Let them populate $fields_data[field_id] if needed
732
     *
733
     * @param array &$fields_data The user submitted value
734
     *
735
     * @return void
736
     */
737
    public function augmentDataFromRequest(&$fields_data) {
738
        if (!isset($fields_data[$this->getId()]) || !is_array($fields_data[$this->getId()])) {
739
            $fields_data[$this->getId()] = array();
740
        }
741
        $files_infos = $this->getSubmittedInfoFromFILES();
742
        if (isset($files_infos['name'][$this->getId()])) {
743
            $info_keys = array_keys($files_infos); //name, type, error, ...
744
            $nb = count($files_infos['name'][$this->getId()]);
745
            for ($i = 0 ; $i < $nb ; ++$i) {
746
                $tab = array();
747
                foreach ($info_keys as $key) {
748
                    $tab[$key] = $files_infos[$key][$this->getId()][$i]['file'];
749
                }
750
                if (isset($fields_data[$this->getId()][$i])) {
751
                    $fields_data[$this->getId()][$i] = array_merge($fields_data[$this->getId()][$i], $tab);
752
                } else {
753
                    $fields_data[$this->getId()][] = $tab;
754
                }
755
            }
756
        }
757
    }
758
759
    /**
760
     * Get the array wich contains files submitted by the user
761
     *
762
     * @return array or null if not found
763
     */
764
    protected function getSubmittedInfoFromFILES() {
765
        return isset($_FILES['artifact']) ? $_FILES['artifact'] : null;
766
    }
767
768
    protected $files_info_from_request = null;
769
    /**
770
     * Extract the file information (name, error, tmp, ...) from the request
771
     *
772
     * @return array Array of file info
773
     */
774
    protected function extractFilesFromRequest() {
775
        if (!$this->files_info_from_request) {
776
        }
777
        return $this->files_info_from_request;
778
    }
779
780
    /**
781
     * Save the value and return the id
782
     *
783
     * @param Tracker_Artifact                $artifact                The artifact
784
     * @param int                             $changeset_value_id      The id of the changeset_value
785
     * @param mixed                           $value                   The value submitted by the user
786
     * @param Tracker_Artifact_ChangesetValue $previous_changesetvalue The data previously stored in the db
787
     *
788
     * @return boolean
789
     */
790
    protected function saveValue($artifact, $changeset_value_id, $value, Tracker_Artifact_ChangesetValue $previous_changesetvalue = null) {
791
        $save_ok = true;
792
793
        $success = array();
794
        $dao = $this->getValueDao();
795
        //first save the previous files
796
        if ($previous_changesetvalue) {
797
            $previous_fileinfo_ids = array();
798
            foreach($previous_changesetvalue as $previous_attachment) {
0 ignored issues
show
Bug introduced by
The expression $previous_changesetvalue of type object<Tracker_Artifact_ChangesetValue> is not traversable.
Loading history...
799
                if (empty($value['delete']) || !in_array($previous_attachment->getId(), $value['delete'])) {
800
                    $previous_fileinfo_ids[] = $previous_attachment->getId();
801
                }
802
            }
803
            if (count($previous_fileinfo_ids)) {
804
                $save_ok = $save_ok && $dao->create($changeset_value_id, $previous_fileinfo_ids);
805
            }
806
        }
807
808
        //Now save the new submitted files
809
        $current_user = UserManager::instance()->getCurrentUser();
810
        $r = new Rule_File();
811
        foreach ($value as $i => $file_info) {
812
            if ("$i" != 'delete' && $r->isValid($file_info)) {
813
                $temporary = new Tracker_Artifact_Attachment_TemporaryFileManager(
814
                    $this->getCurrentUser(),
815
                    $this->getTemporaryFileManagerDao(),
816
                    $this->getFileInfoFactory()
817
                );
818
819
                if (isset($file_info['id'])) {
820
                    $temporary_file = $temporary->getFileByTemporaryName($file_info['id']);
821
                }
822
823
                if (isset($temporary_file)) {
824
                    $attachment = new Tracker_FileInfo(
825
                        $temporary_file->getId(),
826
                        $this,
827
                        $current_user->getId(),
828
                        trim($temporary_file->getDescription()),
829
                        $temporary_file->getName(),
830
                        $temporary_file->getSize(),
831
                        $temporary_file->getType()
832
                    );
833
834
                    if ($this->createAttachmentForRest($attachment, $file_info)) {
835
                        $success[] = $attachment->getId();
836
                    }
837
838
                } else {
839
                    $submitted_by = $current_user;
840
                    if (isset($file_info['submitted_by'])) {
841
                        $submitted_by = $file_info['submitted_by'];
842
                    }
843
                    $attachment = new Tracker_FileInfo(
844
                        null,
845
                        $this,
846
                        $submitted_by->getId(),
847
                        trim($file_info['description']),
848
                        $file_info['name'],
849
                        $file_info['size'],
850
                        $file_info['type']
851
                    );
852
853
                    if ($this->createAttachment($attachment, $file_info)) {
854
                        $success[] = $attachment->getId();
855
                    }
856
                }
857
            }
858
        }
859
860
        if (count($success)) {
861
            $save_ok = $save_ok && $dao->create($changeset_value_id, $success);
862
        }
863
        return $save_ok;
864
    }
865
866
    protected function createAttachment(Tracker_FileInfo $attachment, $file_info) {
867
        if ($attachment->save()) {
868
            $path = $this->getRootPath();
869
            if (!is_dir($path .'/thumbnails')) {
870
                mkdir($path .'/thumbnails', 0777, true);
871
            }
872
            $method   = 'move_uploaded_file';
873
            $tmp_name = $file_info['tmp_name'];
874
875
            if(isset($file_info['id'])) {
876
                $temporary = new Tracker_SOAP_TemporaryFile($this->getCurrentUser(), $file_info['id']);
877
878
                if (!$temporary->exists()) {
879
                    $attachment->delete();
880
                    return false;
881
                }
882
883
                $method   = 'rename';
884
                $tmp_name = $temporary->getPath();
885
886
            } else if ($this->isImportOfArtifact($file_info)) {
887
                $method   = 'copy';
888
            }
889
890
            return $this->moveAttachmentToFinalPlace($attachment, $method, $tmp_name);
891
        }
892
        return false;
893
    }
894
895
    private function isImportOfArtifact(array $file_info) {
896
        return isset($file_info[Tracker_Artifact_XMLImport_XMLImportFieldStrategyAttachment::FILE_INFO_COPY_OPTION]) &&
897
            $file_info[Tracker_Artifact_XMLImport_XMLImportFieldStrategyAttachment::FILE_INFO_COPY_OPTION];
898
    }
899
900
    protected function createAttachmentForRest(Tracker_FileInfo $attachment, $file_info) {
901
        $path = $this->getRootPath();
902
        if (!is_dir($path .'/thumbnails')) {
903
            mkdir($path .'/thumbnails', 0777, true);
904
        }
905
        $method   = 'move_uploaded_file';
906
        $tmp_name = $file_info['tmp_name'];
907
908
        if(isset($file_info['id'])) {
909
            $filename  = $file_info['id'];
910
            $temporary = new Tracker_Artifact_Attachment_TemporaryFileManager(
911
                $this->getCurrentUser(),
912
                $this->getTemporaryFileManagerDao(),
913
                $this->getFileInfoFactory()
914
            );
915
916
            if (!$temporary->exists($filename)) {
917
                $attachment->delete();
918
                return false;
919
            }
920
921
            $method   = 'rename';
922
            $tmp_name = $temporary->getPath($filename);
923
924
            $temporary->removeTemporaryFileInDBByTemporaryName($filename);
925
926
        }
927
928
        return $this->moveAttachmentToFinalPlace($attachment, $method, $tmp_name);
929
    }
930
931
    private function moveAttachmentToFinalPlace(Tracker_FileInfo $attachment, $method, $src_path) {
932
        if ($method($src_path, $attachment->getPath())) {
933
            $attachment->postUploadActions();
934
            return true;
935
        } else {
936
            $attachment->delete();
937
            return false;
938
        }
939
    }
940
941
    /**
942
     * Check if there are changes between old and new value for this field
943
     *
944
     * @param Tracker_Artifact_ChangesetValue $old_value The data stored in the db
945
     * @param mixed                           $new_value May be string or array
946
     *
947
     * @return bool true if there are differences
948
     */
949
    public function hasChanges(Tracker_Artifact_ChangesetValue $old_value, $new_value) {
950
        //"old" and "new" value are irrelevant in this context.
951
        //We just have to know if there is at least one file successfully uploaded
952
        return $this->checkThatAtLeastOneFileIsUploaded($new_value) || !empty($new_value['delete']);
953
    }
954
955
    /**
956
     * Tells if the field takes two columns
957
     * Ugly legacy hack to display fields in columns
958
     *
959
     * @return boolean
960
     */
961
    public function takesTwoColumns() {
962
        return true;
963
    }
964
965
    /**
966
     * Get the value of this field
967
     *
968
     * @param Tracker_Artifact_Changeset $changeset   The changeset (needed in only few cases like 'lud' field)
969
     * @param int                        $value_id    The id of the value
970
     * @param boolean                    $has_changed If the changeset value has changed from the rpevious one
971
     *
972
     * @return Tracker_Artifact_ChangesetValue or null if not found
973
     */
974
    public function getChangesetValue($changeset, $value_id, $has_changed) {
975
        $changeset_value = null;
976
977
        $files = array();
978
        $file_value = $this->getValueDao()->searchById($value_id, $this->id);
979
        foreach ($file_value as $row) {
0 ignored issues
show
Bug introduced by
The expression $file_value 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...
980
            if ($fileinfo_row = $this->getFileInfoDao()->searchById($row['fileinfo_id'])->getRow()) {
981
                $files[] = $this->getFileInfo($fileinfo_row['id'], $fileinfo_row);
982
            }
983
        }
984
        $changeset_value = new Tracker_Artifact_ChangesetValue_File($value_id, $this, $has_changed, $files);
985
        return $changeset_value;
986
    }
987
988
    /**
989
     * @return Tracker_FileInfo
990
     */
991
    protected function getFileInfo($id, $row) {
992
        return Tracker_FileInfo::instance($this, $id, $row);
993
    }
994
995
    /**
996
     * Get the file dao
997
     *
998
     * @return Tracker_FileInfoDao
999
     */
1000
    protected function getFileInfoDao() {
1001
        return new Tracker_FileInfoDao();
1002
    }
1003
1004
    /**
1005
     * Get file info factory
1006
     *
1007
     * @return Tracker_FileInfoFactory
1008
     */
1009
    protected function getFileInfoFactory() {
1010
        return new Tracker_FileInfoFactory(
1011
            $this->getFileInfoDao(),
1012
            Tracker_FormElementFactory::instance(),
1013
            Tracker_ArtifactFactory::instance()
1014
        );
1015
    }
1016
1017
    /**
1018
     * Get available values of this field for SOAP usage
1019
     * Fields like int, float, date, string don't have available values
1020
     *
1021
     * @return mixed The values or null if there are no specific available values
1022
     */
1023
    public function getSoapAvailableValues() {
1024
        return null;
1025
    }
1026
1027
    /**
1028
     * Override default value as it's not possible to import a file via CSV
1029
     *
1030
     * @param type $csv_value
1031
     *
1032
     * @return array
1033
     */
1034
    public function getFieldDataFromCSVValue($csv_value) {
1035
        return array();
1036
    }
1037
1038
    public function getFieldDataFromSoapValue(stdClass $soap_value, Tracker_Artifact $artifact = null) {
1039
        return $this->getFieldData($soap_value->field_value);
1040
    }
1041
1042
    public function getFieldDataFromRESTValue(array $rest_value, Tracker_Artifact $artifact = null) {
1043
        //Transform array to object
1044
        $value = json_decode(json_encode($rest_value), FALSE);
1045
1046
        $this->validateDataFromREST($value);
1047
1048
        $file_manager = $this->getFileManager();
1049
        return $file_manager->buildFieldDataForREST($value, $artifact);
1050
    }
1051
1052
    public function getFieldDataFromRESTValueByField($value, Tracker_Artifact $artifact = null) {
1053
        throw new Tracker_FormElement_RESTValueByField_NotImplementedException();
1054
    }
1055
1056
    private function validateDataFromREST($data) {
1057
        if (! property_exists($data, 'value') || ! is_array($data->value)){
1058
            throw new Tracker_FormElement_InvalidFieldException('Invalid format for file field "'.$data->field_id.'". '
1059
                . ' Correct format is {"field_id" : 425, "value" : [457, 258]}');
1060
        }
1061
    }
1062
1063
    /**
1064
     * @return Tracker_Artifact_Attachment_TemporaryFileManager
1065
     */
1066
    private function getFileManager() {
1067
        return new Tracker_Artifact_Attachment_TemporaryFileManager(
1068
            UserManager::instance()->getCurrentUser(),
1069
            new Tracker_Artifact_Attachment_TemporaryFileManagerDao(),
1070
            $this->getTrackerFileInfoFactory()
1071
        );
1072
    }
1073
1074
    private function getTrackerFileInfoFactory() {
1075
        return new Tracker_FileInfoFactory(
1076
            new Tracker_FileInfoDao(),
1077
            Tracker_FormElementFactory::instance(),
1078
            Tracker_ArtifactFactory::instance()
1079
        );
1080
    }
1081
1082
    /**
1083
     * Get the field data for artifact submission
1084
     *
1085
     * @param string the soap field value
1086
     *
1087
     * @return String the field data corresponding to the soap_value for artifact submision
1088
     */
1089
    public function getFieldData($soap_value) {
1090
        if (!($soap_value instanceof stdClass)) {
1091
            throw new SoapFault(self::SOAP_FAULT_INVALID_REQUEST_FORMAT, "Invalid submitted value for file field");
1092
        }
1093
        if (!isset($soap_value->file_info) || !is_array($soap_value->file_info)) {
1094
            throw new SoapFault(self::SOAP_FAULT_INVALID_REQUEST_FORMAT, "A File FieldValue must have a 'file_info' array (ArrayOfFieldValueFileInfo)");
1095
        }
1096
        $field_data = array();
1097
        foreach ($soap_value->file_info as $fileinfo) {
1098
            if (!($fileinfo instanceof stdClass)) {
1099
                throw new SoapFault(self::SOAP_FAULT_INVALID_REQUEST_FORMAT, "Fileinfo must be an array of FieldValueFileInfo");
1100
            }
1101
            if (!(isset($fileinfo->description) && isset($fileinfo->filename) && isset($fileinfo->filetype) && isset($fileinfo->filesize))) {
1102
                throw new SoapFault(self::SOAP_FAULT_INVALID_REQUEST_FORMAT, "Fileinfo must be an array of FieldValueFileInfo");
1103
            }
1104
            if (!(isset($fileinfo->id) && $fileinfo->id)) {
1105
                throw new SoapFault(self::SOAP_FAULT_INVALID_REQUEST_FORMAT, "FieldValueFileInfo must have an id of a temporary file");
1106
            }
1107
            if (isset($fileinfo->action) && $fileinfo->action == 'delete') {
1108
                $field_data['delete'][] = $fileinfo->id;
1109
            } else {
1110
                $temporary_file = new Tracker_SOAP_TemporaryFile($this->getCurrentUser(), $fileinfo->id);
1111
                if (! $temporary_file->exists($fileinfo->id)) {
1112
                    throw new SoapFault(self::SOAP_FAULT_INVALID_REQUEST_FORMAT, "Invalid FieldValueFileInfo->id, file doesn't exist");
1113
                }
1114
                $field_data[] = array(
1115
                    'id'          => $fileinfo->id,
1116
                    'description' => $fileinfo->description,
1117
                    'name'        => $fileinfo->filename,
1118
                    'type'        => $fileinfo->filetype,
1119
                    'size'        => $fileinfo->filesize,
1120
                    'error'       => UPLOAD_ERR_OK,
1121
                    'tmp_name'    => $temporary_file->getPath($fileinfo->id),
1122
                );
1123
            }
1124
        }
1125
        return $field_data;
1126
    }
1127
1128
    protected function getTemporaryFileManagerDao() {
1129
        return new Tracker_Artifact_Attachment_TemporaryFileManagerDao();
1130
    }
1131
1132
    public function deleteChangesetValue($changeset_value_id) {
1133
        $values = $this->getChangesetValue(null, $changeset_value_id, false);
1134
        foreach($values as $fileinfo) {
1135
            $fileinfo->delete();
1136
        }
1137
        parent::deleteChangesetValue($changeset_value_id);
1138
    }
1139
1140
    public function accept(Tracker_FormElement_FieldVisitor $visitor) {
1141
        return $visitor->visitFile($this);
1142
    }
1143
1144
    public function isPreviousChangesetEmpty(Tracker_Artifact $artifact, $value) {
1145
        $last_changeset = $artifact->getLastChangeset();
1146
1147
        if ($last_changeset && count($last_changeset->getValue($this)->getFiles()) > 0) {
1148
            return $this->areAllFilesDeletedFromPreviousChangeset($last_changeset, $value);
1149
        }
1150
        return true;
1151
    }
1152
1153
    private function areAllFilesDeletedFromPreviousChangeset($last_changeset, $value) {
1154
        $files = $last_changeset->getValue($this)->getFiles();
1155
        if (isset($value['delete']) && (count($files) == count($value['delete']))) {
1156
            return true;
1157
        }
1158
        return false;
1159
    }
1160
1161
    public function isEmpty($value, $artifact) {
1162
        $is_empty = !$this->checkThatAtLeastOneFileIsUploaded($value);
1163
        if ($is_empty) {
1164
            $is_empty = $this->isPreviousChangesetEmpty($artifact, $value);
1165
        }
1166
        return $is_empty;
1167
    }
1168
}
1169