Completed
Push — developer ( 8830e0...c20012 )
by Błażej
63:58 queued 51:15
created

Workflow::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
/* +**********************************************************************************
3
 * The contents of this file are subject to the vtiger CRM Public License Version 1.0
4
 * ("License"); You may not use this file except in compliance with the License
5
 * The Original Code is:  vtiger CRM Open Source
6
 * The Initial Developer of the Original Code is vtiger.
7
 * Portions created by vtiger are Copyright (C) vtiger.
8
 * All Rights Reserved.
9
 * Contributor(s): YetiForce.com
10
 * ********************************************************************************** */
11
12
/**
13
 * Workflow class.
14
 */
15
class Workflow
16
{
17
	/**
18
	 * Scheduled hourly.
19
	 *
20
	 * @var int
21
	 */
22
	public static $SCHEDULED_HOURLY = 1;
23
24
	/**
25
	 * Scheduled daily.
26
	 *
27
	 * @var int
28
	 */
29
	public static $SCHEDULED_DAILY = 2;
30
31
	/**
32
	 * Scheduled weekly.
33
	 *
34
	 * @var int
35
	 */
36
	public static $SCHEDULED_WEEKLY = 3;
37
38
	/**
39
	 * Scheduled on specific date.
40
	 *
41
	 * @var int
42
	 */
43
	public static $SCHEDULED_ON_SPECIFIC_DATE = 4;
44
45
	/**
46
	 * Scheduled monthly by date.
47
	 *
48
	 * @var int
49
	 */
50
	public static $SCHEDULED_MONTHLY_BY_DATE = 5;
51
52
	/**
53
	 * Scheduled monthly by weekday.
54
	 *
55
	 * @var int
56
	 */
57
	public static $SCHEDULED_MONTHLY_BY_WEEKDAY = 6;
58
59
	/**
60
	 * Scheduled annually.
61
	 *
62
	 * @var int
63
	 */
64
	public static $SCHEDULED_ANNUALLY = 7;
65
66
	/**
67
	 * Constructor.
68
	 */
69 2
	public function __construct()
70
	{
71 2
		$this->conditionStrategy = new VTJsonCondition();
72 2
	}
73
74
	/**
75
	 * Setup workflow.
76
	 *
77
	 * @param array $row
78
	 */
79 2
	public function setup($row)
80
	{
81 2
		$this->id = $row['workflow_id'] ?? '';
82 2
		$this->moduleName = $row['module_name'] ?? '';
83 2
		$this->description = $row['summary'] ?? '';
84 2
		$this->test = $row['test'] ?? '';
85 2
		$this->executionCondition = $row['execution_condition'] ?? '';
86 2
		$this->schtypeid = $row['schtypeid'] ?? '';
87 2
		$this->schtime = $row['schtime'] ?? '';
88 2
		$this->schdayofmonth = $row['schdayofmonth'] ?? '';
89 2
		$this->schdayofweek = $row['schdayofweek'] ?? '';
90 2
		$this->schannualdates = $row['schannualdates'] ?? '';
91 2
		if (isset($row['defaultworkflow'])) {
92 1
			$this->defaultworkflow = $row['defaultworkflow'];
93
		}
94 2
		$this->filtersavedinnew = $row['filtersavedinnew'] ?? '';
95 2
		$this->nexttrigger_time = $row['nexttrigger_time'] ?? '';
96 2
	}
97
98
	/**
99
	 * Evaluate.
100
	 *
101
	 * @param Vtiger_Record_Model $recordModel
102
	 *
103
	 * @return bool
104
	 */
105 8
	public function evaluate($recordModel)
106
	{
107 8
		if ($this->test == '') {
108
			return true;
109
		} else {
110 8
			$cs = $this->conditionStrategy;
111
112 8
			return $cs->evaluate($this->test, $recordModel);
113
		}
114
	}
115
116
	/**
117
	 * Check if workfow is completed for record.
118
	 *
119
	 * @param int $recordId
120
	 *
121
	 * @return bool
122
	 */
123 1
	public function isCompletedForRecord($recordId)
124
	{
125 1
		$isExistsActivateDonce = (new \App\Db\Query())->from('com_vtiger_workflow_activatedonce')->where(['entity_id' => $recordId, 'workflow_id' => $this->id])->exists();
126 1
		$isExistsWorkflowTasks = (new \App\Db\Query())->from('com_vtiger_workflowtasks')
127 1
			->innerJoin('com_vtiger_workflowtask_queue', 'com_vtiger_workflowtasks.task_id= com_vtiger_workflowtask_queue.task_id')
128 1
			->where(['entity_id' => $recordId, 'workflow_id' => $this->id])->exists();
129
130 1
		if (!$isExistsActivateDonce && !$isExistsWorkflowTasks) { // Workflow not done for specified record
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return !(!$isExistsActiv...isExistsWorkflowTasks);.
Loading history...
131 1
			return false;
132
		} else {
133
			return true;
134
		}
135
	}
136
137
	/**
138
	 * Mark workflow as completed for record.
139
	 *
140
	 * @param int $recordId
141
	 */
142
	public function markAsCompletedForRecord($recordId)
143
	{
144
		\App\Db::getInstance()->createCommand()
145
			->insert('com_vtiger_workflow_activatedonce', [
146
				'entity_id' => $recordId,
147
				'workflow_id' => $this->id,
148
			])->execute();
149
	}
150
151
	/**
152
	 * Perform tasks.
153
	 *
154
	 * @param Vtiger_Record_Model $recordModel
155
	 */
156 8
	public function performTasks(Vtiger_Record_Model $recordModel)
157
	{
158 8
		require_once 'modules/com_vtiger_workflow/VTTaskManager.php';
159 8
		require_once 'modules/com_vtiger_workflow/VTTaskQueue.php';
160
161 8
		$tm = new VTTaskManager();
162 8
		$taskQueue = new VTTaskQueue();
163 8
		$tasks = $tm->getTasksForWorkflow($this->id);
164 8
		foreach ($tasks as &$task) {
0 ignored issues
show
Bug introduced by
The expression $tasks of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
165 8
			if ($task->active) {
166 6
				$trigger = $task->trigger;
167 6 View Code Duplication
				if ($trigger !== null) {
168
					$delay = strtotime($recordModel->get($trigger['field'])) + $trigger['days'] * 86400;
169
				} else {
170 6
					$delay = 0;
171
				}
172 6
				if ($task->executeImmediately === true) {
173
					$task->doTask($recordModel);
174
				} else {
175 6
					$hasContents = $task->hasContents($recordModel);
176 6
					if ($hasContents) {
177 8
						$taskQueue->queueTask($task->id, $recordModel->getId(), $delay, $task->getContents($recordModel));
178
					}
179
				}
180
			}
181
		}
182 8
	}
183
184
	/**
185
	 * Execution condition as label.
186
	 *
187
	 * @param string $label
188
	 *
189
	 * @return string
190
	 */
191
	public function executionConditionAsLabel($label = null)
192
	{
193
		if ($label === null) {
194
			$arr = ['ON_FIRST_SAVE', 'ONCE', 'ON_EVERY_SAVE', 'ON_MODIFY', 'ON_DELETE', 'ON_SCHEDULE', 'MANUAL', 'TRIGGER', 'BLOCK_EDIT', 'ON_RELATED'];
195
196
			return $arr[$this->executionCondition - 1];
197
		} else {
198
			$arr = ['ON_FIRST_SAVE' => 1, 'ONCE' => 2, 'ON_EVERY_SAVE' => 3, 'ON_MODIFY' => 4,
199
				'ON_DELETE' => 5, 'ON_SCHEDULE' => 6, 'MANUAL' => 7, 'TRIGGER' => 8, 'BLOCK_EDIT' => 9, 'ON_RELATED' => 10, ];
200
			$this->executionCondition = $arr[$label];
201
		}
202
	}
203
204
	/**
205
	 * Sets next trigger time.
206
	 *
207
	 * @param timestamp $time
208
	 */
209
	public function setNextTriggerTime($time)
210
	{
211
		if ($time) {
212
			\App\Db::getInstance()->createCommand()->update('com_vtiger_workflows', ['nexttrigger_time' => $time], ['workflow_id' => $this->id])->execute();
213
			$this->nexttrigger_time = $time;
214
		}
215
	}
216
217
	/**
218
	 * Return next trigger timestamp.
219
	 *
220
	 * @return timestamp
221
	 */
222
	public function getNextTriggerTimeValue()
223
	{
224
		return $this->nexttrigger_time;
225
	}
226
227
	/**
228
	 * Return schedule type.
229
	 *
230
	 * @return int
231
	 */
232
	public function getWFScheduleType()
233
	{
234
		return $this->executionCondition == 6 ? $this->schtypeid : 0;
235
	}
236
237
	/**
238
	 * Return workflow schedule timestamp.
239
	 *
240
	 * @return timestamp
241
	 */
242
	public function getWFScheduleTime()
243
	{
244
		return $this->schtime;
245
	}
246
247
	/**
248
	 * Return workflow schedule day.
249
	 *
250
	 * @return int
251
	 */
252
	public function getWFScheduleDay()
253
	{
254
		return $this->schdayofmonth;
255
	}
256
257
	/**
258
	 * Return workflow schedule week.
259
	 *
260
	 * @return int
261
	 */
262
	public function getWFScheduleWeek()
263
	{
264
		return $this->schdayofweek;
265
	}
266
267
	/**
268
	 * Return workflow schedule annual dates.
269
	 *
270
	 * @return bool
271
	 */
272
	public function getWFScheduleAnnualDates()
273
	{
274
		return $this->schannualdates;
275
	}
276
277
	/**
278
	 * Function gets the next trigger for the workflows.
279
	 *
280
	 * @global string $default_timezone
281
	 *
282
	 * @return timestamp
283
	 */
284
	public function getNextTriggerTime()
285
	{
286
		$default_timezone = \AppConfig::main('default_timezone');
287
		$admin = Users::getActiveAdminUser();
288
		$adminTimeZone = $admin->time_zone;
289
		date_default_timezone_set($adminTimeZone);
290
291
		$scheduleType = $this->getWFScheduleType();
292
		$nextTime = null;
293
294
		if ($scheduleType == self::$SCHEDULED_HOURLY) {
295
			$nextTime = date('Y-m-d H:i:s', strtotime('+1 hour'));
296
		}
297
298
		if ($scheduleType == self::$SCHEDULED_DAILY) {
299
			$nextTime = $this->getNextTriggerTimeForDaily($this->getWFScheduleTime());
300
		}
301
302
		if ($scheduleType == self::$SCHEDULED_WEEKLY) {
303
			$nextTime = $this->getNextTriggerTimeForWeekly($this->getWFScheduleWeek(), $this->getWFScheduleTime());
304
		}
305
306
		if ($scheduleType == self::$SCHEDULED_ON_SPECIFIC_DATE) {
307
			$nextTime = date('Y-m-d H:i:s', strtotime('+10 year'));
308
		}
309
310
		if ($scheduleType == self::$SCHEDULED_MONTHLY_BY_DATE) {
311
			$nextTime = $this->getNextTriggerTimeForMonthlyByDate($this->getWFScheduleDay(), $this->getWFScheduleTime());
312
		}
313
314
		if ($scheduleType == self::$SCHEDULED_MONTHLY_BY_WEEKDAY) {
315
			$nextTime = $this->getNextTriggerTimeForMonthlyByWeekDay($this->getWFScheduleDay(), $this->getWFScheduleTime());
316
		}
317
318
		if ($scheduleType == self::$SCHEDULED_ANNUALLY) {
319
			$nextTime = $this->getNextTriggerTimeForAnnualDates($this->getWFScheduleAnnualDates(), $this->getWFScheduleTime());
320
		}
321
		date_default_timezone_set($default_timezone);
322
323
		return $nextTime;
324
	}
325
326
	/**
327
	 * get next trigger time for daily.
328
	 *
329
	 * @param type $schTime
330
	 *
331
	 * @return time
332
	 */
333
	public function getNextTriggerTimeForDaily($scheduledTime)
334
	{
335
		$now = strtotime(date('Y-m-d H:i:s'));
336
		$todayScheduledTime = strtotime(date('Y-m-d H:i:s', strtotime($scheduledTime)));
337
		if ($now > $todayScheduledTime) {
338
			$nextTime = date('Y-m-d H:i:s', strtotime('+1 day ' . $scheduledTime));
339
		} else {
340
			$nextTime = date('Y-m-d H:i:s', $todayScheduledTime);
341
		}
342
		return $nextTime;
343
	}
344
345
	/**
346
	 * get next trigger Time For weekly.
347
	 *
348
	 * @param json $scheduledDaysOfWeek
349
	 * @param time $scheduledTime
350
	 *
351
	 * @return time
352
	 */
353
	public function getNextTriggerTimeForWeekly($scheduledDaysOfWeek, $scheduledTime)
354
	{
355
		$weekDays = ['1' => 'Monday', '2' => 'Tuesday', '3' => 'Wednesday', '4' => 'Thursday', '5' => 'Friday', '6' => 'Saturday', '7' => 'Sunday'];
356
		$currentTime = time();
357
		$currentWeekDay = date('N', $currentTime);
358
		if ($scheduledDaysOfWeek) {
359
			$scheduledDaysOfWeek = \App\Json::decode($scheduledDaysOfWeek);
360
			if (is_array($scheduledDaysOfWeek)) {
361
				/*
362
				  algorithm :
363
				  1. First sort all the weekdays(stored as 0,1,2,3 etc in db) and find the closest weekday which is greater than currentWeekDay
364
				  2. If found, set the next trigger date to the next weekday value in the same week.
365
				  3. If not found, set the trigger date to the next first value.
366
				 */
367
				$nextTriggerWeekDay = null;
368
				sort($scheduledDaysOfWeek);
369
				foreach ($scheduledDaysOfWeek as $index => $weekDay) {
370
					if ($weekDay == $currentWeekDay) { //if today is the weekday selected
371
						$scheduleWeekDayInTime = strtotime(date('Y-m-d', strtotime($weekDays[$currentWeekDay])) . ' ' . $scheduledTime);
372
						if ($currentTime < $scheduleWeekDayInTime) { //if the scheduled time is greater than current time, selected today
373
							$nextTriggerWeekDay = $weekDay;
374
							break;
375
						} else {
376
							//current time greater than scheduled time, get the next weekday
377
							if (count($scheduledDaysOfWeek) == 1) { //if only one weekday selected, then get next week
378
								$nextTime = date('Y-m-d', strtotime('next ' . $weekDays[$weekDay])) . ' ' . $scheduledTime;
379
							} else {
380
								$nextWeekDay = $scheduledDaysOfWeek[$index + 1]; // its the last day of the week i.e. sunday
381
								if (empty($nextWeekDay)) {
382
									$nextWeekDay = $scheduledDaysOfWeek[0];
383
								}
384
								$nextTime = date('Y-m-d', strtotime('next ' . $weekDays[$nextWeekDay])) . ' ' . $scheduledTime;
385
							}
386
						}
387
					} elseif ($weekDay > $currentWeekDay) {
388
						$nextTriggerWeekDay = $weekDay;
389
						break;
390
					}
391
				}
392
393
				if ($nextTime === null) {
394
					if (!empty($nextTriggerWeekDay)) {
395
						$nextTime = date('Y-m-d H:i:s', strtotime($weekDays[$nextTriggerWeekDay] . ' ' . $scheduledTime));
396
					} else {
397
						$nextTime = date('Y-m-d H:i:s', strtotime($weekDays[$scheduledDaysOfWeek[0]] . ' ' . $scheduledTime));
398
					}
399
				}
400
			}
401
		}
402
		return $nextTime;
403
	}
404
405
	/**
406
	 * get next triggertime for monthly.
407
	 *
408
	 * @param int $scheduledDayOfMonth
409
	 * @param int $scheduledTime
410
	 *
411
	 * @return time
412
	 */
413
	public function getNextTriggerTimeForMonthlyByDate($scheduledDayOfMonth, $scheduledTime)
414
	{
415
		$currentDayOfMonth = date('j', time());
416
		if ($scheduledDayOfMonth) {
417
			$scheduledDaysOfMonth = \App\Json::decode($scheduledDayOfMonth);
418
			if (is_array($scheduledDaysOfMonth)) {
419
				/*
420
				  algorithm :
421
				  1. First sort all the days in ascending order and find the closest day which is greater than currentDayOfMonth
422
				  2. If found, set the next trigger date to the found value which is in the same month.
423
				  3. If not found, set the trigger date to the next month's first selected value.
424
				 */
425
				$nextTriggerDay = null;
426
				sort($scheduledDaysOfMonth);
427
				foreach ($scheduledDaysOfMonth as $day) {
428
					if ($day == $currentDayOfMonth) {
429
						$currentTime = time();
430
						$schTime = strtotime(date('Y') . '-' . date('m') . '-' . $day . ' ' . $scheduledTime);
431
						if ($schTime > $currentTime) {
432
							$nextTriggerDay = $day;
433
							break;
434
						}
435
					} elseif ($day > $currentDayOfMonth) {
436
						$nextTriggerDay = $day;
437
						break;
438
					}
439
				}
440
				if (!empty($nextTriggerDay)) {
441
					$firstDayofNextMonth = date('Y:m:d H:i:s', strtotime('first day of this month'));
442
					$nextTime = date('Y:m:d', strtotime($firstDayofNextMonth . ' + ' . ($nextTriggerDay - 1) . ' days'));
443
					$nextTime = $nextTime . ' ' . $scheduledTime;
444
				} else {
445
					$firstDayofNextMonth = date('Y:m:d H:i:s', strtotime('first day of next month'));
446
					$nextTime = date('Y:m:d', strtotime($firstDayofNextMonth . ' + ' . ($scheduledDaysOfMonth[0] - 1) . ' days'));
447
					$nextTime = $nextTime . ' ' . $scheduledTime;
448
				}
449
			}
450
		}
451
		return $nextTime;
452
	}
453
454
	/**
455
	 * to get next trigger time for weekday of the month.
456
	 *
457
	 * @param int       $scheduledWeekDayOfMonth
458
	 * @param timestamp $scheduledTime
459
	 *
460
	 * @return time
461
	 */
462
	public function getNextTriggerTimeForMonthlyByWeekDay($scheduledWeekDayOfMonth, $scheduledTime)
463
	{
464
		$currentTime = time();
465
		$currentDayOfMonth = date('j', $currentTime);
466
		$scheduledTime = $this->getWFScheduleTime();
467
		if ($scheduledWeekDayOfMonth == $currentDayOfMonth) {
468
			$nextTime = date('Y-m-d H:i:s', strtotime('+1 month ' . $scheduledTime));
469
		} else {
470
			$monthInFullText = date('F', $currentTime);
471
			$yearFullNumberic = date('Y', $currentTime);
472
			if ($scheduledWeekDayOfMonth < $currentDayOfMonth) {
473
				$nextMonth = date('Y-m-d H:i:s', strtotime('next month'));
474
				$monthInFullText = date('F', strtotime($nextMonth));
475
			}
476
			$nextTime = date('Y-m-d H:i:s', strtotime($scheduledWeekDayOfMonth . ' ' . $monthInFullText . ' ' . $yearFullNumberic . ' ' . $scheduledTime));
477
		}
478
		return $nextTime;
479
	}
480
481
	/**
482
	 * to get next trigger time.
483
	 *
484
	 * @param json      $annualDates
485
	 * @param timestamp $scheduledTime
486
	 *
487
	 * @return time
488
	 */
489
	public function getNextTriggerTimeForAnnualDates($annualDates, $scheduledTime)
490
	{
491
		if ($annualDates) {
492
			$today = date('Y-m-d');
493
			$annualDates = \App\Json::decode($annualDates);
494
			$nextTriggerDay = null;
495
			// sort the dates
496
			sort($annualDates);
497
			$currentTime = time();
498
			$currentDayOfMonth = date('Y-m-d', $currentTime);
499
			foreach ($annualDates as $day) {
500
				if ($day == $currentDayOfMonth) {
501
					$schTime = strtotime($day . ' ' . $scheduledTime);
502
					if ($schTime > $currentTime) {
503
						$nextTriggerDay = $day;
504
						break;
505
					}
506
				} elseif ($day > $today) {
507
					$nextTriggerDay = $day;
508
					break;
509
				}
510
			}
511
			if (!empty($nextTriggerDay)) {
512
				$nextTime = date('Y:m:d H:i:s', strtotime($nextTriggerDay . ' ' . $scheduledTime));
513
			} else {
514
				$nextTriggerDay = $annualDates[0];
515
				$nextTime = date('Y:m:d H:i:s', strtotime($nextTriggerDay . ' ' . $scheduledTime . '+1 year'));
516
			}
517
		}
518
		return $nextTime;
519
	}
520
}
521