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 — dev ( fead3a...925885 )
by Андрей
07:22
created

AbstractWorkflow::isValidGlobalActions()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 7
Ratio 43.75 %

Code Coverage

Tests 6
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 7
loc 16
ccs 6
cts 12
cp 0.5
rs 9.2
cc 4
eloc 9
nc 4
nop 4
crap 6
1
<?php
2
/**
3
 * @link https://github.com/old-town/old-town-workflow
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace OldTown\Workflow;
7
8
use OldTown\Log\LogFactory;
9
use OldTown\PropertySet\PropertySetInterface;
10
use OldTown\PropertySet\PropertySetManager;
11
use OldTown\Workflow\Config\ConfigurationInterface;
12
use OldTown\Workflow\Config\DefaultConfiguration;
13
use OldTown\Workflow\Exception\FactoryException;
14
use OldTown\Workflow\Exception\InternalWorkflowException;
15
use OldTown\Workflow\Exception\InvalidActionException;
16
use OldTown\Workflow\Exception\InvalidArgumentException;
17
use OldTown\Workflow\Exception\InvalidEntryStateException;
18
use OldTown\Workflow\Exception\InvalidRoleException;
19
use OldTown\Workflow\Exception\StoreException;
20
use OldTown\Workflow\Exception\WorkflowException;
21
use OldTown\Workflow\Loader\ActionDescriptor;
22
use OldTown\Workflow\Loader\PermissionDescriptor;
23
use OldTown\Workflow\Loader\WorkflowDescriptor;
24
use OldTown\Workflow\Query\WorkflowExpressionQuery;
25
use OldTown\Workflow\Spi\SimpleWorkflowEntry;
26
use OldTown\Workflow\Spi\StepInterface;
27
use OldTown\Workflow\Spi\WorkflowEntryInterface;
28
use OldTown\Workflow\Spi\WorkflowStoreInterface;
29
use OldTown\Workflow\TransientVars\TransientVarsInterface;
30
use Psr\Log\LoggerInterface;
31
use SplObjectStorage;
32
use OldTown\Workflow\TransientVars\BaseTransientVars;
33
use ReflectionClass;
34
use ArrayObject;
35
use OldTown\Workflow\Engine\EngineManagerInterface;
36
use OldTown\Workflow\Engine\EngineManager;
37
38
39
40
/**
41
 * Class AbstractWorkflow
42
 *
43
 * @package OldTown\Workflow
44
 */
