Passed
Push — master ( fbd180...23ab98 )
by y
02:18
created

Project::getIterator()   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
c 0
b 0
f 0
nc 1
nop 0
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\WorkspaceTrait;
8
use Helix\Asana\CustomField\FieldSetting;
9
use Helix\Asana\CustomField\FieldSettingsTrait;
10
use Helix\Asana\Event\ProjectEvent;
11
use Helix\Asana\Event\StoryEvent;
12
use Helix\Asana\Event\TaskEvent;
13
use Helix\Asana\Project\Section;
14
use Helix\Asana\Project\Status;
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
 * @method bool         isArchived          ()
26
 * @method $this        setArchived         (bool $archived)
27
 * @method string       getColor            ()
28
 * @method $this        setColor            (null|string $color)
29
 * @method string       getCreatedAt        () RFC3339x
30
 * @method null|Status  getCurrentStatus    ()
31
 * @method string       getDueOn            ()
32
 * @method $this        setDueOn            (string $date)
33
 * @method User[]       getFollowers        ()
34
 * @method User[]       selectFollowers     ()
35
 * @method string       getIcon             ()
36
 * @method string       getLayout           ()
37
 * @method $this        setLayout           (string $layout)
38
 * @method User[]       getMembers          ()
39
 * @method User[]       selectMembers       ()
40
 * @method string       getModifiedAt       ()
41
 * @method string       getName             ()
42
 * @method $this        setName             (string $name)
43
 * @method string       getNotes            ()
44
 * @method $this        setNotes            (string $notes)
45
 * @method null|User    getOwner            ()
46
 * @method $this        setOwner            (User $owner)
47
 * @method bool         isPublic            ()
48
 * @method $this        setPublic           (bool $public)
49
 * @method string       getStartOn          ()
50
 * @method $this        setStartOn          (string $date)
51
 * @method null|Team    getTeam             ()
52
 * @method bool         hasTeam             ()
53
 */
