Project::duplicate()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
c 0
b 0
f 0
nc 4
nop 4
dl 0
loc 14
rs 9.9332
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 string       getDefaultView      () See the layout constants.
36
 * @method User[]       getFollowers        ()
37
 * @method null|string  getIcon             () read-only
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        setDefaultView      (string $layout) See the layout constants.
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 $filter, array $apiFilter = 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_CALENDAR = 'calendar';
79
    const LAYOUT_LIST = 'list';
80
    const LAYOUT_TIMELINE = 'timeline';
81
82
    const GET_ACTIVE = ['archived' => false];
83
    const GET_ARCHIVED = ['archived' => true];
84
85
    protected const MAP = [
86
        'current_status' => Status::class,
87
        'custom_field_settings' => [FieldSetting::class],
88
        'followers' => [User::class],
89
        'members' => [User::class],
90
        'owner' => User::class,
91
        'team' => Team::class,
92
        'workspace' => Workspace::class
93
    ];
94
95
    /**
96
     * @var Section
97
     */
98
    private $defaultSection;
99
100
    protected function _setData (array $data): void {
101
        // this is always empty. fields are in the settings, values are in tasks.
102
        unset($data['custom_fields']);
103
104
        // deprecated for due_on
105
        unset($data['due_date']);
106
107
        parent::_setData($data);
108
    }
109
110
    /**
111
     * @param User $user
112
     * @return $this
113
     */
114
    public function addFollower (User $user) {
115
        return $this->addFollowers([$user]);
116
    }
117
118
    /**
119
     * @param User[] $users
120
     * @return $this
121
     */
122
    public function addFollowers (array $users) {
123
        return $this->_addWithPost("{$this}/addFollowers", [
124
            'followers' => array_column($users, 'gid')
125
        ], 'followers', $users);
126
    }
127
128
    /**
129
     * @param User $user
130
     * @return $this
131
     */
132
    public function addMember (User $user) {
133
        return $this->addMembers([$user]);
134
    }
135
136
    /**
137
     * @param User[] $users
138
     * @return $this
139
     */
140
    public function addMembers (array $users) {
141
        return $this->_addWithPost("{$this}/addMembers", [
142
            'members' => array_column($users, 'gid')
143
        ], 'members', $users);
144
    }
145
146
    /**
147
     * Duplicates the project.
148
     *
149
     * @see https://developers.asana.com/docs/duplicate-a-project
150
     *
151
     * If `$team` is `null`, the duplicate will inherit the existing team.
152
     *
153
     * If `$schedule` is given:
154
     * - It must have either `due_on` or `start_on`
155
     * - `task_dates` is automatically added to `$include`
156
     * - `should_skip_weekends` defaults to `true` if not given.
157
     *
158
     * @param string $name
159
     * @param string[] $include
160
     * @param null|Team $team
161
     * @param array $schedule
162
     * @return Job
163
     */
164
    public function duplicate (string $name, array $include, Team $team = null, array $schedule = []) {
165
        $data = ['name' => $name];
166
        if ($team) {
167
            $data['team'] = $team->getGid();
168
        }
169
        if ($schedule) {
170
            $include[] = 'task_dates';
171
            $schedule += ['should_skip_weekends' => true];
172
            $data['schedule_dates'] = $schedule;
173
        }
174
        $data['include'] = array_values($include);
175
        /** @var array $remote */
176
        $remote = $this->api->post("{$this}/duplicate", $data);
177
        return $this->api->factory($this, Job::class, $remote);
178
    }
179
180
    /**
181
     * @return Section
182
     */
183
    public function getDefaultSection () {
184
        return $this->defaultSection ?? $this->defaultSection = $this->getSections(1)[0];
185
    }
186
187
    /**
188
     * Iterates over sections.
189
     *
190
     * @see https://developers.asana.com/docs/get-sections-in-a-project
191
     *
192
     * @param int $limit
193
     * @return Traversable|Section[]
194
     */
