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

Project::selectTasks()   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 2
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\CustomField\FieldSetting;
10
use Helix\Asana\CustomField\FieldSettingsTrait;
11
use Helix\Asana\Project\Section;
12
use Helix\Asana\Project\Status;
13
use Helix\Asana\Project\TaskCounts;
14
use Helix\Asana\Webhook\ProjectWebhook;
15
use Helix\Asana\Workspace\WorkspaceTrait;
16
use IteratorAggregate;
17
use Traversable;
18
19
/**
20
 * A project.
21
 *
22
 * @see https://developers.asana.com/docs/asana-projects
23
 * @see https://developers.asana.com/docs/project
24
 *
25
 * @method bool         isArchived          ()
26
 * @method string       getColor            () @depends after-create
27
 * @method string       getCreatedAt        () RFC3339x
28
 * @method null|Status  getCurrentStatus    ()
29
 * @method User[]       getFollowers        ()
30
 * @method string       getIcon             ()
31
 * @method string       getLayout           ()
32
 * @method User[]       getMembers          ()
33
 * @method string       getModifiedAt       () RFC3339x
34
 * @method string       getName             ()
35
 * @method string       getNotes            ()
36
 * @method null|User    getOwner            ()
37
 * @method bool         isPublic            ()
38
 * @method null|Team    getTeam             ()
39
 *
40
 * @method bool         hasFollowers        ()
41
 * @method bool         hasMembers          ()
42
 * @method bool         hasOwner            ()
43
 * @method bool         hasTeam             ()
44
 *
45
 * @method $this        setArchived         (bool $archived)
46
 * @method $this        setColor            (string $color)
47
 * @method $this        setLayout           (string $layout)
48
 * @method $this        setName             (string $name)
49
 * @method $this        setNotes            (string $notes)
50
 * @method $this        setOwner            (User $owner)
51
 * @method $this        setPublic           (bool $public)
52
 *
53
 * @method User[]       selectFollowers     (callable $filter) `fn( User $user ): bool`
54
 * @method User[]       selectMembers       (callable $filter) `fn( User $user ): bool`
55
 * @method Section[]    selectSections      (callable $filter) `fn( Section $section ): bool`
56
 * @method Status[]     selectStatuses      (callable $filter) `fn( Status $status ): bool`
57
 * @method Task[]       selectTasks         (callable $fn, array $apiX = Task::GET_INCOMPLETE) `fn( Task $task ): bool`
58
 */
