These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Symbiote\AdvancedWorkflow\Extensions; |
||
4 | |||
5 | use SilverStripe\Control\Controller; |
||
6 | use SilverStripe\Control\Director; |
||
7 | use SilverStripe\Forms\DropdownField; |
||
8 | use SilverStripe\Forms\FieldList; |
||
9 | use SilverStripe\Forms\FormAction; |
||
10 | use SilverStripe\Forms\GridField\GridField; |
||
11 | use SilverStripe\Forms\GridField\GridFieldConfig_Base; |
||
12 | use SilverStripe\Forms\GridField\GridFieldDetailForm; |
||
13 | use SilverStripe\Forms\GridField\GridFieldEditButton; |
||
14 | use SilverStripe\Forms\HiddenField; |
||
15 | use SilverStripe\Forms\ListboxField; |
||
16 | use SilverStripe\Forms\ReadonlyField; |
||
17 | use SilverStripe\Forms\Tab; |
||
18 | use SilverStripe\Forms\TabSet; |
||
19 | use SilverStripe\ORM\CMSPreviewable; |
||
20 | use SilverStripe\ORM\DataExtension; |
||
21 | use SilverStripe\ORM\DataList; |
||
22 | use SilverStripe\Security\Permission; |
||
23 | use SilverStripe\Security\Security; |
||
24 | use Symbiote\AdvancedWorkflow\DataObjects\WorkflowActionInstance; |
||
25 | use Symbiote\AdvancedWorkflow\DataObjects\WorkflowDefinition; |
||
26 | use Symbiote\AdvancedWorkflow\DataObjects\WorkflowInstance; |
||
27 | use Symbiote\AdvancedWorkflow\Services\WorkflowService; |
||
28 | use Symbiote\QueuedJobs\Services\AbstractQueuedJob; |
||
29 | |||
30 | /** |
||
31 | * DataObjects that have the WorkflowApplicable extension can have a |
||
32 | * workflow definition applied to them. At some point, the workflow definition is then |
||
33 | * triggered. |
||
34 | * |
||
35 | * @author [email protected] |
||
36 | * @license BSD License (http://silverstripe.org/bsd-license/) |
||
37 | * @package advancedworkflow |
||
38 | */ |
||
39 | class WorkflowApplicable extends DataExtension |
||
40 | { |
||
41 | private static $has_one = [ |
||
42 | 'WorkflowDefinition' => WorkflowDefinition::class, |
||
43 | ]; |
||
44 | |||
45 | private static $many_many = [ |
||
46 | 'AdditionalWorkflowDefinitions' => WorkflowDefinition::class |
||
47 | ]; |
||
48 | |||
49 | private static $dependencies = [ |
||
50 | 'workflowService' => '%$' . WorkflowService::class, |
||
51 | ]; |
||
52 | |||
53 | /** |
||
54 | * |
||
55 | * Used to flag to this extension if there's a WorkflowPublishTargetJob running. |
||
56 | * @var boolean |
||
57 | */ |
||
58 | public $isPublishJobRunning = false; |
||
59 | |||
60 | /** |
||
61 | * |
||
62 | * @param boolean $truth |
||
63 | */ |
||
64 | public function setIsPublishJobRunning($truth) |
||
65 | { |
||
66 | $this->isPublishJobRunning = $truth; |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * |
||
71 | * @return boolean |
||
72 | */ |
||
73 | public function getIsPublishJobRunning() |
||
74 | { |
||
75 | return $this->isPublishJobRunning; |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * |
||
80 | * @see {@link $this->isPublishJobRunning} |
||
81 | * @return boolean |
||
82 | */ |
||
83 | public function isPublishJobRunning() |
||
84 | { |
||
85 | $propIsSet = (bool) $this->getIsPublishJobRunning(); |
||
86 | return class_exists(AbstractQueuedJob::class) && $propIsSet; |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * @var WorkflowService |
||
91 | */ |
||
92 | public $workflowService; |
||
93 | |||
94 | /** |
||
95 | * |
||
96 | * A cache var for the current workflow instance |
||
97 | * |
||
98 | * @var WorkflowInstance |
||
99 | */ |
||
100 | protected $currentInstance; |
||
101 | |||
102 | public function updateSettingsFields(FieldList $fields) |
||
103 | { |
||
104 | $this->updateFields($fields); |
||
105 | } |
||
106 | |||
107 | public function updateCMSFields(FieldList $fields) |
||
108 | { |
||
109 | if (!$this->owner->hasMethod('getSettingsFields')) { |
||
110 | $this->updateFields($fields); |
||
111 | } |
||
112 | |||
113 | // Instantiate a hidden form field to pass the triggered workflow definition through, |
||
114 | // allowing a dynamic form action. |
||
115 | |||
116 | $fields->push(HiddenField::create( |
||
117 | 'TriggeredWorkflowID' |
||
118 | )); |
||
119 | } |
||
120 | |||
121 | public function updateFields(FieldList $fields) |
||
122 | { |
||
123 | if (!$this->owner->ID) { |
||
124 | return $fields; |
||
125 | } |
||
126 | |||
127 | $tab = $fields->fieldByName('Root') ? $fields->findOrMakeTab('Root.Workflow') : $fields; |
||
128 | |||
129 | if (Permission::check('APPLY_WORKFLOW')) { |
||
130 | $definition = new DropdownField( |
||
131 | 'WorkflowDefinitionID', |
||
132 | _t('WorkflowApplicable.DEFINITION', 'Applied Workflow') |
||
133 | ); |
||
134 | $definitions = $this->getWorkflowService()->getDefinitions()->map()->toArray(); |
||
135 | $definition->setSource($definitions); |
||
136 | $definition->setEmptyString(_t('WorkflowApplicable.INHERIT', 'Inherit from parent')); |
||
137 | $tab->push($definition); |
||
138 | |||
139 | // Allow an optional selection of additional workflow definitions. |
||
140 | |||
141 | if ($this->owner->WorkflowDefinitionID) { |
||
142 | $fields->removeByName('AdditionalWorkflowDefinitions'); |
||
143 | unset($definitions[$this->owner->WorkflowDefinitionID]); |
||
144 | $tab->push($additional = ListboxField::create( |
||
145 | 'AdditionalWorkflowDefinitions', |
||
146 | _t('WorkflowApplicable.ADDITIONAL_WORKFLOW_DEFINITIONS', 'Additional Workflows') |
||
147 | )); |
||
148 | $additional->setSource($definitions); |
||
149 | } |
||
150 | } |
||
151 | |||
152 | // Display the effective workflow definition. |
||
153 | |||
154 | if ($effective = $this->getWorkflowInstance()) { |
||
155 | $title = $effective->Definition()->Title; |
||
156 | $tab->push(ReadonlyField::create( |
||
157 | 'EffectiveWorkflow', |
||
158 | _t('WorkflowApplicable.EFFECTIVE_WORKFLOW', 'Effective Workflow'), |
||
159 | $title |
||
160 | )); |
||
161 | } |
||
162 | |||
163 | if ($this->owner->ID) { |
||
164 | $config = new GridFieldConfig_Base(); |
||
165 | $config->addComponent(new GridFieldEditButton()); |
||
166 | $config->addComponent(new GridFieldDetailForm()); |
||
167 | |||
168 | $insts = $this->owner->WorkflowInstances(); |
||
169 | $log = new GridField( |
||
170 | 'WorkflowLog', |
||
171 | _t('WorkflowApplicable.WORKFLOWLOG', 'Workflow Log'), |
||
172 | $insts, |
||
173 | $config |
||
174 | ); |
||
175 | |||
176 | $tab->push($log); |
||
177 | } |
||
178 | } |
||
179 | |||
180 | public function updateCMSActions(FieldList $actions) |
||
181 | { |
||
182 | $active = $this->getWorkflowService()->getWorkflowFor($this->owner); |
||
183 | $c = Controller::curr(); |
||
184 | if ($c && $c->hasExtension(AdvancedWorkflowExtension::class)) { |
||
185 | if ($active) { |
||
186 | if ($this->canEditWorkflow()) { |
||
187 | $workflowOptions = new Tab( |
||
188 | 'WorkflowOptions', |
||
189 | _t( |
||
190 | 'SiteTree.WorkflowOptions', |
||
191 | 'Workflow options', |
||
192 | 'Expands a view for workflow specific buttons' |
||
193 | ) |
||
194 | ); |
||
195 | |||
196 | $menu = $actions->fieldByName('ActionMenus'); |
||
197 | if (!$menu) { |
||
198 | // create the menu for adding to any arbitrary non-sitetree object |
||
199 | $menu = $this->createActionMenu(); |
||
200 | $actions->push($menu); |
||
201 | } |
||
202 | |||
203 | if (!$actions->fieldByName('ActionMenus.WorkflowOptions')) { |
||
204 | $menu->push($workflowOptions); |
||
205 | } |
||
206 | |||
207 | $transitions = $active->CurrentAction()->getValidTransitions(); |
||
208 | |||
209 | foreach ($transitions as $transition) { |
||
210 | if ($transition->canExecute($active)) { |
||
211 | $action = FormAction::create('updateworkflow-' . $transition->ID, $transition->Title) |
||
212 | ->setAttribute('data-transitionid', $transition->ID); |
||
213 | $workflowOptions->push($action); |
||
214 | } |
||
215 | } |
||
216 | |||
217 | // $action = FormAction::create('updateworkflow', $active->CurrentAction() ? |
||
218 | // $active->CurrentAction()->Title : |
||
219 | // _t('WorkflowApplicable.UPDATE_WORKFLOW', 'Update Workflow')) |
||
220 | // ->setAttribute('data-icon', 'navigation'); |
||
221 | |||
222 | // $actions->fieldByName('MajorActions') ? |
||
223 | // $actions->fieldByName('MajorActions')->push($action) : |
||
224 | // $actions->push($action); |
||
225 | } |
||
226 | } else { |
||
227 | // Instantiate the workflow definition initial actions. |
||
228 | $definitions = $this->getWorkflowService()->getDefinitionsFor($this->owner); |
||
229 | if ($definitions) { |
||
230 | $menu = $actions->fieldByName('ActionMenus'); |
||
231 | if (is_null($menu)) { |
||
232 | // Instantiate a new action menu for any data objects. |
||
233 | |||
234 | $menu = $this->createActionMenu(); |
||
235 | $actions->push($menu); |
||
236 | } |
||
237 | $tab = Tab::create( |
||
238 | 'AdditionalWorkflows' |
||
239 | ); |
||
240 | $addedFirst = false; |
||
241 | foreach ($definitions as $definition) { |
||
242 | if ($definition->getInitialAction() && $this->owner->canEdit()) { |
||
243 | $action = FormAction::create( |
||
244 | "startworkflow-{$definition->ID}", |
||
245 | $definition->InitialActionButtonText ? |
||
246 | $definition->InitialActionButtonText : |
||
247 | $definition->getInitialAction()->Title |
||
248 | ) |
||
249 | ->addExtraClass('start-workflow') |
||
250 | ->setAttribute('data-workflow', $definition->ID) |
||
251 | ->addExtraClass('btn-primary'); |
||
252 | |||
253 | // The first element is the main workflow definition, |
||
254 | // and will be displayed as a major action. |
||
255 | if (!$addedFirst) { |
||
256 | $addedFirst = true; |
||
257 | $action->setAttribute('data-icon', 'navigation'); |
||
258 | $majorActions = $actions->fieldByName('MajorActions'); |
||
259 | $majorActions ? $majorActions->push($action) : $actions->push($action); |
||
260 | } else { |
||
261 | $tab->push($action); |
||
262 | } |
||
263 | } |
||
264 | } |
||
265 | // Only display menu if actions pushed to it |
||
266 | if ($tab->Fields()->exists()) { |
||
267 | $menu->insertBefore($tab, 'MoreOptions'); |
||
268 | } |
||
269 | } |
||
270 | } |
||
271 | } |
||
272 | } |
||
273 | |||
274 | protected function createActionMenu() |
||
275 | { |
||
276 | $rootTabSet = new TabSet('ActionMenus'); |
||
277 | $rootTabSet->addExtraClass('ss-ui-action-tabset action-menus'); |
||
278 | return $rootTabSet; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Included in CMS-generated email templates for a NotifyUsersWorkflowAction. |
||
283 | * Returns an absolute link to the CMS UI for a Page object |
||
284 | * |
||
285 | * @return string|null |
||
286 | */ |
||
287 | public function AbsoluteEditLink() |
||
288 | { |
||
289 | $CMSEditLink = null; |
||
290 | |||
291 | if ($this->owner instanceof CMSPreviewable) { |
||
292 | $CMSEditLink = $this->owner->CMSEditLink(); |
||
293 | } elseif ($this->owner->hasMethod('WorkflowLink')) { |
||
294 | $CMSEditLink = $this->owner->WorkflowLink(); |
||
295 | } |
||
296 | |||
297 | if ($CMSEditLink === null) { |
||
298 | return null; |
||
299 | } |
||
300 | |||
301 | return Controller::join_links(Director::absoluteBaseURL(), $CMSEditLink); |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * Included in CMS-generated email templates for a NotifyUsersWorkflowAction. |
||
306 | * Allows users to select a link in an email for direct access to the transition-selection dropdown in the CMS UI. |
||
307 | * |
||
308 | * @return string |
||
309 | */ |
||
310 | public function LinkToPendingItems() |
||
311 | { |
||
312 | $urlBase = Director::absoluteBaseURL(); |
||
313 | $urlFrag = 'admin/workflows/WorkflowDefinition/EditForm/field'; |
||
314 | $urlInst = $this->getWorkflowInstance(); |
||
315 | return Controller::join_links($urlBase, $urlFrag, 'PendingObjects', 'item', $urlInst->ID, 'edit'); |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * After a workflow item is written, we notify the |
||
320 | * workflow so that it can take action if needbe |
||
321 | */ |
||
322 | public function onAfterWrite() |
||
323 | { |
||
324 | $instance = $this->getWorkflowInstance(); |
||
325 | if ($instance && $instance->CurrentActionID) { |
||
326 | $action = $instance->CurrentAction()->BaseAction()->targetUpdated($instance); |
||
327 | } |
||
328 | } |
||
329 | |||
330 | public function WorkflowInstances() |
||
331 | { |
||
332 | return WorkflowInstance::get()->filter([ |
||
333 | 'TargetClass' => $this->owner->baseClass(), |
||
334 | 'TargetID' => $this->owner->ID |
||
335 | ]); |
||
336 | } |
||
337 | |||
338 | /** |
||
339 | * Gets the current instance of workflow |
||
340 | * |
||
341 | * @return WorkflowInstance |
||
342 | */ |
||
343 | public function getWorkflowInstance() |
||
344 | { |
||
345 | if (!$this->currentInstance) { |
||
346 | $this->currentInstance = $this->getWorkflowService()->getWorkflowFor($this->owner); |
||
0 ignored issues
–
show
|
|||
347 | } |
||
348 | |||
349 | return $this->currentInstance; |
||
350 | } |
||
351 | |||
352 | |||
353 | /** |
||
354 | * Gets the history of a workflow instance |
||
355 | * |
||
356 | * @return DataList |
||
357 | */ |
||
358 | public function getWorkflowHistory($limit = null) |
||
359 | { |
||
360 | return $this->getWorkflowService()->getWorkflowHistoryFor($this->owner, $limit); |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * Check all recent WorkflowActionIntances and return the most recent one with a Comment |
||
365 | * |
||
366 | * @param int $limit |
||
367 | * @return WorkflowActionInstance|null |
||
368 | */ |
||
369 | public function RecentWorkflowComment($limit = 10) |
||
370 | { |
||
371 | if ($actions = $this->getWorkflowHistory($limit)) { |
||
372 | foreach ($actions as $action) { |
||
373 | if ($action->Comment != '') { |
||
374 | return $action; |
||
375 | } |
||
376 | } |
||
377 | } |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * Content can never be directly publishable if there's a workflow applied. |
||
382 | * |
||
383 | * If there's an active instance, then it 'might' be publishable |
||
384 | */ |
||
385 | public function canPublish() |
||
386 | { |
||
387 | // Override any default behaviour, to allow queuedjobs to complete |
||
388 | if ($this->isPublishJobRunning()) { |
||
389 | return true; |
||
390 | } |
||
391 | |||
392 | if ($active = $this->getWorkflowInstance()) { |
||
393 | $publish = $active->canPublishTarget($this->owner); |
||
394 | if (!is_null($publish)) { |
||
395 | return $publish; |
||
396 | } |
||
397 | } |
||
398 | |||
399 | // use definition to determine if publishing directly is allowed |
||
400 | $definition = $this->getWorkflowService()->getDefinitionFor($this->owner); |
||
401 | |||
402 | if ($definition) { |
||
403 | if (!Security::getCurrentUser()) { |
||
404 | return false; |
||
405 | } |
||
406 | $member = Security::getCurrentUser(); |
||
407 | |||
408 | $canPublish = $definition->canWorkflowPublish($member, $this->owner); |
||
409 | |||
410 | return $canPublish; |
||
411 | } |
||
412 | } |
||
413 | |||
414 | /** |
||
415 | * Can only edit content that's NOT in another person's content changeset |
||
416 | * |
||
417 | * @return bool |
||
418 | */ |
||
419 | public function canEdit($member) |
||
420 | { |
||
421 | // Override any default behaviour, to allow queuedjobs to complete |
||
422 | if ($this->isPublishJobRunning()) { |
||
423 | return true; |
||
424 | } |
||
425 | |||
426 | if ($active = $this->getWorkflowInstance()) { |
||
427 | return $active->canEditTarget(); |
||
428 | } |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * Can a user edit the current workflow attached to this item? |
||
433 | * |
||
434 | * @return bool |
||
435 | */ |
||
436 | public function canEditWorkflow() |
||
437 | { |
||
438 | $active = $this->getWorkflowInstance(); |
||
439 | if ($active) { |
||
440 | return $active->canEdit(); |
||
441 | } |
||
442 | return false; |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * @param WorkflowService $workflowService |
||
447 | * @return $this |
||
448 | */ |
||
449 | public function setWorkflowService(WorkflowService $workflowService) |
||
450 | { |
||
451 | $this->workflowService = $workflowService; |
||
452 | return $this; |
||
453 | } |
||
454 | |||
455 | /** |
||
456 | * @return WorkflowService |
||
457 | */ |
||
458 | public function getWorkflowService() |
||
459 | { |
||
460 | return $this->workflowService; |
||
461 | } |
||
462 | } |
||
463 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.