Passed
Push — master ( 66162c...998eef )
by y
01:39
created

Task::__construct()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 6
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 10
rs 10
1
<?php
2
3
namespace Helix\Asana;
4
5
use Helix\Asana\Base\AbstractEntity;
6
use Helix\Asana\Base\AbstractEntity\CrudTrait;
7
use Helix\Asana\Base\AbstractEntity\WorkspaceTrait;
8
use Helix\Asana\Event\StoryEvent;
9
use Helix\Asana\Event\TaskEvent;
10
use Helix\Asana\Project\Section;
11
use Helix\Asana\Task\Attachment;
12
use Helix\Asana\Task\CustomValues;
13
use Helix\Asana\Task\Like;
14
use Helix\Asana\Task\Membership;
15
use Helix\Asana\Task\Story;
16
use Helix\Asana\Webhook\TaskWebhook;
17
18
/**
19
 * A task.
20
 *
21
 * @see https://developers.asana.com/docs/#asana-tasks
22
 * @see https://developers.asana.com/docs/#tocS_Task
23
 *
24
 * @method null|User    getAssignee         ()
25
 * @method $this        setAssignee         (null|User $user)
26
 * @method string       getAssigneeStatus   ()
27
 * @method $this        setAssigneeStatus   (string $status)
28
 * @method bool         isCompleted         ()
29
 * @method $this        setCompleted        (bool $completed)
30
 * @method string       getCompletedAt      ()
31
 * @method string       getCreatedAt        ()
32
 * @method null|CustomValues getCustomFields() Premium only.
33
 * @method bool         hasCustomFields     () Premium only.
34
 * @method string       getDueOn            ()
35
 * @method $this        setDueOn            (string $date)
36
 * @method User[]       getFollowers        ()
37
 * @method string       getHtmlNotes        ()
38
 * @method $this        setHtmlNotes        (string $notes)
39
 * @method bool         isLiked             ()
40
 * @method $this        setLiked            (bool $liked)
41
 * @method Like[]       getLikes            ()
42
 * @method Membership[] getMemberships      ()
43
 * @method string       getModifiedAt       ()
44
 * @method string       getName             ()
45
 * @method $this        setName             (string $name)
46
 * @method string       getNotes            ()
47
 * @method $this        setNotes            (string $notes)
48
 * @method int          getNumLikes         ()
49
 * @method int          getNumSubtasks      ()
50
 * @method null|Task    getParent           ()
51
 * @method bool         hasParent           ()
52
 * @method string       getResourceSubtype  ()
53
 * @method $this        setResourceSubtype  (string $type)
54
 * @method string       getStartOn          ()
55
 * @method $this        setStartOn          (string $date)
56
 * @method Tag[]        getTags             ()
57
 */