59
class Project extends AbstractEntity implements IteratorAggregate {
60
61
    use CrudTrait;
62
    use DateTrait;
63
    use FieldSettingsTrait;
64
    use PostMutatorTrait;
65
    use WorkspaceTrait;
66
67
    const TYPE = 'project';
68
69
    const LAYOUT_BOARD = 'board';
70
    const LAYOUT_LIST = 'list';
71
72
    const GET_ACTIVE = ['archived' => false];
73
    const GET_ARCHIVED = ['archived' => true];
74
75
    protected const MAP = [
76
        'current_status' => Status::class,
77
        'custom_field_settings' => [FieldSetting::class],
78
        'followers' => [User::class],
79
        'members' => [User::class],
80
        'owner' => User::class,
81
        'team' => Team::class,
82
        'workspace' => Workspace::class
83
    ];
84
85
    /**
86
     * @var Section
87
     */
88
    private $defaultSection;
89
90
    /**
91
     * `projects/{gid}`
92
     *
93
     * @return string
94
     */
95
    final public function __toString (): string {
96
        return "projects/{$this->getGid()}";
97
    }
98
99
    /**
100
     * `projects`
101
     *
102
     * @return string
103
     */
104
    final protected function _getDir (): string {
105
        return 'projects';
106
    }
107
108
    protected function _setData (array $data): void {
109
        // this is always empty. fields are in the settings, values are in tasks.
110
        unset($data['custom_fields']);
111
112
        // deprecated for due_on
113
        unset($data['due_date']);
114
115
        parent::_setData($data);
116
    }
117
118
    /**
119
     * @depends after-create
120
     * @param User $user
121
     * @return $this
122
     */
123
    public function addMember (User $user) {
124
        return $this->addMembers([$user]);
125
    }
126
127
    /**
128
     * @depends after-create
129
     * @param User[] $users
130
     * @return $this
131
     */
132
    public function addMembers (array $users) {
133
        return $this->_addWithPost("{$this}/addMembers", [
134
            'members' => array_column($users, 'gid')
135
        ], 'members', $users);
136
    }
137
138
    /**
139
     * @depends after-create
140
     * @param string $target
141
     * @return ProjectWebhook
142
     */
143
    public function addWebhook (string $target) {
144
        /** @var ProjectWebhook $webhook */
145
        $webhook = $this->api->factory($this, ProjectWebhook::class);
146
        return $webhook->create($this, $target);
147
    }
148
149
    /**
150
     * Duplicates the project.
151
     *
152
     * @see https://developers.asana.com/docs/duplicate-a-project
153
     *
154
     * If `$team` is `null`, the duplicate will inherit the existing team.
155
     *
156
     * If `$schedule` is given:
157
     * - It must have either `due_on` or `start_on`
158
     * - `task_dates` is automatically added to `$include`
159
     * - `should_skip_weekends` defaults to `true` if not given.
160
     *
161
     * @depends after-create
162
     * @param string $name
163
     * @param string[] $include
164
     * @param null|Team $team
165
     * @param array $schedule
166
     * @return Job
167
     */
168
    public function duplicate (string $name, array $include, Team $team = null, array $schedule = []) {
169
        $data = ['name' => $name];
170
        if ($team) {
171
            $data['team'] = $team->getGid();
172
        }
173
        if ($schedule) {
174
            $include[] = 'task_dates';
175
            $schedule += ['should_skip_weekends' => true];
176
            $data['schedule_dates'] = $schedule;
177
        }
178
        $data['include'] = array_values($include);
179
        /** @var array $remote */
180
        $remote = $this->api->post("{$this}/duplicate", $data);
181
        return $this->api->factory($this, Job::class, $remote);
182
    }
183
184
    /**
185
     * @depends after-create
186
     * @return Section
187
     */
188
    public function getDefaultSection () {
189
        return $this->defaultSection ?? $this->defaultSection = $this->getSections(1)[0];
190
    }
191
192
    /**
193
     * Events since the last sync.
194
     *
195
     * Notably, task custom field value changes are reported here, but not when syncing the task.
196
     *
197
     * @depends after-create
198
     * @param null|string $token
199
     * @return Event[]
200
     */
201
    public function getEvents (&$token) {
202
        return $this->api->sync($this->getGid(), $token);
203
    }
204
205
    /**
206
     * Iterates over sections.
207
     *
208
     * @see https://developers.asana.com/docs/get-sections-in-a-project
209
     *
210
     * @param int $limit
211
     * @return Traversable|Section[]
212
     */
213
    public function getIterator (int $limit = PHP_INT_MAX) {
214
        return $this->api->loadEach($this, Section::class, "{$this}/sections", ['limit' => $limit]);
215
    }
216
217
    /**
218
     * @depends after-create
219
     * @param int $limit
220
     * @return Section[]
221
     */
222
    public function getSections (int $limit = PHP_INT_MAX) {
223
        return iterator_to_array($this->getIterator($limit));
224
    }
225
226
    /**
227
     * @depends after-create
228
     * @return Status[]
229
     */
230
    public function getStatuses () {
231
        return $this->api->loadAll($this, Status::class, "{$this}/project_statuses");
232
    }
233
234
    /**
235
     * @depends after-create
236
     * @return TaskCounts
237
     */
238
    public function getTaskCounts () {
239
        /** @var array $remote */
240
        $remote = $this->api->get("{$this}/task_counts", [], TaskCounts::OPT);
241
        return $this->api->factory($this, TaskCounts::class, $remote);
242
    }
243
244
    /**
245
     * The project's tasks.
246
     *
247
     * @depends after-create
248
     * @param array $filter
249
     * @return Task[]
250
     */
251
    public function getTasks (array $filter = Task::GET_INCOMPLETE) {
252
        $filter['project'] = $this->getGid();
253
        return $this->api->loadAll($this, Task::class, "tasks", $filter);
254
    }
255
256
    /**
257
     * @depends after-create
258
     * @return string
259
     */
260
    public function getUrl (): string {
261
        return "https://app.asana.com/0/{$this->getGid()}";
262
    }
263
264
    /**
265
     * @return ProjectWebhook[]
266
     */
267
    public function getWebhooks () {
268
        return $this->api->loadAll($this, ProjectWebhook::class, 'webhooks', [
269
            'workspace' => $this->getWorkspace()->getGid(),
270
            'resource' => $this->getGid()
271
        ]);
272
    }
273
274
    final public function isBoard (): bool {
275
        return $this->getLayout() === self::LAYOUT_BOARD;
276
    }
277
278
    final public function isList (): bool {
279
        return $this->getLayout() === self::LAYOUT_LIST;
280
    }
281
282
    /**
283
     * Factory.
284
     *
285
     * @depends after-create
286
     * @return Section
287
     */
288
    public function newSection () {
289
        return $this->api->factory($this, Section::class, ['project' => $this]);
290
    }
291
292
    /**
293
     * Factory.
294
     *
295
     * @depends after-create
296
     * @return Status
297
     */
298
    public function newStatus () {
299
        return $this->api->factory($this, Status::class);
300
    }
301
302
    /**
303
     * Factory.
304
     *
305
     * @depends after-create
306
     * @return Task
307
     */
308
    public function newTask () {
309
        return $this->getDefaultSection()->newTask();
310
    }
311
312
    /**
313
     * @depends after-create
314
     * @param User $user
315
     * @return $this
316
     */
317
    public function removeMember (User $user) {
318
        return $this->removeMembers([$user]);
319
    }
320
321
    /**
322
     * @depends after-create
323
     * @param User[] $users
324
     * @return $this
325
     */
326
    public function removeMembers (array $users) {
327
        return $this->_removeWithPost("{$this}/removeMembers", [
328
            'members' => array_column($users, 'gid')
329
        ], 'members', $users);
330
    }
331
332
    /**
333
     * @depends create-only
334
     * @param null|Team $team
335
     * @return $this
336
     */
337
    public function setTeam (?Team $team) {
338
        if ($team and !$this->hasWorkspace()) {
339
            $this->setWorkspace($team->getOrganization());
340
        }
341
        return $this->_set('team', $team);
342
    }
343
}