Changeset View
Changeset View
Standalone View
Standalone View
branches/5.2.x/core/units/helpers/cron_helper.php
<?php | <?php | ||||
/** | /** | ||||
* @version $Id$ | * @version $Id$ | ||||
* @package In-Portal | * @package In-Portal | ||||
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. | * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. | ||||
* @license GNU/GPL | * @license GNU/GPL | ||||
* In-Portal is Open Source software. | * In-Portal is Open Source software. | ||||
* This means that this software may have been modified pursuant | * This means that this software may have been modified pursuant | ||||
* the GNU General Public License, and as distributed it includes | * the GNU General Public License, and as distributed it includes | ||||
* or is derivative of works licensed under the GNU General Public License | * or is derivative of works licensed under the GNU General Public License | ||||
* or other free or open source software licenses. | * or other free or open source software licenses. | ||||
* See http://www.in-portal.org/license for copyright notices and details. | * See http://www.in-portal.org/license for copyright notices and details. | ||||
*/ | */ | ||||
use Cron\CronExpression; | |||||
use Cron\FieldFactory; | |||||
use Cron\FieldInterface; | |||||
defined('FULL_PATH') or die('restricted access!'); | defined('FULL_PATH') or die('restricted access!'); | ||||
class kCronHelper extends kHelper { | class kCronHelper extends kHelper { | ||||
const COMMON = 0; | const COMMON = 0; | ||||
const MINUTE = 1; | const MINUTE = 1; | ||||
const HOUR = 2; | const HOUR = 2; | ||||
const DAY = 3; | const DAY = 3; | ||||
const MONTH = 4; | const MONTH = 4; | ||||
const WEEKDAY = 5; | const WEEKDAY = 5; | ||||
/** | /** | ||||
* Field factory. | |||||
* | |||||
* @var FieldFactory | |||||
*/ | |||||
protected $fieldFactory; | |||||
/** | |||||
* Creates an instance of kCronHelper class. | |||||
*/ | |||||
public function __construct() | |||||
{ | |||||
parent::__construct(); | |||||
$this->fieldFactory = new FieldFactory(); | |||||
} | |||||
/** | |||||
* Defines possible cron fields and their matching priority | * Defines possible cron fields and their matching priority | ||||
* | * | ||||
* @var Array | * @var Array | ||||
* @access protected | * @access protected | ||||
*/ | */ | ||||
protected $fieldTypes = Array (self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE); | protected $fieldTypes = Array (self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE); | ||||
protected $commonSettings = Array ( | protected $commonSettings = Array ( | ||||
▲ Show 20 Lines • Show All 153 Lines • ▼ Show 20 Line(s) | |||||
self::MONTH => 'Month', | self::MONTH => 'Month', | ||||
self::WEEKDAY => 'Weekday', | self::WEEKDAY => 'Weekday', | ||||
); | ); | ||||
return $field_prefix . $field_mapping[$field_type]; | return $field_prefix . $field_mapping[$field_type]; | ||||
} | } | ||||
/** | /** | ||||
* Get an instance of a field object for a cron expression field type. | |||||
* | |||||
* @param integer $field_type Field type. | |||||
* | |||||
* @return FieldInterface | |||||
*/ | |||||
protected function getField($field_type) | |||||
{ | |||||
$field_mapping = array( | |||||
self::MINUTE => 0, | |||||
self::HOUR => 1, | |||||
self::DAY => 2, | |||||
self::MONTH => 3, | |||||
self::WEEKDAY => 4, | |||||
); | |||||
return $this->fieldFactory->getField($field_mapping[$field_type]); | |||||
} | |||||
/** | |||||
* Creates virtual fields for given unit | * Creates virtual fields for given unit | ||||
* | * | ||||
* @param string $prefix | * @param string $prefix | ||||
* @param string $field_prefix | * @param string $field_prefix | ||||
* @return void | * @return void | ||||
* @access public | * @access public | ||||
*/ | */ | ||||
public function initUnit($prefix, $field_prefix = '') | public function initUnit($prefix, $field_prefix = '') | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Line(s) | |||||
* @param string $field_prefix | * @param string $field_prefix | ||||
* @return bool | * @return bool | ||||
* @access public | * @access public | ||||
*/ | */ | ||||
public function validateAndSave(kDBItem $object, $field_prefix = '') | public function validateAndSave(kDBItem $object, $field_prefix = '') | ||||
{ | { | ||||
$validated = true; | $validated = true; | ||||
$combined_value = Array (); | $combined_value = Array (); | ||||
$cron_field = new kCronField(); | |||||
foreach ($this->fieldTypes as $field_type) { | foreach ($this->fieldTypes as $field_type) { | ||||
$field_name = $this->_getFieldNameByType($field_type, $field_prefix); | $field_name = $this->_getFieldNameByType($field_type, $field_prefix); | ||||
$value = preg_replace('/\s+/s', '', mb_strtoupper($object->GetDBField($field_name))); | $value = preg_replace('/\s+/s', '', mb_strtoupper($object->GetDBField($field_name))); | ||||
if ( $cron_field->validate($field_type, $value) ) { | if ( $this->getField($field_type)->validate($value) ) { | ||||
$object->SetDBField($field_name, $value); | $object->SetDBField($field_name, $value); | ||||
} | } | ||||
else { | else { | ||||
$validated = false; | $validated = false; | ||||
$object->SetError($field_name, 'invalid_format'); | $object->SetError($field_name, 'invalid_format'); | ||||
} | } | ||||
$combined_value[$field_type] = $value; | $combined_value[$field_type] = $value; | ||||
} | } | ||||
ksort($combined_value); | ksort($combined_value); | ||||
$object->SetDBField($field_prefix, implode(' ', $combined_value)); | $object->SetDBField($field_prefix, implode(' ', $combined_value)); | ||||
return $validated; | return $validated; | ||||
} | } | ||||
/** | /** | ||||
* Replaces aliases in the field | |||||
* | |||||
* @param int $field_type | |||||
* @param string $value | |||||
* @return string | |||||
* @access public | |||||
*/ | |||||
public static function replaceAliases($field_type, $value) | |||||
{ | |||||
$replacements = Array (); | |||||
$value = mb_strtolower($value); | |||||
if ( $field_type == self::MONTH ) { | |||||
$replacements = Array ( | |||||
'jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4, 'may' => 5, 'jun' => 6, | |||||
'jul' => 7, 'aug' => 8, 'sep' => 9, 'oct' => 10, 'nov' => 11, 'dec' => 12, | |||||
); | |||||
} | |||||
elseif ( $field_type == self::WEEKDAY ) { | |||||
$replacements = Array ('sun' => 0, 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6); | |||||
} | |||||
if ( $replacements ) { | |||||
$value = str_replace(array_keys($replacements), array_values($replacements), $value); | |||||
} | |||||
return $value; | |||||
} | |||||
/** | |||||
* Returns next (after given one or now) timestamp matching given cron expression | * Returns next (after given one or now) timestamp matching given cron expression | ||||
* | * | ||||
* @param string $expression | * @param string $expression | ||||
* @param int $date | * @param int $date | ||||
* @param bool $inverse | * @param bool $inverse | ||||
* @param bool $allow_current_date | * @param bool $allow_current_date | ||||
* @return int | * @return int | ||||
* @access public | * @access public | ||||
* @throws RuntimeException | * @throws RuntimeException | ||||
*/ | */ | ||||
public function getMatch($expression, $date = NULL, $inverse = false, $allow_current_date = false) | public function getMatch($expression, $date = NULL, $inverse = false, $allow_current_date = false) | ||||
{ | { | ||||
if ( !isset($date) ) { | if ( !isset($date) ) { | ||||
$date = TIMENOW; | $date = TIMENOW; | ||||
} | } | ||||
$next_run = strtotime('-' . (int)adodb_date('s', $date) . ' seconds', $date); | $cron = CronExpression::factory($expression); | ||||
$expression_parts = explode(' ', $expression); | |||||
$cron_field = new kCronField(); | |||||
// set a hard limit to bail on an impossible date | |||||
for ($i = 0; $i < 1000; $i++) { | |||||
foreach ($this->fieldTypes as $field_type) { | |||||
$matched = false; | |||||
$part = $expression_parts[$field_type - 1]; | |||||
// check if this is singular or a list | |||||
if ( strpos($part, ',') === false ) { | |||||
$matched = $cron_field->match($field_type, $next_run, $part); | |||||
} | |||||
else { | |||||
$rules = explode(',', $part); | |||||
foreach ($rules as $rule) { | |||||
if ( $cron_field->match($field_type, $next_run, $rule) ) { | |||||
$matched = true; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
// if the field is not matched, then start over | |||||
if ( !$matched ) { | |||||
$next_run = $cron_field->increment($field_type, $next_run, $inverse); | |||||
continue 2; | |||||
} | |||||
} | |||||
// Skip this match if needed | |||||
if ( (!$allow_current_date && $next_run == $date) ) { | |||||
$next_run = $cron_field->increment(self::MINUTE, $next_run, $inverse); | |||||
continue; | |||||
} | |||||
return $next_run; | |||||
} | |||||
throw new RuntimeException('Impossible CRON expression'); | |||||
} | |||||
} | |||||
class kCronField extends kBase { | |||||
/** | |||||
* Validates field value | |||||
* | |||||
* @param int $field_type | |||||
* @param string $value | |||||
* @param bool $asterisk_allowed | |||||
* @return bool | |||||
* @access public | |||||
*/ | |||||
public function validate($field_type, $value, $asterisk_allowed = true) | |||||
{ | |||||
$rules = explode(',', kCronHelper::replaceAliases($field_type, $value)); | |||||
foreach ($rules as $rule) { | |||||
if ( $this->_isIncrementRule($rule) ) { | |||||
if ( !$this->_validateIncrementRule($field_type, $rule) ) { | |||||
return false; | |||||
} | |||||
} | |||||
elseif ( $this->_isRangeRule($rule) ) { | |||||
if ( !$this->_validateRangeRule($field_type, $rule) ) { | |||||
return false; | |||||
} | |||||
} | |||||
elseif ( !$this->_validateNumberRule($field_type, $rule, $asterisk_allowed) ) { | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
/** | |||||
* Determines if expression is range | |||||
* | |||||
* @param string $rule | |||||
* @return bool | |||||
* @access protected | |||||
*/ | |||||
protected function _isRangeRule($rule) | |||||
{ | |||||
return strpos($rule, '-') !== false; | |||||
} | |||||
/** | |||||
* Validates range rule | |||||
* | |||||
* @param int $field_type | |||||
* @param string $rule | |||||
* @return bool | |||||
* @access protected | |||||
*/ | |||||
protected function _validateRangeRule($field_type, $rule) | |||||
{ | |||||
$parts = explode('-', $rule); | |||||
if ( count($parts) != 2 ) { | |||||
return false; | |||||
} | |||||
$min_value = $parts[0]; | |||||
$max_value = $parts[1]; | |||||
if ( !$this->_validateNumberRule($field_type, $min_value) || !$this->_validateNumberRule($field_type, $max_value) || $min_value >= $max_value ) { | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
/** | |||||
* Determines if expression is increment | |||||
* | |||||
* @param string $rule | |||||
* @return bool | |||||
* @access protected | |||||
*/ | |||||
protected function _isIncrementRule($rule) | |||||
{ | |||||
return strpos($rule, '/') !== false; | |||||
} | |||||
/** | |||||
* Validates increment rule | |||||
* | |||||
* @param int $field_type | |||||
* @param string $rule | |||||
* @return bool | |||||
* @access protected | |||||
*/ | |||||
protected function _validateIncrementRule($field_type, $rule) | |||||
{ | |||||
$parts = explode('/', $rule); | |||||
if ( count($parts) != 2 ) { | |||||
return false; | |||||
} | |||||
$interval = $parts[0]; | |||||
$increment = $parts[1]; | |||||
if ( $this->_isRangeRule($interval) ) { | $date_formatted = date('Y-m-d H:i:s', $date); | ||||
if ( !$this->_validateRangeRule($field_type, $interval) ) { | |||||
return false; | |||||
} | |||||
} | |||||
elseif ( !$this->_validateNumberRule($field_type, $interval, true) ) { | |||||
return false; | |||||
} | |||||
if ( !$this->_validateNumberRule($field_type, $increment) ) { | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
/** | |||||
* Validates, that number within range OR an asterisk is given | |||||
* | |||||
* @param int $field_type | |||||
* @param string $rule | |||||
* @param bool $asterisk_allowed | |||||
* @return bool | |||||
* @access protected | |||||
*/ | |||||
protected function _validateNumberRule($field_type, $rule, $asterisk_allowed = false) | |||||
{ | |||||
if ( "$rule" === '*' ) { | |||||
return $asterisk_allowed; | |||||
} | |||||
$int_rule = (int)$rule; | |||||
if ( !is_numeric($rule) || "$int_rule" !== "$rule" ) { | |||||
// not integer | |||||
return false; | |||||
} | |||||
$range_mapping = Array ( | |||||
kCronHelper::MINUTE => Array ('from' => 0, 'to' => 59), | |||||
kCronHelper::HOUR => Array ('from' => 0, 'to' => 23), | |||||
kCronHelper::DAY => Array ('from' => 1, 'to' => 31), | |||||
kCronHelper::MONTH => Array ('from' => 1, 'to' => 12), | |||||
kCronHelper::WEEKDAY => Array ('from' => 0, 'to' => 7), | |||||
); | |||||
return $int_rule >= $range_mapping[$field_type]['from'] && $int_rule <= $range_mapping[$field_type]['to']; | |||||
} | |||||
/** | |||||
* Tries to match given date to given expression | |||||
* | |||||
* @param int $field_type | |||||
* @param int $date | |||||
* @param string $rule | |||||
* @return bool | |||||
* @access public | |||||
*/ | |||||
public function match($field_type, $date, $rule) | |||||
{ | |||||
$date_part = $this->_getDatePart($field_type, $date, $rule); | |||||
if ( $this->_isIncrementRule($rule) ) { | |||||
return $this->_isInIncrement($date_part, $rule); | |||||
} | |||||
elseif ( $this->_isRangeRule($rule) ) { | |||||
return $this->_isInRange($date_part, $rule); | |||||
} | |||||
return $rule == '*' || $date_part == $rule; | |||||
} | |||||
/** | |||||
* Returns only part, needed based on field type of date in timestamp | |||||
* | |||||
* @param int $field_type | |||||
* @param int $date | |||||
* @param string $rule | |||||
* @return int | |||||
* @access protected | |||||
*/ | |||||
protected function _getDatePart($field_type, $date, $rule) | |||||
{ | |||||
$mapping = Array ( | |||||
kCronHelper::MINUTE => 'i', | |||||
kCronHelper::HOUR => 'G', | |||||
kCronHelper::DAY => 'j', | |||||
kCronHelper::MONTH => 'n', | |||||
kCronHelper::WEEKDAY => 'N', | |||||
); | |||||
if ( $field_type == kCronHelper::WEEKDAY ) { | |||||
// Test to see which Sunday to use -- 0 == 7 == Sunday | |||||
$mapping[$field_type] = in_array(7, str_split($rule)) ? 'N' : 'w'; | |||||
} | |||||
return (int)adodb_date($mapping[$field_type], $date); | |||||
} | |||||
/** | |||||
* Test if a value is within a range | |||||
* | |||||
* @param string $date_value Set date value | |||||
* @param string $rule Value to test | |||||
* @return bool | |||||
* @access protected | |||||
*/ | |||||
protected function _isInRange($date_value, $rule) | |||||
{ | |||||
$parts = array_map('trim', explode('-', $rule, 2)); | |||||
return $date_value >= $parts[0] && $date_value <= $parts[1]; | |||||
} | |||||
/** | |||||
* Test if a value is within an increments of ranges (offset[-to]/step size) | |||||
* | |||||
* @param string $date_value Set date value | |||||
* @param string $rule Value to test | |||||
* @return bool | |||||
* @access protected | |||||
*/ | |||||
protected function _isInIncrement($date_value, $rule) | |||||
{ | |||||
$parts = array_map('trim', explode('/', $rule, 2)); | |||||
$stepSize = isset($parts[1]) ? $parts[1] : 0; | |||||
if ( $parts[0] == '*' || $parts[0] == 0 ) { | |||||
return (int)$date_value % $stepSize == 0; | |||||
} | |||||
$range = explode('-', $parts[0], 2); | |||||
$offset = $range[0]; | |||||
$to = isset($range[1]) ? $range[1] : $date_value; | |||||
// Ensure that the date value is within the range | |||||
if ( $date_value < $offset || $date_value > $to ) { | |||||
return false; | |||||
} | |||||
for ($i = $offset; $i <= $to; $i += $stepSize) { | |||||
if ( $i == $date_value ) { | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
/** | |||||
* Increments/decrements given date for 1 unit based on field type | |||||
* | |||||
* @param int $field_type | |||||
* @param int $date | |||||
* @param bool $inverse | |||||
* @return int | |||||
* @access public | |||||
*/ | |||||
public function increment($field_type, $date, $inverse = false) | |||||
{ | |||||
$mapping = Array ( | |||||
kCronHelper::MINUTE => '1 minute', | |||||
kCronHelper::HOUR => '1 hour', | |||||
kCronHelper::DAY => '1 day', | |||||
kCronHelper::MONTH => '1 month', | |||||
kCronHelper::WEEKDAY => '1 day', | |||||
); | |||||
return $this->_resetTime($field_type, strtotime(($inverse ? '-' : '+') . $mapping[$field_type], $date), $inverse); | |||||
} | |||||
/** | |||||
* Resets time based on field type | |||||
* | |||||
* @param int $field_type | |||||
* @param int $date | |||||
* @param bool $inverse | |||||
* @return int | |||||
* @access public | |||||
*/ | |||||
protected function _resetTime($field_type, $date, $inverse = false) | |||||
{ | |||||
if ( $field_type == kCronHelper::MONTH || $field_type == kCronHelper::WEEKDAY || $field_type == kCronHelper::DAY ) { | |||||
if ( $inverse ) { | |||||
$date = strtotime(adodb_date('Y-m-d 23:59:59', $date)); | |||||
// set time 23:59:00 | |||||
} | |||||
else { | |||||
// set time 00:00:00 | |||||
$date = strtotime(adodb_date('Y-m-d 00:00:00', $date)); | |||||
} | |||||
} | |||||
elseif ( $field_type == kCronHelper::HOUR ) { | |||||
if ( $inverse ) { | if ( $inverse ) { | ||||
// set time <current_hour>:59:00 | return $cron->getPreviousRunDate($date_formatted, 0, $allow_current_date)->format('U'); | ||||
$date = strtotime(adodb_date('Y-m-d H:59:59', $date)); | |||||
} | |||||
else { | |||||
// set time <current_hour>:00:00 | |||||
$date = strtotime(adodb_date('Y-m-d H:00:00', $date)); | |||||
} | } | ||||
return $cron->getNextRunDate($date_formatted, 0, $allow_current_date)->format('U'); | |||||
} | } | ||||
return $date; | |||||
} | } | ||||
} | |||||
No newline at end of file |