Skip to content
This repository
Browse code

Implemented a new editing interface compatible with SS3.

  • Loading branch information...
commit 6c94de9416cecf7461eb05bbac362d0688f3f7ca 1 parent 46f778a
Andrew Short authored May 06, 2012
14  _config.php
@@ -4,12 +4,18 @@
4 4
  * @package advancedworkflow
5 5
  */
6 6
 
7  
-// Add the following to your config to enable workflow 
  7
+// Add the following to your config to enable workflow
8 8
 // DataObject::add_extension('SiteTree', 'WorkflowApplicable');
9 9
 
10 10
 Object::add_extension('Member', 'Hierarchy');
11 11
 Object::add_extension('LeftAndMain', 'AdvancedWorkflowExtension');
12 12
 
13  
-if(($MODULE_DIR = basename(dirname(__FILE__))) != 'advancedworkflow') {
14  
-	throw new Exception("The advanced workflow module must be in a directory named 'advancedworkflow', not $MODULE_DIR");
15  
-}
  13
+define('ADVANCED_WORKFLOW_DIR', basename(dirname(__FILE__)));
  14
+
  15
+if(ADVANCED_WORKFLOW_DIR != 'advancedworkflow') {
  16
+	throw new Exception(
  17
+		"The advanced workflow module must be in a directory named 'advancedworkflow', not " . ADVANCED_WORKFLOW_DIR
  18
+	);
  19
+}
  20
+
  21
+LeftAndMain::require_css(ADVANCED_WORKFLOW_DIR . '/css/AdvancedWorkflowAdmin.css');
10  code/actions/NotifyUsersWorkflowAction.php
@@ -25,12 +25,12 @@ public function getCMSFields() {
25 25
 			new LiteralField('NotificationNote', '<p>' . $this->fieldLabel('NotificationNote') . '</p>'),
26 26
 			new TextField('EmailSubject', $this->fieldLabel('EmailSubject')),
27 27
 			new TextField('EmailFrom', $this->fieldLabel('EmailFrom')),
28  
-			
29  
-			new TextareaField('EmailTemplate', $this->fieldLabel('EmailTemplate'), 10),
  28
+
  29
+			new TextareaField('EmailTemplate', $this->fieldLabel('EmailTemplate')),
30 30
 			new ToggleCompositeField('FormattingHelpContainer',
31 31
 				$this->fieldLabel('FormattingHelp'), new LiteralField('FormattingHelp', $this->getFormattingHelp()))
32 32
 		));
33  
-		
  33
