Passed
Push — master ( 1a00b5...984447 )
by y
03:02
created

Task::_getMap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 0
dl 0
loc 11
rs 9.9332
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Task::_getDir() 0 2 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
        create as private _create;
67
        update as private _update;
68
    }
69
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
    /**
103
     * Clears the diffs for `custom_fields` and `external` sub-objects after saving.
104
     */
105
    protected function _clearSubDiffs (): void {
106
        // use isset() to avoid has() fetch.
107
        if (isset($this->data['custom_fields'])) {
108
            $this->getCustomFields()->diff = [];
109
        }
110
        if (isset($this->data['external'])) {
111
            $this->getExternal()->diff = [];
112
        }
113
    }
114
115
    final protected function _getDir (): string {
116
        return 'tasks';
117
    }
118
119
    /**
120
     * Uploads a file attachment.
121
     *
122
     * @depends after-create
123
     * @param string $file
124
     * @return Attachment
125
     */
126
    public function addAttachment (string $file) {
127
        /** @var Attachment $attachment */
128
        $attachment = $this->factory(Attachment::class, ['parent' => $this]);
129
        return $attachment->upload($file);
130
    }
131
132
    /**
133
     * Posts a comment on an existing task and returns it.
134
     *
135
     * @param string $text
136
     * @return Story
137
     */
138
    public function addComment (string $text) {
139
        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

139
        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...
140
    }
141
142
    /**
143
     * Premium feature.
144
     *
145
     * @depends after-create
146
     * @param Task[] $tasks
147
     * @return $this
148
     */
149
    public function addDependencies (array $tasks) {
150
        $this->api->post("{$this}/addDependencies", ['dependents' => static::_getGids($tasks)]);
151
        return $this;
152
    }
153
154
    /**
155
     * Premium feature.
156
     *
157
     * @depends after-create
158
     * @param Task $task
159
     * @return $this
160
     */
161
    public function addDependency (Task $task) {
162
        return $this->addDependencies([$task]);
163
    }
164
165
    /**
166
     * Premium feature.
167
     *
168
     * @depends after-create
169
     * @param Task $task
170
     * @return $this
171
     */
172
    public function addDependent (Task $task) {
173
        return $this->addDependents([$task]);
174
    }
175
176
    /**
177
     * Premium feature.
178
     *
179
     * @depends after-create
180
     * @param Task[] $tasks
181
     * @return $this
182
     */
183
    public function addDependents (array $tasks) {
184
        $this->api->post("{$this}/addDependents", ['dependents' => static::_getGids($tasks)]);
185
        return $this;
186
    }
187
188
    /**
189
     * Adds a follower.
190
     *
191
     * @param User $user
192
     * @return $this
193
     */
194
    public function addFollower (User $user) {
195
        return $this->addFollowers([$user]);
196
    }
197
198
    /**
199
     * Adds followers.
200
     *
201
     * @param User[] $users
202
     * @return $this
203
     */
204
    public function addFollowers (array $users) {
205
        if ($this->hasGid()) {
206
            $this->api->post("{$this}/addFollowers", ['followers' => static::_getGids($users)]);
207
            $this->_merge('followers', $users);
208
        }
209
        else {
210
            $this->_merge('followers', $users, true);
211
        }
212
        return $this;
213
    }
214
215
    /**
216
     * Adds a tag.
217
     *
218
     * @param Tag $tag
219
     * @return $this
220
     */
221
    public function addTag (Tag $tag) {
222
        if ($this->hasGid()) {
223
            $this->api->post("{$this}/addTag", ['tag' => $tag->getGid()]);
224
            $this->_merge('tags', [$tag]);
225
        }
226
        else {
227
            $this->_merge('tags', [$tag], true);
228
        }
229
        return $this;
230
    }
231
232
    /**
233
     * Adds the task to a project section.
234
     *
235
     * @param Section $section
236
     * @return $this
237
     */
238
    public function addToProject (Section $section) {
239
        $project = $section->getProject();
240
        if ($this->hasGid()) {
241
            $this->api->post("{$this}/addProject", [
242
                'project' => $project->getGid(),
243
                'section' => $section->getGid()
244
            ]);
245
            if (isset($this->data['memberships'])) {
246
                $this->data['memberships'][] = $this->factory(Membership::class)->_setData([
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->data['memberships'][] is correct as $this->factory(Helix\Asa...'section' => $section)) targeting Helix\Asana\Base\Data::_setData() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
247
                    'project' => $project,
248
                    'section' => $section
249
                ]);
250
            }
251
        }
252
        else {
253
            if (!$this->hasWorkspace()) {
254
                $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

254
                $this->setWorkspace(/** @scrutinizer ignore-type */ $project->getWorkspace());
Loading history...
255
            }
256
            $membership = $this->factory(Membership::class);
257
            $membership->_set('project', $section->getProject());
258
            $membership->_set('section', $section);
259
            $this->data['memberships'][] = $membership;
260
            $this->diff['memberships'] = true;
261
        }
262
        return $this;
263
    }
264
265
    /**
266
     * Creates and returns a webhook.
267
     *
268
     * @depends after-create
269
     * @param string $target
270
     * @return TaskWebhook
271
     */
272
    public function addWebhook (string $target) {
273
        /** @var TaskWebhook $webhook */
274
        $webhook = $this->factory(TaskWebhook::class);
275
        return $webhook->create($this, $target);
276
    }
277
278
    /**
279
     * @return $this
280
     */
281
    public function create () {
282
        $this->_create();
283
        $this->_clearSubDiffs();
284
        return $this;
285
    }
286
287
    /**
288
     * Creates and returns job to duplicate the task.
289
     *
290
     * @see https://developers.asana.com/docs/#duplicate-a-task
291
     *
292
     * @depends after-create
293
     * @param string $name
294
     * @param string[] $include
295
     * @return Job
296
     */
297
    public function duplicate (string $name, array $include) {
298
        $remote = $this->api->post("{$this}/duplicate", [
299
            'name' => $name,
300
            'include' => array_values($include)
301
        ]);
302
        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

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