45
abstract class  AbstractWorkflow implements WorkflowInterface
46
{
47
    /**
48
     * @var string
49
     */
50
    const CURRENT_STEPS = 'currentSteps';
51
52
    /**
53
     * @var string
54
     */
55
    const HISTORY_STEPS = 'historySteps';
56
57
    /**
58
     * @var WorkflowContextInterface
59
     */
60
    protected $context;
61
62
    /**
63
     * @var ConfigurationInterface
64
     */
65
    protected $configuration;
66
67
68
    /**
69
     * @var TypeResolverInterface
70
     */
71
    protected $typeResolver;
72
73
    /**
74
     * Логер
75
     *
76
     * @var LoggerInterface
77
     */
78
    protected $log;
79
80
    /**
81
     * Резолвер для создания провайдеров отвечающих за исполнение функций, проверку условий, выполнение валидаторов и т.д.
82
     *
83
     * @var string
84
     */
85
    protected $defaultTypeResolverClass = TypeResolver::class;
86
87
    /**
88
     * Карта переходов состояния процесса workflow
89
     *
90
     * @var null|array
91
     */
92
    protected $mapEntryState;
93
94
    /**
95
     * Менеджер движков
96
     *
97
     * @var EngineManagerInterface
98
     */
99
    protected $engineManager;
100
101
    /**
102
     * AbstractWorkflow constructor.
103
     *
104
     * @throws InternalWorkflowException
105
     */
106 19
    public function __construct()
107
    {
108 19
        $this->initLoger();
109 19
        $this->initMapEntryState();
110 19
    }
111
112
    /**
113
     * Устанавливает менеджер движков
114
     *
115
     * @return EngineManagerInterface
116
     */
117 19
    public function getEngineManager()
118
    {
119 19
        if ($this->engineManager) {
120 19
            return $this->engineManager;
121
        }
122 19
        $this->engineManager = new EngineManager($this);
123
124 19
        return $this->engineManager;
125
    }
126
127
    /**
128
     * Возвращает менеджер движков
129
     *
130
     * @param EngineManagerInterface $engineManager
131
     *
132
     * @return $this
133
     */
134
    public function setEngineManager(EngineManagerInterface $engineManager)
135
    {
136
        $this->engineManager = $engineManager;
137
138
        return $this;
139
    }
140
141
    /**
142
     * Инициация карты переходов состояния процесса workflow
143
     */
144 19
    protected function initMapEntryState()
145
    {
146 19
        $this->mapEntryState = [
147 19
            WorkflowEntryInterface::COMPLETED => [
148 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED
149 19
            ],
150 19
            WorkflowEntryInterface::CREATED => [],
151 19
            WorkflowEntryInterface::ACTIVATED => [
152 19
                WorkflowEntryInterface::CREATED => WorkflowEntryInterface::CREATED,
153 19
                WorkflowEntryInterface::SUSPENDED => WorkflowEntryInterface::SUSPENDED,
154
155 19
            ],
156 19
            WorkflowEntryInterface::SUSPENDED => [
157 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED
158 19
            ],
159 19
            WorkflowEntryInterface::KILLED => [
160 19
                WorkflowEntryInterface::SUSPENDED => WorkflowEntryInterface::SUSPENDED,
161 19
                WorkflowEntryInterface::ACTIVATED => WorkflowEntryInterface::ACTIVATED,
162 19
                WorkflowEntryInterface::CREATED => WorkflowEntryInterface::CREATED
163 19
            ]
164 19
        ];
165 19
    }
166
167
    /**
168
     * Инициализация системы логирования
169
     *
170
     * @throws InternalWorkflowException
171
     */
172 19
    protected function initLoger()
173
    {
174 1
        try {
175 19
            $this->log = LogFactory::getLog();
176 19
        } catch (\Exception $e) {
177
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
178
        }
179 19
    }
180
181
    /**
182
     * Инициализация workflow. Workflow нужно иницаилизровать прежде, чем выполнять какие либо действия.
183
     * Workflow может быть инициализированно только один раз
184
     *
185
     * @param string $workflowName Имя workflow
186
     * @param integer $initialAction Имя первого шага, с которого начинается workflow
187
     * @param TransientVarsInterface $inputs Данные введеные пользователем
188
     *
189
     * @return integer
190
     *
191
     * @throws InternalWorkflowException
192
     * @throws InvalidActionException
193
     * @throws InvalidRoleException
194
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
195
     * @throws InvalidArgumentException
196
     * @throws WorkflowException
197
     */
198 19
    public function initialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
199
    {
200
        try {
201 19
            $initialAction = (integer)$initialAction;
202
203 19
            $wf = $this->getConfiguration()->getWorkflow($workflowName);
204
205 19
            $store = $this->getPersistence();
206
207 19
            $entry = $store->createEntry($workflowName);
208
209 19
            $ps = $store->getPropertySet($entry->getId());
210
211
212 19
            if (null === $inputs) {
213
                $inputs = $this->transientVarsFactory();
214
            }
215
216 19
            $engineManager = $this->getEngineManager();
217
218 19
            $transientVars = $inputs;
219 19
            $inputs = clone $transientVars;
220
221 19
            $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $initialAction, new ArrayObject(), $ps);
222
223 19
            if (!$this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps)) {
224 1
                $this->context->setRollbackOnly();
225 1
                $errMsg = 'You are restricted from initializing this workflow';
226 1
                throw new InvalidRoleException($errMsg);
227
            }
228
229 18
            $action = $wf->getInitialAction($initialAction);
230
231 18
            if (null === $action) {
232
                $errMsg = sprintf('Invalid initial action id: %s', $initialAction);
233
                throw new InvalidActionException($errMsg);
234
            }
235
236 18
            $currentSteps = new SplObjectStorage();
237 18
            $transitionManager = $engineManager->getTransitionEngine();
238 18
            $transitionManager->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps);
239
240 16
            $entryId = $entry->getId();
241 19
        } catch (WorkflowException $e) {
242
            $this->context->setRollbackOnly();
243
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
244
        }
245
246 16
        return $entryId;
247
    }
248
249
    /**
250
     * @param $id
251
     * @param $inputs
252
     *
253
     * @return array
254
     *
255
     */
256 2
    public function getAvailableActions($id, TransientVarsInterface $inputs = null)
257 2
    {
258
        try {
259
            $store = $this->getPersistence();
260
            $entry = $store->findEntry($id);
261
262
            if (null === $entry) {
263
                $errMsg = sprintf(
264
                    'Не существует экземпляра workflow c id %s',
265
                    $id
266
                );
267
                throw new InvalidArgumentException($errMsg);
268
            }
269
270
            if (WorkflowEntryInterface::ACTIVATED === $entry->getState()) {
271
                return [];
272
            }
273
274
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
275
276
            $l = [];
277
            $ps = $store->getPropertySet($id);
278
279
            $transientVars = $inputs;
280
            if (null === $transientVars) {
281
                $transientVars = $this->transientVarsFactory();
282
            }
283
284
            $currentSteps = $store->findCurrentSteps($id);
285
286
            $engineManager =  $this->getEngineManager();
287
            $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), 0, $currentSteps, $ps);
288
289
            $globalActions = $wf->getGlobalActions();
290
291
            $conditionsEngine = $engineManager->getConditionsEngine();
292
            foreach ($globalActions as $action) {
293
                $restriction = $action->getRestriction();
294
                $conditions = null;
295
296
                $transientVars['actionId'] = $action->getId();
297
298
                if (null !== $restriction) {
299
                    $conditions = $restriction->getConditionsDescriptor();
300
                }
301
302
                $flag = $conditionsEngine->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, 0) && $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
303
                if ($flag) {
304
                    $l[] = $action->getId();
305
                }
306
            }