+
34 34
 		if (class_exists('ListingPage')) {
35 35
 			// allow the user to select an existing 'listing template'. The "getItems()" for that template
36 36
 			// will be the list of items in the workflow
@@ -39,10 +39,10 @@ public function getCMSFields() {
39 39
 			if ($templates) {
40 40
 				$opts = $templates->map();
41 41
 			}
42  
-		
  42
+
43 43
 			$fields->addFieldToTab('Root.Main', new DropdownField('ListingTemplateID', $this->fieldLabel('ListingTemplateID'), $opts, '', null, '(choose)'), 'EmailTemplate');
44 44
 		}
45  
-		
  45
+
46 46
 		if ($this->ListingTemplateID) {
47 47
 			$fields->removeFieldFromTab('Root.Main', 'EmailTemplate');
48 48
 		}
298  code/admin/AdvancedWorkflowAdmin.php
... ...
@@ -1,300 +1,16 @@
1 1
 <?php
2 2
 /**
3  
- * An admin interface for managing workflow definitions, actions and transitions.
4 3
  *
5  
- * @license    BSD License (http://silverstripe.org/bsd-license/)
6  
- * @package    advancedworkflow
7  
- * @subpackage admin
  4
+ *
  5
+ * @package advancedworkflow
8 6
  */
9 7
 class AdvancedWorkflowAdmin extends ModelAdmin {
10 8
 
11  
-	public static $title = 'Workflows';
12  
-	public static $menu_title = 'Workflows';
13  
-	public static $url_segment = 'workflows';
14  
-
15  
-	public static $managed_models = array(
16  
-		'WorkflowDefinition' => array('record_controller' => 'AdvancedWorkflowAdmin_RecordController'),
17  
-		'WorkflowAction'     => array('record_controller' => 'AdvancedWorkflowAdmin_RecordController'),
18  
-		'WorkflowTransition' => array('record_controller' => 'AdvancedWorkflowAdmin_RecordController')
19  
-	);
20  
-
21  
-	public static $allowed_actions = array(
22  
-		'tree',
23  
-		'sort',
24  
-		'CreateDefinitionForm',
25  
-		'CreateActionForm',
26  
-		'CreateTransitionForm'
27  
-	);
28  
-	
29  
-	/**
30  
-	 * @return string
31  
-	 */
32  
-	public function tree($request) {
33  
-		$data     = array();
34  
-		$class    = $request->getVar('class');
35  
-		$id       = $request->getVar('id');
36  
-
37  
-		if($id == 0) {
38  
-			$items = singleton('WorkflowService')->getDefinitions();
39  
-			$type  = 'WorkflowDefinition';
40  
-		} elseif($class == 'WorkflowDefinition') {
41  
-			$items = DataObject::get('WorkflowAction', '"WorkflowDefID" = ' . (int) $id);
42  
-			$type  = 'WorkflowAction';
43  
-		} else {
44  
-			$items   = DataObject::get('WorkflowTransition', '"ActionID" = ' . (int) $id);
45  
-			$type    = 'WorkflowTransition';
46  
-		}
47  
-
48  
-		if($items) foreach($items as $item) {
49  
-			$new = array(
50  
-				'data' => array(
51  
-					'title' => $item->Title,
52  
-					'attr'  => array('href' => $this->Link("$type/{$item->ID}/edit")),
53  
-					'icon'  => $item->stat('icon')),
54  
-				'attr' => array(
55  
-					'id'         => "{$type}_{$item->ID}",
56  
-					'title'      => Convert::raw2att($item->Title),
57  
-					'data-id'    => $item->ID,
58  
-					'data-type'  => $type,
59  
-					'data-class' => $item->class)
60  
-			);
61  
-
62  
-			if($item->numChildren() > 0) {
63  
-				$new['state'] = 'closed';
64  
-			}
65  
-
66  
-			$data[] = $new;
67  
-		}
68  
-
69  
-		return Convert::raw2json($data);
70  
-	}
71  
-
72  
-	/**
73  
-	 * @return string
74  
-	 */
75  
-	public function sort($request) {
76  
-		$service  = singleton('WorkflowService');
77  
-		$type     = $request->postVar('type');
78  
-		$order    = $request->postVar('ids');
79  
-		$parentId = $request->postVar('parent_id');
80  
-
81  
-		switch($type) {
82  
-			case 'WorkflowDefinition':
83  
-				$current = $service->getDefinitions();
84  
-				break;
85  
-			case 'WorkflowAction':
86  
-				$current = DataObject::get('WorkflowAction', sprintf('"WorkflowDefID" = %d', $parentId));
87  
-				break;
88  
-			case 'WorkflowTransition':
89  
-				$current = DataObject::get('WorkflowTransition', sprintf('"ActionID" = %d', $parentId));
90  
-				break;
91  
-			default:
92  
-				return $this->httpError(400, _t('AdvancedWorkflowAdmin.INVALIDSORTTYPE', 'Invalid sort type.'));
93  
-		}
94  
-
95  
-		if(!$order || count($order) != count($current)) {
96  
-			return new SS_HTTPResponse(
97  
-				null, 400, _t('AdvancedWorkflowAdmin.INVALIDSORT', 'An invalid sort order was specified.')
98  
-			);
99  
-		}
100  
-
101  
-		$service->reorder($current, $order);
102  
-
103  
-		return new SS_HTTPResponse(
104  
-			null, 200, _t('AdvancedWorkflowAdmin.SORTORDERSAVED', 'The sort order has been saved.')
105  
-		);
106  
-	}
107  
-
108  
-	/**
109  
-	 * @return Form
110  
-	 */
111  
-	public function CreateDefinitionForm() {
112  
-		return new Form(
113  
-			$this,
114  
-			'CreateDefinitionForm',
115  
-			new FieldSet(
116  
-				$this->getClassCreationField('WorkflowDefinition')),
117  
-			new FieldSet(
118  
-				new FormAction('doCreateWorkflowItem', _t('AdvancedWorkflowAdmin.CREATE', 'Create')))
119  
-		);
120  
-	}
121  
-
122  
-	/**
123  
-	 * @return Form
124  
-	 */
125  
-	public function CreateActionForm() {
126  
-		return new Form(
127  
-			$this,
128  
-			'CreateActionForm',
129  
-			new FieldSet(
130  
-				$this->getClassCreationField('WorkflowAction', false),
131  
-				new HiddenField('ParentID')),
132  
-			new FieldSet(
133  
-				new FormAction('doCreateWorkflowItem', _t('AdvancedWorkflowAdmin.CREATE', 'Create')))
134  
-		);
135  
-	}
136  
-
137  
-	/**
138  
-	 * @return Form
139  
-	 */
140  
-	public function CreateTransitionForm() {
141  
-		return new Form(
142  
-			$this,
143  
-			'CreateTransitionForm',
144  
-			new FieldSet(
145  
-				$this->getClassCreationField('WorkflowTransition'),
146  
-				new HiddenField('ParentID')),
147  
-			new FieldSet(
148  
-				new FormAction('doCreateWorkflowItem', _t('AdvancedWorkflowAdmin.CREATE', 'Create')))
149  
-		);
150  
-	}
151  
-
152  
-	/**
153  
-	 * Creates a workflow item - a definition, action, transition or any subclasses
154  
-	 * of these.
155  
-	 *
156  
-	 * @param  array $data
157  
-	 * @param  Form $form
158  
-	 * @return string
159  
-	 */
160  
-	public function doCreateWorkflowItem($data, $form) {
161  
-		// assume the form name is in the form CreateTypeForm
162  
-		$data      = $form->getData();
163  
-		$type      = 'Workflow' . substr($form->Name(), 6, -4);
164  
-		$allowSelf = ($type != 'WorkflowAction');
165  
-
166  
-		// determine the class to create - if it is manually specified then use that,
167  
-		// falling back to creating an object of the root type if allowed.
168  
-		if(isset($data['Class']) && class_exists($data['Class'])) {
169  
-			$class = $data['Class'];
170  
-			$valid = is_subclass_of($class, $type) || ($allowSelf && $class == $type);
171  
-
172  
-			if(!$valid) return new SS_HTTPResponse(
173  
-				null, 400, _t('AdvancedWorkflowAdmin.INVALIDITEM', 'An invalid workflow item was specified.')
174  
-			);
175  
-		} else {
176  
-			$class = $type;
177  
-
178  
-			if(!$allowSelf) return new SS_HTTPResponse(
179  
-				null, 400, _t('AdvancedWorkflowAdmin.MUSTSPECIFYITEM', 'You must specify a workflow item to create.')
180  
-			);
181  
-		}
182  
-
183  
-		// check that workflow actions and transitions have valid parent id values.
184  
-		if($type != 'WorkflowDefinition') {
185  
-			$parentId = $data['ParentID'];
186  
-			$parentClass = ($type == 'WorkflowAction') ? 'WorkflowDefinition' : 'WorkflowAction';
  9
+	public static $menu_title    = 'Workflows';
  10
+	public static $menu_priority = -1;
  11
+	public static $url_segment   = 'workflows';
187 12
 
188  
-			if(!is_numeric($parentId) || !DataObject::get_by_id($parentClass, $parentId)) {
189  
-				return new SS_HTTPResponse(
190  
-					null, 400, _t('AdvancedWorkflowAdmin.INVALIDPARENT', 'An invalid parent was specified.')
191  
-				);
192  
-			}
193  
-		}
194  
-
195  
-		// if an add form can be returned without writing a new rcord to the database,
196  
-		// then just do that
197  
-		if(array_key_exists($class, $this->getManagedModels())) {
198  
-			$form  = $this->$type()->AddForm();
199  
-			$title = singleton($type)->singular_name();
200  
-
201  
-			if($type == 'WorkflowTransition') {
202  
-				$form->dataFieldByName('ActionID')->setValue($parentId);
203  
-			}
204  
-		} else {
205  
-			$record = new $class;
206  
-			$record->Title = sprintf(_t('AdvancedWorkflowAdmin.NEWITEM', 'New %s'), $record->singular_name());
207  
-
208  
-			if($type == 'WorkflowAction') {
209  
-				$record->WorkflowDefID = $parentId;
210  
-			} elseif($type == 'WorkflowTransition') {
211  
-				$record->ActionID = $parentId;
212  
-			}
213  
-
214  
-			$record->write();
215  
-
216  
-			$control = $this->getRecordControllerClass('WorkflowDefinition');
217  
-			$control = new $control($this->$type(), null, $record->ID);
218  
-			$form    = $control->EditForm();
219  
-			$title   = $record->singular_name();
220  
-		}
221  
-
222  
-		return new SS_HTTPResponse(
223  
-			$this->isAjax() ? $form->forAjaxTemplate() : $form->forTemplate(), 200,
224  
-			sprintf(_t('AdvancedWorkflowAdmin.CREATEITEM', 'Fill out this form to create a "%s".'), $title)
225  
-		);
226  
-	}
227  
-
228  
-	/**
229  
-	 * Returns a dropdown createable classes which all have a common parent class,
230  
-	 * or a label field if only one option is available.
231  
-	 *
232  
-	 * @param  string $class The parent class.
233  
-	 * @param  bool $includeSelf Include the parent class itself.
234  
-	 * @return DropdownField|LabelField
235  
-	 */
236  
-	protected function getClassCreationField($class, $includeSelf = true) {
237  
-		$classes    = ClassInfo::subclassesFor($class);
238  
-		$createable = array();
239  
-
240  
-		if(!$includeSelf) {
241  
-			array_shift($classes);
242  
-		}
243  
-
244  
-		foreach($classes as $class) {
245  
-			if(singleton($class)->canCreate()) $createable[$class] = singleton($class)->singular_name();
246  
-		}
247  
-
248  
-		if(count($classes) == 1) {
249  
-			return new LabelField('Class', current($createable));
250  
-		}
251  
-
252  
-		return new DropdownField(
253  
-			'Class', '', $createable, '', null, ($includeSelf ? false : _t('AdvancedWorkflowAdmin.SELECT', '(select)'))
254  
-		);
255  
-	}
  13
+	public static $managed_models  = 'WorkflowDefinition';
  14
+	public static $model_importers = array();
256 15
 
257 16
 }
258  
-
259  
-/**
260  
- * A record controller that hides the "Back" button, and shows a message on deletion rather than redirecting to
261  
- * the search form.
262  
- *
263  
- * @package    advancedworkflow
264  
- * @subpackage admin
265  
- */
266  
-class AdvancedWorkflowAdmin_RecordController extends ModelAdmin_RecordController {
267  
-
268  
-	/**
269  
-	 * @return Form
270  
-	 */
271  
-	public function EditForm() {
272  
-		$form = parent::EditForm();
273  
-		$form->Actions()->removeByName('action_goBack');
274  
-
275  
-		return $form;
276  
-	}
277  
-
278  
-	/**
279  
-	 * @return string
280  
-	 */
281  
-	public function doDelete() {
282  
-		if($this->currentRecord->canDelete()) {
283  
-			$this->currentRecord->delete();
284  
-
285  
-			$form = new Form(
286  
-				$this,
287  
-				'EditForm',
288  
-				new FieldSet(new LiteralField(
289  
-					'RecordDeleted',
290  
-					'<p>' . _t('AdvancedWorkflowAdmin.RECORDDELETED', 'This record has been deleted.') . '</p>'
291  
-				)),
292  
-				new FieldSet()
293  
-			);
294  
-			return $form->forTemplate();
295  
-		} else {
296  
-			return $this->redirectBack();
297  
-		}
298  
-	}
299  
-
300  
-}
12  code/dataobjects/WorkflowAction.php
@@ -124,16 +124,22 @@ public function numChildren() {
124 124
 	}
125 125
 
126 126
 	public function getCMSFields() {
127  
-		$fields = new FieldSet(new TabSet('Root'));
  127
+		$fields = new FieldList(new TabSet('Root'));
128 128
 		$fields->addFieldToTab('Root.Main', new TextField('Title', _t('WorkflowAction.TITLE', 'Title')));
129 129
 		$label = _t('WorkflowAction.ALLOW_EDITING', 'Allow editing during this step?');
130 130
 		$fields->addFieldToTab('Root.Main', new DropdownField('AllowEditing', $label, $this->dbObject('AllowEditing')->enumValues(), 'No'));
131 131
 		return $fields;
132 132
 	}
133  
-	
  133
+
  134
+	public function getValidator() {
  135
+		return new RequiredFields('Title');
  136
+	}
  137
+
134 138
 	public function summaryFields() {
135 139
 		return array('Title' => 'Title', 'Transitions' => 'Transitions');
136 140
 	}
137 141
 
138  
-	
  142
+	public function Icon() {
  143
+		return $this->stat('icon');
  144
+	}
139 145
 }
