Passed
Push — master ( 2428ce...06a092 )
by y
02:04
created

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