307
308
            foreach ($currentSteps as $currentStep) {
309
                $availableActionsForStep = $this->getAvailableActionsForStep($wf, $currentStep, $transientVars, $ps);
310
                foreach ($availableActionsForStep as $actionId) {
311
                    $l[] = $actionId;
312
                }
313
            }
314
315
316
            return array_unique($l);
317
        } catch (\Exception $e) {
318
            $errMsg = 'Ошибка проверки доступных действий';
319
            $this->getLog()->error($errMsg, [$e]);
320
        }
321
322
        return [];
323
    }
324
325
    /**
326
     * Создает хранилище переменных
327
     *
328
     * @param $class
329
     *
330
     * @return TransientVarsInterface
331
     */
332
    protected function transientVarsFactory($class = BaseTransientVars::class)
333
    {
334
        $r = new \ReflectionClass($class);
335
        return $r->newInstance();
336
    }
337
338
    /**
339
     *
340
     *
341
     * Осуществляет переходл в новое состояние, для заданного процесса workflow
342
     *
343
     * @param integer $entryId id запущенного процесса workflow
344
     * @param integer $actionId id действия, доступного та текущем шаеге процессса workflow
345
     * @param TransientVarsInterface $inputs Входные данные для перехода
346
     *
347
     * @return void
348
     *
349
     * @throws WorkflowException
350
     * @throws InvalidActionException
351
     * @throws InvalidArgumentException
352
     * @throws InternalWorkflowException
353
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
354
     */
355 8
    public function doAction($entryId, $actionId, TransientVarsInterface $inputs = null)
356
    {
357 8
        $actionId = (integer)$actionId;
358 8
        if (null === $inputs) {
359
            $inputs = $this->transientVarsFactory();
360
        }
361 8
        $transientVars = $inputs;
362 8
        $inputs = clone $transientVars;
363
364 8
        $store = $this->getPersistence();
365 8
        $entry = $store->findEntry($entryId);
366
367 8
        if (WorkflowEntryInterface::ACTIVATED !== $entry->getState()) {
368
            return;
369
        }
370
371 8
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
372
373 8
        $currentSteps = $store->findCurrentSteps($entryId);
374 8
        if (!$currentSteps instanceof SplObjectStorage) {
375
            $errMsg = 'Invalid currentSteps';
376
            throw new InternalWorkflowException($errMsg);
377
        }
378
379 8
        $action = null;
380
381 8
        $ps = $store->getPropertySet($entryId);
382
383 8
        $engineManager = $this->getEngineManager();
384 8
        $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), $actionId, $currentSteps, $ps);
385
386 8
        $validAction = $this->isValidGlobalActions($wf, $actionId, $transientVars, $ps)
387 8
            || $this->isValidActionsFromCurrentSteps($currentSteps, $wf, $actionId, $transientVars, $ps);
388
389
390
        if (!$validAction) {
391
            $errMsg = sprintf(
392
                'Action %s is invalid',
393
                $actionId
394
            );
395
            throw new InvalidActionException($errMsg);
396
        }
397
398
399
        try {
400
            $transitionManager = $this->getEngineManager()->getTransitionEngine();
401
            if ($transitionManager->transitionWorkflow($entry, $currentSteps, $store, $wf, $action, $transientVars, $inputs, $ps)) {
0 ignored issues
show
Documentation introduced by
$action is of type null, but the function expects a object<OldTown\Workflow\Loader\ActionDescriptor>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
402
                $this->checkImplicitFinish($action, $entryId);
0 ignored issues
show
Documentation introduced by
$action is of type null, but the function expects a object<OldTown\Workflow\Loader\ActionDescriptor>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
403
            }
404
        } catch (WorkflowException $e) {
405
            $this->context->setRollbackOnly();
406
            /** @var  WorkflowException $e*/
407
            throw $e;
408
        }
409
    }
410
411
    /**
412
     * @param WorkflowDescriptor     $wf
413
     * @param                        $actionId
414
     * @param TransientVarsInterface $transientVars
415
     * @param PropertySetInterface   $ps
416
     *
417
     * @return bool
418
     */
419 8
    protected function isValidGlobalActions(WorkflowDescriptor $wf, $actionId, TransientVarsInterface $transientVars, PropertySetInterface $ps)
420
    {
421 8
        $validAction = false;
422 8
        $transitionEngine = $this->getEngineManager()->getTransitionEngine();
423 8
        foreach ($wf->getGlobalActions() as $actionDesc) {
424 View Code Duplication
            if ($actionId === $actionDesc->getId()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
425
                $action = $actionDesc;
426
427
                if ($transitionEngine->isActionAvailable($action, $transientVars, $ps, 0)) {
428
                    $validAction = true;
429
                }
430
            }
431 8
        }
432
433 8
        return $validAction;
434
    }
435
436
437
    /**
438
     *
439
     * @param SplObjectStorage       $currentSteps
440
     * @param WorkflowDescriptor     $wf
441
     * @param                        $actionId
442
     * @param TransientVarsInterface $transientVars
443
     * @param PropertySetInterface   $ps
444
     *
445
     * @return bool
446
     *
447
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
448
     * @throws InternalWorkflowException
449
     */
