Passed
Push — master ( 1f9a54...993a87 )
by y
02:21
created

Project::getStatuses()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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