Passed
Push — master ( 866c3b...50a671 )
by y
08:13
created

Task::removeDependencies()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 4
b 0
f 1
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\External;
14
use Helix\Asana\Task\Like;
15
use Helix\Asana\Task\Membership;
16
use Helix\Asana\Task\Story;
17
use Helix\Asana\Webhook\TaskWebhook;
18
19
/**
20
 * A task.
21
 *
22
 * @see https://developers.asana.com/docs/asana-tasks
23
 * @see https://developers.asana.com/docs/task
24
 *
25
 * @method null|User    getAssignee         ()
26
 * @method bool         hasAssignee         ()
27
 * @method $this        setAssignee         (null|User $user)
28
 * @method string       getAssigneeStatus   ()
29
 * @method $this        setAssigneeStatus   (string $status)
30
 * @method bool         isCompleted         ()
31
 * @method $this        setCompleted        (bool $completed)
32
 * @method string       getCompletedAt      ()
33
 * @method string       getCreatedAt        ()
34
 * @method null|CustomValues getCustomFields() Premium only.
35
 * @method bool         hasCustomFields     () Premium only.
36
 * @method string       getDueOn            ()
37
 * @method $this        setDueOn            (string $date)
38
 * @method User[]       getFollowers        ()
39
 * @method User[]       selectFollowers     (callable $filter) `fn( User $user ): bool`
40
 * @method bool         hasFollowers        ()
41
 * @method string       getHtmlNotes        ()
42
 * @method $this        setHtmlNotes        (string $notes)
43
 * @method bool         isLiked             ()
44
 * @method $this        setLiked            (bool $liked)
45
 * @method Like[]       getLikes            ()
46
 * @method Like[]       selectLikes         (callable $filter) `fn( Like $like ): bool`
47
 * @method Membership[] getMemberships      ()
48
 * @method Membership[] selectMemberships   (callable $filter) `fn( Membership $membership ): bool`
49
 * @method bool         hasMemberships      ()
50
 * @method string       getModifiedAt       ()
51
 * @method string       getName             ()
52
 * @method $this        setName             (string $name)
53
 * @method string       getNotes            ()
54
 * @method $this        setNotes            (string $notes)
55
 * @method int          getNumLikes         ()
56
 * @method int          getNumSubtasks      ()
57
 * @method null|Task    getParent           ()
58
 * @method bool         hasParent           ()
59
 * @method string       getResourceSubtype  ()
60
 * @method $this        setResourceSubtype  (string $type)
61
 * @method string       getStartOn          ()
62
 * @method $this        setStartOn          (string $date)
63
 * @method Tag[]        getTags             ()
64
 * @method Tag[]        selectTags          (callable $filter) `fn( Tag $tag ): bool`
65
 * @method bool         hasTags             ()
66
 */