450 8
    protected function isValidActionsFromCurrentSteps(SplObjectStorage $currentSteps, WorkflowDescriptor $wf, $actionId, TransientVarsInterface $transientVars, PropertySetInterface $ps)
451
    {
452 8
        $transitionEngine = $this->getEngineManager()->getTransitionEngine();
453 8
        $validAction = false;
454 8
        foreach ($currentSteps as $step) {
455 8
            if ($step instanceof StepInterface) {
456 8
                $errMsg = 'Invalid step';
457 8
                throw new InternalWorkflowException($errMsg);
458
            }
459
            $s = $wf->getStep($step->getStepId());
460
461
            foreach ($s->getActions() as $actionDesc) {
462
                if (!$actionDesc instanceof ActionDescriptor) {
463
                    $errMsg = 'Invalid action descriptor';
464
                    throw new InternalWorkflowException($errMsg);
465
                }
466
467 View Code Duplication
                if ($actionId === $actionDesc->getId()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
468
                    $action = $actionDesc;
469
470
                    if ($transitionEngine->isActionAvailable($action, $transientVars, $ps, $s->getId())) {
471
                        $validAction = true;
472
                    }
473
                }
474
            }
475
        }
476
477
        return $validAction;
478
    }
479
480
    /**
481
     * @param ActionDescriptor $action
482
     * @param                  $id
483
     *
484
     * @return void
485
     *
486
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
487
     * @throws InvalidArgumentException
488
     * @throws InternalWorkflowException
489
     */
490
    protected function checkImplicitFinish(ActionDescriptor $action, $id)
491
    {
492
        $store = $this->getPersistence();
493
        $entry = $store->findEntry($id);
494
495
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
496
497
        $currentSteps = $store->findCurrentSteps($id);
498
499
        $isCompleted = $wf->getGlobalActions()->count() === 0;
500
501
        foreach ($currentSteps as $step) {
502
            if ($isCompleted) {
503
                break;
504
            }
505
506
            $stepDes = $wf->getStep($step->getStepId());
507
508
            if ($stepDes->getActions()->count() > 0) {
509
                $isCompleted = true;
510
            }
511
        }
512
513
        if ($isCompleted) {
514
            $entryEngine = $this->getEngineManager()->getEntryEngine();
515
            $entryEngine->completeEntry($action, $id, $currentSteps, WorkflowEntryInterface::COMPLETED);
516
        }
517
    }
518
519
520
521
    /**
522
     *
523
     * Check if the state of the specified workflow instance can be changed to the new specified one.
524
     *
525
     * @param integer $id The workflow instance id.
526
     * @param integer $newState The new state id.
527
     *
528
     * @return boolean true if the state of the workflow can be modified, false otherwise.
529
     *
530
     * @throws InternalWorkflowException
531
     */
532 16
    public function canModifyEntryState($id, $newState)
533
    {
534 16
        $store = $this->getPersistence();
535 16
        $entry = $store->findEntry($id);
536
537 16
        $currentState = $entry->getState();
538
539 16
        return array_key_exists($newState, $this->mapEntryState) && array_key_exists($currentState, $this->mapEntryState[$newState]);
540
    }
541
542
543
    /**
544
     *
545
     * Возвращает коллекцию объектов описывающие состояние для текущего экземпляра workflow
546
     *
547
     * @param integer $entryId id экземпляра workflow
548
     *
549
     * @return SplObjectStorage|StepInterface[]
550
     *
551
     * @throws InternalWorkflowException
552
     */
553
    public function getCurrentSteps($entryId)
554
    {
555
        return $this->getStepFromStorage($entryId, static::CURRENT_STEPS);
556
    }
557
558
    /**
559
     * Возвращает информацию о том в какие шаги, были осуществленны переходы, для процесса workflow с заданным id
560
     *
561
     * @param integer $entryId уникальный идентификатор процесса workflow
562
     *
563
     * @return StepInterface[]|SplObjectStorage список шагов
564
     *
565
     * @throws InternalWorkflowException
566
     */
567
    public function getHistorySteps($entryId)
568
    {
569
        return $this->getStepFromStorage($entryId, static::HISTORY_STEPS);
570
    }
571
572
    /**
573
     * Получение шагов информации о шагах процесса workflow
574
     *
575
     * @param $entryId
576
     * @param $type
577
     *
578
     * @return Spi\StepInterface[]|SplObjectStorage
579
     *
580
     * @throws InternalWorkflowException
581
     */
582 19
    protected function getStepFromStorage($entryId, $type)
583
    {
584
        try {
585
            $store = $this->getPersistence();
586
587
            if (static::CURRENT_STEPS === $type) {
588 19
                return $store->findCurrentSteps($entryId);
589
            } elseif (static::HISTORY_STEPS === $type) {
590
                return $store->findHistorySteps($entryId);
591
            }
592
        } catch (StoreException $e) {
593
            $errMsg = sprintf(
594
                'Ошибка при получение истории шагов для экземпляра workflow c id# %s',
595
                $entryId
596
            );
597
            $this->getLog()->error($errMsg, [$e]);
598
        }
599
600
        return new SplObjectStorage();
601
    }
602
603
604
605
    /**
606
     *
607
     *
608
     * Modify the state of the specified workflow instance.
609
     * @param integer $id The workflow instance id.
610
     * @param integer $newState the new state to change the workflow instance to.
611
     *
612
     * @throws InvalidArgumentException
613
     * @throws InvalidEntryStateException
614
     * @throws InternalWorkflowException
615
     */
616 16
    public function changeEntryState($id, $newState)
617
    {
618 16
        $store = $this->getPersistence();
619 16
        $entry = $store->findEntry($id);
620
621 16
        if ($newState === $entry->getState()) {
622
            return;
623
        }
624
625 16
        if ($this->canModifyEntryState($id, $newState)) {
626 16
            if (WorkflowEntryInterface::KILLED === $newState || WorkflowEntryInterface::COMPLETED === $newState) {
627
                $currentSteps = $this->getCurrentSteps($id);
628
629
                if (count($currentSteps) > 0) {
630
                    $entryEngine = $this->getEngineManager()->getEntryEngine();
631
                    $entryEngine->completeEntry(null, $id, $currentSteps, $newState);
632
                }
633
            }
634
635 16
            $store->setEntryState($id, $newState);
636 16
        } else {
637
            $errMsg = sprintf(
638
                'Не возможен переход в экземпляре workflow #%s. Текущее состояние %s, ожидаемое состояние %s',
639
                $id,
640
                $entry->getState(),
641
                $newState
642
            );
643
644
            throw new InvalidEntryStateException($errMsg);
645
        }
646
647 16
        $msg = sprintf(
648 16
            '%s : Новое состояние: %s',
649 16
            $entry->getId(),
650 16
            $entry->getState()
651 16
        );
652 16
        $this->getLog()->debug($msg);
653 16
    }
654
655
656
657
658
659
    /**
660
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
661
     *
662
     * @param string $workflowName имя workflow
663
     * @param integer $initialAction id начального состояния
664
     * @param TransientVarsInterface $inputs
665
     *
666
     * @return bool
667
     *
668
     * @throws InvalidArgumentException
669
     * @throws WorkflowException
670
     * @throws InternalWorkflowException
671
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
672
     */
673
    public function canInitialize($workflowName, $initialAction, TransientVarsInterface $inputs = null)
674
    {
675
        $mockWorkflowName = $workflowName;
676
        $mockEntry = new SimpleWorkflowEntry(0, $mockWorkflowName, WorkflowEntryInterface::CREATED);
677
678
        try {
679
            $ps = PropertySetManager::getInstance('memory', []);
680
            if (!$ps instanceof PropertySetInterface) {
681
                $errMsg = 'Invalid create PropertySet';
682
                throw new InternalWorkflowException($errMsg);
683
            }
684
        } catch (\Exception $e) {
685
            throw new InternalWorkflowException($e->getMessage(), $e->getCode(), $e);
686
        }
687
688
689
690
691
        if (null === $inputs) {
692
            $inputs = $this->transientVarsFactory();
693
        }
694
        $transientVars = $inputs;
695
696
        try {
697
            $this->getEngineManager()->getDataEngine()->populateTransientMap($mockEntry, $transientVars, [], $initialAction, [], $ps);
698
699
            $result = $this->canInitializeInternal($workflowName, $initialAction, $transientVars, $ps);
700
701
            return $result;
702
        } catch (InvalidActionException $e) {
703
            $this->getLog()->error($e->getMessage(), [$e]);
704
705
            return false;
706
        } catch (WorkflowException $e) {
707
            $errMsg = sprintf(
708
                'Ошибка при проверки canInitialize: %s',
709
                $e->getMessage()
710
            );
711
            $this->getLog()->error($errMsg, [$e]);
712
713
            return false;
714
        }
715
    }
716
717
718
    /**
719
     * Проверяет имеет ли пользователь достаточно прав, что бы иниициировать вызываемый процесс
720
     *
721
     * @param string $workflowName имя workflow
722
     * @param integer $initialAction id начального состояния
723
     * @param TransientVarsInterface $transientVars
724
     *
725
     * @param PropertySetInterface $ps
726
     *
727
     * @return bool
728
     *
729
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
730
     * @throws InvalidActionException
731
     * @throws InternalWorkflowException
732
     * @throws WorkflowException
733
     */
734 19
    protected function canInitializeInternal($workflowName, $initialAction, TransientVarsInterface $transientVars, PropertySetInterface $ps)
735
    {
736 19
        $wf = $this->getConfiguration()->getWorkflow($workflowName);
737
738 19
        $actionDescriptor = $wf->getInitialAction($initialAction);
739
740 19
        if (null === $actionDescriptor) {
741
            $errMsg = sprintf(
742
                'Некорректное инициирующие действие # %s',
743
                $initialAction
744
            );
745
            throw new InvalidActionException($errMsg);
746
        }
747
748 19
        $restriction = $actionDescriptor->getRestriction();
749
750
751 19
        $conditions = null;
752 19
        if (null !== $restriction) {
753 2
            $conditions = $restriction->getConditionsDescriptor();
754 2
        }
755
756 19
        $conditionsEngine = $this->getEngineManager()->getConditionsEngine();
757 19
        $passesConditions = $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, 0);
