This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * An overall definition of a workflow |
||
4 | * |
||
5 | * The workflow definition has a series of steps to it. Each step has a series of possible transitions |
||
6 | * that it can take - the first one that meets certain criteria is followed, which could lead to |
||
7 | * another step. |
||
8 | * |
||
9 | * A step is either manual or automatic; an example 'manual' step would be requiring a person to review |
||
10 | * a document. An automatic step might be to email a group of people, or to publish documents. |
||
11 | * Basically, a manual step requires the interaction of someone to pick which action to take, an automatic |
||
12 | * step will automatically determine what to do once it has finished. |
||
13 | * |
||
14 | * @author [email protected] |
||
15 | * @license BSD License (http://silverstripe.org/bsd-license/) |
||
16 | * @package advancedworkflow |
||
17 | */ |
||
18 | class WorkflowDefinition extends DataObject { |
||
19 | |||
20 | private static $db = array( |
||
21 | 'Title' => 'Varchar(128)', |
||
22 | 'Description' => 'Text', |
||
23 | 'Template' => 'Varchar', |
||
24 | 'TemplateVersion' => 'Varchar', |
||
25 | 'RemindDays' => 'Int', |
||
26 | 'Sort' => 'Int', |
||
27 | 'InitialActionButtonText' => 'Varchar' |
||
28 | ); |
||
29 | |||
30 | private static $default_sort = 'Sort'; |
||
31 | |||
32 | private static $has_many = array( |
||
33 | 'Actions' => 'WorkflowAction', |
||
34 | 'Instances' => 'WorkflowInstance' |
||
35 | ); |
||
36 | |||
37 | /** |
||
38 | * By default, a workflow definition is bound to a particular set of users or groups. |
||
39 | * |
||
40 | * This is covered across to the workflow instance - it is up to subsequent |
||
41 | * workflow actions to change this if needbe. |
||
42 | * |
||
43 | * @var array |
||
44 | */ |
||
45 | private static $many_many = array( |
||
46 | 'Users' => 'Member', |
||
47 | 'Groups' => 'Group' |
||
48 | ); |
||
49 | |||
50 | public static $icon = 'advancedworkflow/images/definition.png'; |
||
51 | |||
52 | public static $default_workflow_title_base = 'My Workflow'; |
||
53 | |||
54 | public static $workflow_defs = array(); |
||
55 | |||
56 | private static $dependencies = array( |
||
57 | 'workflowService' => '%$WorkflowService', |
||
58 | ); |
||
59 | |||
60 | /** |
||
61 | * @var WorkflowService |
||
62 | */ |
||
63 | public $workflowService; |
||
64 | |||
65 | /** |
||
66 | * Gets the action that first triggers off the workflow |
||
67 | * |
||
68 | * @return WorkflowAction |
||
69 | */ |
||
70 | public function getInitialAction() { |
||
71 | if($actions = $this->Actions()) return $actions->First(); |
||
72 | } |
||
73 | |||
74 | /** |
||
75 | * Ensure a sort value is set and we get a useable initial workflow title. |
||
76 | */ |
||
77 | public function onBeforeWrite() { |
||
78 | if(!$this->Sort) { |
||
79 | $this->Sort = DB::query('SELECT MAX("Sort") + 1 FROM "WorkflowDefinition"')->value(); |
||
80 | } |
||
81 | if(!$this->ID) { |
||
82 | $this->Title = $this->getDefaultWorkflowTitle(); |
||
83 | } |
||
84 | parent::onBeforeWrite(); |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * After we've been written, check whether we've got a template and to then |
||
89 | * create the relevant actions etc. |
||
90 | */ |
||
91 | public function onAfterWrite() { |
||
92 | parent::onAfterWrite(); |
||
93 | |||
94 | // Request via ImportForm where TemplateVersion is already set, so unset it |
||
95 | $posted = Controller::curr()->getRequest()->postVars(); |
||
96 | if(isset($posted['_CsvFile']) && $this->TemplateVersion) { |
||
97 | $this->TemplateVersion = null; |
||
98 | } |
||
99 | if($this->numChildren() == 0 && $this->Template && !$this->TemplateVersion) { |
||
100 | $this->workflowService->defineFromTemplate($this, $this->Template); |
||
101 | } |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * Ensure all WorkflowDefinition relations are removed on delete. If we don't do this, |
||
106 | * we see issues with targets previously under the control of a now-deleted workflow, |
||
107 | * becoming stuck, even if a new workflow is subsequently assigned to it. |
||
108 | * |
||
109 | * @return null |
||
110 | */ |
||
111 | public function onBeforeDelete() { |
||
112 | parent::onBeforeDelete(); |
||
113 | |||
114 | // Delete related import |
||
115 | $this->deleteRelatedImport(); |
||
116 | |||
117 | // Reset/unlink related HasMany|ManyMany relations and their orphaned objects |
||
118 | $this->removeRelatedHasLists(); |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * Removes User+Group relations from this object as well as WorkflowAction relations. |
||
123 | * When a WorkflowAction is deleted, its own relations are also removed: |
||
124 | * - WorkflowInstance |
||
125 | * - WorkflowTransition |
||
126 | * @see WorkflowAction::onAfterDelete() |
||
127 | * |
||
128 | * @return void |
||
129 | */ |
||
130 | private function removeRelatedHasLists() { |
||
131 | $this->Users()->removeAll(); |
||
132 | $this->Groups()->removeAll(); |
||
133 | $this->Actions()->each(function($action) { |
||
134 | if($orphan = DataObject::get_by_id('WorkflowAction', $action->ID)) { |
||
135 | $orphan->delete(); |
||
136 | } |
||
137 | }); |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * |
||
142 | * Deletes related ImportedWorkflowTemplate objects. |
||
143 | * |
||
144 | * @return void |
||
145 | */ |
||
146 | private function deleteRelatedImport() { |
||
147 | if($import = DataObject::get('ImportedWorkflowTemplate')->filter('DefinitionID', $this->ID)->first()) { |
||
148 | $import->delete(); |
||
149 | } |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * @return int |
||
154 | */ |
||
155 | public function numChildren() { |
||
156 | return count($this->Actions()); |
||
157 | } |
||
158 | |||
159 | public function fieldLabels($includerelations = true) { |
||
160 | $labels = parent::fieldLabels($includerelations); |
||
161 | $labels['Title'] = _t('WorkflowDefinition.TITLE', 'Title'); |
||
162 | $labels['Description'] = _t('WorkflowDefinition.DESCRIPTION', 'Description'); |
||
163 | $labels['Template'] = _t('WorkflowDefinition.TEMPLATE_NAME', 'Source Template'); |
||
164 | $labels['TemplateVersion'] = _t('WorkflowDefinition.TEMPLATE_VERSION', 'Template Version'); |
||
165 | |||
166 | return $labels; |
||
167 | } |
||
168 | |||
169 | public function getCMSFields() { |
||
170 | |||
171 | $cmsUsers = Member::mapInCMSGroups(); |
||
172 | |||
173 | $fields = new FieldList(new TabSet('Root')); |
||
174 | |||
175 | $fields->addFieldToTab('Root.Main', new TextField('Title', $this->fieldLabel('Title'))); |
||
176 | $fields->addFieldToTab('Root.Main', new TextareaField('Description', $this->fieldLabel('Description'))); |
||
177 | $fields->addFieldToTab('Root.Main', TextField::create( |
||
178 | 'InitialActionButtonText', |
||
179 | _t('WorkflowDefinition.INITIAL_ACTION_BUTTON_TEXT', 'Initial Action Button Text') |
||
180 | )); |
||
181 | if($this->ID) { |
||
182 | $fields->addFieldToTab('Root.Main', new CheckboxSetField('Users', _t('WorkflowDefinition.USERS', 'Users'), $cmsUsers)); |
||
183 | $fields->addFieldToTab('Root.Main', new TreeMultiselectField('Groups', _t('WorkflowDefinition.GROUPS', 'Groups'), 'Group')); |
||
184 | } |
||
185 | |||
186 | if (class_exists('AbstractQueuedJob')) { |
||
187 | $before = _t('WorkflowDefinition.SENDREMINDERDAYSBEFORE', 'Send reminder email after '); |
||
188 | $after = _t('WorkflowDefinition.SENDREMINDERDAYSAFTER', ' days without action.'); |
||
189 | |||
190 | $fields->addFieldToTab('Root.Main', new FieldGroup( |
||
191 | _t('WorkflowDefinition.REMINDEREMAIL', 'Reminder Email'), |
||
192 | new LabelField('ReminderEmailBefore', $before), |
||
193 | new NumericField('RemindDays', ''), |
||
194 | new LabelField('ReminderEmailAfter', $after) |
||
195 | )); |
||
196 | } |
||
197 | |||
198 | if($this->ID) { |
||
199 | if ($this->Template) { |
||
200 | $template = $this->workflowService->getNamedTemplate($this->Template); |
||
201 | $fields->addFieldToTab('Root.Main', new ReadonlyField('Template', $this->fieldLabel('Template'), $this->Template)); |
||
0 ignored issues
–
show
|
|||
202 | $fields->addFieldToTab('Root.Main', new ReadonlyField('TemplateDesc', _t('WorkflowDefinition.TEMPLATE_INFO', 'Template Info'), $template ? $template->getDescription() : '')); |
||
203 | $fields->addFieldToTab('Root.Main', $tv = new ReadonlyField('TemplateVersion', $this->fieldLabel('TemplateVersion'))); |
||
204 | $tv->setRightTitle(sprintf(_t('WorkflowDefinition.LATEST_VERSION', 'Latest version is %s'), $template ? $template->getVersion() : '')); |
||
205 | |||
206 | } |
||
207 | |||
208 | $fields->addFieldToTab('Root.Main', new WorkflowField( |
||
209 | 'Workflow', _t('WorkflowDefinition.WORKFLOW', 'Workflow'), $this |
||
210 | )); |
||
211 | } else { |
||
212 | // add in the 'template' info |
||
213 | $templates = $this->workflowService->getTemplates(); |
||
214 | |||
215 | if (is_array($templates)) { |
||
216 | $items = array('' => ''); |
||
217 | foreach ($templates as $template) { |
||
218 | $items[$template->getName()] = $template->getName(); |
||
219 | } |
||
220 | $templates = array_combine(array_keys($templates), array_keys($templates)); |
||
221 | |||
222 | $fields->addFieldToTab('Root.Main', $dd = new DropdownField('Template', _t('WorkflowDefinition.CHOOSE_TEMPLATE', 'Choose template (optional)'), $items)); |
||
223 | $dd->setRightTitle(_t('WorkflowDefinition.CHOOSE_TEMPLATE_RIGHT', 'If set, this workflow definition will be automatically updated if the template is changed')); |
||
224 | } |
||
225 | |||
226 | /* |
||
227 | * Uncomment to allow pre-uploaded exports to appear in a new DropdownField. |
||
228 | * |
||
229 | * $import = singleton('WorkflowDefinitionImporter')->getImportedWorkflows(); |
||
230 | * if (is_array($import)) { |
||
231 | * $_imports = array('' => ''); |
||
232 | * foreach ($imports as $import) { |
||
233 | * $_imports[$import->getName()] = $import->getName(); |
||
234 | * } |
||
235 | * $imports = array_combine(array_keys($_imports), array_keys($_imports)); |
||
236 | * $fields->addFieldToTab('Root.Main', new DropdownField('Import', _t('WorkflowDefinition.CHOOSE_IMPORT', 'Choose import (optional)'), $imports)); |
||
237 | * } |
||
238 | */ |
||
239 | |||
240 | $message = _t( |
||
241 | 'WorkflowDefinition.ADDAFTERSAVING', |
||
242 | 'You can add workflow steps after you save for the first time.' |
||
243 | ); |
||
244 | $fields->addFieldToTab('Root.Main', new LiteralField( |
||
245 | 'AddAfterSaving', "<p class='message notice'>$message</p>" |
||
246 | )); |
||
247 | } |
||
248 | |||
249 | if($this->ID && Permission::check('VIEW_ACTIVE_WORKFLOWS')) { |
||
250 | $active = $this->Instances()->filter(array( |
||
251 | 'WorkflowStatus' => array('Active', 'Paused') |
||
252 | )); |
||
253 | |||
254 | $active = new GridField( |
||
255 | 'Active', |
||
256 | _t('WorkflowDefinition.WORKFLOWACTIVEIINSTANCES', 'Active Workflow Instances'), |
||
257 | $active, |
||
258 | new GridFieldConfig_RecordEditor()); |
||
259 | |||
260 | $active->getConfig()->removeComponentsByType('GridFieldAddNewButton'); |
||
261 | $active->getConfig()->removeComponentsByType('GridFieldDeleteAction'); |
||
262 | |||
263 | if(!Permission::check('REASSIGN_ACTIVE_WORKFLOWS')) { |
||
264 | $active->getConfig()->removeComponentsByType('GridFieldEditButton'); |
||
265 | $active->getConfig()->addComponent(new GridFieldViewButton()); |
||
266 | $active->getConfig()->addComponent(new GridFieldDetailForm()); |
||
267 | } |
||
268 | |||
269 | $completed = $this->Instances()->filter(array( |
||
270 | 'WorkflowStatus' => array('Complete', 'Cancelled') |
||
271 | )); |
||
272 | |||
273 | $config = new GridFieldConfig_Base(); |
||
274 | $config->addComponent(new GridFieldEditButton()); |
||
275 | $config->addComponent(new GridFieldDetailForm()); |
||
276 | |||
277 | $completed = new GridField( |
||
278 | 'Completed', |
||
279 | _t('WorkflowDefinition.WORKFLOWCOMPLETEDIINSTANCES', 'Completed Workflow Instances'), |
||
280 | $completed, |
||
281 | $config); |
||
282 | |||
283 | $fields->findOrMakeTab( |
||
284 | 'Root.Active', |
||
285 | _t('WorkflowEmbargoExpiryExtension.ActiveWorkflowStateTitle', 'Active') |
||
286 | ); |
||
287 | $fields->addFieldToTab('Root.Active', $active); |
||
288 | |||
289 | $fields->findOrMakeTab( |
||
290 | 'Root.Completed', |
||
291 | _t('WorkflowEmbargoExpiryExtension.CompletedWorkflowStateTitle', 'Completed') |
||
292 | ); |
||
293 | $fields->addFieldToTab('Root.Completed', $completed); |
||
294 | } |
||
295 | |||
296 | $this->extend('updateCMSFields', $fields); |
||
297 | |||
298 | return $fields; |
||
299 | } |
||
300 | |||
301 | public function updateAdminActions($actions) { |
||
302 | if ($this->Template) { |
||
303 | $template = $this->workflowService->getNamedTemplate($this->Template); |
||
304 | if ($template && $this->TemplateVersion != $template->getVersion()) { |
||
305 | $label = sprintf(_t('WorkflowDefinition.UPDATE_FROM_TEMLPATE', 'Update to latest template version (%s)'), $template->getVersion()); |
||
306 | $actions->push($action = FormAction::create('updatetemplateversion', $label)); |
||
307 | } |
||
308 | } |
||
309 | } |
||
310 | |||
311 | public function updateFromTemplate() { |
||
312 | if ($this->Template) { |
||
313 | $template = $this->workflowService->getNamedTemplate($this->Template); |
||
314 | $template->updateDefinition($this); |
||
315 | } |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * If a workflow-title doesn't already exist, we automatically create a suitable default title |
||
320 | * when users attempt to create title-less workflow definitions or upload/create Workflows that would |
||
321 | * otherwise have the same name. |
||
322 | * |
||
323 | * @return string |
||
324 | * @todo Filter query on current-user's workflows. Avoids confusion when other users may already have 'My Workflow 1' |
||
325 | * and user sees 'My Workflow 2' |
||
326 | */ |
||
327 | public function getDefaultWorkflowTitle() { |
||
328 | // Where is the title coming from that we wish to test? |
||
329 | $incomingTitle = $this->incomingTitle(); |
||
330 | $defs = DataObject::get('WorkflowDefinition')->map()->toArray(); |
||
331 | $tmp = array(); |
||
332 | |||
333 | foreach($defs as $def) { |
||
334 | $parts = preg_split("#\s#", $def, -1, PREG_SPLIT_NO_EMPTY); |
||
335 | $lastPart = array_pop($parts); |
||
336 | $match = implode(' ', $parts); |
||
337 | // @todo do all this in one preg_match_all() call |
||
338 | if(preg_match("#$match#", $incomingTitle)) { |
||
339 | // @todo use a simple incrementer?? |
||
340 | if($incomingTitle.' '.$lastPart == $def) { |
||
341 | array_push($tmp, $lastPart); |
||
342 | } |
||
343 | } |
||
344 | } |
||
345 | |||
346 | $incr = 1; |
||
347 | if(count($tmp)) { |
||
348 | sort($tmp,SORT_NUMERIC); |
||
349 | $incr = (int)end($tmp)+1; |
||
350 | } |
||
351 | return $incomingTitle.' '.$incr; |
||
352 | } |
||
353 | |||
354 | /** |
||
355 | * Return the workflow definition title according to the source |
||
356 | * |
||
357 | * @return string |
||
358 | */ |
||
359 | public function incomingTitle() { |
||
360 | $req = Controller::curr()->getRequest(); |
||
361 | if(isset($req['_CsvFile']['name']) && !empty($req['_CsvFile']['name'])) { |
||
362 | $import = DataObject::get('ImportedWorkflowTemplate')->filter('Filename', $req['_CsvFile']['name'])->first(); |
||
363 | $incomingTitle = $import->Name; |
||
364 | } |
||
365 | else if(isset($req['Template']) && !empty($req['Template'])) { |
||
366 | $incomingTitle = $req['Template']; |
||
367 | } |
||
368 | else if(isset($req['Title']) && !empty($req['Title'])) { |
||
369 | $incomingTitle = $req['Title']; |
||
370 | } |
||
371 | else { |
||
372 | $incomingTitle = self::$default_workflow_title_base; |
||
373 | } |
||
374 | return $incomingTitle; |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * |
||
379 | * @param Member $member |
||
380 | * @return boolean |
||
381 | */ |
||
382 | View Code Duplication | public function canCreate($member=null) { |
|
383 | if (is_null($member)) { |
||
384 | if (!Member::currentUserID()) { |
||
385 | return false; |
||
386 | } |
||
387 | $member = Member::currentUser(); |
||
388 | } |
||
389 | return Permission::checkMember($member, 'CREATE_WORKFLOW'); |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * |
||
394 | * @param Member $member |
||
395 | * @return boolean |
||
396 | */ |
||
397 | public function canView($member=null) { |
||
398 | return $this->userHasAccess($member); |
||
399 | } |
||
400 | |||
401 | /** |
||
402 | * |
||
403 | * @param Member $member |
||
404 | * @return boolean |
||
405 | */ |
||
406 | public function canEdit($member=null) { |
||
407 | return $this->canCreate($member); |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * |
||
412 | * @param Member $member |
||
413 | * @return boolean |
||
414 | * @see {@link $this->onBeforeDelete()} |
||
415 | */ |
||
416 | View Code Duplication | public function canDelete($member = null) { |
|
417 | if(!$member) { |
||
418 | if(!Member::currentUserID()) { |
||
419 | return false; |
||
420 | } |
||
421 | $member = Member::currentUser(); |
||
422 | } |
||
423 | |||
424 | if(Permission::checkMember($member, 'ADMIN')) { |
||
425 | return true; |
||
426 | } |
||
427 | |||
428 | /* |
||
429 | * DELETE_WORKFLOW should trump all other canDelete() return values on |
||
430 | * related objects. |
||
431 | * @see {@link $this->onBeforeDelete()} |
||
432 | */ |
||
433 | return Permission::checkMember($member, 'DELETE_WORKFLOW'); |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Checks whether the passed user is able to view this ModelAdmin |
||
438 | * |
||
439 | * @param $memberID |
||
440 | */ |
||
441 | View Code Duplication | protected function userHasAccess($member) { |
|
442 | if (!$member) { |
||
443 | if (!Member::currentUserID()) { |
||
444 | return false; |
||
445 | } |
||
446 | $member = Member::currentUser(); |
||
447 | } |
||
448 | |||
449 | if(Permission::checkMember($member, "VIEW_ACTIVE_WORKFLOWS")) { |
||
450 | return true; |
||
451 | } |
||
452 | } |
||
453 | } |
||
454 |
Since your code implements the magic getter
_get
, this function will be called for any read access on an undefined variable. You can add the@property
annotation to your class or interface to document the existence of this variable.If the property has read access only, you can use the @property-read annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.