Workflow::getStepMeta()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
namespace Fabrica\Workflow;
3
4
use Fabrica\Workflow\Eloquent\Definition;
5
use Fabrica\Workflow\Eloquent\Entry;
6
use Fabrica\Workflow\Eloquent\CurrentStep;
7
use Fabrica\Workflow\Eloquent\HistoryStep;
8
9
use Fabrica\Workflow\StateNotActivatedException;
10
use Fabrica\Workflow\StepNotFoundException;
11
use Fabrica\Workflow\CurrentStepNotFoundException;
12
use Fabrica\Workflow\ActionNotFoundException;
13
use Fabrica\Workflow\ActionNotAvailableException;
14
use Fabrica\Workflow\ResultNotFoundException;
15
use Fabrica\Workflow\ResultNotAvailableException;
16
use Fabrica\Workflow\FunctionNotFoundException;
17
use Fabrica\Workflow\EntryNotFoundException;
18
use Fabrica\Workflow\ConfigNotFoundException;
19
use Fabrica\Workflow\SplitNotFoundException;
20
use Fabrica\Workflow\JoinNotFoundException;
21
22
class Workflow
23
{
24
25
    /**
26
     * The workflow five states.
27
     *
28
     * @var int 
29
     */
30
    const OSWF_CREATED    = 1;
31
    const OSWF_ACTIVATED  = 2;
32
    const OSWF_SUSPENDED  = 3;
33
    const OSWF_COMPLETED  = 4;
34
    const OSWF_KILLED     = 5;
35
36
    /**
37
     * The workflow instance object.
38
     *
39
     * @var App\Workflow\Eloquent\Entry 
40
     */
41
    protected $entry;
42
43
    /**
44
     * The workflow config description.
45
     *
46
     * @var array
47
     */
48
    protected $wf_config;
49
50
    /**
51
     * workflow options 
52
     *
53
     * @var array
54
     */
55
    protected $options = [];
56
57
    /**
58
     * workflow constructor
59
     *
60
     * @param  string $entry_id
61
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
62
     */
63
    public function __construct($entry_id)
64
    {
65
        $entry = Entry::find($entry_id);
66
        if ($entry) {
67
            $this->entry = $entry;
68
            $definition = Definition::find($entry->definition_id);
69
            if (!$definition) {
70
                throw new ConfigNotFoundException();
71
            }
72
73
            if (isset($definition->contents) && $definition->contents) {
74
                $this->wf_config = $definition->contents;
75
            }
76
            else
77
            {
78
                throw new ConfigNotFoundException();
79
            }
80
        }
81
        else
82
        {
83
            throw new EntryNotFoundException();
84
        }
85
    }
86
87
    /**
88
     * create workflow.
89
     *
90
     * @param  string $definition_id
91
     * @param  string $caller
92
     * @return string
93
     */
94
    public static function createInstance($definition_id, $caller)
95
    {
96
        $entry = new Entry;
97
        $entry->definition_id = $definition_id;
98
        $entry->creator = $caller;
99
        $entry->state = self::OSWF_CREATED;
100
        $entry->save();
101
        return new Workflow($entry->id);
102
    }
103
104
    /**
105
     * get entry id.
106
     *
107
     * @return string
108
     */
109
    public function getEntryId()
110
    {
111
        return $this->entry->id; 
112
    }
113
114
    /**
115
     * check action is available
116
     *
117
     * @param  array $action_descriptor
118
     * @return boolean
119
     */
120
    private function isActionAvailable($action_descriptor)
121
    {
122
        if (isset($action_descriptor['restrict_to']) && isset($action_descriptor['restrict_to']['conditions']) && $action_descriptor['restrict_to']['conditions']) {
123
            if (!$this->passesConditions($action_descriptor['restrict_to']['conditions'])) {
124
                return false;
125
            }
126
        }
127
        return true;
128
    }
129
130
    /**
131
     * initialize workflow.
132
     *
133
     * @param  array options 
134
     * @return void
135
     */
136
    public function start($options=[])
137
    {
138
        $this->options = array_merge($this->options, $options);
139
140
        if (!isset($this->wf_config['initial_action']) || !$this->wf_config['initial_action']) {
141
            throw new ActionNotFoundException();
142
        }
143
144
        //$available_action_flg = false;
145
        //foreach ($this->wf_config['initial_actions'] as $action_descriptor)
146
        //{
147
        //    if ($this->isActionAvailable($action_descriptor))
148
        //    {
149
        //        $available_action_flg = true;
150
        //        break;
151
        //    }
152
        //}
153
        //if (!$available_action_flg)
154
        //{
155
        //    throw new ActionNotAvailableException();
156
        //}
157
158
        $action_descriptor = $this->wf_config['initial_action'];
159
        if (!$this->isActionAvailable($action_descriptor)) {
160
            throw new ActionNotAvailableException();
161
        }
162
163
        // confirm result whose condition is satified.
164
        if (!isset($action_descriptor['results']) || !$action_descriptor['results']) {
165
            throw new ResultNotFoundException();
166
        }
167
168
        $available_result_descriptor = $this->getAvailableResult($action_descriptor['results']);
169
        if (!$available_result_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $available_result_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
170
            throw new ResultNotAvailableException();
171
        }
172
        // create new current step
173
        $this->createNewCurrentStep($available_result_descriptor, $action_descriptor['id'], '');
174
        // change workflow state to activited
175
        $this->changeEntryState(self::OSWF_ACTIVATED);
176
177
        return $this;
178
    }
179
180
    /**
181
     * get workflow state.
182
     *
183
     * @return string
184
     */
185
    public function getEntryState()
186
    {
187
        return $this->entry->state;
188
    }
189
190
    /**
191
     * change workflow state.
192
     *
193
     * @param  string $new_state
194
     * @return void
195
     */
196
    public function changeEntryState($new_state)
197
    {
198
        $entry = Entry::find($this->entry->id);
199
        $entry->state = $new_state;
200
        $entry->save();
201
    }
202
203
    /**
204
     * complete workflow.
205
     *
206
     * @param  string $entry_id
207
     * @return void
208
     */
209
    protected function completeEntry($entry_id)
210
    {
211
        return $this->changeEntryState($entry_id, self::OSWF_COMPLETED);
0 ignored issues
show
Unused Code introduced by
The call to Workflow::changeEntryState() has too many arguments starting with self::OSWF_COMPLETED.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
212
    }
213
214
    /**
215
     * get current steps for workflow.
216
     *
217
     * @return array
218
     */
219
    public function getCurrentSteps()
220
    {
221
        return Entry::find($this->entry->id)->currentSteps;
222
    }
223
224
    /**
225
     *  get step meta.
226
     *
227
     * @param  string $step_id
228
     * @return array
229
     */
230
    public function getStepMeta($step_id, $name='')
231
    {
232
        $step_description = $this->getStepDescriptor($step_id);
233
        if ($name) {
234
            return isset($step_description[$name]) ? $step_description[$name] : '';
235
        }
236
        return $step_description;
237
    }
238
239
    /**
240
     *  move workflow step to history
241
     *
242
     * @param  App\Workflow\Eloquent\CurrentStep $current_step
243
     * @param  int                               $action_id
244
     * @return string previous_id 
245
     */
246
    private function moveToHistory($current_step, $action_id)
247
    {
248
        // add to history records
249
        $history_step = new HistoryStep;
250
        $history_step->fill($current_step->toArray());
251
        $history_step->action_id = $action_id;
252
        $history_step->caller = isset($this->options['caller']) ? $this->options['caller'] : '';
253
        $history_step->finish_time = time();
254
        $history_step->save();
255
256
        // delete from current step
257
        $current_step->delete();
258
259
        return $history_step->id;
260
    }
261
262
    /**
263
     *  create new workflow step.
264
     *
265
     * @param  array  $result_descriptor
266
     * @param  int    $action_id
267
     * @param  string $previous_id
268
     * @return void
269
     */
270
    private function createNewCurrentStep($result_descriptor, $action_id, $previous_id='')
271
    {
272
        $step_descriptor = [];
273
        if (isset($result_descriptor['step']) && $result_descriptor['step']) {
274
            $step_descriptor = $this->getStepDescriptor($result_descriptor['step']);
275
            if (!$step_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $step_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
276
                throw new StepNotFoundException();
277
            }
278
        }
279
        if (!$step_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $step_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
280
            return;
281
        }
282
        // order to use for workflow post-function
283
        if (isset($step_descriptor['state']) && $step_descriptor['state']) {
284
            $this->options['state'] = $step_descriptor['state'];
285
        }
286
287
        $new_current_step = new CurrentStep;
288
        $new_current_step->entry_id = $this->entry->id;
289
        $new_current_step->action_id = $action_id;
290
        $new_current_step->step_id = isset($result_descriptor['step']) ? intval($result_descriptor['step']) : 0;
291
        $new_current_step->previous_id = $previous_id;
292
        $new_current_step->status = isset($result_descriptor['status']) ? $result_descriptor['status'] : 'Finished';
293
        $new_current_step->start_time = time();
294
        $new_current_step->owners =  isset($this->options['owners']) ? $this->options['owners'] : '';
295
        $new_current_step->comments = isset($this->options['comments']) ? $this->options['comments'] : '';
296
        $new_current_step->caller = isset($this->options['caller']) ? $this->options['caller'] : '';
297
        $new_current_step->save();
298
299
        // trigger before step
300
        if (isset($step_descriptor['pre_functions']) && $step_descriptor['pre_functions']) {
301
            $this->executeFunctions($step_descriptor['pre_functions']);
302
        }
303
    }
304
305
    /**
306
     * transfer workflow step.
307
     *
308
     * @param  array $current_steps
309
     * @param  int   $action;
0 ignored issues
show
Bug introduced by
There is no parameter named $action;. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
310
     * @return void
311
     */
312
    private function transitionWorkflow($current_steps, $action_id)
313
    {
314
        foreach ($current_steps as $current_step)
315
        {
316
            $step_descriptor = $this->getStepDescriptor($current_step->step_id);
317
            if (!$step_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $step_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
318
                throw new StepNotFoundException();
319
            }
320
321
            $action_descriptor = $this->getActionDescriptor(isset($step_descriptor['actions']) ? $step_descriptor['actions'] : [], $action_id);
322
            if ($action_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $action_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
323
                break;
324
            }
325
        }
326
        if (!$action_descriptor) {
0 ignored issues
show
Bug introduced by
The variable $action_descriptor does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug Best Practice introduced by
The expression $action_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
327
            throw new ActionNotFoundException(); 
328
        }
329
        if (!$this->isActionAvailable($action_descriptor)) {
330
            throw new ActionNotAvailableException();
331
        }
332
333
        if (!isset($action_descriptor['results']) || !$action_descriptor['results']) {
334
            throw new ResultNotFoundException();
335
        }
336
        // confirm result whose condition is satified.
337
        $available_result_descriptor = $this->getAvailableResult($action_descriptor['results']);
338
        if (!$available_result_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $available_result_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
339
            throw new ResultNotAvailableException();
340
        }
341
342
        // triggers after step
343
        if (isset($step_descriptor['post_functions']) && $step_descriptor['post_functions']) {
344
            $this->executeFunctions($step_descriptor['post_functions']);
345
        }
346
        // triggers before action
347
        if (isset($action_descriptor['pre_functions']) && $action_descriptor['pre_functions']) {
348
            $this->executeFunctions($action_descriptor['pre_functions']);
349
        }
350
        // triggers before result
351
        if (isset($available_result_descriptor['pre_functions']) && $available_result_descriptor['pre_functions']) {
352
            $this->executeFunctions($available_result_descriptor['pre_functions']);
353
        }
354
        // split workflow
355
        if (isset($available_result_descriptor['split']) && $available_result_descriptor['split']) {
356
            // get split result
357
            $split_descriptor = $this->getSplitDescriptor($available_result_descriptor['split']);
358
            if (!$split_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $split_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
359
                throw new SplitNotFoundException();
360
            }
361
362
            // move current to history step
363
            $prevoius_id = $this->moveToHistory($current_step, $action_id);
0 ignored issues
show
Bug introduced by
The variable $current_step seems to be defined by a foreach iteration on line 314. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
364
            foreach ($split_descriptor['list'] as $result_descriptor)
365
            {
366
                $this->createNewCurrentStep($result_descriptor, $action_id, $prevoius_id);
367
            }
368
        }
369
        else if (isset($available_result_descriptor['join']) && $available_result_descriptor['join']) {
370
            // fix me. join logic will be realized, suggest using the propertyset
371
            // get join result
372
            $join_descriptor = $this->getJoinDescriptor($available_result_descriptor['join']);
373
            if (!$join_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $join_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
374
                throw new JoinNotFoundException();
375
            }
376
377
            // move current to history step
378
            $prevoius_id = $this->moveToHistory($current_step, $action_id);
379
            if ($this->isJoinCompleted()) {
380
                // record other previous_ids by propertyset
381
                $this->createNewCurrentStep($join_descriptor, $action_id, $prevoius_id);
382
            }
383
        }
384
        else
385
        {
386
            // move current to history step
387
            $prevoius_id = $this->moveToHistory($current_step, $action_id);
388
            // create current step
389
            $this->createNewCurrentStep($available_result_descriptor, $action_id, $prevoius_id);
390
        }
391
        // triggers after result
392
        if (isset($available_result_descriptor['post_functions']) && $available_result_descriptor['post_functions']) {
393
            $this->executeFunctions($available_result_descriptor['post_functions']);
394
        }
395
        // triggers after action
396
        if (isset($action_descriptor['post_functions']) && $action_descriptor['post_functions']) {
397
            $this->executeFunctions($action_descriptor['post_functions']);
398
        }
399
    }
400
401
    /**
402
     * check if the join is completed 
403
     */
404
    private function isJoinCompleted()
405
    {
406
        return !CurrentStep::where('entry_id', $this->entry->id)->exists();
407
    }
408
409
    /**
410
     * execute action 
411
     *
412
     * @param  string $action_id
413
     * @param  array  $options;
0 ignored issues
show
Documentation introduced by
There is no parameter named $options;. Did you maybe mean $options?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
414
     * @return string
415
     */
416
    public function doAction($action_id, $options=[])
417
    {
418
        $state = $this->getEntryState($this->entry->id);
0 ignored issues
show
Unused Code introduced by
The call to Workflow::getEntryState() has too many arguments starting with $this->entry->id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
419
        if ($state != self::OSWF_CREATED && $state != self::OSWF_ACTIVATED) {
420
            throw new StateNotActivatedException();
421
        }
422
423
        $current_steps = $this->getCurrentSteps();
424
        if (!$current_steps) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $current_steps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
425
            throw new CurrentStepNotFoundException();
426
        }
427
428
        // set options
429
        $this->options = array_merge($this->options, $options);
430
        // complete workflow step transition
431
        $this->transitionWorkflow($current_steps, intval($action_id));
432
    }
