These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * A WorkflowInstance is created whenever a user 'starts' a workflow. |
||
4 | * |
||
5 | * This 'start' is triggered automatically when the user clicks the relevant |
||
6 | * button (eg 'apply for approval'). This creates a standalone object |
||
7 | * that maintains the state of the workflow process. |
||
8 | * |
||
9 | * @method WorkflowDefinition Definition() |
||
10 | * @method WorkflowActionInstance CurrentAction() |
||
11 | * @method Member Initiator() |
||
12 | * |
||
13 | * @author [email protected] |
||
14 | * @license BSD License (http://silverstripe.org/bsd-license/) |
||
15 | * @package advancedworkflow |
||
16 | */ |
||
17 | class WorkflowInstance extends DataObject { |
||
18 | |||
19 | private static $db = array( |
||
20 | 'Title' => 'Varchar(128)', |
||
21 | 'WorkflowStatus' => "Enum('Active,Paused,Complete,Cancelled','Active')", |
||
22 | 'TargetClass' => 'Varchar(64)', |
||
23 | 'TargetID' => 'Int', |
||
24 | ); |
||
25 | |||
26 | private static $has_one = array( |
||
27 | 'Definition' => 'WorkflowDefinition', |
||
28 | 'CurrentAction' => 'WorkflowActionInstance', |
||
29 | 'Initiator' => 'Member', |
||
30 | ); |
||
31 | |||
32 | private static $has_many = array( |
||
33 | 'Actions' => 'WorkflowActionInstance', |
||
34 | ); |
||
35 | |||
36 | /** |
||
37 | * The list of users who are responsible for performing the current WorkflowAction |
||
38 | * |
||
39 | * @var array |
||
40 | */ |
||
41 | private static $many_many = array( |
||
42 | 'Users' => 'Member', |
||
43 | 'Groups' => 'Group' |
||
44 | ); |
||
45 | |||
46 | private static $summary_fields = array( |
||
47 | 'Title', |
||
48 | 'WorkflowStatus', |
||
49 | 'Created' |
||
50 | ); |
||
51 | |||
52 | /** |
||
53 | * If set to true, actions that cannot be executed by the user will not show |
||
54 | * on the frontend (just like the backend). |
||
55 | * |
||
56 | * @var boolean |
||
57 | */ |
||
58 | private static $hide_disabled_actions_on_frontend = false; |
||
59 | |||
60 | /** |
||
61 | * Get the CMS view of the instance. This is used to display the log of |
||
62 | * this workflow, and options to reassign if the workflow hasn't been |
||
63 | * finished yet |
||
64 | * |
||
65 | * @return \FieldList |
||
66 | */ |
||
67 | public function getCMSFields() { |
||
68 | $fields = new FieldList(); |
||
69 | |||
70 | if (Permission::check('REASSIGN_ACTIVE_WORKFLOWS')) { |
||
71 | if ($this->WorkflowStatus == 'Paused' || $this->WorkflowStatus == 'Active') { |
||
72 | $cmsUsers = Member::mapInCMSGroups(); |
||
73 | |||
74 | $fields->push(new HiddenField('DirectUpdate', '', 1)); |
||
75 | |||
76 | $fields->push(new HeaderField('InstanceReassignHeader',_t('WorkflowInstance.REASSIGN_HEADER', 'Reassign workflow'))); |
||
77 | $fields->push(new CheckboxSetField('Users', _t('WorkflowDefinition.USERS', 'Users'), $cmsUsers)); |
||
78 | $fields->push(new TreeMultiselectField('Groups', _t('WorkflowDefinition.GROUPS', 'Groups'), 'Group')); |
||
79 | |||
80 | } |
||
81 | } |
||
82 | |||
83 | if ($this->canEdit()) { |
||
84 | $action = $this->CurrentAction(); |
||
85 | if ($action->exists()) { |
||
86 | $actionFields = $this->getWorkflowFields(); |
||
87 | $fields->merge($actionFields); |
||
88 | |||
89 | $transitions = $action->getValidTransitions(); |
||
90 | if ($transitions) { |
||
91 | $fields->replaceField('TransitionID', DropdownField::create("TransitionID", "Next action", $transitions->map())); |
||
92 | } |
||
93 | } |
||
94 | } |
||
95 | |||
96 | $items = WorkflowActionInstance::get()->filter(array( |
||
97 | 'Finished' => 1, |
||
98 | 'WorkflowID' => $this->ID |
||
99 | )); |
||
100 | |||
101 | $grid = new GridField( |
||
102 | 'Actions', |
||
103 | _t('WorkflowInstance.ActionLogTitle','Log'), |
||
104 | $items |
||
105 | ); |
||
106 | |||
107 | $fields->push($grid); |
||
108 | |||
109 | return $fields; |
||
110 | } |
||
111 | |||
112 | public function fieldLabels($includerelations = true) { |
||
113 | $labels = parent::fieldLabels($includerelations); |
||
114 | $labels['Title'] = _t('WorkflowInstance.TitleLabel', 'Title'); |
||
115 | $labels['WorkflowStatus'] = _t('WorkflowInstance.WorkflowStatusLabel', 'Workflow Status'); |
||
116 | $labels['TargetClass'] = _t('WorkflowInstance.TargetClassLabel', 'Target Class'); |
||
117 | $labels['TargetID'] = _t('WorkflowInstance.TargetIDLabel', 'Target'); |
||
118 | |||
119 | return $labels; |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * See if we've been saved in context of managing the workflow directly |
||
124 | */ |
||
125 | public function onBeforeWrite() { |
||
126 | parent::onBeforeWrite(); |
||
127 | |||
128 | $vars = $this->record; |
||
129 | |||
130 | if (isset($vars['DirectUpdate'])) { |
||
131 | // Unset now so that we don't end up in an infinite loop! |
||
132 | unset($this->record['DirectUpdate']); |
||
133 | $this->updateWorkflow($vars); |
||
134 | } |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Update the current state of the workflow |
||
139 | * |
||
140 | * Typically, this is triggered by someone modifiying the workflow instance via the modeladmin form |
||
141 | * side of things when administering things, such as re-assigning or manually approving a stuck workflow |
||
142 | * |
||
143 | * Note that this is VERY similar to AdvancedWorkflowExtension::updateworkflow |
||
144 | * but without the formy bits. These two implementations should PROBABLY |
||
145 | * be merged |
||
146 | * |
||
147 | * @todo refactor with AdvancedWorkflowExtension |
||
148 | * |
||
149 | * @param type $data |
||
150 | * @return |
||
151 | */ |
||
152 | public function updateWorkflow($data) { |
||
153 | $action = $this->CurrentAction(); |
||
154 | |||
155 | if (!$this->getTarget() || !$this->getTarget()->canEditWorkflow()) { |
||
156 | return; |
||
157 | } |
||
158 | |||
159 | $allowedFields = $this->getWorkflowFields()->saveableFields(); |
||
160 | unset($allowedFields['TransitionID']); |
||
161 | foreach ($allowedFields as $field) { |
||
162 | $fieldName = $field->getName(); |
||
163 | $action->$fieldName = $data[$fieldName]; |
||
164 | } |
||
165 | $action->write(); |
||
166 | |||
167 | $svc = singleton('WorkflowService'); |
||
168 | if (isset($data['TransitionID']) && $data['TransitionID']) { |
||
169 | $svc->executeTransition($this->getTarget(), $data['TransitionID']); |
||
170 | } else { |
||
171 | // otherwise, just try to execute the current workflow to see if it |
||
172 | // can now proceed based on user input |
||
173 | $this->execute(); |
||
174 | } |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * Get the target-object that this WorkflowInstance "points" to. |
||
179 | * |
||
180 | * Workflows are not restricted to being active on SiteTree objects, |
||
181 | * so we need to account for being attached to anything. |
||
182 | * |
||
183 | * Sets Versioned::set_reading_mode() to allow fetching of Draft _and_ Published |
||
184 | * content. |
||
185 | * |
||
186 | * @return (null | DataObject) |
||
187 | */ |
||
188 | public function getTarget() { |
||
189 | if($this->TargetID && $this->TargetClass) { |
||
190 | $versionable = Injector::inst()->get($this->TargetClass)->has_extension('Versioned'); |
||
191 | if($versionable) { |
||
192 | Versioned::set_reading_mode("Stage.Stage"); |
||
193 | } |
||
194 | |||
195 | // Default |
||
196 | return DataObject::get_by_id($this->TargetClass, $this->TargetID); |
||
197 | } |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * |
||
202 | * @see {@link {$this->getTarget()} |
||
203 | * @return (null | DataObject) |
||
204 | */ |
||
205 | public function Target() { |
||
206 | return $this->getTarget(); |
||
207 | } |
||
208 | |||
209 | /** |
||
210 | * Start a workflow based on a particular definition for a particular object. |
||
211 | * |
||
212 | * The object is optional; if not specified, it is assumed that this workflow |
||
213 | * is simply a task based checklist type of workflow. |
||
214 | * |
||
215 | * @param WorkflowDefinition $definition |
||
216 | * @param DataObject $for |
||
217 | */ |
||
218 | public function beginWorkflow(WorkflowDefinition $definition, DataObject $for=null) { |
||
219 | if(!$this->ID) { |
||
220 | $this->write(); |
||
221 | } |
||
222 | |||
223 | if ($for && ($for->hasExtension('WorkflowApplicable') || $for->hasExtension('FileWorkflowApplicable'))) { |
||
224 | $this->TargetClass = ClassInfo::baseDataClass($for); |
||
225 | $this->TargetID = $for->ID; |
||
226 | } |
||
227 | |||
228 | // lets create the first WorkflowActionInstance. |
||
229 | $action = $definition->getInitialAction()->getInstanceForWorkflow(); |
||
230 | $action->WorkflowID = $this->ID; |
||
231 | $action->write(); |
||
232 | |||
233 | $title = $for && $for->hasField('Title') ? |
||
234 | sprintf(_t('WorkflowInstance.TITLE_FOR_DO', '%s - %s'), $definition->Title, $for->Title) : |
||
235 | sprintf(_t('WorkflowInstance.TITLE_STUB', 'Instance #%s of %s'), $this->ID, $definition->Title); |
||
236 | |||
237 | $this->Title = $title; |
||
238 | $this->DefinitionID = $definition->ID; |
||
239 | $this->CurrentActionID = $action->ID; |
||
240 | $this->InitiatorID = Member::currentUserID(); |
||
241 | $this->write(); |
||
242 | |||
243 | $this->Users()->addMany($definition->Users()); |
||
244 | $this->Groups()->addMany($definition->Groups()); |
||
245 | } |
||
246 | |||
247 | /** |
||
248 | * Execute this workflow. In rare cases this will actually execute all actions, |
||
249 | * but typically, it will stop and wait for the user to input something |
||
250 | * |
||
251 | * The basic process is to get the current action, and see whether it has been finished |
||
252 | * by some process, if not it attempts to execute it. |
||
253 | * |
||
254 | * If it has been finished, we check to see if there's some transitions to follow. If there's |
||
255 | * only one transition, then we execute that immediately. |
||
256 | * |
||
257 | * If there's multiple transitions, we just stop and wait for the user to manually |
||
258 | * trigger a transition. |
||
259 | * |
||
260 | * If there's no transitions, we make the assumption that we've finished the workflow and |
||
261 | * mark it as such. |
||
262 | * |
||
263 | * |
||
264 | */ |
||
265 | public function execute() { |
||
266 | if (!$this->CurrentActionID) { |
||
267 | throw new Exception( |
||
268 | sprintf(_t('WorkflowInstance.EXECUTE_EXCEPTION', 'Attempted to start an invalid workflow instance #%s!'), $this->ID) |
||
269 | ); |
||
270 | } |
||
271 | |||
272 | $action = $this->CurrentAction(); |
||
273 | $transition = false; |
||
274 | |||
275 | // if the action has already finished, it means it has either multiple (or no |
||
276 | // transitions at the time), so a subsequent check should be run. |
||
277 | if($action->Finished) { |
||
278 | $transition = $this->checkTransitions($action); |
||
279 | } else { |
||
280 | $result = $action->BaseAction()->execute($this); |
||
281 | |||
282 | // if the action was successful, then the action has finished running and |
||
283 | // next transition should be run (if only one). |
||
284 | // input. |
||
285 | if($result) { |
||
286 | $action->MemberID = Member::currentUserID(); |
||
287 | $action->Finished = true; |
||
288 | $action->write(); |
||
289 | $transition = $this->checkTransitions($action); |
||
290 | } |
||
291 | } |
||
292 | |||
293 | // if the action finished, and there's only one available transition then |
||
294 | // move onto that step - otherwise check if the workflow has finished. |
||
295 | if($transition) { |
||
296 | $this->performTransition($transition); |
||
297 | } else { |
||
298 | // see if there are any transitions available, even if they are not valid. |
||
299 | if($action->Finished && !count($action->BaseAction()->Transitions())) { |
||
300 | $this->WorkflowStatus = 'Complete'; |
||
301 | $this->CurrentActionID = 0; |
||
302 | } else { |
||
303 | $this->WorkflowStatus = 'Paused'; |
||
304 | } |
||
305 | |||
306 | $this->write(); |
||
307 | } |
||
308 | } |
||
309 | |||
310 | /** |
||
311 | * Evaluate all the transitions of an action and determine whether we should |
||
312 | * follow any of them yet. |
||
313 | * |
||
314 | * @param WorkflowActionInstance $action |
||
315 | * @return WorkflowTransition |
||
316 | */ |
||
317 | protected function checkTransitions(WorkflowActionInstance $action) { |
||
318 | $transitions = $action->getValidTransitions(); |
||
319 | // if there's JUST ONE transition, then we need should |
||
320 | // immediately follow it. |
||
321 | if ($transitions && $transitions->count() == 1) { |
||
322 | return $transitions->First(); |
||
323 | } |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Transitions a workflow to the next step defined by the given transition. |
||
328 | * |
||
329 | * After transitioning, the action is 'executed', and next steps |
||
330 | * determined. |
||
331 | * |
||
332 | * @param WorkflowTransition $transition |
||
333 | */ |
||
334 | public function performTransition(WorkflowTransition $transition) { |
||
335 | // first make sure that the transition is valid to execute! |
||
336 | $action = $this->CurrentAction(); |
||
337 | $allTransitions = $action->BaseAction()->Transitions(); |
||
338 | |||
339 | $valid = $allTransitions->find('ID', $transition->ID); |
||
340 | if (!$valid) { |
||
341 | throw new Exception ( |
||
342 | sprintf(_t('WorkflowInstance.WORKFLOW_TRANSITION_EXCEPTION', 'Invalid transition state for action #%s'), $action->ID) |
||
343 | ); |
||
344 | |||
345 | } |
||
346 | |||
347 | $action->actionComplete($transition); |
||
348 | |||
349 | $definition = DataObject::get_by_id('WorkflowAction', $transition->NextActionID); |
||
350 | $action = $definition->getInstanceForWorkflow(); |
||
351 | $action->WorkflowID = $this->ID; |
||
352 | $action->write(); |
||
353 | |||
354 | $this->CurrentActionID = $action->ID; |
||
355 | $this->write(); |
||
356 | $this->components = array(); // manually clear the has_one cache |
||
357 | |||
358 | $action->actionStart($transition); |
||
359 | |||
360 | $transition->extend('onTransition'); |
||
361 | $this->execute(); |
||
362 | } |
||
363 | |||
364 | /** |
||
365 | * Returns a list of all Members that are assigned to this instance, either directly or via a group. |
||
366 | * |
||
367 | * @todo This could be made more efficient. |
||
368 | * @return ArrayList |
||
369 | */ |
||
370 | View Code Duplication | public function getAssignedMembers() { |
|
371 | $list = new ArrayList(); |
||
372 | $groups = $this->Groups(); |
||
373 | |||
374 | $list->merge($this->Users()); |
||
375 | |||
376 | foreach($groups as $group) { |
||
377 | $list->merge($group->Members()); |
||
378 | } |
||
379 | |||
380 | $list->removeDuplicates(); |
||
381 | return $list; |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * |
||
386 | * @param \Member $member |
||
387 | * @return boolean |
||
388 | */ |
||
389 | public function canView($member=null) { |
||
390 | $extended = $this->extendedCan(__FUNCTION__, $member); |
||
391 | if($extended !== null) return $extended; |
||
392 | |||
393 | $hasAccess = $this->userHasAccess($member); |
||
394 | /* |
||
395 | * If the next action is AssignUsersToWorkflowAction, execute() resets all user+group relations. |
||
396 | * Therefore current user no-longer has permission to view this WorkflowInstance in PendingObjects Gridfield, even though; |
||
397 | * - She had permissions granted via the workflow definition to run the preceeding Action that took her here. |
||
398 | */ |
||
399 | if(!$hasAccess) { |
||
400 | if($this->getMostRecentActionForUser($member)) { |
||
401 | return true; |
||
402 | } |
||
403 | } |
||
404 | return $hasAccess; |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * |
||
409 | * @param \Member $member |
||
410 | * @return boolean |
||
411 | */ |
||
412 | public function canEdit($member = null) { |
||
413 | $extended = $this->extendedCan(__FUNCTION__, $member); |
||
414 | if($extended !== null) return $extended; |
||
415 | |||
416 | return $this->userHasAccess($member); |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * |
||
421 | * @param \Member $member |
||
422 | * @return boolean |
||
423 | */ |
||
424 | public function canDelete($member = null) { |
||
425 | $extended = $this->extendedCan(__FUNCTION__, $member); |
||
426 | if($extended !== null) return $extended; |
||
427 | |||
428 | if(Permission::checkMember($member, "DELETE_WORKFLOW")) { |
||
429 | return true; |
||
430 | } |
||
431 | return false; |
||
432 | } |
||
433 | |||
434 | /** |
||
435 | * Checks whether the given user is in the list of users assigned to this |
||
436 | * workflow |
||
437 | * |
||
438 | * @param $memberID |
||
439 | */ |
||
440 | protected function userHasAccess($member) { |
||
441 | if (!$member) { |
||
442 | if (!Member::currentUserID()) { |
||
443 | return false; |
||
444 | } |
||
445 | $member = Member::currentUser(); |
||
446 | } |
||
447 | |||
448 | if(Permission::checkMember($member, "ADMIN")) { |
||
449 | return true; |
||
450 | } |
||
451 | |||
452 | // This method primarily "protects" access to a WorkflowInstance, but assumes access only to be granted to users assigned-to that WorkflowInstance. |
||
453 | // However; lowly authors (users entering items into a workflow) are not assigned - but we still wish them to see their submitted content. |
||
454 | $inWorkflowGroupOrUserTables = ($member->inGroups($this->Groups()) || $this->Users()->find('ID', $member->ID)); |
||
455 | // This method is used in more than just the ModelAdmin. Check for the current controller to determine where canView() expectations differ |
||
456 | if($this->getTarget() && Controller::curr()->getAction() == 'index' && !$inWorkflowGroupOrUserTables) { |
||
457 | if($this->getVersionedConnection($this->getTarget()->ID, $member->ID)) { |
||
458 | return true; |
||
459 | } |
||
460 | return false; |
||
461 | } |
||
462 | return $inWorkflowGroupOrUserTables; |
||
463 | } |
||
464 | |||
465 | /** |
||
466 | * Can documents in the current workflow state be edited? |
||
467 | */ |
||
468 | public function canEditTarget() { |
||
469 | if ($this->CurrentActionID) { |
||
470 | return $this->CurrentAction()->canEditTarget($this->getTarget()); |
||
0 ignored issues
–
show
|
|||
471 | } |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Does this action restrict viewing of the document? |
||
476 | * |
||
477 | * @return boolean |
||
478 | */ |
||
479 | public function canViewTarget() { |
||
480 | $action = $this->CurrentAction(); |
||
481 | if ($action) { |
||
482 | return $action->canViewTarget($this->getTarget()); |
||
483 | } |
||
484 | return true; |
||
485 | } |
||
486 | |||
487 | /** |
||
488 | * Does this action restrict the publishing of a document? |
||
489 | * |
||
490 | * @return boolean |
||
491 | */ |
||
492 | public function canPublishTarget() { |
||
493 | if ($this->CurrentActionID) { |
||
494 | return $this->CurrentAction()->canPublishTarget($this->getTarget()); |
||
0 ignored issues
–
show
It seems like
$this->getTarget() can be null ; however, canPublishTarget() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
Loading history...
|
|||
495 | } |
||
496 | } |
||
497 | |||
498 | /** |
||
499 | * Get the current set of transitions that are valid for the current workflow state, |
||
500 | * and are available to the current user. |
||
501 | * |
||
502 | * @return array |
||
503 | */ |
||
504 | public function validTransitions() { |
||
505 | $action = $this->CurrentAction(); |
||
506 | $transitions = $action->getValidTransitions(); |
||
507 | |||
508 | // Filter by execute permission |
||
509 | $self = $this; |
||
510 | return $transitions->filterByCallback(function($transition) use ($self) { |
||
511 | return $transition->canExecute($self); |
||
512 | }); |
||
513 | } |
||
514 | |||
515 | /* UI RELATED METHODS */ |
||
516 | |||
517 | /** |
||
518 | * Gets fields for managing this workflow instance in its current step |
||
519 | * |
||
520 | * @return FieldList |
||
521 | */ |
||
522 | public function getWorkflowFields() { |
||
523 | $action = $this->CurrentAction(); |
||
524 | $options = $this->validTransitions(); |
||
525 | $wfOptions = $options->map('ID', 'Title', ' '); |
||
526 | $fields = new FieldList(); |
||
527 | |||
528 | $fields->push(new HeaderField('WorkflowHeader', $action->Title)); |
||
529 | |||
530 | $fields->push(HiddenField::create('TransitionID', '')); |
||
531 | // Let the Active Action update the fields that the user can interact with so that data can be |
||
532 | // stored for the workflow. |
||
533 | $action->updateWorkflowFields($fields); |
||
534 | |||
535 | return $fields; |
||
536 | } |
||
537 | |||
538 | /** |
||
539 | * Gets Front-End form fields from current Action |
||
540 | * |
||
541 | * @return FieldList |
||
542 | */ |
||
543 | public function getFrontEndWorkflowFields() { |
||
544 | $action = $this->CurrentAction(); |
||
545 | |||
546 | $fields = new FieldList(); |
||
547 | $action->updateFrontEndWorkflowFields($fields); |
||
548 | |||
549 | return $fields; |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * Gets Transitions for display as Front-End Form Actions |
||
554 | * |
||
555 | * @return FieldList |
||
556 | */ |
||
557 | public function getFrontEndWorkflowActions() { |
||
558 | $action = $this->CurrentAction(); |
||
559 | $options = $action->getValidTransitions(); |
||
560 | $actions = new FieldList(); |
||
561 | |||
562 | $hide_disabled_actions_on_frontend = $this->config()->hide_disabled_actions_on_frontend; |
||
563 | |||
564 | foreach ($options as $option) { |
||
565 | $btn = new FormAction("transition_{$option->ID}", $option->Title); |
||
566 | |||
567 | // add cancel class to passive actions, this prevents js validation (using jquery.validate) |
||
568 | if($option->Type == 'Passive'){ |
||
569 | $btn->addExtraClass('cancel'); |
||
570 | } |
||
571 | |||
572 | // disable the button if canExecute() returns false |
||
573 | if(!$option->canExecute($this)) |
||
574 | { |
||
575 | if ($hide_disabled_actions_on_frontend) |
||
576 | { |
||
577 | continue; |
||
578 | } |
||
579 | |||
580 | $btn = $btn->performReadonlyTransformation(); |
||
581 | $btn->addExtraClass('hide'); |
||
582 | } |
||
583 | |||
584 | $actions->push($btn); |
||
585 | } |
||
586 | |||
587 | $action->updateFrontEndWorkflowActions($actions); |
||
588 | |||
589 | return $actions; |
||
590 | } |
||
591 | |||
592 | /** |
||
593 | * Gets Front-End DataObject |
||
594 | * |
||
595 | * @return DataObject |
||
596 | */ |
||
597 | public function getFrontEndDataObject() { |
||
598 | $action = $this->CurrentAction(); |
||
599 | $obj = $action->getFrontEndDataObject(); |
||
600 | |||
601 | return $obj; |
||
602 | } |
||
603 | |||
604 | /** |
||
605 | * Gets Front-End DataObject |
||
606 | * |
||
607 | * @return DataObject |
||
608 | */ |
||
609 | public function getFrontEndRequiredFields() { |
||
610 | $action = $this->CurrentAction(); |
||
611 | $validator = $action->getRequiredFields(); |
||
612 | |||
613 | return $validator; |
||
614 | } |
||
615 | |||
616 | public function setFrontendFormRequirements() { |
||
617 | $action = $this->CurrentAction(); |
||
618 | $action->setFrontendFormRequirements(); |
||
619 | } |
||
620 | |||
621 | public function doFrontEndAction(array $data, Form $form, SS_HTTPRequest $request) { |
||
622 | $action = $this->CurrentAction(); |
||
623 | $action->doFrontEndAction($data, $form, $request); |
||
624 | } |
||
625 | |||
626 | /* |
||
627 | * We need a way to "associate" an author with this WorkflowInstance and its Target() to see if she is "allowed" to view WorkflowInstances within GridFields |
||
628 | * @see {@link $this->userHasAccess()} |
||
629 | * |
||
630 | * @param number $recordID |
||
631 | * @param number $userID |
||
632 | * @param number $wasPublished |
||
633 | * @return boolean |
||
634 | */ |
||
635 | public function getVersionedConnection($recordID, $userID, $wasPublished = 0) { |
||
636 | // Turn this into an array and run through implode() |
||
637 | $filter = "RecordID = {$recordID} AND AuthorID = {$userID} AND WasPublished = {$wasPublished}"; |
||
638 | $query = new SQLQuery(); |
||
639 | $query->setFrom('"SiteTree_versions"')->setSelect('COUNT("ID")')->setWhere($filter); |
||
640 | $query->firstRow(); |
||
641 | $hasAuthored = $query->execute(); |
||
642 | if($hasAuthored) { |
||
643 | return true; |
||
644 | } |
||
645 | return false; |
||
646 | } |
||
647 | |||
648 | /* |
||
649 | * Simple method to retrieve the current action, on the current WorkflowInstance |
||
650 | */ |
||
651 | public function getCurrentAction() { |
||
652 | $join = '"WorkflowAction"."ID" = "WorkflowActionInstance"."BaseActionID"'; |
||
653 | $action = WorkflowAction::get() |
||
654 | ->leftJoin('WorkflowActionInstance',$join) |
||
655 | ->where('"WorkflowActionInstance"."ID" = '.$this->CurrentActionID) |
||
656 | ->first(); |
||
657 | if(!$action) { |
||
658 | return 'N/A'; |
||
659 | } |
||
660 | return $action->getField('Title'); |
||
661 | } |
||
662 | |||
663 | /** |
||
664 | * Tells us if $member has had permissions over some part of the current WorkflowInstance. |
||
665 | * |
||
666 | * @param $member |
||
667 | * @return \WorkflowAction | boolean |
||
668 | */ |
||
669 | public function getMostRecentActionForUser($member = null) { |
||
670 | if(!$member) { |
||
671 | if (!Member::currentUserID()) { |
||
672 | return false; |
||
673 | } |
||
674 | $member = Member::currentUser(); |
||
675 | } |
||
676 | |||
677 | // WorkflowActionInstances in reverse creation-order so we get the most recent one's first |
||
678 | $history = $this->Actions()->filter(array( |
||
679 | 'Finished' =>1, |
||
680 | 'BaseAction.ClassName' => 'AssignUsersToWorkflowAction' |
||
681 | ))->Sort('Created', 'DESC'); |
||
682 | |||
683 | $i=0; |
||
684 | foreach($history as $inst) { |
||
685 | /* |
||
686 | * This iteration represents the 1st instance in the list - the most recent AssignUsersToWorkflowAction in $history. |
||
687 | * If there's no match for $member here or on the _previous_ AssignUsersToWorkflowAction, then bail out: |
||
688 | */ |
||
689 | $assignedMembers = $inst->BaseAction()->getAssignedMembers(); |
||
690 | if($i<=1 && $assignedMembers->count()>0 && $assignedMembers->find('ID', $member->ID)) { |
||
691 | return $inst; |
||
692 | } |
||
693 | ++$i; |
||
694 | } |
||
695 | return false; |
||
696 | } |
||
697 | } |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: