Passed
Push — master ( b66c10...181037 )
by y
02:15
created

Task::getStories()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 2
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 bool         hasFollowers        ()
40
 * @method string       getHtmlNotes        ()
41
 * @method $this        setHtmlNotes        (string $notes)
42
 * @method bool         isLiked             ()
43
 * @method $this        setLiked            (bool $liked)
44
 * @method Like[]       getLikes            ()
45
 * @method Membership[] getMemberships      ()
46
 * @method bool         hasMemberships      ()
47
 * @method string       getModifiedAt       ()
48
 * @method string       getName             ()
49
 * @method $this        setName             (string $name)
50
 * @method string       getNotes            ()
51
 * @method $this        setNotes            (string $notes)
52
 * @method int          getNumLikes         ()
53
 * @method int          getNumSubtasks      ()
54
 * @method null|Task    getParent           ()
55
 * @method bool         hasParent           ()
56
 * @method string       getResourceSubtype  ()
57
 * @method $this        setResourceSubtype  (string $type)
58
 * @method string       getStartOn          ()
59
 * @method $this        setStartOn          (string $date)
60
 * @method Tag[]        getTags             ()
61
 * @method bool         hasTags             ()
62
 */
63
class Task extends AbstractEntity {
64
65
    use CrudTrait;
66
    use WorkspaceTrait;
67
68
    const TYPE = 'task';
69
    const TYPE_DEFAULT = 'default_task';
70
    const TYPE_MILESTONE = 'milestone';
71
72
    const ASSIGN_INBOX = 'inbox';
73
    const ASSIGN_LATER = 'later';
74
    const ASSIGN_NEW = 'new';
75
    const ASSIGN_TODAY = 'today';
76
    const ASSIGN_UPCOMING = 'upcoming';
77
78
    protected static $map = [
79
        'assignee' => User::class,
80
        'custom_fields' => CustomValues::class,
81
        'external' => External::class, // not included by expanding "this". always lazy.
82
        'followers' => [User::class],
83
        'likes' => [Like::class],
84
        'memberships' => [Membership::class],
85
        'parent' => self::class,
86
        'tags' => [Tag::class],
87
        'workspace' => Workspace::class
88
    ];
89
90
    protected static $optFields = [
91
        'memberships' => 'memberships.(project|section)'
92
    ];
93
94
    final public function __toString (): string {
95
        return "tasks/{$this->getGid()}";
96
    }
97
98
    final protected function _getDir (): string {
99
        return 'tasks';
100
    }
101
102
    protected function _save (string $dir = null) {
103
        parent::_save($dir);
104
        // use isset() to avoid has() fetch.
105
        if (isset($this->data['custom_fields'])) {
106
            $this->getCustomFields()->diff = [];
107
        }
108
        if (isset($this->data['external'])) {
109
            $this->getExternal()->diff = [];
110
        }
111
        return $this;
112
    }
113
114
    protected function _setData (array $data): void {
115
        unset($data['hearted']); // deprecated for likes
116
        unset($data['hearts']); // deprecated for likes
117
        unset($data['projects']); // redundant, memberships are used instead
118
        parent::_setData($data);
119
    }
120
121
    /**
122
     * Uploads a file attachment.
123
     *
124
     * @depends after-create
125
     * @param string $file
126
     * @return Attachment
127
     */
128
    public function addAttachment (string $file) {
129
        /** @var Attachment $attachment */
130
        $attachment = $this->factory(Attachment::class, ['parent' => $this]);
131
        return $attachment->upload($file);
132
    }
133
134
    /**
135
     * Posts a comment on an existing task and returns it.
136
     *
137
     * @param string $text
138
     * @return Story
139
     */
140
    public function addComment (string $text) {
141
        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

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