433
434
    /**
435
     * get join descriptor from list.
436
     *
437
     * @param  string $join_id
438
     * @return array 
439
     */
440
    private function getJoinDescriptor($join_id)
441
    {
442
        foreach ($this->wf_config['joins'] as $join)
443
        {
444
            if ($join['id'] == $join_id) {
445
                return $join;
446
            }
447
        }
448
        return [];
449
    }
450
451
    /**
452
     * get split descriptor from list.
453
     *
454
     * @param  string $split_id
455
     * @return array 
456
     */
457
    private function getSplitDescriptor($split_id)
458
    {
459
        foreach ($this->wf_config['splits'] as $split)
460
        {
461
            if ($split['id'] == $split_id) {
462
                return $split;
463
            }
464
        }
465
        return [];
466
    }
467
468
    /**
469
     * get action descriptor from list.
470
     *
471
     * @param  array  $actions
472
     * @param  string $action_id
473
     * @return array 
474
     */
475
    private function getActionDescriptor($actions, $action_id)
476
    {
477
        // get global config
478
        $actions = $actions ?: [];
479
        foreach ($actions as $action)
480
        {
481
            if ($action['id'] == $action_id) {
482
                return $action;
483
            }
484
        }
485
        return [];
486
    }
487
488
    /**
489
     *  get step configuration.
490
     *
491
     * @param  array  $steps
0 ignored issues
show
Bug introduced by
There is no parameter named $steps. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
492
     * @param  string $step_id
493
     * @return array
494
     */
