Passed
Push — master ( 02e6b9...8f2f39 )
by y
01:57
created

Task::selectProjects()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
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\DateTrait;
8
use Helix\Asana\Base\AbstractEntity\PostMutatorTrait;
9
use Helix\Asana\Project\Section;
10
use Helix\Asana\Task\Attachment;
11
use Helix\Asana\Task\External;
12
use Helix\Asana\Task\FieldEntries;
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
use Helix\Asana\Workspace\WorkspaceTrait;
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 string               getAssigneeStatus           ()
27
 * @method bool                 isCompleted                 ()
28
 * @method string               getCompletedAt              () RFC3339x
29
 * @method string               getCreatedAt                () RFC3339x
30
 * @method null|FieldEntries    getCustomFields             () Premium feature.
31
 * @method User[]               getFollowers                ()
32
 * @method bool                 getIsRenderedAsSeparator    ()
33
 * @method bool                 isLiked                     () Whether you like the task.
34
 * @method Like[]               getLikes                    ()
35
 * @method Membership[]         getMemberships              ()
36
 * @method string               getModifiedAt               () RFC3339x
37
 * @method string               getName                     ()
38
 * @method string               getNotes                    ()
39
 * @method int                  getNumLikes                 ()
40
 * @method int                  getNumSubtasks              ()
41
 * @method null|Task            getParent                   ()
42
 * @method string               getResourceSubtype          ()
43
 * @method Tag[]                getTags                     ()
44
 *
45
 * @method bool                 hasAssignee                 ()
46
 * @method bool                 hasCustomFields             () Premium feature.
47
 * @method bool                 hasFollowers                ()
48
 * @method bool                 hasLikes                    ()
49
 * @method bool                 hasMemberships              ()
50
 * @method bool                 hasName                     ()
51
 * @method bool                 hasNotes                    ()
52
 * @method bool                 hasParent                   ()
53
 * @method bool                 hasTags                     ()
54
 *
55
 * @method $this                setAssignee                 (?User $user)
56
 * @method $this                setAssigneeStatus           (string $status)
57
 * @method $this                setCompleted                (bool $completed)
58
 * @method $this                setIsRenderedAsSeparator    (bool $flag)
59
 * @method $this                setLiked                    (bool $liked) Like or unlike the task.
60
 * @method $this                setName                     (string $name)
61
 * @method $this                setNotes                    (string $notes)
62
 * @method $this                setResourceSubtype          (string $type) @depends create-only
63
 *
64
 * @method Attachment[]         selectAttachments           (callable $filter) `fn( Attachment $attachment): bool`
65
 * @method Task[]               selectDependencies          (callable $filter) `fn( Task $dependency ): bool`
66
 * @method Task[]               selectDependents            (callable $filter) `fn( Task $dependent ): bool`
67
 * @method User[]               selectFollowers             (callable $filter) `fn( User $user ): bool`
68
 * @method Story[]              selectComments              (callable $filter) `fn( Story $comment ): bool`
69
 * @method Like[]               selectLikes                 (callable $filter) `fn( Like $like ): bool`
70
 * @method Membership[]         selectMemberships           (callable $filter) `fn( Membership $membership ): bool`
71
 * @method Project[]            selectProjects              (callable $filter) `fn( Project $project ): bool`
72
 * @method Story[]              selectStories               (callable $filter) `fn( Story $story ): bool`
73
 * @method Task[]               selectSubTasks              (callable $filter) `fn( Task $subtask ): bool`
74
 * @method Tag[]                selectTags                  (callable $filter) `fn( Tag $tag ): bool`
75
 */