758
759 19
        return $passesConditions;
760
    }
761
762
    /**
763
     * Возвращает резолвер
764
     *
765
     * @return TypeResolverInterface
766
     */
767 19
    public function getResolver()
768
    {
769 19
        if (null !== $this->typeResolver) {
770 8
            return $this->typeResolver;
771
        }
772
773 19
        $classResolver = $this->getDefaultTypeResolverClass();
774 19
        $r = new ReflectionClass($classResolver);
775 19
        $resolver = $r->newInstance();
776 19
        $this->typeResolver = $resolver;
777
778 19
        return $this->typeResolver;
779
    }
780
781
    /**
782
     * Возвращает хранилище состояния workflow
783
     *
784
     * @return WorkflowStoreInterface
785
     *
786
     * @throws InternalWorkflowException
787
     */
788 19
    protected function getPersistence()
789
    {
790 19
        return $this->getConfiguration()->getWorkflowStore();
791
    }
792
793
    /**
794
     * Получить конфигурацию workflow. Метод также проверяет была ли иницилазированн конфигурация, если нет, то
795
     * инициализирует ее.
796
     *
797
     * Если конфигурация не была установленна, то возвращает конфигурацию по умолчанию
798
     *
799
     * @return ConfigurationInterface|DefaultConfiguration Конфигурация которая была установленна
800
     *
801
     * @throws InternalWorkflowException
802
     */
803 19
    public function getConfiguration()
804
    {
805 19
        $config = null !== $this->configuration ? $this->configuration : DefaultConfiguration::getInstance();
806
807 19
        if (!$config->isInitialized()) {
808
            try {
809
                $config->load(null);
810
            } catch (FactoryException $e) {
811
                $errMsg = 'Ошибка при иницилазации конфигурации workflow';
812
                $this->getLog()->critical($errMsg, ['exception' => $e]);
813
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
814
            }
815
        }
816
817 19
        return $config;
818
    }
819
820
    /**
821
     * @return LoggerInterface
822
     */
823 17
    public function getLog()
824
    {
825 17
        return $this->log;
826
    }
827
828
    /**
829
     * @param LoggerInterface $log
830
     *
831
     * @return $this
832
     *
833
     * @throws InternalWorkflowException
834
     */
835
    public function setLog($log)
836
    {
837
        try {
838
            LogFactory::validLogger($log);
839
        } catch (\Exception $e) {
840
            $errMsg = 'Ошибка при валидации логера';
841
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
842
        }
843
844
845
        $this->log = $log;
846
847
        return $this;
848
    }
849
850
851
    /**
852
     * Get the workflow descriptor for the specified workflow name.
853
     *
854
     * @param string $workflowName The workflow name.
855
     * @return WorkflowDescriptor
856
     *
857
     * @throws InternalWorkflowException
858
     */
859
    public function getWorkflowDescriptor($workflowName)
860
    {
861
        try {
862
            return $this->getConfiguration()->getWorkflow($workflowName);
863
        } catch (FactoryException $e) {
864
            $errMsg = 'Ошибка при загрузке workflow';
865
            $this->getLog()->error($errMsg, ['exception' => $e]);
866
            throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
867
        }
868
    }
869
870
871
    /**
872
     * Executes a special trigger-function using the context of the given workflow instance id.
873
     * Note that this method is exposed for Quartz trigger jobs, user code should never call it.
874
     *
875
     * @param integer $id The workflow instance id
876
     * @param integer $triggerId The id of the special trigger-function
877
     *
878
     * @throws InvalidArgumentException
879
     * @throws WorkflowException
880
     * @throws InternalWorkflowException
881
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
882
     */
883
    public function executeTriggerFunction($id, $triggerId)
884
    {
885
        $store = $this->getPersistence();
886
        $entry = $store->findEntry($id);
887
888
        if (null === $entry) {
889
            $errMsg = sprintf(
890
                'Ошибка при выполнение тригера # %s для несуществующего экземпляра workflow id# %s',
891
                $triggerId,
892
                $id
893
            );
894
            $this->getLog()->warning($errMsg);
895
            return;
896
        }
897
898
        $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
899
900
        $ps = $store->getPropertySet($id);
901
        $transientVars = $this->transientVarsFactory();
902
903
        $this->getEngineManager()->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $store->findCurrentSteps($id), $ps);
904
905
        $functionsEngine = $this->getEngineManager()->getFunctionsEngine();
906
        $functionsEngine->executeFunction($wf->getTriggerFunction($triggerId), $transientVars, $ps);
907
    }
908
909
910
    /**
911
     * @param WorkflowDescriptor   $wf
912
     * @param StepInterface        $step
913
     * @param TransientVarsInterface                $transientVars
914
     * @param PropertySetInterface $ps
915
     *
916
     * @return array
917
     *
918
     * @throws InternalWorkflowException
919
     * @throws WorkflowException
920
     * @throws \OldTown\Workflow\Exception\ArgumentNotNumericException
921
     */