66  code/dataobjects/WorkflowDefinition.php
@@ -76,7 +76,7 @@ public function numChildren() {
76 76
 	/**
77 77
 	 */
78 78
 	public function getCMSFields() {
79  
-		$fields = new FieldSet(new TabSet('Root'));
  79
+		$fields = new FieldList(new TabSet('Root'));
80 80
 
81 81
 		$fields->addFieldToTab('Root.Main', new TextField('Title', _t('WorkflowDefinition.TITLE', 'Title')));
82 82
 		$fields->addFieldToTab('Root.Main', new TextareaField('Description', _t('WorkflowDefinition.DESCRIPTION', 'Description')));
@@ -95,49 +95,41 @@ public function getCMSFields() {
95 95
 			));
96 96
 		}
97 97
 
98  
-		if ($this->ID && Permission::check('VIEW_ACTIVE_WORKFLOWS')) {
99  
-			$fields->addFieldToTab('Root.ActiveInstances', $active = new ComplexTableField(
100  
-				$this,
101  
-				'Instances',
102  
-				'WorkflowInstance',
103  
-				array(
104  
-					'Title'               => 'Title',
105  
-					'Target.Title'        => 'Target Title',
106  
-					'WorkflowStatus'      => 'Status',
107  
-					'CurrentAction.Title' => 'Current Action',
108  
-					'LastEdited'          => 'Last Actioned'
109  
-				),
110  
-				'getInstanceManagementFields',
111  
-				'"WorkflowStatus" IN (\'Active\', \'Paused\')',
112  
-				'"LastEdited" DESC'
  98
+		if($this->ID) {
  99
+			$fields->addFieldToTab('Root.Main', new WorkflowField(
  100
+				'Workflow', _t('WorkflowDefinition.WORKFLOW', 'Workflow'), $this 
113 101
 			));
  102
+		} else {
  103
+			$message = _t(
  104
+				'WorkflowDefinition.ADDAFTERSAVING',
  105
+				'You can add workflow steps after you save for the first time.'
  106
+			);
  107
+			$fields->addFieldToTab('Root.Main', new LiteralField(
  108
+				'AddAfterSaving', "<p class='message notice'>$message</p>"
  109
+			));
  110
+		}
114 111
 
115  
-			if (Permission::check('REASSIGN_ACTIVE_WORKFLOWS')) {
116  
-				$active->setPermissions(array('show', 'edit'));
117  
-			} else {
118  
-				$active->setPermissions(array('show'));
  112
+		if($this->ID && Permission::check('VIEW_ACTIVE_WORKFLOWS')) {
  113
+			$active = $this->Instances()->filter(array(
  114
+				'WorkflowStatus' => array('Active', 'Paused')
  115
+			));
  116
+			$active = new GridField('Active', 'Active Workflow Instances', $active);
  117
+
  118
+			if(!Permission::check('REASSIGN_ACTIVE_WORKFLOWS')) {
  119
+				$active->removeComponentsByType('GridFieldEditButton');
  120
+				$active->removeComponentsByType('GridFieldDeleteAction');
119 121
 			}
120  
-			
121  
-			$fields->addFieldToTab('Root.Completed', $complete = new ComplexTableField(
122  
-				$this,
123  
-				'CompletedInstances',
124  
-				'WorkflowInstance',
125  
-				array(
126  
-					'Title'               => 'Title',
127  
-					'Target.Title'        => 'Target Title',
128  
-					'WorkflowStatus'      => 'Status',
129  
-					'CurrentAction.Title' => 'Current Action',
130  
-					'LastEdited'          => 'Last Actioned'
131  
-				),
132  
-				'getActionsSummaryFields',
133  
-				'"WorkflowStatus" IN (\'Complete\', \'Cancelled\')',
134  
-				'"LastEdited" DESC'
  122
+
  123
+			$completed = $this->Instances()->filter(array(
  124
+				'WorkflowStatus' => array('Complete', 'Cancelled')
135 125
 			));
  126
+			$completed = new GridField('Completed', 'Completed Workflow Instances', $completed);
136 127
 
137  
-			$complete->setPermissions(array('show'));
  128
+			$fields->addFieldToTab('Root.Active', $active);
  129
+			$fields->addFieldToTab('Root.Completed', $completed);
138 130
 		}
139 131
 
140 132
 		return $fields;
141 133
 	}
142 134
 
143  
-}
  135
+}
8  code/dataobjects/WorkflowTransition.php
@@ -72,11 +72,11 @@ public function validate() {
72 72
 	/* CMS FUNCTIONS */
73 73
 
74 74
 	public function getCMSFields() {
75  
-		$fields = new FieldSet(new TabSet('Root'));
  75
+		$fields = new FieldList(new TabSet('Root'));
76 76
 		$fields->addFieldToTab('Root.Main', new TextField('Title', _t('WorkflowAction.TITLE', 'Title')));
77 77
 
78 78
 		$filter = '';
79  
-		
  79
+
80 80
 		$reqParent = isset($_REQUEST['ParentID']) ? (int) $_REQUEST['ParentID'] : 0;
81 81
         $attachTo = $this->ActionID ? $this->ActionID : $reqParent;
82 82
 
@@ -107,6 +107,10 @@ public function getCMSFields() {
107 107
 		return $fields;
108 108
 	}
109 109
 
  110
+	public function getValidator() {
  111
+		return new RequiredFields('Title', 'ActionID', 'NextActionID');
  112
+	}
  113
+
110 114
 	public function numChildren() {
111 115
 		return 0;
112 116
 	}
75  code/extensions/WorkflowApplicable.php
@@ -26,48 +26,42 @@ public function extraStatics($class = null, $extension = null) {
26 26
 		);
27 27
 	}
28 28
 
29  
-	public function updateCMSFields(FieldSet $fields) {
30  
-		$service = singleton('WorkflowService');
  29
+	public function updateSettingsFields(FieldList $fields) {
  30
+		$this->updateFields($fields);
  31
+	}
31 32
 
32  
-		if($effective = $service->getDefinitionFor($this->owner)) {
33  
-			$effectiveTitle = $effective->Title;
34  
-		} else {
35  
-			$effectiveTitle = _t('WorkflowApplicable.NONE', '(none)');
36  
-		}
  33
+	public function updateCMSFields(FieldList $fields) {
  34
+		if(!$this->owner->hasMethod('getSettingsFields')) $this->updateFields($fields);
  35
+	}
  36
+
  37
+	public function updateFields(FieldList $fields) {
  38
+		$service   = singleton('WorkflowService');
  39
+		$effective = $service->getDefinitionFor($this->owner);
  40
+		$tab       = $fields->fieldByName('Root') ? 'Root.Workflow' : 'BottomRoot.Workflow';
37 41
 
38  
-		$allDefinitions = array(_t('WorkflowApplicable.INHERIT', 'Inherit from parent'));
  42
+		if(Permission::check('APPLY_WORKFLOW')) {
  43
+			$definition = new DropdownField('WorkflowDefinitionID', _t('WorkflowApplicable.DEFINITION', 'Applied Workflow'));
  44
+			$definition->setSource($service->getDefinitions()->map());
  45
+			$definition->setEmptyString(_t('WorkflowApplicable.INHERIT', 'Inherit from parent'));
39 46
 
40  
-		if($definitions = $service->getDefinitions()) {
41  
-			$allDefinitions += $definitions->map();
  47
+			$fields->addFieldToTab($tab, $definition);
42 48
 		}
43  
-		
44  
-		$tab = $fields->fieldByName('Root') ? 'Root.Workflow' : 'BottomRoot.Workflow';
45  
-		
46  
-		$applyWorkflowField = null;
47  
-		
48  
-		
49  
-		$fields->addFieldToTab($tab, new HeaderField('AppliedWorkflowHeader', _t('WorkflowApplicable.APPLIEDWORKFLOW', 'Applied Workflow')));
50  
-
51  
-		if (Permission::check('APPLY_WORKFLOW')) {
52  
-			$fields->addFieldToTab($tab, new DropdownField('WorkflowDefinitionID',
53  
-				_t('WorkflowApplicable.DEFINITION', 'Applied Workflow'), $allDefinitions));
54  
-			
  49
+
  50
+		$fields->addFieldToTab($tab, new ReadonlyField(
  51
+			'EffectiveWorkflow',
  52
+			_t('WorkflowApplicable.EFFECTIVE_WORKFLOW', 'Effective Workflow'),
  53
+			$effective ? $effective->Title : _t('WorkflowApplicable.NONE', '(none)')
  54
+		));
  55
+
  56
+		if($this->owner->ID) {
  57
+			$insts = $this->owner->WorkflowInstances();
  58
+			$log   = new GridField('WorkflowLog', _t('WorkflowApplicable.WORKFLOWLOG', 'Workflow Log'), $insts);
  59
+
  60
+			$fields->addFieldToTab($tab, $log);
55 61
 		}
56  
-		
57  
-		$fields->addFieldToTab($tab, new ReadonlyField('EffectiveWorkflow',
58  
-				_t('WorkflowApplicable.EFFECTIVE_WORKFLOW', 'Effective Workflow'), $effectiveTitle));
59  
-		$fields->addFieldToTab($tab, new HeaderField('WorkflowLogHeader', _t('WorkflowApplicable.WORKFLOWLOG', 'Workflow Log')));
60  
-		$fields->addFieldToTab($tab, $logTable = new ComplexTableField(
61  
-				$this->owner, 'WorkflowLog', 'WorkflowInstance', null, 'getActionsSummaryFields',
62  
-				sprintf('"TargetClass" = \'%s\' AND "TargetID" = %d', $this->owner->class, $this->owner->ID)
63  
-			));
64  
-
65  
-		$logTable->setRelationAutoSetting(false);
66  
-		$logTable->setPermissions(array('show'));
67  
-		$logTable->setPopupSize(760, 420);
68 62
 	}
69 63
 
70  
-	public function updateCMSActions($actions) {
  64
+	public function updateCMSActions(FieldList $actions) {
71 65
 		$svc = singleton('WorkflowService');
72 66
 		$active = $svc->getWorkflowFor($this->owner);
73 67
 
@@ -96,6 +90,13 @@ public function onAfterWrite() {
96 90
 		}
97 91
 	}
98 92
 
  93
+	public function WorkflowInstances() {
  94
+		return DataList::create('WorkflowInstance')->filter(array(
  95
+			'TargetClass' => $this->ownerBaseClass,
  96
+			'TargetID'    => $this->owner->ID
  97
+		));
  98
+	}
  99
+
99 100
 	/**
100 101
 	 * Gets the current instance of workflow
101 102
 	 *
@@ -126,13 +127,13 @@ public function canPublish() {
126 127
 		if ($effective = singleton('WorkflowService')->getDefinitionFor($this->owner)) {
127 128
 			return false;
128 129
 		}
129  
-		
  130
+
130 131
 	}
131 132
 
132 133
 	/**
133 134
 	 * Can only edit content that's NOT in another person's content changeset
134 135
 	 */
135  
-	public function canEdit() {
  136
+	public function canEdit($member) {
136 137
 		if ($active = $this->getWorkflowInstance()) {
137 138
 			return $active->canEditTarget($this->owner);
138 139
 		}
69  code/formfields/WorkflowField.php
... ...
@@ -0,0 +1,69 @@
  1
+<?php
  2
+/**
  3
+ * A form field that allows workflow actions and transitions to be edited,
  4
+ * while showing a visual overview of the flow.
  5
+ *
  6
+ * @package advancedworkflow
  7
+ */
  8
+class WorkflowField extends FormField {
  9
+
  10
+	public static $allowed_actions = array(
  11
+		'action',
  12
+		'transition'
  13
+	);
  14
+
  15
+	protected $definition;
  16
+
  17
+	public function __construct($name, $title, WorkflowDefinition $definition) {
  18
+		$this->definition = $definition;
  19
+		$this->addExtraClass('workflow-field');
  20
+
  21
+		parent::__construct($name, $title);
  22
+	}
  23
+
  24
+	public function action() {
  25
+		return new WorkflowFieldActionController($this, 'action');
  26
+	}
  27
+
  28
+	public function transition() {
  29
+		return new WorkflowFieldTransitionController($this, 'transition');
  30
+	}
  31
+
  32
+	public function getTemplate() {
  33
+		return 'WorkflowField';
  34
+	}
  35
+
  36
+	public function FieldHolder($properties = array()) {
  37
+		Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
  38
+		Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
  39
+		Requirements::javascript(ADVANCED_WORKFLOW_DIR . '/javascript/WorkflowField.js');
  40
+		Requirements::css(ADVANCED_WORKFLOW_DIR . '/css/WorkflowField.css');
  41
+
  42
+		return $this->Field($properties);
  43
+	}
  44
+
  45
+	public function Definition() {
  46
+		return $this->definition;
  47
+	}
  48
+
  49
+	public function CreateableActions() {
  50
+		$list    = new ArrayList();
  51
+		$classes = ClassInfo::subclassesFor('WorkflowAction');
  52
+
  53
+		array_shift($classes);
  54
+		sort($classes);
  55
+
  56
+		foreach($classes as $class) {
  57
+			$reflect = new ReflectionClass($class);
  58
+			$can     = singleton($class)->canCreate() && !$reflect->isAbstract();
  59
+
  60
+			if($can) $list->push(new ArrayData(array(
  61
+				'Title' => singleton($class)->singular_name(),
  62
+				'Class' => $class
  63
+			)));
  64
+		}
  65
+
  66
+		return $list;
  67
+	}
  68
+
  69
+}
72  code/formfields/WorkflowFieldActionController.php
... ...
@@ -0,0 +1,72 @@
  1
+<?php
  2
+/**
  3
+ * Handles requests for creating or editing actions.
  4
+ *
  5
+ * @package silverstripe-advancedworkflow
  6
+ */
  7
+class WorkflowFieldActionController extends RequestHandler {
  8
+
  9
+	public static $url_handlers = array(
  10
+		'new/$Class' => 'handleAdd',
  11
+		'item/$ID'   => 'handleItem'
  12
+	);
  13
+
  14
+	public static $allowed_actions = array(
  15
+		'handleAdd',
  16
+		'handleItem'
  17
+	);
  18
+
  19
+	protected $parent;
  20
+	protected $name;
  21
+
  22
+	public function __construct($parent, $name) {
  23
+		$this->parent = $parent;
  24
+		$this->name   = $name;
  25
+
  26
+		parent::__construct();
  27
+	}
  28
+
  29
+	public function handleAdd() {
  30
+		$class = $this->request->param('Class');
  31
+
  32
+		if(!class_exists($class) || !is_subclass_of($class, 'WorkflowAction')) {
  33
+			$this->httpError(400);
  34
+		}
  35
+
  36
+		$reflector = new ReflectionClass($class);
  37
+
  38
+		if($reflector->isAbstract() || !singleton($class)->canCreate()) {
  39
+			$this->httpError(400);
  40
+		}
  41
+
  42
+		$record = new $class();
  43
+		$record->WorkflowDefID = $this->parent->Definition()->ID;
  44
+
  45
+		return new WorkflowFieldItemController($this, "new/$class", $record);
  46
+	}
  47
+
  48
+	public function handleItem() {
  49
+		$id     = $this->request->param('ID');
  50
+		$defn   = $this->parent->Definition();
  51
+		$action = $defn->Actions()->byID($id);
  52
+
  53
+		if(!$action) {
  54
+			$this->httpError(404);
  55
+		}
  56
+
  57
+		if(!$action->canEdit()) {
  58
+			$this->httpError(403);
  59
+		}
  60
+
  61
+		return new WorkflowFieldItemController($this, "item/$id", $action);
  62
+	}
  63
+
  64
+	public function RootField() {
  65
+		return $this->parent;
  66
+	}
  67
+
  68
+	public function Link($action = null) {
  69
+		return Controller::join_links($this->parent->Link(), $this->name, $action);
  70
+	}
  71
+
  72
+}
92  code/formfields/WorkflowFieldItemController.php
... ...
@@ -0,0 +1,92 @@
  1
+<?php
  2
+/**
  3
+ * Handles individual record data editing or deleting.
  4
+ *
  5
+ * @package silverstripe-advancedworkflow
  6
+ */
  7
+class WorkflowFieldItemController extends Controller {
  8
+
  9
+	public static $allowed_actions = array(
  10
+		'index',
  11
+		'edit',
  12
+		'delete',
  13
+		'Form'
  14
+	);
  15
+
  16
+	protected $parent;
  17
+	protected $name;
  18
+
  19
+	public function __construct($parent, $name, $record) {
  20
+		$this->parent = $parent;
  21
+		$this->name   = $name;
  22
+		$this->record = $record;
  23
+
  24
+		parent::__construct();
  25
+	}
  26
+
  27
+	public function index() {
  28
+		return $this->edit();
  29
+	}
  30
+
  31
+	public function edit() {
  32
+		return $this->Form()->forTemplate();
  33
+	}
  34
+
  35
+	public function Form() {
  36
+		$record    = $this->record;
  37
+		$fields    = $record->getCMSFields();
  38
+		$validator = $record->hasMethod('getValidator') ? $record->getValidator() : null;
  39
+
  40
+		$save = FormAction::create('doSave', 'Save');
  41
+		$save->addExtraClass('ss-ui-button ss-ui-action-constructive')
  42
+		     ->setAttribute('data-icon', 'accept')
  43
+		     ->setUseButtonTag(true);
  44
+
  45
+		$form = new Form($this, 'Form', $fields, new FieldList($save), $validator);
  46
+		$form->loadDataFrom($record);
  47
+		return $form;
  48
+	}
  49
+
  50
+	public function doSave($data, $form) {
  51
+		$record = $form->getRecord();
  52
+
  53
+		if(!$record->canEdit()) {
  54
+			$this->httpError(403);
  55
+		}
  56
+
  57
+		if(!$record->isInDb()) {
  58
+			$record->write();
  59
+		}
  60
+
  61
+		$form->saveInto($record);
  62
+		$record->write();
  63
+
  64
+		return $this->RootField()->forTemplate();
  65
+	}
  66
+
  67
+	public function delete($request) {
  68
+		if(!SecurityToken::inst()->checkRequest($request)) {
  69
+			$this->httpError(400);
  70
+		}
  71
+
  72
+		if(!$request->isPOST()) {
  73
+			$this->httpError(400);
  74
+		}
  75
+
  76
+		if(!$this->record->canDelete()) {
  77
+			$this->httpError(403);
  78
+		}
  79
+
  80
+		$this->record->delete();
  81
+		return $this->RootField()->forTemplate();
  82
+	}
  83
+
  84
+	public function RootField() {
  85
+		return $this->parent->RootField();
  86
+	}
  87
+
  88
+	public function Link($action = null) {
  89
+		return Controller::join_links($this->parent->Link(), $this->name, $action);
  90
+	}
  91
+
  92
+}
70  code/formfields/WorkflowFieldTransitionController.php
... ...
@@ -0,0 +1,70 @@
  1
+<?php
  2
+/**
  3
+ * Handles requests for creating or editing transitions.
  4
+ *
  5
+ * @package silverstripe-advancedworkflow
  6
+ */
  7
+class WorkflowFieldTransitionController extends RequestHandler {
  8
+
  9
+	public static $url_handlers = array(
  10
+		'new/$ParentID!' => 'handleAdd',
  11
+		'item/$ID!'      => 'handleItem'
  12
+	);
  13
+
  14
+	public static $allowed_actions = array(
  15
+		'handleAdd',
  16
+		'handleItem'
  17
+	);
  18
+
  19
+	protected $parent;
  20
+	protected $name;
  21
+
  22
+	public function __construct($parent, $name) {
  23
+		$this->parent = $parent;
  24
+		$this->name   = $name;
  25
+
  26
+		parent::__construct();
  27
+	}
  28
+
  29
+	public function handleAdd() {
  30
+		$parent = $this->request->param('ParentID');
  31
+		$action = WorkflowAction::get()->byID($this->request->param('ParentID'));
  32
+
  33
+		if(!$action || $action->WorkflowDefID != $this->RootField()->Definition()->ID) {
  34
+			$this->httpError(404);
  35
+		}
  36
+
  37
+		if(!singleton('WorkflowTransition')->canCreate()) {
  38
+			$this->httpError(403);
  39
+		}
  40
+
  41
+		$transition = new WorkflowTransition();
  42
+		$transition->ActionID = $action->ID;
  43
+
  44
+		return new WorkflowFieldItemController($this, "new/$parent", $transition);
  45
+	}
  46
+
  47
+	public function handleItem() {
  48
+		$id    = $this->request->param('ID');
  49
+		$trans = WorkflowTransition::get()->byID($id);
  50
+
  51
+		if(!$trans || $trans->Action()->WorkflowDefID != $this->RootField()->Definition()->ID) {
  52
+			$this->httpError(404);
  53
+		}
  54
+
  55
+		if(!$trans->canEdit()) {
  56
+			$this->httpError(403);
  57
+		}
  58
+
  59
+		return new WorkflowFieldItemController($this, "item/$id", $trans);
  60
+	}
  61
+
  62
+	public function RootField() {
  63
+		return $this->parent;
  64
+	}
  65
+
  66
+	public function Link($action = null) {
  67
+		return Controller::join_links($this->parent->Link(), $this->name, $action);
  68
+	}
  69
+
  70
+}
16  css/AdvancedWorkflowAdmin.css
... ...
@@ -1,13 +1,3 @@
1  
-#left { background: #FFF; }
2  
-#left #ToggleForms { height: 24px; }
3  
-#ToggleForms, #Form_CreateDefinitionForm { background: #EEE; border-bottom: 1px solid #CCC; padding: 5px; }
4  
-#left form { overflow: auto; }
5  
-#left fieldset { float: left; }
6  
-#left .Actions { float: right; }
7  
-#left label { font-size: 12px; line-height: 24px; }
8  
-#left .dropdown .middleColumn { padding-top: 2px; }
9  
-#left select { font-size: 12px; width: 185px !important; }
10  
-#left .jstree { margin-top: 5px; }
11  
-#left .jstree-focused { background: none; }
12  
-#left .jstree-clicked, #left .jstree-hovered { border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; }
13  
-#Form_CreateActionForm, #Form_CreateTransitionForm { display: none; }
  1
+.icon.icon-16.icon-advancedworkflowadmin {
  2
+	background-image: url(../images/workflow-menu-icon.png);
  3
+}
20  css/WorkflowField.css
... ...
@@ -0,0 +1,20 @@
  1
+.workflow-field .workflow-field-header { background: #95a5ab; border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 3px 3px 0 0; }
  2
+.workflow-field .workflow-field-header h3 { background: #7f9198; border-bottom: 1px solid rgba(0, 0, 0, 0.1); color: #FFF; text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); margin: 0; padding: 8px; }
  3
+.workflow-field .workflow-field-create { padding: 4px 8px; }
  4
+.workflow-field .workflow-field-create .chzn-container { float: left; margin-right: 4px; width: 250px !important; }
  5
+.workflow-field .workflow-field-create .chzn-container .chzn-single { border-color: rgba(0, 0, 0, 0.2); border-radius: 3px; box-shadow: none; }
  6
+.workflow-field .workflow-field-create .chzn-container .chzn-single-with-drop { border-radius: 3px 3px 0 0; }
  7
+.workflow-field .workflow-field-create .ss-ui-button { border-color: rgba(0, 0, 0, 0.2); }
  8
+.workflow-field .workflow-field-actions { background: #FFF; border: 1px solid rgba(0, 0, 0, 0.2); border-top: none; border-radius: 0 0 3px 3px; padding: 8px; position: relative; }
  9
+.workflow-field .workflow-field-actions .workflow-field-action { margin-bottom: 4px; }
  10
+.workflow-field .workflow-field-actions .workflow-field-action:last-child { margin-bottom: 0; }
  11
+.workflow-field .workflow-field-actions .workflow-field-action-header { background: #d7dde0; border: 1px solid #95a5ab; border-left: none; overflow: auto; padding: 2px 8px 2px 0; }
  12
+.workflow-field .workflow-field-actions .workflow-field-action-header .workflow-field-action-icon, .workflow-field .workflow-field-actions .workflow-field-action-header .workflow-field-action-drag { display: block; float: left; }
  13
+.workflow-field .workflow-field-actions .workflow-field-action-header .workflow-field-action-drag { background: #95a5ab; cursor: move; margin: -2px 0; width: 4px; height: 33px; }
  14
+.workflow-field .workflow-field-actions .workflow-field-action-header .workflow-field-action-icon { width: 16px; height: 16px; margin: 7px 8px 0 8px; }
  15
+.workflow-field .workflow-field-actions .workflow-field-action-header h4 { float: left; margin: 7px 8px 0 0; min-width: 360px; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4); }
  16
+.workflow-field .workflow-field-actions .workflow-field-action-transitions li { background: #f8f8f8; border: 1px solid #d7dde0; border-left: none; border-top: none; overflow: auto; padding: 4px 4px 4px 0; }
  17
+.workflow-field .workflow-field-actions .workflow-field-action-transitions li .workflow-field-action-drag { background: #d7dde0; cursor: move; float: left; margin: -4px 0; width: 4px; height: 24px; }
  18
+.workflow-field .workflow-field-actions .workflow-field-action-transitions li .workflow-field-transition-title { float: left; min-width: 390px; overflow: auto; }
  19
+.workflow-field .workflow-field-actions .workflow-field-action-transitions li .ui-icon, .workflow-field .workflow-field-actions .workflow-field-action-transitions li span { float: left; }
  20
+.workflow-field .workflow-field-loading { background: rgba(0, 0, 0, 0.15) url(../../sapphire/admin/images/spinner.gif) no-repeat center center; border-radius: 0 0 3px 3px; bottom: 0; display: none; left: 0; position: absolute; right: 0; top: 0; z-index: 999; }
BIN  images/workflow-menu-icon.png
219  javascript/AdvancedWorkflowAdmin.js
... ...
@@ -1,219 +0,0 @@
1  
-;(function($) {
2  
-/**
3  
- * A workaround for $.post not passing the XHR to the callback function.
4  
- */
5  
-var __last_xhr;
6  
-
7  
-$(function() {
8  
-	$('#left').fn({
9  
-		/**
10  
-		 * Updates the creation forms in the left area when the selected workflow item has changed.
11  
-		 *
12  
-		 * @param {DOMObject} item
13  
-		 */
14  
-		changeWorkflowItem: function() {
15  
-			var current = $('#Workflows').jstree('get_selected');
16  
-
17  
-			if(!current.length) {
18  
-				$('#Form_CreateActionForm, #Form_CreateTransitionForm').hide();
19  
-				return;
20  
-			}
21  
-
22  
-			switch(current.attr('data-type')) {
23  
-				case 'WorkflowDefinition':
24  
-					$('#Form_CreateActionForm').show().find('input[name=ParentID]').val(current.attr('data-id'));
25  
-					$('#Form_CreateTransitionForm').hide();
26  
-					break;
27  
-				case 'WorkflowAction':
28  
-					$('#Form_CreateActionForm').hide();
29  
-					$('#Form_CreateTransitionForm').show().find('input[name=ParentID]').val(current.attr('data-id'));
30  
-					break;
31  
-				default:
32  
-					$('#Form_CreateActionForm, #Form_CreateTransitionForm').hide();
33  
-					break;
34  
-			}
35  
-		}
36  
-	});
37  
-
38  
-	$('#ModelAdminPanel').fn({
39  
-		/**
40  
-		 * Loads a new form from a URL, and displays the status message attached to the response. This overloads the
41  
-		 * default ModelAdmin implementation to allow attaching data to the request.
42  
-		 *
43  
-		 * @param {String} url
44  
-		 * @param {Object} data
45  
-		 * @param {Function} callback
46  
-		 */
47  
-		loadForm: function(url, data, callback) {
48  
-			statusMessage(ss.i18n._t('AdvancedWorkflow.LOADING', 'Loading...'));
49  
-			$(this).load(url, data, responseHandler(function(response, status, xhr) {
50  
-				$('#form_actions_right').remove();
51  
-				Behaviour.apply();
52  
-				if(window.onresize) window.onresize();
53  
-
54  
-				if(callback) $(this).each(callback, [response, status, xhr]);
55  
-			}));
56  
-		}
57  
-	});
58  
-
59  
-	$('#Workflows').jstree({
60  
-		plugins: [ 'themes', 'json_data', 'ui', 'crrm', 'dnd' ],
61  
-		json_data: {
62  
-			ajax: {
63  
-				url:  function() { return this.get_container().attr('href'); },
64  
-				data: function(n) { return { id : n.attr ? n.attr('data-id') : 0, "class": n.attr ? n.attr('data-class') : '' }; }
65  
-			}
66  
-		},
67  
-		crrm: {move: {check_move: function(move) {
68  
-			var parent = this._get_parent(move.o);
69  
-
70  
-			if(!parent) return false;
71  
-			if(parent == -1) parent = this.get_container();
72  
-
73  
-			return (parent === move.np || parent[0] && move.np[0] && parent[0] === move.np[0]);
74  
-		}}},
75  
-		dnd: {
76  
-			drag_target: false,
77  
-			drop_target: false
78  
-		}
79  
-	});
80  
-
81  
-	$('#Workflows').bind('move_node.jstree', function(event, args) {
82  
-		var link = $('#Workflows').attr('data-href-sort');
83  
-		var tree = args.inst;
84  
-		var obj  = $(args.rslt.o);
85  
-		var data = 'type=' + obj.attr('data-type');
86  
-
87  
-		if(obj.parent().parent().is('li')) {
88  
-			data += '&parent_id=' + obj.parent().parent().attr('data-id');
89  
-		}
90  
-
91  
-		obj.parent().children('li').each(function() {
92  
-			data += '&ids[]=' + $(this).attr('data-id');
93  
-		});
94  
-
95  
-		statusMessage(ss.i18n._t('AdvancedWorkflow.SAVINGORDER', 'Saving order...'));
96  
-		__last_xhr = $.post(link, data, responseHandler());
97  
-	});
98  
-
99  
-	$('#Workflows').bind('select_node.jstree', function(event, args) {
100  
-		var self = $(args.args[0]);
101  
-
102  
-		$('#ModelAdminPanel').fn('loadForm', self.attr('href'));
103  
-		$('#left').fn('changeWorkflowItem');
104  
-
105  
-		return false;
106  
-	});
107  
-
108  
-	$('#Workflows').bind('reopen.jstree', function(event, args) {
109  
-		$('#left').fn('changeWorkflowItem');
110  
-	});
111  
-