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