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/services/WorkflowService.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
 * A central point for interacting with workflows
4
 *
5
 * @author  [email protected]
6
 * @license BSD License (http://silverstripe.org/bsd-license/)
7
 * @package advancedworkflow
8
 */
9
class WorkflowService implements PermissionProvider {
10
	
11
	/**
12
	 * An array of templates that we can create from
13
	 * 
14
	 * @var array
15
	 */
16
	protected $templates;
17
	
18
	public function  __construct() {
19
	}
20
21
	
22
	/**
23
	 * Set the list of templates that can be created
24
	 * 
25
	 * @param type $templates 
26
	 */
27
	public function setTemplates($templates) {
28
		$this->templates = $templates;
29
	}
30
	
31
	/**
32
	 * Return the list of available templates
33
	 * @return type 
34
	 */
35
	public function getTemplates() {
36
		return $this->templates;
37
	}
38
	
39
	/**
40
	 * Get a template by name
41
	 * 
42
	 * @param string $name 
43
	 * @return WorkflowTemplate
44
	 */
45
	public function getNamedTemplate($name) {
46
		if($importedTemplate = singleton('WorkflowDefinitionImporter')->getImportedWorkflows($name)) {
47
			return $importedTemplate;
48
		}
49
		
50
		if (!is_array($this->templates)) {
51
			return;
52
		}
53
		foreach ($this->templates as $template) {
54
			if ($template->getName() == $name) {
55
				return $template;
56
			}
57
		}
58
	}
59
60
	/**
61
	 * Gets the workflow definition for a given dataobject, if there is one
62
	 * 
63
	 * Will recursively query parent elements until it finds one, if available
64
	 *
65
	 * @param DataObject $dataObject
66
	 */
67
	public function getDefinitionFor(DataObject $dataObject) {
68
		if ($dataObject->hasExtension('WorkflowApplicable') || $dataObject->hasExtension('FileWorkflowApplicable')) {
69
			if ($dataObject->WorkflowDefinitionID) {
70
				return DataObject::get_by_id('WorkflowDefinition', $dataObject->WorkflowDefinitionID);
71
			}
72
			if ($dataObject->hasMethod('useInheritedWorkflow') && !$this->useInheritedWorkflow()) {
73
				return null;
74
			}
75
			if ($dataObject->ParentID) {
76
				return $this->getDefinitionFor($dataObject->Parent());
77
			}
78
			if ($dataObject->hasMethod('workflowParent')) {
79
				$obj = $dataObject->workflowParent();
80
				if ($obj) {
81
					return $this->getDefinitionFor($obj);
82
				}
83
			}
84
		}
85
		return null;
86
	}
87
88
	/**
89
	 *	Retrieves a workflow definition by ID for a data object.
90
	 *
91
	 *	@param data object
92
	 *	@param integer
93
	 *	@return workflow definition
94
	 */
95
96
	public function getDefinitionByID($object, $workflowID) {
97
98
		// Make sure the correct extensions have been applied to the data object.
99
100
		$workflow = null;
101
		if($object->hasExtension('WorkflowApplicable') || $object->hasExtension('FileWorkflowApplicable')) {
102
103
			// Validate the workflow ID against the data object.
104
105
			if(($object->WorkflowDefinitionID == $workflowID) || ($workflow = $object->AdditionalWorkflowDefinitions()->byID($workflowID))) {
106
				if(is_null($workflow)) {
107
					$workflow = DataObject::get_by_id('WorkflowDefinition', $workflowID);
108
				}
109
			}
110
		}
111
		return $workflow ? $workflow : null;
112
	}
113
114
	/**
115
	 *	Retrieves and collates the workflow definitions for a data object, where the first element will be the main workflow definition.
116
	 *
117
	 *	@param data object
118
	 *	@return array
119
	 */
120
121
	public function getDefinitionsFor($object) {
122
123
		// Retrieve the main workflow definition.
124
125
		$default = $this->getDefinitionFor($object);
126
		if($default) {
127
128
			// Merge the additional workflow definitions.
129
130
			return array_merge(array(
131
				$default
132
			), $object->AdditionalWorkflowDefinitions()->toArray());
133
		}
134
		return null;
135
	}
136
137
	/**
138
	 * Gets the workflow for the given item
139
	 *
140
	 * The item can be
141
	 *
142
	 * a data object in which case the ActiveWorkflow will be returned,
143
	 * an action, in which case the Workflow will be returned
144
	 * an integer, in which case the workflow with that ID will be returned
145
	 *
146
	 * @param mixed $item
147
	 *
148
	 * @return WorkflowInstance
149
	 */
150
	public function getWorkflowFor($item, $includeComplete = false) {
151
		$id = $item;
152
153
		if ($item instanceof WorkflowAction) {
154
			$id = $item->WorkflowID;
155
			return DataObject::get_by_id('WorkflowInstance', $id);
156
		} else if (is_object($item) && ($item->hasExtension('WorkflowApplicable') || $item->hasExtension('FileWorkflowApplicable'))) {
157
			$filter = sprintf('"TargetClass" = \'%s\' AND "TargetID" = %d', ClassInfo::baseDataClass($item), $item->ID);
158
			$complete = $includeComplete ? 'OR "WorkflowStatus" = \'Complete\' ' : '';
159
			return DataObject::get_one('WorkflowInstance', $filter . ' AND ("WorkflowStatus" = \'Active\' OR "WorkflowStatus"=\'Paused\' ' . $complete . ')');
160
		}
161
	}
162
163
	/**
164
	 * Get all the workflow action instances for an item
165
	 *
166
	 * @return DataObjectSet
167
	 */
168
	public function getWorkflowHistoryFor($item, $limit = null){
169
		if($active = $this->getWorkflowFor($item, true)){
170
			$limit = $limit ? "0,$limit" : '';
171
			return $active->Actions('', 'ID DESC ', null, $limit);	
0 ignored issues
show
The method Actions() does not exist on WorkflowInstance. Did you maybe mean getFrontEndWorkflowActions()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
172
		}
173
	}
174
175
	/**
176
	 * Get all the available workflow definitions
177
	 *
178
	 * @return DataList
179
	 */
180
	public function getDefinitions() {
181
		return DataList::create('WorkflowDefinition');
182
	}
183
184
	/**
185
	 * Given a transition ID, figure out what should happen to
186
	 * the given $subject.
187
	 *
188
	 * In the normal case, this will load the current workflow instance for the object
189
	 * and then transition as expected. However, in some cases (eg to start the workflow)
190
	 * it is necessary to instead create a new instance. 
191
	 *
192
	 * @param DataObject $target
193
	 * @param int $transitionId
194
	 */
195
	public function executeTransition(DataObject $target, $transitionId) {
196
		$workflow   = $this->getWorkflowFor($target);
197
		$transition = DataObject::get_by_id('WorkflowTransition', $transitionId);
198
199
		if(!$transition) {
200
			throw new Exception(_t('WorkflowService.INVALID_TRANSITION_ID', "Invalid transition ID $transitionId"));
201
		}
202
203
		if(!$workflow) {
204
			throw new Exception(_t('WorkflowService.INVALID_WORKFLOW_TARGET', "A transition was executed on a target that does not have a workflow."));
205
		}
206
207
		if($transition->Action()->WorkflowDefID != $workflow->DefinitionID) {
208
			throw new Exception(_t('WorkflowService.INVALID_TRANSITION_WORKFLOW', "Transition #$transition->ID is not attached to workflow #$workflow->ID."));
209
		}
210
211
		$workflow->performTransition($transition);
212
	}
213
214
	/**
215
	 * Starts the workflow for the given data object, assuming it or a parent has
216
	 * a definition specified. 
217
	 * 
218
	 * @param DataObject $object
219
	 */
220
	public function startWorkflow(DataObject $object, $workflowID = null) {
221
		$existing = $this->getWorkflowFor($object);
222
		if ($existing) {
223
			throw new ExistingWorkflowException(_t('WorkflowService.EXISTING_WORKFLOW_ERROR', "That object already has a workflow running"));
224
		}
225
226
		$definition = null;
227
		if($workflowID) {
228
229
			// Retrieve the workflow definition that has been triggered.
230
231
			$definition = $this->getDefinitionByID($object, $workflowID);
232
		}
233
		if(is_null($definition)) {
234
235
			// Fall back to the main workflow definition.
236
237
			$definition = $this->getDefinitionFor($object);
238
		}
239
240
		if ($definition) {
241
			$instance = new WorkflowInstance();
242
			$instance->beginWorkflow($definition, $object);
243
			$instance->execute();
244
		}
245
	}
246
	
247
	/**
248
	 * Get all the workflows that this user is responsible for
249
	 * 
250
	 * @param Member $user 
251
	 *				The user to get workflows for
252
	 * 
253
	 * @return ArrayList
254
	 *				The list of workflow instances this user owns
255
	 */
256
	public function usersWorkflows(Member $user) {
257
		
258
		$groupIds = $user->Groups()->column('ID');
259
		
260
		$groupInstances = null;
261
		
262
		$filter = array('');
263
		
264
		if (is_array($groupIds)) {
265
			$groupInstances = DataList::create('WorkflowInstance')
266
				->filter(array('Group.ID:ExactMatchMulti' => $groupIds))
267
				->where('"WorkflowStatus" != \'Complete\'');
268
		}
269
270
		$userInstances = DataList::create('WorkflowInstance')
271
			->filter(array('Users.ID:ExactMatch' => $user->ID))
272
			->where('"WorkflowStatus" != \'Complete\'');
273
274
		if ($userInstances) {
275
			$userInstances = $userInstances->toArray();
276
		} else {
277
			$userInstances = array();
278
		}
279
		
280
		if ($groupInstances) {
281
			$groupInstances = $groupInstances->toArray();
282
		} else {
283
			$groupInstances = array();
284
		}
285
286
		$all = array_merge($groupInstances, $userInstances);
287
		
288
		return ArrayList::create($all);
289
	}
290
291
	/**
292
	 * Get items that the passed-in user has awaiting for them to action
293
	 * 
294
	 * @param Member $member
295
	 * @return DataList $userInstances
296
	 */
297
	public function userPendingItems(Member $user) {
298
		// Don't restrict anything for ADMIN users
299
		$userInstances = DataList::create('WorkflowInstance')
300
			->where('"WorkflowStatus" != \'Complete\'')
301
			->sort('LastEdited DESC');
302
303
		if(Permission::checkMember($user, 'ADMIN')) {
304
			return $userInstances;
305
		}
306
		$instances = new ArrayList();
307
		foreach($userInstances as $inst) {
308
			$instToArray = $inst->getAssignedMembers();
309
			if(!count($instToArray)>0 || !in_array($user->ID,$instToArray->column())) {
310
				continue;
311
			}
312
			$instances->push($inst);
313
		}
314
		
315
		return $instances;
316
	}
317
318
	/**
319
	 * Get items that the passed-in user has submitted for workflow review
320
	 *
321
	 * @param Member $member
322
	 * @return DataList $userInstances
323
	 */
324
	public function userSubmittedItems(Member $user) {
325
		$userInstances = DataList::create('WorkflowInstance')
326
			->where('"WorkflowStatus" != \'Complete\'')
327
			->sort('LastEdited DESC');
328
329
		// Restrict the user if they're not an ADMIN.
330
		if(!Permission::checkMember($user, 'ADMIN')) {
331
			$userInstances = $userInstances->filter('InitiatorID:ExactMatch', $user->ID);
332
		}
333
334
		return $userInstances;
335
	}
336
	
337
	/**
338
	 * Generate a workflow definition based on a template
339
	 * 
340
	 * @param WorkflowDefinition $definition
341
	 * @param string $templateName 
342
	 */
343
	public function defineFromTemplate(WorkflowDefinition $definition, $templateName) {
344
		$template = null;
345
		/* @var $template WorkflowTemplate */
346
		
347
		if (!is_array($this->templates)) {
348
			return;
349
		}
350
351
		$template = $this->getNamedTemplate($templateName);		
352
		
353
		if (!$template) {
354
			return;
355
		}
356
357
		$template->createRelations($definition);
358
		
359
		// Set the version and do the write at the end so that we don't trigger an infinite loop!!
360
		$definition->Description = $template->getDescription();
361
		$definition->TemplateVersion = $template->getVersion();
362
		$definition->RemindDays = $template->getRemindDays();
363
		$definition->Sort = $template->getSort();
364
		$definition->write();
365
		return $definition;
366
	}
367
368
	/**
369
	 * Reorders actions within a definition
370
	 *
371
	 * @param WorkflowDefinition|WorkflowAction $objects
372
	 *				The objects to be reordered
373
	 * @param array $newOrder
374
	 *				An array of IDs of the actions in the order they should be.
375
	 */
376
	public function reorder($objects, $newOrder) {
377
		$sortVals = array_values($objects->map('ID', 'Sort')->toArray());
378
		sort($sortVals);
379
380
		// save the new ID values - but only use existing sort values to prevent
381
		// conflicts with items not in the table
382
		foreach($newOrder as $key => $id) {
383
			if (!$id) {
384
				continue;
385
			}
386
			$object = $objects->find('ID', $id);
387
			$object->Sort = $sortVals[$key];
388
			$object->write();
389
		}
390
	}
391
392
	/**
393
	 * 
394
	 * @return array
395
	 */
396
	public function providePermissions() {
397
		return array(
398
			'CREATE_WORKFLOW' => array(
399
				'name' => _t('AdvancedWorkflow.CREATE_WORKFLOW', 'Create workflow'),
400
				'category' => _t('AdvancedWorkflow.ADVANCED_WORKFLOW', 'Advanced Workflow'),
401
				'help' => _t('AdvancedWorkflow.CREATE_WORKFLOW_HELP', 'Users can create workflow definitions'),
402
				'sort' => 0
403
			),
404
			'DELETE_WORKFLOW' => array(
405
				'name' => _t('AdvancedWorkflow.DELETE_WORKFLOW', 'Delete workflow'),
406
				'category' => _t('AdvancedWorkflow.ADVANCED_WORKFLOW', 'Advanced Workflow'),
407
				'help' => _t('AdvancedWorkflow.DELETE_WORKFLOW_HELP', 'Users can delete workflow definitions and active workflows'),
408
				'sort' => 0
409
			),
410
			'APPLY_WORKFLOW' => array(
411
				'name' => _t('AdvancedWorkflow.APPLY_WORKFLOW', 'Apply workflow'),
412
				'category' => _t('AdvancedWorkflow.ADVANCED_WORKFLOW', 'Advanced Workflow'),
413
				'help' => _t('AdvancedWorkflow.APPLY_WORKFLOW_HELP', 'Users can apply workflows to items'),
414
				'sort' => 0
415
			),
416
			'VIEW_ACTIVE_WORKFLOWS' => array(
417
				'name'     => _t('AdvancedWorkflow.VIEWACTIVE', 'View active workflows'),
418
				'category' => _t('AdvancedWorkflow.ADVANCED_WORKFLOW', 'Advanced Workflow'),
419
				'help'     => _t('AdvancedWorkflow.VIEWACTIVEHELP', 'Users can view active workflows via the workflows admin panel'),
420
				'sort'     => 0
421
			),
422
			'REASSIGN_ACTIVE_WORKFLOWS' => array(
423
				'name'     => _t('AdvancedWorkflow.REASSIGNACTIVE', 'Reassign active workflows'),
424
				'category' => _t('AdvancedWorkflow.ADVANCED_WORKFLOW', 'Advanced Workflow'),
425
				'help'     => _t('AdvancedWorkflow.REASSIGNACTIVEHELP', 'Users can reassign active workflows to different users and groups'),
426
				'sort'     => 0
427
			)
428
		);
429
	}
430
	
431
}
432
433
class ExistingWorkflowException extends Exception {};
434