195
    public function getIterator (int $limit = PHP_INT_MAX) {
196
        return $this->api->loadEach($this, Section::class, "{$this}/sections", ['limit' => $limit]);
197
    }
198
199
    /**
200
     * @param int $limit
201
     * @return Section[]
202
     */
203
    public function getSections (int $limit = PHP_INT_MAX) {
204
        return iterator_to_array($this->getIterator($limit));
205
    }
206
207
    /**
208
     * @return Status[]
209
     */
210
    public function getStatuses () {
211
        return $this->api->loadAll($this, Status::class, "{$this}/project_statuses");
212
    }
213
214
    /**
215
     * @return TaskCounts
216
     */
217
    public function getTaskCounts () {
218
        $remote = $this->api->get("{$this}/task_counts", [
219
            'opt_fields' => // opt_expand doesn't work.
220
                'num_completed_milestones,'
221
                . 'num_completed_tasks,'
222
                . 'num_incomplete_milestones,'
223
                . 'num_incomplete_tasks,'
224
                . 'num_milestones,'
225
                . 'num_tasks'
226
        ]);
227
        return $this->api->factory($this, TaskCounts::class, $remote);
228
    }
229
230
    /**
231
     * The project's tasks.
232
     *
233
     * @param array $filter
234
     * @return Task[]
235
     */
236
    public function getTasks (array $filter = Task::GET_INCOMPLETE) {
237
        $filter['project'] = $this->getGid();
238
        return $this->api->loadAll($this, Task::class, "tasks", $filter);
239
    }
240
241
    /**
242
     * @return string
243
     */
244
    final public function getUrl (): string {
245
        return "https://app.asana.com/0/{$this->getGid()}";
246
    }
247
248
    /**
249
     * @return ProjectWebhook[]
250
     */
251
    public function getWebhooks () {
252
        return $this->api->loadAll($this, ProjectWebhook::class, 'webhooks', [
253
            'workspace' => $this->getWorkspace()->getGid(),
254
            'resource' => $this->getGid()
255
        ]);
256
    }
257
258
    /**
259
     * @return bool
260
     */
261
    final public function isTemplate (): bool {
262
        return $this->_is('is_template');
263
    }
264
265
    /**
266
     * Factory.
267
     *
268
     * @return Section
269
     */
270
    public function newSection () {
271
        return $this->api->factory($this, Section::class, ['project' => $this]);
272
    }
273
274
    /**
275
     * Factory.
276
     *
277
     * @return Status
278
     */
279
    public function newStatus () {
280
        return $this->api->factory($this, Status::class);
281
    }
282
283
    /**
284
     * Factory.
285
     *
286
     * @return Task
287
     */
288
    public function newTask () {
289
        return $this->getDefaultSection()->newTask();
290
    }
291
292
    /**
293
     * Factory.
294
     *
295
     * @return ProjectWebhook
296
     */
297
    public function newWebhook () {
298
        /** @var ProjectWebhook $webhook */
299
        $webhook = $this->api->factory($this, ProjectWebhook::class);
300
        return $webhook->setResource($this);
301
    }
302
303
    /**
304
     * @param User $user
305
     * @return $this
306
     */
307
    public function removeFollower (User $user) {
308
        return $this->removeFollowers([$user]);
309
    }
310
311
    /**
312
     * @param User[] $users
313
     * @return $this
314
     */
315
    public function removeFollowers (array $users) {
316
        return $this->_removeWithPost("{$this}/removeFollowers", [
317
            'followers' => array_column($users, 'gid')
318
        ], 'followers', $users);
319
    }
320
321
    /**
322
     * @param User $user
323
     * @return $this
324
     */
325
    public function removeMember (User $user) {
326
        return $this->removeMembers([$user]);
327
    }
328
329
    /**
330
     * @param User[] $users
331
     * @return $this
332
     */
333
    public function removeMembers (array $users) {
334
        return $this->_removeWithPost("{$this}/removeMembers", [
335
            'members' => array_column($users, 'gid')
336
        ], 'members', $users);
337
    }
338
}