Index: branches/5.2.x/admin/system_presets/simple/scheduled_tasks_scheduled-task.php =================================================================== diff -u -N -r15033 -r15433 --- branches/5.2.x/admin/system_presets/simple/scheduled_tasks_scheduled-task.php (.../scheduled_tasks_scheduled-task.php) (revision 15033) +++ branches/5.2.x/admin/system_presets/simple/scheduled_tasks_scheduled-task.php (.../scheduled_tasks_scheduled-task.php) (revision 15433) @@ -23,7 +23,7 @@ // fields to hide $hidden_fields = Array ( - /* 'ScheduledTaskId', 'Name', 'Type', 'Status', 'Event', 'RunInterval', 'LastRunOn', + /* 'ScheduledTaskId', 'Name', 'Type', 'Status', 'Event', 'RunSchedule', 'LastRunOn', 'LastRunStatus', 'NextRunOn', 'RunTime', */ ); @@ -33,7 +33,7 @@ // fields to make required $required_fields = Array ( - /* 'ScheduledTaskId', 'Name', 'Type', 'Status', 'Event', 'RunInterval', 'LastRunOn', + /* 'ScheduledTaskId', 'Name', 'Type', 'Status', 'Event', 'RunSchedule', 'LastRunOn', 'LastRunStatus', 'NextRunOn', 'RunTime', */ ); @@ -47,5 +47,5 @@ // hide columns in grids $hide_columns = Array ( // currently not in user -// 'Default' => Array ( 'ScheduledTaskId', 'Name', 'Type', 'Status', 'Event', 'RunInterval', 'LastRunOn', 'LastRunStatus', 'NextRunOn', ), +// 'Default' => Array ( 'ScheduledTaskId', 'Name', 'Type', 'Status', 'Event', 'RunSchedule', 'LastRunOn', 'LastRunStatus', 'NextRunOn', ), ); \ No newline at end of file Index: branches/5.2.x/core/units/mailing_lists/mailing_lists_config.php =================================================================== diff -u -N -r15033 -r15433 --- branches/5.2.x/core/units/mailing_lists/mailing_lists_config.php (.../mailing_lists_config.php) (revision 15033) +++ branches/5.2.x/core/units/mailing_lists/mailing_lists_config.php (.../mailing_lists_config.php) (revision 15433) @@ -1,6 +1,6 @@ Array ( - 'generate_mailing_queue' => Array ('EventName' => 'OnGenerateEmailQueue', 'RunInterval' => 1800), - 'process_mailing_queue' => Array ('EventName' => 'OnProcessEmailQueue', 'RunInterval' => 1800), + 'generate_mailing_queue' => Array ('EventName' => 'OnGenerateEmailQueue', 'RunSchedule' => '*/30 * * * *'), + 'process_mailing_queue' => Array ('EventName' => 'OnProcessEmailQueue', 'RunSchedule' => '*/30 * * * *'), ), 'IDField' => 'MailingId', Index: branches/5.2.x/core/install/install_schema.sql =================================================================== diff -u -N -r15390 -r15433 --- branches/5.2.x/core/install/install_schema.sql (.../install_schema.sql) (revision 15390) +++ branches/5.2.x/core/install/install_schema.sql (.../install_schema.sql) (revision 15433) @@ -719,7 +719,7 @@ `Type` tinyint(3) unsigned NOT NULL DEFAULT '1', `Status` tinyint(3) unsigned NOT NULL DEFAULT '1', `Event` varchar(255) NOT NULL DEFAULT '', - RunInterval int(10) unsigned NOT NULL DEFAULT '0', + RunSchedule varchar(255) NOT NULL DEFAULT '* * * * *', LastRunOn int(10) unsigned DEFAULT NULL, LastRunStatus tinyint(3) unsigned NOT NULL DEFAULT '1', NextRunOn int(11) DEFAULT NULL, @@ -729,7 +729,6 @@ SiteDomainLimitation varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (ScheduledTaskId), KEY `Status` (`Status`), - KEY RunInterval (RunInterval), KEY LastRunOn (LastRunOn), KEY LastRunStatus (LastRunStatus), KEY RunTime (RunTime), Index: branches/5.2.x/core/units/forms/forms/forms_config.php =================================================================== diff -u -N -r15033 -r15433 --- branches/5.2.x/core/units/forms/forms/forms_config.php (.../forms_config.php) (revision 15033) +++ branches/5.2.x/core/units/forms/forms/forms_config.php (.../forms_config.php) (revision 15433) @@ -1,6 +1,6 @@ Array ( - 'check_submission_repies' => Array('EventName' => 'OnProcessReplies', 'RunInterval' => 3600), - 'check_bounced_submission_repies' => Array('EventName' => 'OnProcessBouncedReplies', 'RunInterval' => 18000), + 'check_submission_repies' => Array('EventName' => 'OnProcessReplies', 'RunSchedule' => '0 * * * *'), + 'check_bounced_submission_repies' => Array('EventName' => 'OnProcessBouncedReplies', 'RunSchedule' => '0 */5 * * *'), ), 'Hooks' => Array( Index: branches/5.2.x/core/units/images/images_config.php =================================================================== diff -u -N -r15033 -r15433 --- branches/5.2.x/core/units/images/images_config.php (.../images_config.php) (revision 15033) +++ branches/5.2.x/core/units/images/images_config.php (.../images_config.php) (revision 15433) @@ -1,6 +1,6 @@ Array ( - 'clean_catalog_images' => Array ('EventName' => 'OnCleanImages', 'RunInterval' => 604800, 'Status' => STATUS_DISABLED), - 'clean_resized_catalog_images' => Array ('EventName' => 'OnCleanResizedImages', 'RunInterval' => 2592000, 'Status' => STATUS_DISABLED), + 'clean_catalog_images' => Array ('EventName' => 'OnCleanImages', 'RunSchedule' => '0 0 * * 0', 'Status' => STATUS_DISABLED), + 'clean_resized_catalog_images' => Array ('EventName' => 'OnCleanResizedImages', 'RunSchedule' => '0 0 1 * *', 'Status' => STATUS_DISABLED), ), 'IDField' => 'ImageId', Index: branches/5.2.x/core/units/admin/admin_config.php =================================================================== diff -u -N -r15033 -r15433 --- branches/5.2.x/core/units/admin/admin_config.php (.../admin_config.php) (revision 15033) +++ branches/5.2.x/core/units/admin/admin_config.php (.../admin_config.php) (revision 15433) @@ -1,6 +1,6 @@ Array( - 'optimize_performance' => Array('EventName' => 'OnOptimizePerformance', 'RunInterval' => 86400), + 'optimize_performance' => Array('EventName' => 'OnOptimizePerformance', 'RunSchedule' => '0 0 * * *'), ), 'TitlePresets' => Array ( Index: branches/5.2.x/core/admin_templates/scheduled_tasks/scheduled_task_edit.tpl =================================================================== diff -u -N -r15033 -r15433 --- branches/5.2.x/core/admin_templates/scheduled_tasks/scheduled_task_edit.tpl (.../scheduled_task_edit.tpl) (revision 15033) +++ branches/5.2.x/core/admin_templates/scheduled_tasks/scheduled_task_edit.tpl (.../scheduled_task_edit.tpl) (revision 15433) @@ -76,11 +76,6 @@ - - - - - @@ -89,8 +84,31 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: branches/5.2.x/core/units/logs/email_logs/email_logs_config.php =================================================================== diff -u -N -r15363 -r15433 --- branches/5.2.x/core/units/logs/email_logs/email_logs_config.php (.../email_logs_config.php) (revision 15363) +++ branches/5.2.x/core/units/logs/email_logs/email_logs_config.php (.../email_logs_config.php) (revision 15433) @@ -1,6 +1,6 @@ Array( - 'rotate_email_logs' => Array('EventName' => 'OnRotate', 'RunInterval' => 86400), + 'rotate_email_logs' => Array('EventName' => 'OnRotate', 'RunSchedule' => '0 0 * * *'), ), 'IDField' => 'EmailLogId', Index: branches/5.2.x/core/install/english.lang =================================================================== diff -u -N -r15421 -r15433 --- branches/5.2.x/core/install/english.lang (.../english.lang) (revision 15421) +++ branches/5.2.x/core/install/english.lang (.../english.lang) (revision 15433) @@ -367,6 +367,12 @@ Q291bnRyeQ== Q3JlYXRlZCBCeQ== Q3JlYXRlZCBPbg== + Q29tbW9uIFNldHRpbmdz + RGF5 + SG91cg== + TWludXRl + TW9udGg= + V2Vla2RheQ== Q1NTIFRlbXBsYXRl Q1NTIENsYXNzIE5hbWU= Q3Vyc29y @@ -607,7 +613,7 @@ Q29tbWVudA== UHJvbW8gUm90YXRpb24gRGVsYXkgKHNlY29uZHMp UnVsZSBUeXBl - UnVuIEludGVydmFs + UnVuIFNjaGVkdWxl UnVuIFRpbWU= U2FtZSBBcyBUaHVtYg== U2NoZWR1bGUgRGF0ZQ== @@ -757,6 +763,10 @@ Tm8= Tm9uZQ== Tm8gUGVybWlzc2lvbnM= + bmQ= + cmQ= + c3Q= + dGg= T2Zm T24= T25lIFdheQ== @@ -770,6 +780,8 @@ Q3VzdG9t RmFkZQ== U2xpZGU= + QXByaWw= + QXVndXN0 QXV0by1EZXRlY3Q= QXV0b21hdGlj QmVmb3Jl @@ -783,10 +795,48 @@ Q291bnRyaWVz Q291bnRyeQ== Q3JlYXRlZCBPbg== + LS0gQ29tbW9uIFNldHRpbmdzIC0t + LS0gRGF5cyAtLQ== + RXZlcnkgZGF5 + RXZlcnkgMTUgbWludXRlcw== + RXZlcnkgNSBtaW51dGVz + RXZlcnkgNCBob3Vycw== + RXZlcnkgaG91cg== + RXZlcnkgbWludXRl + RXZlcnkgbW9udGg= + RXZlcnkgb3RoZXIgZGF5 + RXZlcnkgb3RoZXIgaG91cg== + RXZlcnkgb3RoZXIgbWludXRl + RXZlcnkgb3RoZXIgbW9udGg= + RXZlcnkgNiBob3Vycw== + RXZlcnkgNiBtb250aHM= + RXZlcnkgMTAgbWludXRlcw== + RXZlcnkgMzAgbWludXRlcw== + RXZlcnkgMyBob3Vycw== + RXZlcnkgMyBtb250aHM= + RXZlcnkgMTIgaG91cnM= + RXZlcnkgd2Vla2RheQ== + LS0gSG91cnMgLS0= + LS0gTWludXRlcyAtLQ== + TW9uIHRocnUgRnJp + TW9uLCBXZWQsIEZyaQ== + LS0gTW9udGhzIC0t + T25jZSBhIGRheQ== + T25jZSBhIG1vbnRo + T25jZSBhbiBob3Vy + T25jZSBhIHdlZWs= + T25jZSBhIHllYXI= + U2F0IGFuZCBTdW4= + VHVlcywgVGh1cnM= + VHdpY2UgYSBkYXk= + MXN0IGFuZCAxNXRo + VHdpY2UgYW4gaG91cg== + LS0gV2Vla2RheXMgLS0= Q3VycmVudCBEb21haW4= Q3VzdG9tICJUbyIgUmVjaXBpZW50KC1zKQ== Q3VzdG9tIFNlbmRlcg== ZGF5KHMp + RGVjZW1iZXI= RGVjbGluZWQ= RGVmYXVsdCBXZWJzaXRlIGFkZHJlc3M= RGVueQ== @@ -806,7 +856,9 @@ RXh0ZXJuYWw= RXh0ZXJuYWwgVXJs RmFpbGVk + RmVicnVhcnk= Rmlyc3QgTmFtZQ== + RnJpZGF5 R3JvdXA= R3Vlc3RzIE9ubHk= aG91cihzKQ== @@ -815,17 +867,25 @@ SW52YWxpZA== SVAgQWRkcmVzcw== SXMgdW5pcXVl + SmFudWFyeQ== + SnVseQ== + SnVuZQ== TGFzdCBOYW1l TG9nZ2VkIE91dA== TWFudWFs + TWFyY2g= + TWF5 bWludXRlKHMp TW9kYWwgV2luZG93 + TW9uZGF5 bW9udGgocyk= TmV3IEUtbWFpbA== Tm90IGVtcHR5 Tm90IGxpa2U= Tm90IFByb2Nlc3NlZA== Tm90IFJlcGxpZWQ= + Tm92ZW1iZXI= + T2N0b2Jlcg== MSBkYXk= MSBtb250aA== MSB3ZWVr @@ -844,20 +904,25 @@ UmVwbGllZA== UnVubmluZw== U2FtZSBXaW5kb3c= + U2F0dXJkYXk= c2Vjb25kKHMp U2VtaS1jb2xvbg== + U2VwdGVtYmVy U2lsZW50 U3BhY2U= U3RhdGU= U3ViLW1hdGNo U3VjY2Vzcw== + U3VuZGF5 RnJvbSBvdGhlcnM= VG8gb3RoZXJz U3lzdGVt VGFi VGVtcGxhdGU= MyBtb250aHM= + VGh1cnNkYXk= VGl0bGU= + VHVlc2RheQ== MiB3ZWVrcw== VXNlcg== RW1haWwgQWN0aXZhdGlvbg== @@ -866,6 +931,7 @@ Tm90IEFsbG93ZWQ= VXBvbiBBcHByb3ZhbA== VmlydHVhbA== + V2VkbmVzZGF5 d2VlayhzKQ== eWVhcihzKQ== Wmlw @@ -1396,6 +1462,8 @@ UmVsYXRpb25z UmVwbHkgUE9QMyBTZXJ2ZXIgU2V0dGluZ3M= Q29tbWVudHM= + UnVuIFNjaGVkdWxl + UnVuIFNldHRpbmdz U2NoZWR1bGVkIFRhc2tz U2VsZWN0IEdyb3VwKHMp U2VsZWN0IFVzZXI= Index: branches/5.2.x/core/kernel/event_manager.php =================================================================== diff -u -N -r15290 -r15433 --- branches/5.2.x/core/kernel/event_manager.php (.../event_manager.php) (revision 15290) +++ branches/5.2.x/core/kernel/event_manager.php (.../event_manager.php) (revision 15433) @@ -1,6 +1,6 @@ ScheduledTasks->add($short_name, $event_string, $run_interval, $status); + $this->ScheduledTasks->add($short_name, $event_string, $run_schedule, $status); } /** Index: branches/5.2.x/core/kernel/languages/phrases_cache.php =================================================================== diff -u -N -r15137 -r15433 --- branches/5.2.x/core/kernel/languages/phrases_cache.php (.../phrases_cache.php) (revision 15137) +++ branches/5.2.x/core/kernel/languages/phrases_cache.php (.../phrases_cache.php) (revision 15433) @@ -1,6 +1,6 @@ 'la_opt_CronEveryMinute', + '*/5 * * * *' => 'la_opt_CronEveryFiveMinutes', + '0,30 * * * *' => 'la_opt_CronTwiceAnHour', + '0 * * * *' => 'la_opt_CronOnceAnHour', + '0 0,12 * * *' => 'la_opt_CronTwiceADay', + '0 0 * * *' => 'la_opt_CronOnceADay', + '0 0 * * 0' => 'la_opt_CronOnceAWeek', + '0 0 1,15 * *' => 'la_opt_CronTwiceAMonth', + '0 0 1 * *' => 'la_opt_CronOnceAMonth', + '0 0 1 1 *' => 'la_opt_CronOnceAYear', + ); + + protected $minuteSettings = Array ( + '*' => 'la_opt_CronEveryMinute', + '*/2' => 'la_opt_CronEveryOtherMinute', + '*/5' => 'la_opt_CronEveryFiveMinutes', + '*/10' => 'la_opt_CronEveryTenMinutes', + '*/15' => 'la_opt_CronEveryFifteenMinutes', + '0,30' => 'la_opt_CronEveryThirtyMinutes', + '--' => 'la_opt_CronMinutes', + // minutes added dynamically later + ); + + protected $hourSettings = Array ( + '*' => 'la_opt_CronEveryHour', + '*/2' => 'la_opt_CronEveryOtherHour', + '*/3' => 'la_opt_CronEveryThreeHours', + '*/4' => 'la_opt_CronEveryFourHours', + '*/6' => 'la_opt_CronEverySixHours', + '0,12' => 'la_opt_CronEveryTwelveHours', + '--' => 'la_opt_CronHours', + // hours added dynamically later + ); + + protected $daySettings = Array ( + '*' => 'la_opt_CronEveryDay', + '*/2' => 'la_opt_CronEveryOtherDay', + '1,15' => 'la_opt_CronTwiceAMonth', + '--' => 'la_opt_CronDays', + // days added dynamically later + ); + + protected $monthSettings = Array ( + '*' => 'la_opt_CronEveryMonth', + '*/2' => 'la_opt_CronEveryOtherMonth', + '*/4' => 'la_opt_CronEveryThreeMonths', + '1,7' => 'la_opt_CronEverySixMonths', + '--' => 'la_opt_CronMonths', + '1' => 'la_opt_January', + '2' => 'la_opt_February', + '3' => 'la_opt_March', + '4' => 'la_opt_April', + '5' => 'la_opt_May', + '6' => 'la_opt_June', + '7' => 'la_opt_July', + '8' => 'la_opt_August', + '9' => 'la_opt_September', + '10' => 'la_opt_October', + '11' => 'la_opt_November', + '12' => 'la_opt_December', + ); + + protected $weekdaySettings = Array ( + '*' => 'la_opt_CronEveryWeekday', + '1-5' => 'la_opt_CronMondayThroughFriday', + '0,6' => 'la_opt_CronSaturdayAndSunday', + '1,3,5' => 'la_opt_CronMondayWednesdayAndFriday', + '2,4' => 'la_opt_CronTuesdayAndThursday', + '--' => 'la_opt_CronWeekdays', + '0' => 'la_opt_Sunday', + '1' => 'la_opt_Monday', + '2' => 'la_opt_Tuesday', + '3' => 'la_opt_Wednesday', + '4' => 'la_opt_Thursday', + '5' => 'la_opt_Friday', + '6' => 'la_opt_Saturday', + ); + + /** + * Returns possible field options by type + * + * @param int $field_type + * @return Array + */ + public function getOptions($field_type) + { + $mapping = Array ( + self::COMMON => $this->commonSettings, + self::MINUTE => $this->minuteSettings, + self::HOUR => $this->hourSettings, + self::DAY => $this->daySettings, + self::MONTH => $this->monthSettings, + self::WEEKDAY => $this->weekdaySettings, + ); + + $ret = $mapping[$field_type]; + /* @var $ret Array */ + + foreach ($ret as $option_key => $option_title) { + $option_title = substr($option_title, 0, 1) == '+' ? substr($option_title, 1) : $this->Application->Phrase($option_title); + $ret[$option_key] = $option_title; + + if ( "$option_key" !== '--' ) { + $ret[$option_key] .= ' (' . $option_key . ')'; + } + } + + if ( $field_type == self::MINUTE ) { + for ($i = 0; $i <= 59; $i++) { + $ret[$i] = ':' . str_pad($i, 2, '0', STR_PAD_LEFT) . ' (' . $i . ')'; + } + } + elseif ( $field_type == self::HOUR ) { + $language = $this->Application->recallObject('lang.current'); + /* @var $language LanguagesItem */ + + $short_time_format = str_replace(':s', '', $language->GetDBField('TimeFormat')); + + for ($i = 0; $i <= 23; $i++) { + $ret[$i] = adodb_date($short_time_format, adodb_mktime($i, 0, 0)) . ' (' . $i . ')'; + } + } + elseif ( $field_type == self::DAY ) { + $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); + /* @var $ml_helper kMultiLanguageHelper */ + + $forms = Array ( + 'phrase1' => 'la_NumberSuffixSt', 'phrase2' => 'la_NumberSuffixNd', 'phrase3' => 'la_NumberSuffixRd', + 'phrase4' => 'la_NumberSuffixTh', 'phrase5' => 'la_NumberSuffixTh' + ); + + for ($i = 1; $i <= 31; $i++) { + $ret[$i] = $i . $ml_helper->getPluralPhrase($i, $forms) . ' (' . $i . ')'; + } + } + + return $ret; + } + + /** + * Returns field name by type + * + * @param int $field_type + * @param string $field_prefix + * @return string + * @access protected + */ + protected function _getFieldNameByType($field_type, $field_prefix) + { + $field_mapping = Array ( + self::MINUTE => 'Minute', + self::HOUR => 'Hour', + self::DAY => 'Day', + self::MONTH => 'Month', + self::WEEKDAY => 'Weekday', + ); + + return $field_prefix . $field_mapping[$field_type]; + } + + /** + * Creates virtual fields for given unit + * + * @param string $prefix + * @param string $field_prefix + * @return void + * @access public + */ + public function initUnit($prefix, $field_prefix = '') + { + $virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields', Array ()); + + $virtual_fields[$field_prefix . 'CommonHints'] = Array ( + 'type' => 'string', + 'formatter' => 'kOptionsFormatter', 'options' => $this->getOptions(self::COMMON), + 'default' => '' + ); + + foreach ($this->fieldTypes as $field_type) { + $field_name = $this->_getFieldNameByType($field_type, $field_prefix); + $virtual_fields[$field_name] = Array ('type' => 'string', 'max_len' => 30, 'default' => '*'); + + $virtual_fields[$field_name . 'Hints'] = Array ( + 'type' => 'string', + 'formatter' => 'kOptionsFormatter', 'options' => $this->getOptions($field_type), + 'default' => '' + ); + } + + $this->Application->setUnitOption($prefix, 'VirtualFields', $virtual_fields); + } + + /** + * Loads schedule values from database into virtual fields + * + * @param kDBItem $object + * @param string $field_prefix + */ + public function load(kDBItem $object, $field_prefix = '') + { + $combined_value = explode(' ', $object->GetDBField($field_prefix)); + + foreach ($this->fieldTypes as $field_type) { + $field_name = $this->_getFieldNameByType($field_type, $field_prefix); + $object->SetDBField($field_name, $combined_value[$field_type - 1]); + } + } + + /** + * Validates schedule values and saves them to database + * + * @param kDBItem $object + * @param string $field_prefix + * @return bool + * @access public + */ + public function validateAndSave(kDBItem $object, $field_prefix = '') + { + $validated = true; + $combined_value = Array (); + $cron_field = new kCronField(); + + foreach ($this->fieldTypes as $field_type) { + $field_name = $this->_getFieldNameByType($field_type, $field_prefix); + $value = preg_replace('/\s+/s', '', mb_strtoupper($object->GetDBField($field_name))); + + if ( $cron_field->validate($field_type, $value) ) { + $object->SetDBField($field_name, $value); + } + else { + $validated = false; + $object->SetError($field_name, 'invalid_format'); + } + + $combined_value[$field_type] = $value; + } + + ksort($combined_value); + $object->SetDBField($field_prefix, implode(' ', $combined_value)); + + 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 + * + * @param string $expression + * @param int $date + * @param bool $inverse + * @param bool $allow_current_date + * @return int + * @access public + * @throws RuntimeException + */ + public function getMatch($expression, $date = NULL, $inverse = false, $allow_current_date = false) + { + if ( !isset($date) ) { + $date = TIMENOW; + } + + $next_run = strtotime('-' . (int)adodb_date('s', $date) . ' seconds', $date); + $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) ) { + 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 ) { + // set time :59:00 + $date = strtotime(adodb_date('Y-m-d H:59:59', $date)); + } + else { + // set time :00:00 + $date = strtotime(adodb_date('Y-m-d H:00:00', $date)); + } + } + + return $date; + } +} \ No newline at end of file Index: branches/5.2.x/core/units/scheduled_tasks/scheduled_tasks_config.php =================================================================== diff -u -N -r15250 -r15433 --- branches/5.2.x/core/units/scheduled_tasks/scheduled_tasks_config.php (.../scheduled_tasks_config.php) (revision 15250) +++ branches/5.2.x/core/units/scheduled_tasks/scheduled_tasks_config.php (.../scheduled_tasks_config.php) (revision 15433) @@ -1,6 +1,6 @@ 'string', 'max_len' => 255, 'required' => 1, 'not_null' => 1, 'default' => '' ), - 'RunInterval' => Array ('type' => 'int', 'required' => 1, 'not_null' => 1, 'default' => 0), + 'RunSchedule' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => '* * * * *'), 'LastRunOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL), 'LastRunStatus' => Array ( @@ -126,7 +126,7 @@ 'not_null' => 1, 'default' => 1 ), - 'NextRunOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'required' => 1, 'default' => '#NOW#'), + 'NextRunOn' => Array ('type' => 'int', 'formatter' => 'kDateFormatter', 'default' => NULL), 'RunTime' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), 'Timeout' => Array ( 'type' => 'int', @@ -148,18 +148,18 @@ 0 => 'icon16_disabled.png', ), 'Fields' => Array ( - 'ScheduledTaskId' => Array ('title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 50, ), - 'Name' => Array ('filter_block' => 'grid_like_filter', 'width' => 200, ), - 'Type' => Array ('filter_block' => 'grid_options_filter', 'width' => 60, ), - 'Event' => Array ('filter_block' => 'grid_like_filter', 'width' => 280, ), - 'RunInterval' => Array ('filter_block' => 'grid_range_filter', 'width' => 100, ), - 'LastRunOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145, ), - 'RunTime' => Array ('filter_block' => 'grid_range_filter', 'width' => 145, ), - 'LastRunStatus' => Array ('filter_block' => 'grid_options_filter', 'width' => 120, ), - 'NextRunOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145, ), - 'Status' => Array ('filter_block' => 'grid_options_filter', 'width' => 65, ), - 'Timeout' => Array ('filter_block' => 'grid_range_filter', 'width' => 85, ), - 'LastTimeoutOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145, ), + 'ScheduledTaskId' => Array ('title' => 'column:la_fld_Id', 'filter_block' => 'grid_range_filter', 'width' => 80), + 'Name' => Array ('filter_block' => 'grid_like_filter', 'width' => 200), + 'Type' => Array ('filter_block' => 'grid_options_filter', 'width' => 60), + 'Event' => Array ('filter_block' => 'grid_like_filter', 'width' => 280), + 'RunSchedule' => Array ('filter_block' => 'grid_range_filter', 'width' => 100), + 'LastRunOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145), + 'RunTime' => Array ('filter_block' => 'grid_range_filter', 'width' => 100), + 'LastRunStatus' => Array ('filter_block' => 'grid_options_filter', 'width' => 90), + 'NextRunOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145), + 'Status' => Array ('filter_block' => 'grid_options_filter', 'width' => 65), + 'Timeout' => Array ('filter_block' => 'grid_range_filter', 'width' => 85), + 'LastTimeoutOn' => Array ('filter_block' => 'grid_date_range_filter', 'width' => 145), 'SiteDomainLimitation' => Array ('data_block' => 'grid_picker_td', 'filter_block' => 'grid_multioptions_filter', 'separator' => ', ', 'width' => 145), ), ), Index: branches/5.2.x/core/units/scheduled_tasks/scheduled_task_eh.php =================================================================== diff -u -N -r15250 -r15433 --- branches/5.2.x/core/units/scheduled_tasks/scheduled_task_eh.php (.../scheduled_task_eh.php) (revision 15250) +++ branches/5.2.x/core/units/scheduled_tasks/scheduled_task_eh.php (.../scheduled_task_eh.php) (revision 15433) @@ -1,6 +1,6 @@ Conn->Query($object->GetSelectSQL(), 'Name'); + $cron_helper = $this->Application->recallObject('kCronHelper'); + /* @var $cron_helper kCronHelper */ + foreach ($scheduled_tasks_from_cache as $scheduled_task_name => $scheduled_task_params) { if ( !isset($scheduled_tasks_from_db[$scheduled_task_name]) ) { $fields_hash = Array ( 'Event' => $scheduled_task_params['Event'], 'Name' => $scheduled_task_name, 'Type' => ScheduledTask::TYPE_SYSTEM, 'Status' => isset($scheduled_task_params['Status']) ? $scheduled_task_params['Status'] : STATUS_ACTIVE, - 'RunInterval' => $scheduled_task_params['RunInterval'], + 'RunSchedule' => $scheduled_task_params['RunSchedule'], ); $object->Clear(); $object->SetDBFieldsFromHash($fields_hash); + $cron_helper->load($object, 'RunSchedule'); $object->Create(); } else { @@ -203,4 +207,92 @@ $this->clearSelectedIDs($event); } + + /** + * Loads schedule from database to virtual fields + * + * @param kEvent $event + * @return void + * @access protected + */ + protected function OnAfterItemLoad(kEvent $event) + { + parent::OnAfterItemLoad($event); + + $object = $event->getObject(); + /* @var $object kDBItem */ + + $cron_helper = $this->Application->recallObject('kCronHelper'); + /* @var $cron_helper kCronHelper */ + + $cron_helper->load($object, 'RunSchedule'); + } + + /** + * Validates schedule + * + * @param kEvent $event + * @return void + * @access protected + */ + protected function OnBeforeItemCreate(kEvent $event) + { + parent::OnBeforeItemCreate($event); + + $this->_itemChanged($event); + } + + /** + * Validates schedule + * + * @param kEvent $event + * @return void + * @access protected + */ + protected function OnBeforeItemUpdate(kEvent $event) + { + parent::OnBeforeItemUpdate($event); + + $this->_itemChanged($event); + } + + /** + * Validates schedule + * + * @param kEvent $event + * @return void + * @access protected + */ + protected function _itemChanged(kEvent $event) + { + $object = $event->getObject(); + /* @var $object kDBItem */ + + $cron_helper = $this->Application->recallObject('kCronHelper'); + /* @var $cron_helper kCronHelper */ + + if ( $cron_helper->validateAndSave($object, 'RunSchedule') && !$object->GetDBField('NextRunOn_date') ) { + $next_run = $cron_helper->getMatch($object->GetDBField('RunSchedule')); + $object->SetDBField('NextRunOn_date', $next_run); + $object->SetDBField('NextRunOn_time', $next_run); + } + } + + /** + * Creates schedule fields + * + * @param kEvent $event + * @return void + * @access protected + */ + protected function OnAfterConfigRead(kEvent $event) + { + parent::OnAfterConfigRead($event); + + $cron_helper = $this->Application->recallObject('kCronHelper'); + /* @var $cron_helper kCronHelper */ + + $cron_helper->initUnit($event->Prefix, 'RunSchedule'); + + } } \ No newline at end of file Index: branches/5.2.x/core/kernel/managers/scheduled_task_manager.php =================================================================== diff -u -N -r15137 -r15433 --- branches/5.2.x/core/kernel/managers/scheduled_task_manager.php (.../scheduled_task_manager.php) (revision 15137) +++ branches/5.2.x/core/kernel/managers/scheduled_task_manager.php (.../scheduled_task_manager.php) (revision 15433) @@ -1,6 +1,6 @@ tasks[$short_name] = Array ( - 'Event' => $event_string, 'RunInterval' => $run_interval, 'Status' => $status + 'Event' => $event_string, 'RunSchedule' => $run_schedule, 'Status' => $status ); } @@ -162,13 +162,16 @@ return false; } + $cron_helper = $this->Application->recallObject('kCronHelper'); + /* @var $cron_helper kCronHelper */ + $start_time = adodb_mktime(); // remember, when scheduled task execution started $fields_hash = Array ( 'LastRunOn' => $start_time, 'LastRunStatus' => ScheduledTask::LAST_RUN_RUNNING, - 'NextRunOn' => $start_time + $scheduled_task_data['RunInterval'], + 'NextRunOn' => $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $start_time), ); $this->update($scheduled_task_data['Name'], $fields_hash); @@ -177,11 +180,11 @@ $this->Application->HandleEvent($event); $now = adodb_mktime(); - $next_run = $scheduled_task_data['RunInterval'] ? $start_time + $scheduled_task_data['RunInterval'] : $now; + $next_run = $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $start_time); while ($next_run < $now) { - // in case event execution took longer, then RunInterval (don't use <=, because RunInterval can be 0) - $next_run += $scheduled_task_data['RunInterval']; + // in case event execution took longer, then RunSchedule (don't use <=, because RunSchedule can be 0) + $next_run = $cron_helper->getMatch($scheduled_task_data['RunSchedule'], $next_run); } // remember, when scheduled task execution ended Index: branches/5.2.x/core/kernel/utility/unit_config_reader.php =================================================================== diff -u -N -r15250 -r15433 --- branches/5.2.x/core/kernel/utility/unit_config_reader.php (.../unit_config_reader.php) (revision 15250) +++ branches/5.2.x/core/kernel/utility/unit_config_reader.php (.../unit_config_reader.php) (revision 15433) @@ -1,6 +1,6 @@ $scheduled_task_info) { $event_status = array_key_exists('Status', $scheduled_task_info) ? $scheduled_task_info['Status'] : STATUS_ACTIVE; - $this->Application->delayUnitProcessing('registerScheduledTask', Array ( $short_name, $config['Prefix'] . ':' . $scheduled_task_info['EventName'], $scheduled_task_info['RunInterval'], $event_status )); + $this->Application->delayUnitProcessing('registerScheduledTask', Array ( $short_name, $config['Prefix'] . ':' . $scheduled_task_info['EventName'], $scheduled_task_info['RunSchedule'], $event_status )); } } Index: branches/5.2.x/core/kernel/application.php =================================================================== diff -u -N -r15374 -r15433 --- branches/5.2.x/core/kernel/application.php (.../application.php) (revision 15374) +++ branches/5.2.x/core/kernel/application.php (.../application.php) (revision 15433) @@ -1,6 +1,6 @@ EventManager->registerScheduledTask($short_name, $event_string, $run_interval, $status); + $this->EventManager->registerScheduledTask($short_name, $event_string, $run_schedule, $status); } /** Index: branches/5.2.x/core/admin_templates/js/cron.js =================================================================== diff -u -N --- branches/5.2.x/core/admin_templates/js/cron.js (revision 0) +++ branches/5.2.x/core/admin_templates/js/cron.js (revision 15433) @@ -0,0 +1,86 @@ +function CronManager ($field_mask, $field_prefix) { + this.fieldMask = $field_mask; + this.fieldPrefix = $field_prefix; + this.fields = ['Minute', 'Hour', 'Day', 'Month', 'Weekday']; +} + +CronManager.prototype.init = function () { + var $manager = this; + + // populate all controls when common hint control changes + this.getHintControl('Common').change(function($e) { + var $values = $(this).val().split(' '); + + $manager.fields.each(function ($i) { + var $field = this; + + $manager.getInputControl($field).val($values[$i]).change(); + }); + }); + + this.fields.each(function () { + var $field = this; + + // put selected hint value into the input + $manager.getHintControl($field).change(function ($e) { + $manager.getInputControl($field).val($(this).val()).change(); + }); + + // put select field hint + common hint based on input value + $manager.getInputControl($field).change(function ($e) { + $manager.syncHintControl($field); + $manager.syncHintControl('Common', $manager.getCombinedValue()); + }).change(); + + }); +} + +CronManager.prototype.getCombinedValue = function () { + var $ret = [], + $manager = this; + + this.fields.each(function ($i) { + $ret.push($manager.getInputControl(this).val()); + }); + + return $ret.join(' '); +} + +CronManager.prototype.getHintControl = function ($field) { + return this.getInputControl($field, 'Hints'); +} + +CronManager.prototype.getInputControl = function ($field, $suffix) { + if ( $suffix == undefined ) { + $suffix = ''; + } + + return $(get_control(this.fieldMask, this.fieldPrefix + $field + $suffix)); +} + +CronManager.prototype.syncHintControl = function ($field, $input_value) { + var $hint_control = this.getHintControl($field); + + if ( $input_value === undefined ) { + $input_value = this.getInputControl($field).val(); + } + + if ( $('option[value="' + jq($input_value) + '"]', $hint_control).length == 1 ) { + $hint_control.val($input_value); + } + else { + $hint_control.val(''); + } + + if ( $field == 'Common' ) { + var $old_value = $hint_control.data('old_value'); + + if ( $old_value !== undefined && $old_value !== $input_value ) { + // reset next run time to be regenerated according to new schedule on save + $(get_control(this.fieldMask, 'NextRunOn_date')).val(''); + $(get_control(this.fieldMask, 'NextRunOn_time')).val(''); + } + + $hint_control.data('old_value', $input_value); + } +} Index: branches/5.2.x/core/units/users/users_config.php =================================================================== diff -u -N -r15164 -r15433 --- branches/5.2.x/core/units/users/users_config.php (.../users_config.php) (revision 15164) +++ branches/5.2.x/core/units/users/users_config.php (.../users_config.php) (revision 15433) @@ -1,6 +1,6 @@ Array( - 'membership_expiration' => Array('EventName' => 'OnCheckExpiredMembership', 'RunInterval' => 1800), - 'delete_expired_sessions' => Array('EventName' => 'OnDeleteExpiredSessions', 'RunInterval' => 43200), + 'membership_expiration' => Array('EventName' => 'OnCheckExpiredMembership', 'RunSchedule' => '*/30 * * * *'), + 'delete_expired_sessions' => Array('EventName' => 'OnDeleteExpiredSessions', 'RunSchedule' => '0 */12 * * *'), ), 'IDField' => 'PortalUserId', Index: branches/5.2.x/core/install/upgrades.sql =================================================================== diff -u -N -r15421 -r15433 --- branches/5.2.x/core/install/upgrades.sql (.../upgrades.sql) (revision 15421) +++ branches/5.2.x/core/install/upgrades.sql (.../upgrades.sql) (revision 15433) @@ -2760,3 +2760,6 @@ # ===== v 5.2.0 ===== INSERT INTO SystemSettings VALUES(DEFAULT, 'CategoryPermissionRebuildMode', '3', 'In-Portal', 'in-portal:configure_categories', 'la_title_General', 'la_config_CategoryPermissionRebuildMode', 'select', NULL, '1=la_opt_Manual||2=la_opt_Silent||3=la_opt_Automatic', 10.11, 0, 0, 'hint:la_config_CategoryPermissionRebuildMode'); DELETE FROM LanguageLabels WHERE PhraseKey = 'LA_CONFIG_QUICKCATEGORYPERMISSIONREBUILD'; +ALTER TABLE ScheduledTasks ADD RunSchedule VARCHAR(255) NOT NULL DEFAULT '* * * * *' AFTER Event; +DELETE FROM UserPersistentSessionData WHERE VariableName = 'scheduled-task[Default]columns_.'; +DELETE FROM LanguageLabels WHERE PhraseKey = 'LA_FLD_RUNINTERVAL'; Index: branches/5.2.x/core/admin_templates/incs/form_blocks.tpl =================================================================== diff -u -N -r15429 -r15433 --- branches/5.2.x/core/admin_templates/incs/form_blocks.tpl (.../form_blocks.tpl) (revision 15429) +++ branches/5.2.x/core/admin_templates/incs/form_blocks.tpl (.../form_blocks.tpl) (revision 15433) @@ -1044,6 +1044,18 @@ + + + + + + + + + + Index: branches/5.2.x/core/units/helpers/helpers_config.php =================================================================== diff -u -N -r15274 -r15433 --- branches/5.2.x/core/units/helpers/helpers_config.php (.../helpers_config.php) (revision 15274) +++ branches/5.2.x/core/units/helpers/helpers_config.php (.../helpers_config.php) (revision 15433) @@ -1,6 +1,6 @@ 'PageHelper', 'class' => 'PageHelper', 'file' => 'page_helper.php', 'build_event' => ''), Array ('pseudo' => 'BackupHelper', 'class' => 'BackupHelper', 'file' => 'backup_helper.php', 'build_event' => ''), Array ('pseudo' => 'AjaxFormHelper', 'class' => 'AjaxFormHelper', 'file' => 'ajax_form_helper.php', 'build_event' => ''), + Array ('pseudo' => 'kCronHelper', 'class' => 'kCronHelper', 'file' => 'cron_helper.php', 'build_event' => ''), ), ); \ No newline at end of file