58
class Task extends AbstractEntity {
59
60
    use CrudTrait {
61
        update as private _update;
62
    }
63
64
    use WorkspaceTrait;
65
66
    const TYPE = 'task';
67
    const TYPE_DEFAULT = 'default_task';
68
    const TYPE_MILESTONE = 'milestone';
69
70
    const ASSIGN_INBOX = 'inbox';
71
    const ASSIGN_LATER = 'later';
72
    const ASSIGN_NEW = 'new';
73
    const ASSIGN_TODAY = 'today';
74
    const ASSIGN_UPCOMING = 'upcoming';
75
76
    public function __construct ($caller, array $data = []) {
77
        parent::__construct($caller, $data);
78
79
        // Asana has a bug where it returns empty memberships when expanding "this".
80
        // If one is found, clear the whole list so it can be reloaded lazily.
81
        if (isset($this->data['memberships'])) {
82
            foreach ($this->getMemberships() as $membership) {
83
                if (!$membership->getProject()) {
84
                    unset($this->data['memberships']);
85
                    break;
86
                }
87
            }
88
        }
89
    }
90
91
    final public function __toString (): string {
92
        return "tasks/{$this->getGid()}";
93
    }
94
95
    final protected function _getDir (): string {
96
        return 'tasks';
97
    }
98
99
    protected function _getMap (): array {
100
        return [
101
            'assignee' => User::class,
102
            'custom_fields' => CustomValues::class,
103
            'followers' => [User::class],
104
            'likes' => [Like::class],
105
            'memberships' => [Membership::class],
106
            'parent' => self::class,
107
            'tags' => [Tag::class],
108
            'workspace' => Workspace::class
109
        ];
110
    }
111
112
    /**
113
     * Uploads a file attachment.
114
     *
115
     * @depends after-create
116
     * @param string $file
117
     * @return Attachment
118
     */
119
    public function addAttachment (string $file) {
120
        /** @var Attachment $attachment */
121
        $attachment = $this->factory(Attachment::class, ['parent' => $this]);
122
        return $attachment->upload($file);
123
    }
124
125
    /**
126
     * Posts a comment.
127
     *
128
     * @depends after-create
129
     * @param string $text
130
     * @return Story
131
     */
132
    public function addComment (string $text) {
133
        return $this->newComment()->setText($text)->create();
1 ignored issue
show
Unused Code introduced by
The call to Helix\Asana\Task\Story::setText() has too many arguments starting with $text. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

133
        return $this->newComment()->/** @scrutinizer ignore-call */ setText($text)->create();

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
134
    }
135
136
    /**
137
     * Premium feature.
138
     *
139
     * @depends after-create
140
     * @param Task[] $tasks
141
     * @return $this
142
     */
143
    public function addDependencies (array $tasks) {
144
        $this->api->post("{$this}/addDependencies", ['dependents' => static::_getGids($tasks)]);
145
        return $this;
146
    }
147
148
    /**
149
     * Premium feature.
150
     *
151
     * @depends after-create
152
     * @param Task $task
153
     * @return $this
154
     */
155
    public function addDependency (Task $task) {
156
        return $this->addDependencies([$task]);
157
    }
158
159
    /**
160
     * Premium feature.
161
     *
162
     * @depends after-create
163
     * @param Task $task
164
     * @return $this
165
     */
166
    public function addDependent (Task $task) {
167
        return $this->addDependents([$task]);
168
    }
169
170
    /**
171
     * Premium feature.
172
     *
173
     * @depends after-create
174
     * @param Task[] $tasks
175
     * @return $this
176
     */
177
    public function addDependents (array $tasks) {
178
        $this->api->post("{$this}/addDependents", ['dependents' => static::_getGids($tasks)]);
179
        return $this;
180
    }
181
182
    /**
183
     * Adds a follower.
184
     *
185
     * @param User $user
186
     * @return $this
187
     */
188
    public function addFollower (User $user) {
189
        return $this->addFollowers([$user]);
190
    }
191
192
    /**
193
     * Adds followers.
194
     *
195
     * @param User[] $users
196
     * @return $this
197
     */
198
    public function addFollowers (array $users) {
199
        if ($this->hasGid()) {
200
            $this->api->post("{$this}/addFollowers", ['followers' => static::_getGids($users)]);
201
            $this->_merge('followers', $users);
202
        }
203
        else {
204
            $this->_merge('followers', $users, true);
205
        }
206
        return $this;
207
    }
208
209
    /**
210
     * Adds a tag.
211
     *
212
     * @param Tag $tag
213
     * @return $this
214
     */
215
    public function addTag (Tag $tag) {
216
        if ($this->hasGid()) {
217
            $this->api->post("{$this}/addTag", ['tag' => $tag->getGid()]);
218
            $this->_merge('tags', [$tag]);
219
        }
220
        else {
221
            $this->_merge('tags', [$tag], true);
222
        }
223
        return $this;
224
    }
225
226
    /**
227
     * Adds the task to a project section.
228
     *
229
     * @param Section $section
230
     * @return $this
231
     */
232
    public function addToProject (Section $section) {
233
        $project = $section->getProject();
234
        if ($this->hasGid()) {
235
            $this->api->post("{$this}/addProject", [
236
                'project' => $project->getGid(),
237
                'section' => $section->getGid()
238
            ]);
239
            if (isset($this->data['memberships'])) {
240
                $this->data['memberships'][] = $this->factory(Membership::class)->_setData([
241
                    'project' => $project,
242
                    'section' => $section
243
                ]);
244
            }
245
        }
246
        else {
247
            if (!$this->hasWorkspace()) {
248
                $this->setWorkspace($project->getWorkspace());
1 ignored issue
show
Bug introduced by
It seems like $project->getWorkspace() can also be of type null; however, parameter $workspace of Helix\Asana\Task::setWorkspace() does only seem to accept Helix\Asana\Workspace, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

248
                $this->setWorkspace(/** @scrutinizer ignore-type */ $project->getWorkspace());
Loading history...
249
            }
250
            $membership = $this->factory(Membership::class);
251
            $membership->_set('project', $section->getProject());
252
            $membership->_set('section', $section);
253
            $this->data['memberships'][] = $membership;
254
            $this->diff['memberships'] = true;
255
        }
256
        return $this;
257
    }
258
259
    /**
260
     * Creates and returns a webhook.
261
     *
262
     * @depends after-create
263
     * @param string $target
264
     * @return TaskWebhook
265
     */
266
    public function addWebhook (string $target) {
267
        /** @var TaskWebhook $webhook */
268
        $webhook = $this->factory(TaskWebhook::class);
269
        return $webhook->create($this, $target);
270
    }
271
272
    /**
273
     * Creates and returns job to duplicate the task.
274
     *
275
     * @see https://developers.asana.com/docs/#duplicate-a-task
276
     *
277
     * @depends after-create
278
     * @param string $name
279
     * @param string[] $include
280
     * @return Job
281
     */
282
    public function duplicate (string $name, array $include) {
283
        $remote = $this->api->post("{$this}/duplicate", [
284
            'name' => $name,
285
            'include' => array_values($include)
286
        ]);
287
        return $this->factory(Job::class, $remote);
1 ignored issue
show
Bug introduced by
It seems like $remote can also be of type null; however, parameter $data of Helix\Asana\Base\Data::factory() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

287
        return $this->factory(Job::class, /** @scrutinizer ignore-type */ $remote);
Loading history...
288
    }
289
290
    /**
291
     * Returns the task's files.
292
     *
293
     * @depends after-create
294
     * @return Attachment[]
295
     */
296
    public function getAttachments () {
297
        return $this->loadAll(Attachment::class, "{$this}/attachments");
298
    }
299
300
    /**
301
     * Returns the task's comments.
302
     *
303
     * @depends after-create
304
     * @return Story[]
305
     */
306
    public function getComments () {
307
        return array_values(array_filter($this->getStories(), function(Story $story) {
308
            return $story->isComment();
309
        }));
310
    }
311
312
    /**
313
     * Premium feature.
314
     *
315
     * @depends after-create
316
     * @return Task[]
317
     */
318
    public function getDependencies () {
319
        return $this->loadAll(self::class, "{$this}/dependencies");
320
    }
321
322
    /**
323
     * Premium feature.
324
     *
325
     * @depends after-create
326
     * @return Task[]
327
     */
328
    public function getDependents () {
329
        return $this->loadAll(self::class, "{$this}/dependents");
330
    }
331
332
    /**
333
     * Returns events since the last sync.
334
     *
335
     * @depends after-create
336
     * @param null|string $token
337
     * @return TaskEvent[]|StoryEvent[]
338
     */
339
    public function getEvents (&$token) {
340
        return $this->api->sync($this, $token);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->api->sync($this, $token) returns an array which contains values of type Helix\Asana\Event\ProjectEvent which are incompatible with the documented value type Helix\Asana\Event\StoryE...x\Asana\Event\TaskEvent.
Loading history...
341
    }
342
343
    /**
344
     * @return Project[]
345
     */
346
    public function getProjects () {
347
        return array_map(function(Membership $membership) {
348
            return $membership->getProject();
349
        }, $this->getMemberships());
350
    }
351
352
    /**
353
     * Returns the task's activity.
354
     *
355
     * @depends after-create
356
     * @return Story[]
357
     */
358
    public function getStories () {
359
        return $this->loadAll(Story::class, "{$this}/stories");
360
    }
361
362
    /**
363
     * Returns the task's subtasks.
364
     *
365
     * @depends after-create
366
     * @return Task[]
367
     */
368
    public function getSubTasks () {
369
        return $this->loadAll(self::class, "{$this}/subtasks");
370
    }
371
372
    /**
373
     * Returns the task's URL.
374
     *
375
     * @depends after-create
376
     * @return string
377
     */
378
    public function getUrl (): string {
379
        return "https://app.asana.com/0/0/{$this->getGid()}";
380
    }
381
382
    /**
383
     * Returns the task's webhooks.
384
     *
385
     * @depends after-create
386
     * @return TaskWebhook[]
387
     */
388
    public function getWebhooks () {
389
        return $this->loadAll(TaskWebhook::class, 'webhooks', [
390
            'workspace' => $this->getWorkspace()->getGid(),
391
            'resource' => $this->getGid()
392
        ]);
393
    }
394
395
    /**
396
     * @return bool
397
     */
398
    public function isRenderedAsSeparator (): bool {
399
        return $this->_is('is_rendered_as_separator');
400
    }
401
402
    /**
403
     * Instantiates and returns a new comment.
404
     *
405
     * @depends after-create
406
     * @return Story
407
     */
408
    public function newComment () {
409
        /** @var Story $comment */
410
        $comment = $this->factory(Story::class, [
411
            'resource_subtype' => Story::TYPE_COMMENT_ADDED
412
        ]);
413
        return $comment->_set('task', $this);
414
    }
415
416
    /**
417
     * Instantiates and returns a new subtask.
418
     *
419
     * @depends after-create
420
     * @return Task
421
     */
422
    public function newSubTask () {
423
        /** @var Task $sub */
424
        $sub = $this->factory(self::class);
425
        return $sub->setParent($this);
426
    }
427
428
    /**
429
     * Premium feature.
430
     *
431
     * @depends after-create
432
     * @param Task[] $tasks
433
     * @return $this
434
     */
435
    public function removeDependencies (array $tasks) {
436
        $this->api->post("{$this}/removeDependencies", ['dependencies' => static::_getGids($tasks)]);
437
        return $this;
438
    }
439
440
    /**
441
     * Premium feature.
442
     *
443
     * @depends after-create
444
     * @param Task $task
445
     * @return $this
446
     */
447
    public function removeDependency (Task $task) {
448
        return $this->removeDependencies([$task]);
449
    }
450
451
    /**
452
     * Premium feature.
453
     *
454
     * @depends after-create
455
     * @param Task $task
456
     * @return $this
457
     */
458
    public function removeDependent (Task $task) {
459
        return $this->removeDependents([$task]);
460
    }
461
462
    /**
463
     * Premium feature.
464
     *
465
     * @depends after-create
466
     * @param Task[] $tasks
467
     * @return $this
468
     */
469
    public function removeDependents (array $tasks) {
470
        $this->api->post("{$this}/removeDependents", ['dependents' => static::_getGids($tasks)]);
471
        return $this;
472
    }
473
474
    /**
475
     * Removes a follower.
476
     *
477
     * @param User $user
478
     * @return $this
479
     */
480
    public function removeFollower (User $user) {
481
        return $this->removeFollowers([$user]);
482
    }
483
484
    /**
485
     * Removes followers.
486
     *
487
     * @param User[] $users
488
     * @return $this
489
     */
490
    public function removeFollowers (array $users) {
491
        if ($this->hasGid()) {
492
            $this->api->post("{$this}/removeFollowers", ['followers' => static::_getGids($users)]);
493
        }
494
        $this->_remove('followers', $users);
495
        return $this;
496
    }
497
498
    /**
499
     * Removes the task from a project.
500
     *
501
     * @param Project $project
502
     * @return $this
503
     */
504
    public function removeFromProject (Project $project) {
505
        $gid = $project->getGid();
506
        if ($this->hasGid()) {
507
            $this->api->post("{$this}/removeProject", ['project' => $gid]);
508
        }
509
        if (isset($this->data['memberships'])) {
510
            $this->data['memberships'] = array_values(array_filter($this->data['memberships'], function(Membership $membership) use ($gid) {
511
                return $membership->getProject()->getGid() !== $gid;
512
            }));
513
        }
514
        return $this;
515
    }
516
517
    /**
518
     * Removes a tag.
519
     *
520
     * @param Tag $tag
521
     * @return $this
522
     */
523
    public function removeTag (Tag $tag) {
524
        if ($this->hasGid()) {
525
            $this->api->post("{$this}/removeTag", ['tag' => $tag->getGid()]);
526
        }
527
        $this->_remove('tags', [$tag]);
528
        return $this;
529
    }
530
531
    /**
532
     * Makes the task a subtask of another.
533
     *
534
     * @param null|Task $parent
535
     * @return $this
536
     */
537
    public function setParent (?self $parent) {
538
        if ($this->hasGid()) {
539
            $this->api->post("{$this}/setParent", ['parent' => $parent ? $parent->getGid() : null]);
540
            $this->data['parent'] = $parent;
541
        }
542
        else {
543
            $this->_set('parent', $parent);
544
        }
545
        return $this;
546
    }
547
548
    /**
549
     * @param bool $flag
550
     * @return $this
551
     */
552
    public function setRenderedAsSeparator (bool $flag) {
553
        return $this->_set('is_rendered_as_separator', $flag);
554
    }
555
556
    /**
557
     * @return $this
558
     */
559
    public function update () {
560
        $this->_update();
561
        // custom_fields is a premium feature, has() and get() will try to fetch unless already set.
562
        if (isset($this->data['custom_fields'])) {
563
            $this->getCustomFields()->diff = [];
564
        }
565
        return $this;
566
    }
567
568
}