495
    private function getStepDescriptor($step_id)
496
    {
497
        foreach ($this->wf_config['steps'] as $step)
498
        {
499
            if ($step['id'] == $step_id) {
500
                return $step;
501
            }
502
        }
503
        return [];
504
    }
505
506
    /**
507
     * save workflow configuration info.
508
     *
509
     * @param  array $info
510
     * @return void
511
     */
512
    public static function saveWorkflowDefinition($info)
513
    {
514
        $definition = $info['_id'] ? Definition::find($info['_id']) : new Definition;
515
        $definition->fill($info);
516
        $definition->save();
517
    }
518
519
    /**
520
     * remove configuration info.
521
     *
522
     * @param  string $definition_id
523
     * @return void
524
     */
525
    public static function removeWorkflowDefinition($definition_id)
526
    {
527
        Definition::find($definition_id)->delete();
528
    }
529
530
    /**
531
     * get all available actions
532
     *
533
     * @param  array $info
0 ignored issues
show
Bug introduced by
There is no parameter named $info. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
534
     * @param  bool  $dest_state added for kanban dnd, is not common param.
535
     * @return array
536
     */
537
    public function getAvailableActions($options=[], $dest_state = false)
538
    {
539
        // set options
540
        $this->options = array_merge($this->options, $options);
541
542
        $available_actions = [];
543
        // get current steps
544
        $current_steps = $this->getCurrentSteps();
545
        foreach ($current_steps as $current_step)
546
        {
547
            $actions = $this->getAvailableActionsFromStep($current_step->step_id, $dest_state);
548
            $actions && $available_actions += $actions;
0 ignored issues
show
Bug Best Practice introduced by
The expression $actions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
549
        }
550
        return $available_actions;
551
    }