54
class Project extends AbstractEntity implements IteratorAggregate {
55
56
    use CrudTrait;
57
    use FieldSettingsTrait;
58
    use WorkspaceTrait;
59
60
    const TYPE = 'project';
61
62
    const LAYOUT_BOARD = 'board';
63
    const LAYOUT_LIST = 'list';
64
65
    protected static $map = [
66
        'current_status' => Status::class,
67
        'custom_field_settings' => [FieldSetting::class],
68
        'followers' => [User::class],
69
        'members' => [User::class],
70
        'owner' => User::class,
71
        'team' => Team::class,
72
        'workspace' => Workspace::class
73
    ];
74
75
    final public function __toString (): string {
76
        return "projects/{$this->getGid()}";
77
    }
78
79
    final protected function _getDir (): string {
80
        return 'projects';
81
    }
82
83
    protected function _setData (array $data): void {
84
        // this is always empty. fields are in the settings, values are in tasks.
85
        unset($data['custom_fields']);
86
        parent::_setData($data);
87
    }
88
89
    /**
90
     * @depends after-create
91
     * @param User $user
92
     * @return $this
93
     */
94
    public function addMember (User $user) {
95
        return $this->addMembers([$user]);
96
    }
97
98
    /**
99
     * @depends after-create
100
     * @param User[] $users
101
     * @return $this
102
     */
103
    public function addMembers (array $users) {
104
        return $this->_addWithPost("{$this}/addMembers", [
105
            'members' => array_column($users, 'gid')
106
        ], 'members', $users);
107
    }
108
109
    /**
110
     * @depends after-create
111
     * @param string $target
112
     * @return ProjectWebhook
113
     */
114
    public function addWebhook (string $target) {
115
        /** @var ProjectWebhook $webhook */
116
        $webhook = $this->_factory(ProjectWebhook::class);
117
        return $webhook->create($this, $target);
118
    }
119
120
    /**
121
     * Duplicates the project.
122
     *
123
     * @see https://developers.asana.com/docs/duplicate-a-project
124
     *
125
     * If `$team` is `null`, the duplicate will inherit the existing team.
126
     *
127
     * If `$schedule` is given:
128
     * - It must have either `due_on` or `start_on`
129
     * - `task_dates` is automatically added to `$include`
130
     * - `should_skip_weekends` defaults to `true` if not given.
131
     *
132
     * @depends after-create
133
     * @param string $name
134
     * @param string[] $include
135
     * @param Team|null $team
136
     * @param array $schedule
137
     * @return Job
138
     */
139
    public function duplicate (string $name, array $include, Team $team = null, array $schedule = []) {
140
        $data = ['name' => $name];
141
        if ($team) {
142
            $data['team'] = $team->getGid();
143
        }
144
        if ($schedule) {
1 ignored issue
show
Bug Best Practice introduced by
The expression $schedule of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
145
            $include[] = 'task_dates';
146
            $schedule += ['should_skip_weekends' => true];
147
            $data['schedule_dates'] = $schedule;
148
        }
149
        $data['include'] = array_values($include);
150
        $remote = $this->api->post("{$this}/duplicate", $data);
151
        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

151
        return $this->_factory(Job::class, /** @scrutinizer ignore-type */ $remote);
Loading history...
152
    }
153
154
    /**
155
     * @depends after-create
156
     * @return Section
157
     */
158
    public function getDefaultSection () {
159
        return $this->_loadAll(Section::class, "{$this}/sections", ['limit' => 1])[0];
160
    }
161
162
    /**
163
     * Returns events since the last sync.
164
     *
165
     * @depends after-create
166
     * @param null|string $token
167
     * @return ProjectEvent[]|TaskEvent[]|StoryEvent[]
168
     */
169
    public function getEvents (&$token) {
170
        return $this->api->sync($this, $token);
171
    }
172
173
    /**
174
     * @return Traversable|Section[]
175
     */
176
    public function getIterator () {
177
        return $this->_loadEach(Section::class, "{$this}/sections");
178
    }
179
180
    /**
181
     * @depends after-create
182
     * @return Section[]
183
     */
184
    public function getSections () {
185
        return iterator_to_array($this);
186
    }
187
188
    /**
189
     * @depends after-create
190
     * @return Status[]
191
     */
192
    public function getStatuses () {
193
        return $this->_loadAll(Status::class, "{$this}/project_statuses");
194
    }
195
196
    /**
197
     * The project's tasks.
198
     *
199
     * @depends after-create
200
     * @param array $filter
201
     * @return Task[]
202
     */
203
    public function getTasks (array $filter = []) {
204
        $filter['project'] = $this->getGid();
205
        return $this->_loadAll(Task::class, "tasks", $filter);
206
    }
207
208
    /**
209
     * @depends after-create
210
     * @return string
211
     */
212
    public function getUrl (): string {
213
        return "https://app.asana.com/0/{$this->getGid()}";
214
    }
215
216
    /**
217
     * @return ProjectWebhook[]
218
     */
219
    public function getWebhooks () {
220
        return $this->_loadAll(ProjectWebhook::class, 'webhooks', [
221
            'workspace' => $this->getWorkspace()->getGid(),
222
            'resource' => $this->getGid()
223
        ]);
224
    }
225
226
    public function isBoard (): bool {
227
        return $this->getLayout() === self::LAYOUT_BOARD;
228
    }
229
230
    public function isList (): bool {
231
        return $this->getLayout() === self::LAYOUT_LIST;
232
    }
233
234
    /**
235
     * @depends after-create
236
     * @return Section
237
     */
238
    public function newSection () {
239
        return $this->_factory(Section::class, ['project' => $this]);
240
    }
241
242
    /**
243
     * @depends after-create
244
     * @return Status
245
     */
246
    public function newStatus () {
247
        return $this->_factory(Status::class);
248
    }
249
250
    /**
251
     * Instantiates and returns a new task in the default section of the project.
252
     *
253
     * @depends after-create
254
     * @return Task
255
     */
256
    public function newTask () {
257
        return $this->getDefaultSection()->newTask();
258
    }
259
260
    /**
261
     * @depends after-create
262
     * @param User $user
263
     * @return $this
264
     */
265
    public function removeMember (User $user) {
266
        return $this->removeMembers([$user]);
267
    }
268
269
    /**
270
     * @depends after-create
271
     * @param User[] $users
272
     * @return $this
273
     */
274
    public function removeMembers (array $users) {
275
        return $this->_removeWithPost("{$this}/removeMembers", [
276
            'members' => array_column($users, 'gid')
277
        ], 'members', $users);
278
    }
279
280
    /**
281
     * @param callable $filter `fn( Section $section ): bool`
282
     * @return Section[]
283
     */
284
    public function selectSections (callable $filter) {
285
        return $this->_select($this->getSections(), $filter);
286
    }
287
288
    /**
289
     * @param callable $filter `fn( Status $status ): bool`
290
     * @return Status[]
291
     */
292
    public function selectStatuses (callable $filter) {
293
        return $this->_select($this->getStatuses(), $filter);
294
    }
295
296
    /**
297
     * @param callable $filter `fn( Task $task ): bool`
298
     * @param array $apiFilter Pre-filter given to the API to reduce network load.
299
     * @return Task[]
300
     */
301
    public function selectTasks (callable $filter, array $apiFilter = []) {
302
        return $this->_select($this->getTasks($apiFilter), $filter);
303
    }
304
305
    /**
306
     * @depends create-only
307
     * @param null|Team $team
308
     * @return $this
309
     */
310
    public function setTeam (?Team $team) {
311
        if ($team and !$this->hasWorkspace()) {
312
            $this->setWorkspace($team->getOrganization());
313
        }
314
        return $this->_set('team', $team);
315
    }
316
}