67
class Task extends AbstractEntity {
68
69
    use CrudTrait;
70
    use WorkspaceTrait;
71
72
    const TYPE = 'task';
73
    const TYPE_DEFAULT = 'default_task';
74
    const TYPE_MILESTONE = 'milestone';
75
76
    const ASSIGN_INBOX = 'inbox';
77
    const ASSIGN_LATER = 'later';
78
    const ASSIGN_NEW = 'new';
79
    const ASSIGN_TODAY = 'today';
80
    const ASSIGN_UPCOMING = 'upcoming';
81
82
    protected static $map = [
83
        'assignee' => User::class,
84
        'custom_fields' => CustomValues::class,
85
        'external' => External::class, // not included by expanding "this". always lazy.
86
        'followers' => [User::class],
87
        'likes' => [Like::class],
88
        'memberships' => [Membership::class],
89
        'parent' => self::class,
90
        'tags' => [Tag::class],
91
        'workspace' => Workspace::class
92
    ];
93
94
    protected static $optFields = [
95
        'memberships' => 'memberships.(project|section)'
96
    ];
97
98
    final public function __toString (): string {
99
        return "tasks/{$this->getGid()}";
100
    }
101
102
    final protected function _getDir (): string {
103
        return 'tasks';
104
    }
105
106
    protected function _save (string $dir = null) {
107
        parent::_save($dir);
108
        // use isset() to avoid has() fetch.
109
        if (isset($this->data['custom_fields'])) {
110
            $this->getCustomFields()->diff = [];
111
        }
112
        if (isset($this->data['external'])) {
113
            $this->getExternal()->diff = [];
114
        }
115
        return $this;
116
    }
117
118
    protected function _setData (array $data): void {
119
        unset($data['hearted']); // deprecated for likes
120
        unset($data['hearts']); // deprecated for likes
121
        unset($data['projects']); // redundant, memberships are used instead
122
        parent::_setData($data);
123
    }
124
125
    /**
126
     * Uploads a file attachment.
127
     *
128
     * @depends after-create
129
     * @param string $file
130
     * @return Attachment
131
     */
132
    public function addAttachment (string $file) {
133
        /** @var Attachment $attachment */
134
        $attachment = $this->factory(Attachment::class, ['parent' => $this]);
135
        return $attachment->upload($file);
136
    }
137
138
    /**
139
     * Posts a comment on an existing task and returns it.
140
     *
141
     * @param string $text
142
     * @return Story
143
     */
144
    public function addComment (string $text) {
145
        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

145
        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...
146
    }
147
148
    /**
149
     * Premium feature.
150
     *
151
     * @depends after-create
152
     * @param Task[] $tasks
153
     * @return $this
154
     */
155
    public function addDependencies (array $tasks) {
156
        $this->api->post("{$this}/addDependencies", ['dependents' => array_column($tasks, 'gid')]);
157
        return $this;
158
    }
159
160
    /**
161
     * Premium feature.
162
     *
163
     * @depends after-create
164
     * @param Task $task
165
     * @return $this
166
     */
167
    public function addDependency (Task $task) {
168
        return $this->addDependencies([$task]);
169
    }
170
171
    /**
172
     * Premium feature.
173
     *
174
     * @depends after-create
175
     * @param Task $task
176
     * @return $this
177
     */
178
    public function addDependent (Task $task) {
179
        return $this->addDependents([$task]);
180
    }
181
182
    /**
183
     * Premium feature.
184
     *
185
     * @depends after-create
186
     * @param Task[] $tasks
187
     * @return $this
188
     */
189
    public function addDependents (array $tasks) {
190
        $this->api->post("{$this}/addDependents", ['dependents' => array_column($tasks, 'gid')]);
191
        return $this;
192
    }
193
194
    /**
195
     * Adds a follower.
196
     *
197
     * @param User $user
198
     * @return $this
199
     */
200
    public function addFollower (User $user) {
201
        return $this->addFollowers([$user]);
202
    }
203
204
    /**
205
     * Adds followers.
206
     *
207
     * @see https://developers.asana.com/docs/add-followers-to-a-task
208
     *
209
     * @param User[] $users
210
     * @return $this
211
     */
212
    public function addFollowers (array $users) {
213
        return $this->_addWithPost("{$this}/addFollowers", [
214
            'followers' => array_column($users, 'gid')
215
        ], 'followers', $users);
216
    }
217
218
    /**
219
     * Adds a tag.
220
     *
221
     * @see https://developers.asana.com/docs/add-a-tag-to-a-task
222
     *
223
     * @param Tag $tag
224
     * @return $this
225
     */
226
    public function addTag (Tag $tag) {
227
        return $this->_addWithPost("{$this}/addTag", [
228
            'tag' => $tag->getGid()
229
        ], 'tags', [$tag]);
230
    }
231
232
    /**
233
     * Adds the task to a project / section.
234
     *
235
     * @see https://developers.asana.com/docs/add-a-project-to-a-task
236
     *
237
     * @param Project $project
238
     * @param Section|null $section
239
     * @return $this
240
     */
241
    public function addToProject (Project $project, Section $section = null) {
242
        /** @var Membership $membership */
243
        $membership = $this->factory(Membership::class)
244
            ->_set('project', $project)
245
            ->_set('section', $section);
246
        return $this->_addWithPost("{$this}/addProject", $membership->toArray(), 'memberships', [$membership]);
247
    }
248
249
    /**
250
     * Creates and returns a webhook.
251
     *
252
     * @depends after-create
253
     * @param string $target
254
     * @return TaskWebhook
255
     */
256
    public function addWebhook (string $target) {
257
        /** @var TaskWebhook $webhook */
258
        $webhook = $this->factory(TaskWebhook::class);
259
        return $webhook->create($this, $target);
260
    }
261
262
    /**
263
     * Creates and returns job to duplicate the task.
264
     *
265
     * @see https://developers.asana.com/docs/duplicate-a-task
266
     *
267
     * @depends after-create
268
     * @param string $name
269
     * @param string[] $include
270
     * @return Job
271
     */
272
    public function duplicate (string $name, array $include) {
273
        /** @var array $remote */
274
        $remote = $this->api->post("{$this}/duplicate", [
275
            'name' => $name,
276
            'include' => array_values($include)
277
        ]);
278
        return $this->factory(Job::class, $remote);
279
    }
280
281
    /**
282
     * Returns the task's files.
283
     *
284
     * @depends after-create
285
     * @return Attachment[]
286
     */
287
    public function getAttachments () {
288
        return $this->loadAll(Attachment::class, "{$this}/attachments");
289
    }
290
291
    /**
292
     * Returns the task's comments.
293
     *
294
     * @depends after-create
295
     * @return Story[]
296
     */
297
    public function getComments () {
298
        return $this->selectStories(function(Story $story) {
299
            return $story->isComment();
300
        });
301
    }
302
303
    /**
304
     * Premium feature.
305
     *
306
     * @depends after-create
307
     * @return Task[]
308
     */
309
    public function getDependencies () {
310
        return $this->loadAll(self::class, "{$this}/dependencies");
311
    }
312
313
    /**
314
     * Premium feature.
315
     *
316
     * @depends after-create
317
     * @return Task[]
318
     */
319
    public function getDependents () {
320
        return $this->loadAll(self::class, "{$this}/dependents");
321
    }
322
323
    /**
324
     * Returns events since the last sync.
325
     *
326
     * @depends after-create
327
     * @param null|string $token
328
     * @return TaskEvent[]|StoryEvent[]
329
     */
330
    public function getEvents (&$token) {
331
        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...
332
    }
333
334
    /**
335
     * This always returns an instance, regardless of whether the task on Asana actually has external data.
336
     *
337
     * Developer's note: Asana will delete the external data object if it's emptied,
338
     * and fetching it via `GET` will then return `null`, so we coalesce.
339
     *
340
     * @return External
341
     */
342
    public function getExternal () {
343
        return $this->_get('external') ?? $this->data['external'] = $this->factory(External::class);
344
    }
345
346
    /**
347
     * @return Project[]
348
     */
349
    public function getProjects () {
350
        return array_map(function(Membership $membership) {
351
            return $membership->getProject();
352
        }, $this->getMemberships());
353
    }
354
355
    /**
356
     * Returns the task's activity.
357
     *
358
     * @depends after-create
359
     * @return Story[]
360
     */
361
    public function getStories () {
362
        return $this->loadAll(Story::class, "{$this}/stories");
363
    }
364
365
    /**
366
     * Returns the task's subtasks.
367
     *
368
     * @depends after-create
369
     * @return Task[]
370
     */
371
    public function getSubTasks () {
372
        return $this->loadAll(self::class, "{$this}/subtasks");
373
    }
374
375
    /**
376
     * Returns the task's URL.
377
     *
378
     * @depends after-create
379
     * @return string
380
     */
381
    public function getUrl (): string {
382
        return "https://app.asana.com/0/0/{$this->getGid()}";
383
    }
384
385
    /**
386
     * Returns the task's webhooks.
387
     *
388
     * @depends after-create
389
     * @return TaskWebhook[]
390
     */
391
    public function getWebhooks () {
392
        return $this->loadAll(TaskWebhook::class, 'webhooks', [
393
            'workspace' => $this->getWorkspace()->getGid(),
394
            'resource' => $this->getGid()
395
        ]);
396
    }
397
398
    /**
399
     * @return bool
400
     */
401
    public function isRenderedAsSeparator (): bool {
402
        return $this->_is('is_rendered_as_separator');
403
    }
404
405
    /**
406
     * Instantiates and returns a new comment.
407
     *
408
     * @depends after-create
409
     * @return Story
410
     */
411
    public function newComment () {
412
        /** @var Story $comment */
413
        $comment = $this->factory(Story::class, ['resource_subtype' => Story::TYPE_COMMENT_ADDED]);
414
        return $comment->_set('task', $this);
415
    }
416
417
    /**
418
     * Instantiates and returns a new subtask.
419
     *
420
     * @depends after-create
421
     * @return Task
422
     */
423
    public function newSubTask () {
424
        /** @var Task $sub */
425
        $sub = $this->factory(self::class);
426
        return $sub->setParent($this);
427
    }
428
429
    /**
430
     * Premium feature.
431
     *
432
     * @depends after-create
433
     * @param Task[] $tasks
434
     * @return $this
435
     */
436
    public function removeDependencies (array $tasks) {
437
        $this->api->post("{$this}/removeDependencies", ['dependencies' => array_column($tasks, 'gid')]);
438
        return $this;
439
    }
440
441
    /**
442
     * Premium feature.
443
     *
444
     * @depends after-create
445
     * @param Task $task
446
     * @return $this
447
     */
448
    public function removeDependency (Task $task) {
449
        return $this->removeDependencies([$task]);
450
    }
451
452
    /**
453
     * Premium feature.
454
     *
455
     * @depends after-create
456
     * @param Task $task
457
     * @return $this
458
     */
459
    public function removeDependent (Task $task) {
460
        return $this->removeDependents([$task]);
461
    }
462
463
    /**
464
     * Premium feature.
465
     *
466
     * @depends after-create
467
     * @param Task[] $tasks
468
     * @return $this
469
     */
470
    public function removeDependents (array $tasks) {
471
        $this->api->post("{$this}/removeDependents", ['dependents' => array_column($tasks, 'gid')]);
472
        return $this;
473
    }
474
475
    /**
476
     * Removes a follower.
477
     *
478
     * @param User $user
479
     * @return $this
480
     */
481
    public function removeFollower (User $user) {
482
        return $this->removeFollowers([$user]);
483
    }
484
485
    /**
486
     * Removes followers.
487
     *
488
     * @see https://developers.asana.com/docs/remove-followers-from-a-task
489
     *
490
     * @param User[] $users
491
     * @return $this
492
     */
493
    public function removeFollowers (array $users) {
494
        return $this->_removeWithPost("{$this}/removeFollowers", [
495
            'followers' => array_column($users, 'gid')
496
        ], 'followers', $users);
497
    }
498
499
    /**
500
     * Removes the task from a project.
501
     *
502
     * @see https://developers.asana.com/docs/remove-a-project-from-a-task
503
     *
504
     * @param Project $project
505
     * @return $this
506
     */
507
    public function removeFromProject (Project $project) {
508
        return $this->_removeWithPost("{$this}/removeProject", [
509
            'project' => $project->getGid()
510
        ], 'memberships', function(Membership $membership) use ($project) {
511
            return $membership->getProject()->getGid() !== $project->getGid();
512
        });
513
    }
514
515
    /**
516
     * Removes a tag.
517
     *
518
     * @see https://developers.asana.com/docs/remove-a-tag-from-a-task
519
     *
520
     * @param Tag $tag
521
     * @return $this
522
     */
523
    public function removeTag (Tag $tag) {
524
        return $this->_removeWithPost("{$this}/removeTag", [
525
            'tag' => $tag->getGid()
526
        ], 'tags', [$tag]);
527
    }
528
529
    /**
530
     * @param callable $filter `fn( Attachment $attachment): bool`
531
     * @return Attachment[]
532
     */
533
    public function selectAttachments (callable $filter) {
534
        return $this->_select($this->getAttachments(), $filter);
535
    }
536
537
    /**
538
     * @param callable $filter `fn( Story $comment ): bool`
539
     * @return Story[]
540
     */
541
    public function selectComments (callable $filter) {
542
        return $this->_select($this->getComments(), $filter);
543
    }
544
545
    /**
546
     * @param callable $filter `fn( Task $dependency ): bool`
547
     * @return Task[]
548
     */
549
    public function selectDependencies (callable $filter) {
550
        return $this->_select($this->getDependencies(), $filter);
551
    }
552
553
    /**
554
     * @param callable $filter `fn( Task $dependent ): bool`
555
     * @return Task[]
556
     */
557
    public function selectDependents (callable $filter) {
558
        return $this->_select($this->getDependents(), $filter);
559
    }
560
561
    /**
562
     * @param callable $filter `fn( Project $project ): bool`
563
     * @return Project[]
564
     */
565
    public function selectProjects (callable $filter) {
566
        return $this->_select($this->getProjects(), $filter);
567
    }
568
569
    /**
570
     * @param callable $filter `fn( Story $story ): bool`
571
     * @return Story[]
572
     */
573
    public function selectStories (callable $filter) {
574
        return $this->_select($this->getStories(), $filter);
575
    }
576
577
    /**
578
     * @param callable $filter `fn( Task $subtask ): bool`
579
     * @return Task[]
580
     */
581
    public function selectSubTasks (callable $filter) {
582
        return $this->_select($this->getSubTasks(), $filter);
583
    }
584
585
    /**
586
     * Makes the task a subtask of another.
587
     *
588
     * @see https://developers.asana.com/docs/set-the-parent-of-a-task
589
     * @param null|Task $parent
590
     * @return $this
591
     */
592
    public function setParent (?self $parent) {
593
        return $this->_setWithPost("{$this}/setParent", [
594
            'parent' => $parent
595
        ], 'parent', $parent);
596
    }
597
598
    /**
599
     * @param bool $flag
600
     * @return $this
601
     */
602
    public function setRenderedAsSeparator (bool $flag) {
603
        return $this->_set('is_rendered_as_separator', $flag);
604
    }
605
606
}