552
553
    /**
554
     * get available actions for step
555
     *
556
     * @param  string $step_id
557
     * @param  bool   $dest_state added for kanban dnd, is not common param.
558
     * @return array
559
     */
560
    private function getAvailableActionsFromStep($step_id, $dest_state = false)
561
    {
562
        $step_descriptor = $this->getStepDescriptor($step_id);
563
        if (!$step_descriptor) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $step_descriptor of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
564
            throw new StepNotFoundException();
565
        }
566
        if (!isset($step_descriptor['actions']) || !$step_descriptor['actions']) {
567
            return [];
568
        }
569
        // global conditions for step
570
        if (!$this->isActionAvailable($step_descriptor)) {
571
            return [];
572
        }
573
574
        $available_actions = [];
575
        foreach ($step_descriptor['actions'] as $action)
576
        {
577
            if ($this->isActionAvailable($action)) {
578
                if ($dest_state) {
579
                    $state = '';
580
                    if (isset($action['results']) && is_array($action['results']) && count($action['results']) > 0 && isset($action['results'][0]['step'])) {
581
                        $dest_step_descriptor = $this->getStepDescriptor($action['results'][0]['step']);
582
                        $state = $dest_step_descriptor['state'];
583
                    }
584
                    
585
                    $available_actions[] = [ 'id' => $action['id'], 'name' => $action['name'], 'screen' => $action['screen'] ?: '', 'state' => $state ];
586
                }
587
                else
588
                {
589
                    $available_actions[] = [ 'id' => $action['id'], 'name' => $action['name'], 'screen' => $action['screen'] ?: '' ];
590
                }
591
            }
592
        }
