Issues (621)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

code/dataobjects/WorkflowDefinition.php (1 issue)

Upgrade to new PHP Analysis Engine

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
The property Template does not exist on object<WorkflowDefinition>. Since you implemented __get, maybe consider adding a @property annotation.

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.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

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.

Loading history...
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