GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 21e795...aea957 )
by
unknown
11s
created

code/dataobjects/WorkflowDefinition.php (1 issue)

Severity

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
namespace Symbiote\AdvancedWorkflow\DataObjects;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Forms\CheckboxSetField;
7
use SilverStripe\Forms\DropdownField;
8
use SilverStripe\Forms\FieldGroup;
9
use SilverStripe\Forms\FieldList;
10
use SilverStripe\Forms\FormAction;
11
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
12
use SilverStripe\Forms\GridField\GridField;
13
use SilverStripe\Forms\GridField\GridFieldAddNewButton;
14
use SilverStripe\Forms\GridField\GridFieldDeleteAction;
15
use SilverStripe\Forms\GridField\GridFieldEditButton;
16
use SilverStripe\Forms\GridField\GridFieldViewButton;
17
use SilverStripe\Forms\GridField\GridFieldDetailForm;
18
use SilverStripe\Forms\GridField\GridFieldConfig_Base;
19
use SilverStripe\Forms\LabelField;
20
use SilverStripe\Forms\LiteralField;
21
use SilverStripe\Forms\NumericField;
22
use SilverStripe\Forms\ReadonlyField;
23
use SilverStripe\Forms\TabSet;
24
use SilverStripe\Forms\TextareaField;
25
use SilverStripe\Forms\TextField;
26
use SilverStripe\Forms\TreeMultiselectField;
27
use SilverStripe\ORM\DataObject;
28
use SilverStripe\ORM\DB;
29
use SilverStripe\Security\Group;
30
use SilverStripe\Security\Member;
31
use SilverStripe\Security\Permission;
32
use SilverStripe\Security\Security;
33
use Symbiote\AdvancedWorkflow\FormFields\WorkflowField;
34
use Symbiote\AdvancedWorkflow\Services\WorkflowService;
35
use Symbiote\QueuedJobs\Services\AbstractQueuedJob;
36
37
/**
38
 * An overall definition of a workflow
39
 *
40
 * The workflow definition has a series of steps to it. Each step has a series of possible transitions
41
 * that it can take - the first one that meets certain criteria is followed, which could lead to
42
 * another step.
43
 *
44
 * A step is either manual or automatic; an example 'manual' step would be requiring a person to review
45
 * a document. An automatic step might be to email a group of people, or to publish documents.
46
 * Basically, a manual step requires the interaction of someone to pick which action to take, an automatic
47
 * step will automatically determine what to do once it has finished.
48
 *
49
 * @author  [email protected]
50
 * @license BSD License (http://silverstripe.org/bsd-license/)
51
 * @package advancedworkflow
52
 */