593
594
        return $available_actions;
595
    }
596
597
    /**
598
     * get available result from result-list 
599
     *
600
     * @param  array $results_descriptor
601
     * @return array
602
     */
603
    public function getAvailableResult($results_descriptor)
604
    {
605
        $available_result_descriptor = [];
606
607
        // confirm result whose condition is satified.
608
        foreach ($results_descriptor as $result_descriptor)
609
        {
610
            if (isset($result_descriptor['conditions']) && $result_descriptor['conditions']) {
611
                if ($this->passesConditions($result_descriptor['conditions'])) {
612
                    $available_result_descriptor = $result_descriptor;
613
                    break;
614
                }
615
            }
616
            else
617
            {
618
                $available_result_descriptor = $result_descriptor;
619
            }
620
        }
621
        return $available_result_descriptor;
622
    }
623
624
    /**
625
     * check conditions is passed
626
     *
627
     * @param  array $conditions
628
     * @return boolean
629
     */
630
    private function passesConditions($conditions)
631
    {
632
        if (!isset($conditions['list']) || !$conditions['list']) {
633
            return true;
634
        }
635
636
        $type = isset($conditions['type']) && isset($conditions['type']) ? $conditions['type'] : 'and';
637
        $result = $type == 'and' ? true : false;
638
639
        foreach ($conditions['list'] as $condition)
640
        {
641
            $tmp = $this->passesCondition($condition);
642
            if ($type == 'and' && !$tmp) {
643
                return false;
644
            }
645
            if ($type == 'or' && $tmp) {
646
                return true;
647
            }
648
        }
649
        return $result;
650
    }
651
652
    /**
653
     * check condition is passed
654
     *
655
     * @param  array $condition
656
     * @return boolean
657
     */
658
    private function passesCondition($condition)
659
    {
660
        return $this->executeFunction($condition);
661
    }
662
663
    /**
664
     * execute functions
665
     *
666
     * @param  array function
667
     * @return void
668
     */
669
    private function executeFunctions($functions)
670
    {
671
        if (!$functions || !is_array($functions)) {
672
            return;
673
        }
674
675
        foreach ($functions as $function) 
676
        {
677
            if (is_array($function) && $function) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $function of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
678
                $this->executeFunction($function);
679
            }
680
        }