922
    protected function getAvailableActionsForStep(WorkflowDescriptor $wf, StepInterface $step, TransientVarsInterface $transientVars, PropertySetInterface $ps)
923
    {
924
        $l = [];
925
        $s = $wf->getStep($step->getStepId());
926
927
        if (null === $s) {
928
            $errMsg = sprintf(
929
                'getAvailableActionsForStep вызван с не существующим id шага %s',
930
                $step->getStepId()
931
            );
932
933
            $this->getLog()->warning($errMsg);
934
935
            return $l;
936
        }
937
938
        $actions  = $s->getActions();
939
940
        if (null === $actions || 0  === $actions->count()) {
941
            return $l;
942
        }
943
944
        $conditionsEngine = $this->getEngineManager()->getConditionsEngine();
945
        foreach ($actions as $action) {
946
            $restriction = $action->getRestriction();
947
            $conditions = null;
948
949
            $transientVars['actionId'] = $action->getId();
950
951
952
            if (null !== $restriction) {
953
                $conditions = $restriction->getConditionsDescriptor();
954
            }
955
956
            $f = $conditionsEngine->passesConditionsByDescriptor($wf->getGlobalConditions(), $transientVars, $ps, $s->getId())
957
                 && $conditionsEngine->passesConditionsByDescriptor($conditions, $transientVars, $ps, $s->getId());
958
            if ($f) {
959
                $l[] = $action->getId();
960
            }
961
        }
962
963
        return $l;
964
    }
965
966
    /**
967
     * @param ConfigurationInterface $configuration
968
     *
969
     * @return $this
970
     */
971 19
    public function setConfiguration(ConfigurationInterface $configuration)
972
    {
973 19
        $this->configuration = $configuration;
974
975 19
        return $this;
976
    }
977
978
    /**
979
     * Возвращает состояние для текущего экземпляра workflow
980
     *
981
     * @param integer $id id экземпляра workflow
982
     * @return integer id текущего состояния
983
     *
984
     * @throws InternalWorkflowException
985
     */
986
    public function getEntryState($id)
987
    {
988
        try {
989
            $store = $this->getPersistence();
990
991
            return $store->findEntry($id)->getState();
992
        } catch (StoreException $e) {
993
            $errMsg = sprintf(
994
                'Ошибка при получение состояния экземпляра workflow c id# %s',
995
                $id
996
            );
997
            $this->getLog()->error($errMsg, [$e]);
998
        }
999
1000
        return WorkflowEntryInterface::UNKNOWN;
1001
    }
1002
1003
1004
    /**
1005
     * Настройки хранилища
1006
     *
1007
     * @return array
1008
     *
1009
     * @throws InternalWorkflowException
1010
     */
1011
    public function getPersistenceProperties()
1012
    {
1013
        return $this->getConfiguration()->getPersistenceArgs();
1014
    }
1015
1016
1017
    /**
1018
     * Get the PropertySet for the specified workflow instance id.
1019
     * @param integer $id The workflow instance id.
1020
     *
1021
     * @return PropertySetInterface
1022
     * @throws InternalWorkflowException
1023
     */
1024
    public function getPropertySet($id)
1025
    {
1026
        $ps = null;
1027
1028
        try {
1029
            $ps = $this->getPersistence()->getPropertySet($id);
1030
        } catch (StoreException $e) {
1031
            $errMsg = sprintf(
1032
                'Ошибка при получение PropertySet для экземпляра workflow c id# %s',
1033
                $id
1034
            );
1035
            $this->getLog()->error($errMsg, [$e]);
1036
        }
1037
1038
        return $ps;
1039
    }
1040
1041
    /**
1042
     * @return string[]
1043
     *
1044
     * @throws InternalWorkflowException
1045
     */
1046
    public function getWorkflowNames()
1047
    {
1048
        try {
1049
            return $this->getConfiguration()->getWorkflowNames();
1050
        } catch (FactoryException $e) {
1051
            $errMsg = 'Ошибка при получение имен workflow';
1052
            $this->getLog()->error($errMsg, [$e]);
1053
        }
1054
1055
        return [];
1056
    }
1057
1058
    /**
1059
     * @param TypeResolverInterface $typeResolver
1060
     *
1061
     * @return $this
1062
     */
1063
    public function setTypeResolver(TypeResolverInterface $typeResolver)
1064
    {
1065
        $this->typeResolver = $typeResolver;
1066
1067
        return $this;
1068
    }
1069
1070
1071
    /**
1072
     * Get a collection (Strings) of currently defined permissions for the specified workflow instance.
1073
     * @param integer $id id the workflow instance id.
1074
     * @param TransientVarsInterface $inputs inputs The inputs to the workflow instance.
1075
     *
1076
     * @return array  A List of permissions specified currently (a permission is a string name).
1077
     *
1078
     */
1079
    public function getSecurityPermissions($id, TransientVarsInterface $inputs = null)