53
class WorkflowDefinition extends DataObject
54
{
55
    private static $db = array(
56
        'Title'                 => 'Varchar(128)',
57
        'Description'       => 'Text',
58
        'Template'          => 'Varchar',
59
        'TemplateVersion'   => 'Varchar',
60
        'RemindDays'        => 'Int',
61
        'Sort'              => 'Int',
62
        'InitialActionButtonText' => 'Varchar',
63
    );
64
65
    private static $default_sort = 'Sort';
66
67
    private static $has_many = array(
68
        'Actions'   => WorkflowAction::class,
69
        'Instances' => WorkflowInstance::class
70
    );
71
72
    /**
73
     * By default, a workflow definition is bound to a particular set of users or groups.
74
     *
75
     * This is covered across to the workflow instance - it is up to subsequent
76
     * workflow actions to change this if needbe.
77
     *
78
     * @var array
79
     */
80
    private static $many_many = array(
81
        'Users' => Member::class,
82
        'Groups' => Group::class,
83
    );
84
85
    private static $icon = 'advancedworkflow/images/definition.png';
0 ignored issues
show
The property $icon is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
86
87
    public static $default_workflow_title_base = 'My Workflow';
88
89
    public static $workflow_defs = array();
90
91
    private static $dependencies = array(
92
        'workflowService' => '%$' . WorkflowService::class,
93
    );
94
95
    private static $table_name = 'WorkflowDefinition';
96
97
    /**
98
     * @var WorkflowService
99
     */
100
    public $workflowService;
101
102
    /**
103
     * Gets the action that first triggers off the workflow
104
     *
105
     * @return WorkflowAction
106
     */
107
    public function getInitialAction()
108
    {
109
        if ($actions = $this->Actions()) {
110
            return $actions->First();
111
        }
112
    }
113
114
    /**
115
     * Ensure a sort value is set and we get a useable initial workflow title.
116
     */
117
    public function onBeforeWrite()
118
    {
119
        if (!$this->Sort) {
120
            $this->Sort = DB::query('SELECT MAX("Sort") + 1 FROM "WorkflowDefinition"')->value();
121
        }
122
        if (!$this->ID && !$this->Title) {
123
            $this->Title = $this->getDefaultWorkflowTitle();
124
        }
125
126
        parent::onBeforeWrite();
127
    }
128
129
    /**
130
     * After we've been written, check whether we've got a template and to then
131
     * create the relevant actions etc.
132
     */
133
    public function onAfterWrite()
134
    {
135
        parent::onAfterWrite();
136
137
        // Request via ImportForm where TemplateVersion is already set, so unset it
138
        $posted = Controller::curr()->getRequest()->postVars();
139
        if (isset($posted['_CsvFile']) && $this->TemplateVersion) {
140
            $this->TemplateVersion = null;
141
        }
142
        if ($this->numChildren() == 0 && $this->Template && !$this->TemplateVersion) {
143
            $this->workflowService->defineFromTemplate($this, $this->Template);
144
        }
145
    }
146
147
    /**
148
     * Ensure all WorkflowDefinition relations are removed on delete. If we don't do this,
149
     * we see issues with targets previously under the control of a now-deleted workflow,
150
     * becoming stuck, even if a new workflow is subsequently assigned to it.
151
     *
152
     * @return null
153
     */
154
    public function onBeforeDelete()
155
    {
156
        parent::onBeforeDelete();
157
158
        // Delete related import
159
        $this->deleteRelatedImport();
160
161
        // Reset/unlink related HasMany|ManyMany relations and their orphaned objects
162
        $this->removeRelatedHasLists();
163
    }
164
165
    /**
166
     * Removes User+Group relations from this object as well as WorkflowAction relations.
167
     * When a WorkflowAction is deleted, its own relations are also removed:
168
     * - WorkflowInstance
169
     * - WorkflowTransition
170
     * @see WorkflowAction::onAfterDelete()
171
     *
172
     * @return void
173
     */
174
    private function removeRelatedHasLists()
175
    {
176
        $this->Users()->removeAll();
177
        $this->Groups()->removeAll();
178
        $this->Actions()->each(function ($action) {
179
            if ($orphan = DataObject::get_by_id(WorkflowAction::class, $action->ID)) {
180
                $orphan->delete();
181
            }
182
        });
183
    }
184
185
    /**
186
     *
187
     * Deletes related ImportedWorkflowTemplate objects.
188
     *
189
     * @return void
190
     */
191
    private function deleteRelatedImport()
192
    {
193
        if ($import = DataObject::get(ImportedWorkflowTemplate::class)->filter('DefinitionID', $this->ID)->first()) {
194
            $import->delete();
195
        }
196
    }
197
198
    /**
199
     * @return int
200
     */
201
    public function numChildren()
202
    {
203
        return $this->Actions()->count();
204
    }
205
206
    public function fieldLabels($includerelations = true)
207
    {
208
        $labels = parent::fieldLabels($includerelations);
209
        $labels['Title'] = _t('WorkflowDefinition.TITLE', 'Title');
210
        $labels['Description'] = _t('WorkflowDefinition.DESCRIPTION', 'Description');
211
        $labels['Template'] = _t('WorkflowDefinition.TEMPLATE_NAME', 'Source Template');
212
        $labels['TemplateVersion'] = _t('WorkflowDefinition.TEMPLATE_VERSION', 'Template Version');
213
214
        return $labels;
215
    }
216
217
    public function getCMSFields()
218
    {
219
220
        $cmsUsers = Member::mapInCMSGroups();
221
222
        $fields = new FieldList(new TabSet('Root'));
223
224
        $fields->addFieldToTab('Root.Main', new TextField('Title', $this->fieldLabel('Title')));
225
        $fields->addFieldToTab('Root.Main', new TextareaField('Description', $this->fieldLabel('Description')));
226
        $fields->addFieldToTab('Root.Main', TextField::create(
227
            'InitialActionButtonText',
228
            _t('WorkflowDefinition.INITIAL_ACTION_BUTTON_TEXT', 'Initial Action Button Text')
229
        ));
230
        if ($this->ID) {
231
            $fields->addFieldToTab('Root.Main', new CheckboxSetField('Users', _t('WorkflowDefinition.USERS', 'Users'), $cmsUsers));
232
            $fields->addFieldToTab('Root.Main', new TreeMultiselectField('Groups', _t('WorkflowDefinition.GROUPS', 'Groups'), Group::class));
233
        }
234
235
        if (class_exists(AbstractQueuedJob::class)) {
236
            $fields->addFieldToTab(
237
                'Root.Main',
238
                NumericField::create(
239
                    'ReminderEmail',
240
                    _t('WorkflowDefinition.REMINDEREMAIL', 'Reminder Email')
241
                )->setDescription(_t(
242
                    __CLASS__ . '.ReminderEmailDescription',
243
                    'Send reminder email after the specified number of days without action.'
244
                ))
245
            );
246
        }
247
248
        if ($this->ID) {
249
            if ($this->Template) {
250
                $template = $this->workflowService->getNamedTemplate($this->Template);
251
                $fields->addFieldToTab('Root.Main', new ReadonlyField('Template', $this->fieldLabel('Template'), $this->Template));
252
                $fields->addFieldToTab('Root.Main', new ReadonlyField('TemplateDesc', _t('WorkflowDefinition.TEMPLATE_INFO', 'Template Info'), $template ? $template->getDescription() : ''));
253
                $fields->addFieldToTab('Root.Main', $tv = new ReadonlyField('TemplateVersion', $this->fieldLabel('TemplateVersion')));
254
                $tv->setRightTitle(sprintf(_t('WorkflowDefinition.LATEST_VERSION', 'Latest version is %s'), $template ? $template->getVersion() : ''));
255
            }
256
257
            $fields->addFieldToTab('Root.Main', new WorkflowField(
258
                'Workflow',
259
                _t('WorkflowDefinition.WORKFLOW', 'Workflow'),
260
                $this
261
            ));
262
        } else {
263
            // add in the 'template' info
264
            $templates = $this->workflowService->getTemplates();
265
266
            if (is_array($templates)) {
267
                $items = array('' => '');
268
                foreach ($templates as $template) {
269
                    $items[$template->getName()] = $template->getName();
270
                }
271
                $templates = array_combine(array_keys($templates), array_keys($templates));
272
273
                $fields->addFieldToTab('Root.Main', $dd = new DropdownField('Template', _t('WorkflowDefinition.CHOOSE_TEMPLATE', 'Choose template (optional)'), $items));
274
                $dd->setRightTitle(_t('WorkflowDefinition.CHOOSE_TEMPLATE_RIGHT', 'If set, this workflow definition will be automatically updated if the template is changed'));
275
            }
276
277
            /*
278
			 * Uncomment to allow pre-uploaded exports to appear in a new DropdownField.
279
			 *
280
			 * $import = singleton('WorkflowDefinitionImporter')->getImportedWorkflows();
281
			 * if (is_array($import)) {
282
			 * $_imports = array('' => '');
283
			 * foreach ($imports as $import) {
284
			 * 		$_imports[$import->getName()] = $import->getName();
285
			 * }
286
			 * $imports = array_combine(array_keys($_imports), array_keys($_imports));
287
			 * $fields->addFieldToTab('Root.Main', new DropdownField('Import', _t('WorkflowDefinition.CHOOSE_IMPORT', 'Choose import (optional)'), $imports));
288
			 * }
289
			 */
290
291
            $message = _t(
292
                'WorkflowDefinition.ADDAFTERSAVING',
293
                'You can add workflow steps after you save for the first time.'
294
            );
295
            $fields->addFieldToTab('Root.Main', new LiteralField(
296
                'AddAfterSaving',
297
                "<p class='message notice'>$message</p>"
298
            ));
299
        }
300
301
        if ($this->ID && Permission::check('VIEW_ACTIVE_WORKFLOWS')) {
302
            $active = $this->Instances()->filter(array(
303
                'WorkflowStatus' => array('Active', 'Paused')
304
            ));
305
306
            $active = new GridField(
307
                'Active',
308
                _t('WorkflowDefinition.WORKFLOWACTIVEIINSTANCES', 'Active Workflow Instances'),
309
                $active,
310
                new GridFieldConfig_RecordEditor()
311
            );
312
313
            $active->getConfig()->removeComponentsByType(GridFieldAddNewButton::class);
314
            $active->getConfig()->removeComponentsByType(GridFieldDeleteAction::class);
315
316
            if (!Permission::check('REASSIGN_ACTIVE_WORKFLOWS')) {
317
                $active->getConfig()->removeComponentsByType(GridFieldEditButton::class);
318
                $active->getConfig()->addComponent(new GridFieldViewButton());
319
                $active->getConfig()->addComponent(new GridFieldDetailForm());
320
            }
321
322
            $completed = $this->Instances()->filter(array(
323
                'WorkflowStatus' => array('Complete', 'Cancelled')
324
            ));
325
326
            $config = new GridFieldConfig_Base();
327
            $config->addComponent(new GridFieldEditButton());
328
            $config->addComponent(new GridFieldDetailForm());
329
330
            $completed = new GridField(
331
                'Completed',
332
                _t('WorkflowDefinition.WORKFLOWCOMPLETEDIINSTANCES', 'Completed Workflow Instances'),
333
                $completed,
334
                $config
335
            );
336
337
            $fields->findOrMakeTab(
338
                'Root.Active',
339
                _t('WorkflowEmbargoExpiryExtension.ActiveWorkflowStateTitle', 'Active')
340
            );
341
            $fields->addFieldToTab('Root.Active', $active);
342
343
            $fields->findOrMakeTab(
344
                'Root.Completed',
345
                _t('WorkflowEmbargoExpiryExtension.CompletedWorkflowStateTitle', 'Completed')
346
            );
347
            $fields->addFieldToTab('Root.Completed', $completed);
348
        }
349
350
        $this->extend('updateCMSFields', $fields);
351
352
        return $fields;
353
    }
354
355
    public function updateAdminActions($actions)
356
    {
357
        if ($this->Template) {
358
            $template = $this->workflowService->getNamedTemplate($this->Template);
359
            if ($template && $this->TemplateVersion != $template->getVersion()) {
360
                $label = sprintf(_t('WorkflowDefinition.UPDATE_FROM_TEMLPATE', 'Update to latest template version (%s)'), $template->getVersion());
361
                $actions->push($action = FormAction::create('updatetemplateversion', $label));
362
            }
363
        }
364
    }
365
366
    public function updateFromTemplate()
367
    {
368
        if ($this->Template) {
369
            $template = $this->workflowService->getNamedTemplate($this->Template);
370
            $template->updateDefinition($this);
371
        }
372
    }
373
374
    /**
375
     * If a workflow-title doesn't already exist, we automatically create a suitable default title
376
     * when users attempt to create title-less workflow definitions or upload/create Workflows that would
377
     * otherwise have the same name.
378
     *
379
     * @return string
380
     * @todo    Filter query on current-user's workflows. Avoids confusion when other users may already have 'My Workflow 1'
381
     *          and user sees 'My Workflow 2'
382
     */
383
    public function getDefaultWorkflowTitle()
384
    {
385
        // Where is the title coming from that we wish to test?
386
        $incomingTitle = $this->incomingTitle();
387
        $defs = WorkflowDefinition::get()->map()->toArray();
388
        $tmp = array();
389
390
        foreach ($defs as $def) {
391
            $parts = preg_split("#\s#", $def, -1, PREG_SPLIT_NO_EMPTY);
392
            $lastPart = array_pop($parts);
393
            $match = implode(' ', $parts);
394
            // @todo do all this in one preg_match_all() call
395
            if (preg_match("#$match#", $incomingTitle)) {
396
                // @todo use a simple incrementer??
397
                if ($incomingTitle.' '.$lastPart == $def) {
398
                    array_push($tmp, $lastPart);
399
                }
400
            }
401
        }
402
403
        $incr = 1;
404
        if (count($tmp)) {
405
            sort($tmp, SORT_NUMERIC);
406
            $incr = (int)end($tmp)+1;
407
        }
408
        return $incomingTitle.' '.$incr;
409
    }
410
411
    /**
412
     * Return the workflow definition title according to the source
413
     *
414
     * @return string
415
     */
416
    public function incomingTitle()
417
    {
418
        $req = Controller::curr()->getRequest();
419
        if (isset($req['_CsvFile']['name']) && !empty($req['_CsvFile']['name'])) {
420
            $import = ImportedWorkflowTemplate::get()->filter('Filename', $req['_CsvFile']['name'])->first();
421
            $incomingTitle = $import->Name;
422
        } elseif (isset($req['Template']) && !empty($req['Template'])) {
423
            $incomingTitle = $req['Template'];
424
        } elseif (isset($req['Title']) && !empty($req['Title'])) {
425
            $incomingTitle = $req['Title'];
426
        } else {
427
            $incomingTitle = self::$default_workflow_title_base;
428
        }
429
        return $incomingTitle;
430
    }
431
432
    /**
433
     * Determines if target can be published directly when no workflow has started yet
434
     * Opens extension hook to allow an extension to determine if this is allowed as well
435
     *
436
     * By default returns false
437
     *
438
     * @param $member
439
     * @param $target
440
     * @return Boolean
441
     */
442
    public function canWorkflowPublish($member, $target)
443
    {
444
        $publish = $this->extendedCan('canWorkflowPublish', $member, $target);
445
446
        if (is_null($publish)) {
447
            return false;
448
        }
449
        return $publish;
450
    }
451
452
    /**
453
     *
454
     * @param Member $member
455
     * @param array $context
456
     * @return bool
457
     */
458
    public function canCreate($member = null, $context = array())
459
    {
460
        if (is_null($member)) {
461
            if (!Security::getCurrentUser()) {
462
                return false;
463
            }
464
            $member = Security::getCurrentUser();
465
        }
466
        return Permission::checkMember($member, 'CREATE_WORKFLOW');
467
    }
468
469
    /**
470
     *
471
     * @param Member $member
472
     * @return boolean
473
     */
474
    public function canView($member = null)
475
    {
476
        return $this->userHasAccess($member);
477
    }
478
479
    /**
480
     *
481
     * @param Member $member
482
     * @return boolean
483
     */
484
    public function canEdit($member = null)
485
    {
486
        return $this->canCreate($member);
487
    }
488
489
    /**
490
     *
491
     * @param Member $member
492
     * @return boolean
493
     * @see {@link $this->onBeforeDelete()}
494
     */
495 View Code Duplication
    public function canDelete($member = null)
496
    {
497
        if (!$member) {
498
            if (!Security::getCurrentUser()) {
499
                return false;
500
            }
501
            $member = Security::getCurrentUser();
502
        }
503
504
        if (Permission::checkMember($member, 'ADMIN')) {
505
            return true;
506
        }
507
508
        /*
509
		 * DELETE_WORKFLOW should trump all other canDelete() return values on
510
		 * related objects.
511
		 * @see {@link $this->onBeforeDelete()}
512
		 */
513
        return Permission::checkMember($member, 'DELETE_WORKFLOW');
514
    }
515
516
    /**
517
     * Checks whether the passed user is able to view this ModelAdmin
518
     *
519
     * @param Member $member
520
     * @return bool
521
     */
522 View Code Duplication
    protected function userHasAccess($member)
523
    {
524
        if (!$member) {
525
            if (!Security::getCurrentUser()) {
526
                return false;
527
            }
528
            $member = Security::getCurrentUser();
529
        }
530
531
        if (Permission::checkMember($member, "VIEW_ACTIVE_WORKFLOWS")) {
532
            return true;
533
        }
534
    }
535
}
536