76
class Task extends AbstractEntity {
77
78
    use CrudTrait {
79
        create as private _create;
80
        update as private _update;
81
    }
82
83
    use DateTrait;
84
    use PostMutatorTrait;
85
    use WorkspaceTrait;
86
87
    const TYPE = 'task';
88
    const TYPE_DEFAULT = 'default_task';
89
    const TYPE_MILESTONE = 'milestone';
90
91
    const ASSIGN_INBOX = 'inbox';
92
    const ASSIGN_LATER = 'later';
93
    const ASSIGN_NEW = 'new';
94
    const ASSIGN_TODAY = 'today';
95
    const ASSIGN_UPCOMING = 'upcoming';
96
97
    const GET_INCOMPLETE = ['completed_since' => 'now'];
98
99
    protected const MAP = [
100
        'assignee' => User::class,
101
        'custom_fields' => FieldEntries::class,
102
        'external' => External::class, // not included by expanding "this". always lazy.
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
    const OPT_FIELDS = [
112
        'memberships' => 'memberships.(project|section)'
113
    ];
114
115
    /**
116
     * `tasks/{gid}`
117
     *
118
     * @return string
119
     */
120
    final public function __toString (): string {
121
        return "tasks/{$this->getGid()}";
122
    }
123
124
    /**
125
     * `tasks`
126
     *
127
     * @return string
128
     */
129
    final protected function _getDir (): string {
130
        return 'tasks';
131
    }
132
133
    private function _onSave (): void {
134
        /** @var FieldEntries $fields */
135
        if ($fields = $this->data['custom_fields'] ?? null) {
136
            $fields->__unset(true);
137
        }
138
        /** @var External $external */
139
        if ($external = $this->data['external'] ?? null) {
140
            $external->diff = [];
141
        }
142
    }
143
144
    protected function _setData (array $data): void {
145
        // hearts were deprecated for likes
146
        unset($data['hearted'], $data['hearts'], $data['num_hearts']);
147
148
        // redundant. memberships are used instead
149
        unset($data['projects']);
150
151
        // time-based deadlines are a little passive-aggressive, don't you think?
152
        unset($data['due_at']);
153
154
        parent::_setData($data);
155
    }
156
157
    /**
158
     * Uploads a file attachment.
159
     *
160
     * @depends after-create
161
     * @param string $file
162
     * @return Attachment
163
     */
164
    public function addAttachment (string $file) {
165
        /** @var Attachment $attachment */
166
        $attachment = $this->api->factory($this, Attachment::class, ['parent' => $this]);
167
        return $attachment->upload($file);
168
    }
169
170
    /**
171
     * Posts a new comment.
172
     *
173
     * @depends after-create
174
     * @param string $text
175
     * @return Story
176
     */
177
    public function addComment (string $text) {
178
        return $this->newComment()->setText($text)->create();
179
    }
180
181
    /**
182
     * Premium feature.
183
     *
184
     * @depends after-create
185
     * @param Task[] $tasks
186
     * @return $this
187
     */
188
    public function addDependencies (array $tasks) {
189
        $this->api->post("{$this}/addDependencies", ['dependents' => array_column($tasks, 'gid')]);
190
        return $this;
191
    }
192
193
    /**
194
     * Premium feature.
195
     *
196
     * @depends after-create
197
     * @param Task $task
198
     * @return $this
199
     */
200
    public function addDependency (Task $task) {
201
        return $this->addDependencies([$task]);
202
    }
203
204
    /**
205
     * Premium feature.
206
     *
207
     * @depends after-create
208
     * @param Task $task
209
     * @return $this
210
     */
211
    public function addDependent (Task $task) {
212
        return $this->addDependents([$task]);
213
    }
214
215
    /**
216
     * Premium feature.
217
     *
218
     * @depends after-create
219
     * @param Task[] $tasks
220
     * @return $this
221
     */
222
    public function addDependents (array $tasks) {
223
        $this->api->post("{$this}/addDependents", ['dependents' => array_column($tasks, 'gid')]);
224
        return $this;
225
    }
226
227
    /**
228
     * Adds a follower.
229
     *
230
     * @param User $user
231
     * @return $this
232
     */
233
    public function addFollower (User $user) {
234
        return $this->addFollowers([$user]);
235
    }
236
237
    /**
238
     * Adds followers.
239
     *
240
     * @see https://developers.asana.com/docs/add-followers-to-a-task
241
     *
242
     * @param User[] $users
243
     * @return $this
244
     */
245
    public function addFollowers (array $users) {
246
        return $this->_addWithPost("{$this}/addFollowers", [
247
            'followers' => array_column($users, 'gid')
248
        ], 'followers', $users);
249
    }
250
251
    /**
252
     * Adds a tag.
253
     *
254
     * @see https://developers.asana.com/docs/add-a-tag-to-a-task
255
     *
256
     * @param Tag $tag
257
     * @return $this
258
     */
259
    public function addTag (Tag $tag) {
260
        return $this->_addWithPost("{$this}/addTag", [
261
            'tag' => $tag->getGid()
262
        ], 'tags', [$tag]);
263
    }
264
265
    /**
266
     * Adds the task to a project.
267
     *
268
     * @see https://developers.asana.com/docs/add-a-project-to-a-task
269
     *
270
     * @see Project::getDefaultSection()
271
     *
272
     * @param Section $section
273
     * @return $this
274
     */
275
    public function addToProject (Section $section) {
276
        /** @var Membership $membership */
277
        $membership = $this->api->factory($this, Membership::class)
278
            ->_set('project', $section->getProject())
279
            ->_set('section', $section);
280
        return $this->_addWithPost("{$this}/addProject", $membership->toArray(), 'memberships', [$membership]);
281
    }
282
283
    /**
284
     * Creates a webhook.
285
     *
286
     * @depends after-create
287
     * @param string $target
288
     * @return TaskWebhook
289
     */
290
    public function addWebhook (string $target) {
291
        /** @var TaskWebhook $webhook */
292
        $webhook = $this->api->factory($this, TaskWebhook::class);
293
        return $webhook->create($this, $target);
294
    }
295
296
    /**
297
     * @return $this
298
     */
299
    public function create () {
300
        $this->_create();
301
        $this->_onSave();
302
        return $this;
303
    }
304
305
    /**
306
     * Duplicates the task.
307
     *
308
     * @see https://developers.asana.com/docs/duplicate-a-task
309
     *
310
     * @depends after-create
311
     * @param string $name
312
     * @param string[] $include
313
     * @return Job
314
     */
315
    public function duplicate (string $name, array $include) {
316
        /** @var array $remote */
317
        $remote = $this->api->post("{$this}/duplicate", [
318
            'name' => $name,
319
            'include' => array_values($include)
320
        ]);
321
        return $this->api->factory($this, Job::class, $remote);
322
    }
323
324
    /**
325
     * Attached files.
326
     *
327
     * @depends after-create
328
     * @return Attachment[]
329
     */
330
    public function getAttachments () {
331
        return $this->api->loadAll($this, Attachment::class, "{$this}/attachments");
332
    }
333
334
    /**
335
     * @depends after-create
336
     * @return Story[]
337
     */
338
    public function getComments () {
339
        return $this->selectStories(function(Story $story) {
1 ignored issue
show
Bug introduced by
The method selectStories() does not exist on Helix\Asana\Task. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

339
        return $this->/** @scrutinizer ignore-call */ selectStories(function(Story $story) {
Loading history...
340
            return $story->isComment();
341
        });
342
    }
343
344
    /**
345
     * Premium feature.
346
     *
347
     * @depends after-create
348
     * @return Task[]
349
     */
350
    public function getDependencies () {
351
        return $this->api->loadAll($this, self::class, "{$this}/dependencies");
352
    }
353
354
    /**
355
     * Premium feature.
356
     *
357
     * @depends after-create
358
     * @return Task[]
359
     */
360
    public function getDependents () {
361
        return $this->api->loadAll($this, self::class, "{$this}/dependents");
362
    }
363
364
    /**
365
     * Events since the last sync.
366
     *
367
     * @depends after-create
368
     * @param null|string $token
369
     * @return Event[]
370
     */
371
    public function getEvents (&$token) {
372
        return $this->api->sync($this->getGid(), $token);
373
    }
374
375
    /**
376
     * A proxy to the task's "external data".
377
     *
378
     * > :info:
379
     * > This always returns an instance, regardless of whether the task on Asana actually has external data.
380
     * >
381
     * > Asana will delete the external data object if it's emptied,
382
     * > and fetching it via `GET` will then return `null`, so we coalesce.
383
     *
384
     * @return External
385
     */
386
    public function getExternal () {
387
        return $this->_get('external') ?? $this->data['external'] = $this->api->factory($this, External::class);
388
    }
389
390
    /**
391
     * @return Project[]
392
     */
393
    public function getProjects () {
394
        return array_column($this->getMemberships(), 'project');
395
    }
396
397
    /**
398
     * @return Section[]
399
     */
400
    public function getSections () {
401
        return array_column($this->getMemberships(), 'section');
402
    }
403
404
    /**
405
     * @depends after-create
406
     * @return Story[]
407
     */
408
    public function getStories () {
409
        return $this->api->loadAll($this, Story::class, "{$this}/stories");
410
    }
411
412
    /**
413
     * @depends after-create
414
     * @return Task[]
415
     */
416
    public function getSubTasks () {
417
        return $this->api->loadAll($this, self::class, "{$this}/subtasks");
418
    }
419
420
    /**
421
     * @depends after-create
422
     * @return string
423
     */
424
    public function getUrl (): string {
425
        return "https://app.asana.com/0/0/{$this->getGid()}";
426
    }
427
428
    /**
429
     * @depends after-create
430
     * @return TaskWebhook[]
431
     */
432
    public function getWebhooks () {
433
        return $this->api->loadAll($this, TaskWebhook::class, 'webhooks', [
434
            'workspace' => $this->getWorkspace()->getGid(),
435
            'resource' => $this->getGid()
436
        ]);
437
    }
438
439
    /**
440
     * Factory.
441
     *
442
     * @depends after-create
443
     * @return Story
444
     */
445
    public function newComment () {
446
        return $this->api->factory($this, Story::class, [
447
            'resource_subtype' => Story::TYPE_COMMENT_ADDED,
448
            'target' => $this
449
        ]);
450
    }
451
452
    /**
453
     * Factory.
454
     *
455
     * @depends after-create
456
     * @return Task
457
     */
458
    public function newSubTask () {
459
        /** @var Task $sub */
460
        $sub = $this->api->factory($this, self::class);
461
        return $sub->setParent($this);
462
    }
463
464
    /**
465
     * Premium feature.
466
     *
467
     * @depends after-create
468
     * @param Task[] $tasks
469
     * @return $this
470
     */
471
    public function removeDependencies (array $tasks) {
472
        $this->api->post("{$this}/removeDependencies", ['dependencies' => array_column($tasks, 'gid')]);
473
        return $this;
474
    }
475
476
    /**
477
     * Premium feature.
478
     *
479
     * @depends after-create
480
     * @param Task $task
481
     * @return $this
482
     */
483
    public function removeDependency (Task $task) {
484
        return $this->removeDependencies([$task]);
485
    }
486
487
    /**
488
     * Premium feature.
489
     *
490
     * @depends after-create
491
     * @param Task $task
492
     * @return $this
493
     */
494
    public function removeDependent (Task $task) {
495
        return $this->removeDependents([$task]);
496
    }
497
498
    /**
499
     * Premium feature.
500
     *
501
     * @depends after-create
502
     * @param Task[] $tasks
503
     * @return $this
504
     */
505
    public function removeDependents (array $tasks) {
506
        $this->api->post("{$this}/removeDependents", ['dependents' => array_column($tasks, 'gid')]);
507
        return $this;
508
    }
509
510
    /**
511
     * Removes a follower.
512
     *
513
     * @param User $user
514
     * @return $this
515
     */
516
    public function removeFollower (User $user) {
517
        return $this->removeFollowers([$user]);
518
    }
519
520
    /**
521
     * Removes followers.
522
     *
523
     * @see https://developers.asana.com/docs/remove-followers-from-a-task
524
     *
525
     * @param User[] $users
526
     * @return $this
527
     */
528
    public function removeFollowers (array $users) {
529
        return $this->_removeWithPost("{$this}/removeFollowers", [
530
            'followers' => array_column($users, 'gid')
531
        ], 'followers', $users);
532
    }
533
534
    /**
535
     * Removes the task from a project.
536
     *
537
     * @see https://developers.asana.com/docs/remove-a-project-from-a-task
538
     *
539
     * @param Project $project
540
     * @return $this
541
     */
542
    public function removeFromProject (Project $project) {
543
        return $this->_removeWithPost("{$this}/removeProject", [
544
            'project' => $project->getGid()
545
        ], 'memberships', function(Membership $membership) use ($project) {
546
            return $membership->getProject()->getGid() !== $project->getGid();
547
        });
548
    }
549
550
    /**
551
     * Removes a tag.
552
     *
553
     * @see https://developers.asana.com/docs/remove-a-tag-from-a-task
554
     *
555
     * @param Tag $tag
556
     * @return $this
557
     */
558
    public function removeTag (Tag $tag) {
559
        return $this->_removeWithPost("{$this}/removeTag", [
560
            'tag' => $tag->getGid()
561
        ], 'tags', [$tag]);
562
    }
563
564
    /**
565
     * Makes the task a subtask of another.
566
     *
567
     * @see https://developers.asana.com/docs/set-the-parent-of-a-task
568
     * @param null|Task $parent
569
     * @return $this
570
     */
571
    public function setParent (?self $parent) {
572
        return $this->_setWithPost("{$this}/setParent", [
573
            'parent' => $parent
574
        ], 'parent', $parent);
575
    }
576
577
    /**
578
     * @return $this
579
     */
580
    public function update () {
581
        $this->_update();
582
        $this->_onSave();
583
        return $this;
584
    }
585
586
}