1080
    {
1081
        try {
1082
            $store = $this->getPersistence();
1083
            $entry = $store->findEntry($id);
1084
            $wf = $this->getConfiguration()->getWorkflow($entry->getWorkflowName());
1085
1086
            $ps = $store->getPropertySet($id);
1087
1088
            if (null === $inputs) {
1089
                $inputs = $this->transientVarsFactory();
1090
            }
1091
            $transientVars = $inputs;
1092
1093
            $currentSteps = $store->findCurrentSteps($id);
1094
1095
            $engineManager = $this->getEngineManager();
1096
            try {
1097
                $engineManager->getDataEngine()->populateTransientMap($entry, $transientVars, $wf->getRegisters(), null, $currentSteps, $ps);
1098
            } catch (\Exception $e) {
1099
                $errMsg = sprintf(
1100
                    'Внутреннея ошибка: %s',
1101
                    $e->getMessage()
1102
                );
1103
                throw new InternalWorkflowException($errMsg, $e->getCode(), $e);
1104
            }
1105
1106
1107
            $s = [];
1108
1109
            $conditionsEngine = $engineManager->getConditionsEngine();
1110
            foreach ($currentSteps as $step) {
1111
                $stepId = $step->getStepId();
1112
1113
                $xmlStep = $wf->getStep($stepId);
1114
1115
                $securities = $xmlStep->getPermissions();
1116
1117
                foreach ($securities as $security) {
1118
                    if (!$security instanceof PermissionDescriptor) {
1119
                        $errMsg = 'Invalid PermissionDescriptor';
1120
                        throw new InternalWorkflowException($errMsg);
1121
                    }
1122
                    $conditionsDescriptor = $security->getRestriction()->getConditionsDescriptor();
1123
                    if (null !== $security->getRestriction() && $conditionsEngine->passesConditionsByDescriptor($conditionsDescriptor, $transientVars, $ps, $xmlStep->getId())) {
1124
                        $s[$security->getName()] = $security->getName();
1125
                    }
1126
                }
1127
            }
1128
1129
            return $s;
1130
        } catch (\Exception $e) {
1131
            $errMsg = sprintf(
1132
                'Ошибка при получение информации о правах доступа для экземпляра workflow c id# %s',
1133
                $id
1134
            );
1135
            $this->getLog()->error($errMsg, [$e]);
1136
        }
1137
1138
        return [];
1139
    }
1140
1141
1142
    /**
1143
     * Get the name of the specified workflow instance.
1144
     *
1145
     * @param integer $id the workflow instance id.
1146
     *
1147
     * @return string
1148
     *
1149
     * @throws InternalWorkflowException
1150
     */
1151
    public function getWorkflowName($id)
1152
    {
1153
        try {
1154
            $store = $this->getPersistence();
1155
            $entry = $store->findEntry($id);
1156
1157
            if (null !== $entry) {
1158
                return $entry->getWorkflowName();
1159
            }
1160
        } catch (FactoryException $e) {
1161
            $errMsg = sprintf(
1162
                'Ошибка при получение имен workflow для инстанса с id # %s',
1163
                $id
1164
            );
1165
            $this->getLog()->error($errMsg, [$e]);
1166
        }
1167
1168
        return null;
1169
    }
1170
1171
    /**
1172
     * Удаляет workflow
1173
     *
1174
     * @param string $workflowName
1175
     *
1176
     * @return bool
1177
     *
1178
     * @throws InternalWorkflowException
1179
     */
1180
    public function removeWorkflowDescriptor($workflowName)
1181
    {
1182
        return $this->getConfiguration()->removeWorkflow($workflowName);
1183
    }
1184
1185
    /**
1186
     * @param                    $workflowName
1187
     * @param WorkflowDescriptor $descriptor
1188
     * @param                    $replace
1189
     *
1190
     * @return bool
1191
     *
1192
     * @throws InternalWorkflowException
1193
     */
1194
    public function saveWorkflowDescriptor($workflowName, WorkflowDescriptor $descriptor, $replace)
1195
    {
1196
        $success = $this->getConfiguration()->saveWorkflow($workflowName, $descriptor, $replace);
1197
1198
        return $success;
1199
    }
1200
1201
1202
    /**
1203
     * Query the workflow store for matching instances
1204
     *
1205
     * @param WorkflowExpressionQuery $query
1206
     *
1207
     * @return array
1208
     *
1209
     * @throws InternalWorkflowException
1210
     */
1211
    public function query(WorkflowExpressionQuery $query)
1212
    {
1213
        return $this->getPersistence()->query($query);
1214
    }
1215
1216
    /**
1217
     * @return string
1218
     */
1219 19
    public function getDefaultTypeResolverClass()
1220
    {
1221 19
        return $this->defaultTypeResolverClass;
1222
    }
1223
1224
    /**
1225
     * @param string $defaultTypeResolverClass
1226
     *
1227
     * @return $this
1228
     */
1229
    public function setDefaultTypeResolverClass($defaultTypeResolverClass)
1230
    {
1231
        $this->defaultTypeResolverClass = (string)$defaultTypeResolverClass;
1232
1233
        return $this;
1234
    }
1235
1236
1237
    /**
1238
     * @return WorkflowContextInterface
1239
     */
1240 19
    public function getContext()
1241
    {
1242 19
        return $this->context;
1243
    }
1244
}
1245