681
    }
682
683
    /**
684
     * execute function
685
     *
686
     * @param  array $function
687
     * @return mixed
688
     */
689
    private function executeFunction($function)
690
    {
691
        $method = explode('@', $function['name']);
692
        $class = $method[0];
693
        $action = isset($method[1]) && $method[1] ? $method[1] : 'handle';
694
695
        // check handle function exists
696
        if (!method_exists($class, $action)) {
697
            throw new FunctionNotFoundException();
698
        }
699
        $args = isset($function['args']) ? $function['args'] : [];
700
        // generate temporary vars
701
        $tmp_vars = $this->genTmpVars($args);
702
        // call handle function
703
        return $class::$action($tmp_vars);
704
    }
705
706
    /**
707
     * get all workflows' name.
708
     *
709
     * @return array
710
     */
711
    public static function getWorkflowNames()
712
    {
713
        return Definition::all(['name']);
714
    }
715
716
    /**
717
     * generate temporary variable.
718
     *
719
     * @return array
720
     */
721
    private function genTmpVars($args=[])
722
    {
723
        $tmp_vars = [];
724
        foreach ($this->entry as $key => $val)
725
        {
726
            $tmp_vars[$key] = $val;
727
        }
728
        $tmp_vars = array_merge($tmp_vars, $this->options);
729
730
        return array_merge($tmp_vars, $args);
731
    }
732
733
    /**
734
     * get property set
735
     *
736
     * @return mixed 
737
     */
738
    public function getPropertySet($key)
739
    {
740
        return $key ? $this->entry->propertysets[$key] : $this->entry->propertysets;
741
    }
742
743
    /**
744
     * add property set 
745
     *
746
     * @return void 
747
     */
748
    public function setPropertySet($key, $val)
749
    {
750
        $this->entry->propertysets = array_merge($this->entry->propertysets ?: [], [ $key => $val ]);
751
        $this->entry->save();
752
    }
753
754
    /**
755
     * remove property set
756
     *
757
     * @return void 
758
     */
759
    public function removePropertySet($key)
760
    {
761
        $this->entry->unset($key ? ('propertysets.' . $key) : 'propertysets');
762
    }
763
764
    /**
765
     * get used states in the workflow
766
     *
767
     * @return array
768
     */
769
    public static function getStates($contents)
770
    {
771
        $state_ids = [];
772
        $steps = isset($contents['steps']) && $contents['steps'] ? $contents['steps'] : [];
773
        foreach ($steps as $step)
774
        {
775
            $state_ids[] = isset($step['state']) ? $step['state'] : '';
776
        }
777
        return $state_ids;
778
    }
779
780
    /**
781
     * get used screens in the workflow 
782
     *
783
     * @return array 
784
     */
785
    public static function getScreens($contents)
786
    {
787
        $screen_ids = [];
788
        $steps = isset($contents['steps']) && $contents['steps'] ? $contents['steps'] : [];
789
        foreach ($steps as $step)
790
        {
791
            if (!isset($step['actions']) || !$step['actions']) {
792
                continue;
793
            }
794
            foreach ($step['actions'] as $action)
795
            {
796
                if (!isset($action['screen']) || !$action['screen']) {
797
                    continue;
798
                }
799
                $action['screen'] !=  '-1' && !in_array($action['screen'], $screen_ids) && $screen_ids[] = $action['screen'];
800
            }
801
        }
802
        return $screen_ids;
803
    }
804
805
    /**
806
     * get step num 
807
     *
808
     * @return int 
809
     */
810
    public static function getStepNum($contents)
811
    {
812
        $steps = isset($contents['steps']) && $contents['steps'] ? $contents['steps'] : [];
813
        return count($steps);
814
    }
815
816
    /**
817
     * fake new workflow step.
818
     *
819
     * @param  array $result_descriptor
820
     * @param  array $caller
821
     * @return void
822
     */
823
    public function fakeNewCurrentStep($result_descriptor, $caller)
824
    {
825
        $new_current_step = new CurrentStep;
826
        $new_current_step->entry_id = $this->entry->id;
827
        $new_current_step->step_id = intval($result_descriptor['id']);
828
        $new_current_step->status = isset($result_descriptor['status']) ? $result_descriptor['status'] : '';
829
        $new_current_step->start_time = time();
830
        $new_current_step->caller = $caller ?: '';
831
        $new_current_step->save();
832
    }
833
}
834