1
0
mirror of https://github.com/e107inc/e107.git synced 2025-04-21 05:02:02 +02:00

Fixes #5096 - Cron timezone issue. eShims::strftime issue. CronParser test added.

This commit is contained in:
camer0n 2023-11-03 15:07:41 -07:00
parent 9c47060bb5
commit bfb515f2b5
5 changed files with 247 additions and 53 deletions

View File

@ -44,6 +44,11 @@ if ((PHP_SAPI === "apache" || PHP_SAPI === "litespeed") && $_E107['debug'] === f
require_once(realpath(__DIR__ . "/class2.php"));
if(!empty($_E107['debug']))
{
error_reporting(E_ALL);
}
require_once(e_HANDLER . "cron_class.php");
$cron = new cronScheduler();

View File

@ -69,7 +69,7 @@ trait StrftimeTrait
self::getSensibleLocale(),
\IntlDateFormatter::NONE,
\IntlDateFormatter::NONE,
null,
'GMT'.date('O'), // More accurate timezone. @see https://stackoverflow.com/questions/31707395/why-php-intldateformatter-returns-wrong-date-1-hour
null,
$format
);

View File

@ -157,7 +157,13 @@ class _system_cron
function sendEmail() // Test Email.
{
global $pref, $_E107;
if($_E107['debug']) { echo "<br />sendEmail() executed"; }
if($_E107['debug'])
{
echo "sendEmail() executed";
error_log('e107: Cron running _system_cron::sendEmail(); ', E_USER_NOTICE);
}
// require_once(e_HANDLER.'mail.php');
$message = "Your Cron test worked correctly. Sent on ".date("r").".";
@ -205,7 +211,10 @@ class _system_cron
'body' => $message
);
e107::getEmail()->sendEmail($pref['siteadminemail'], $pref['siteadmin'], $eml);
if(!e107::getEmail()->sendEmail($pref['siteadminemail'], $pref['siteadmin'], $eml))
{
error_log('e107: Cron _system_cron::sendEmail() failed to send email.', E_ERROR);
}
// sendemail($pref['siteadminemail'], "e107 - TEST Email Sent by cron.".date("r"), $message, $pref['siteadmin'],SITEEMAIL, $pref['siteadmin']);
}
@ -397,6 +406,7 @@ class CronParser
var $bits = Array(); //exploded String like 0 1 * * *
var $now = Array(); //Array of cron-style entries for time()
var $lastRan; //Timestamp of last ran time.
private $lastDue;
var $taken;
var $debug;
var $year;
@ -416,6 +426,16 @@ class CronParser
return explode(",", eShims::strftime("%M,%H,%d,%m,%w,%Y", $this->lastRan)); //Get the values for now in a format we can use
}
public function getLastDue()
{
return $this->lastDue;
}
public function getNow()
{
return $this->now;
}
/**
* @return mixed
*/
@ -432,26 +452,25 @@ class CronParser
return $this->debug;
}
function setDebug($bool)
{
$this->debug = (bool) $bool;
}
/**
* @param $str
* @return void
*/
function debug($str)
{
if (is_array($str))
if(!$this->debug)
{
$this->debug .= "\nArray: ";
foreach($str as $k=>$v)
{
$this->debug .= "$k=>$v, ";
}
return;
}
}
else
{
$this->debug .= "\n$str";
}
//echo nl2br($this->debug);
$text = (is_array($str) ? print_r($str,true) : $str);
error_log('e107: Cron '.$text);
echo $text."\n";
}
/**
@ -513,9 +532,10 @@ class CronParser
*/
function calcLastRan($string)
{
$this->debug(__METHOD__.' ('.__LINE__.'): '.date_default_timezone_get());
$tstart = microtime(true);
$this->debug = "";
$this->lastRan = 0;
$this->year = NULL;
$this->month = NULL;
@ -526,20 +546,20 @@ class CronParser
$this->minutes_arr = array();
$this->months_arr = array();
$string = preg_replace('/[\s]{2,}/', ' ', $string);
$string = preg_replace('/\s{2,}/', ' ', $string);
if (preg_match('/[^-,* \\d]/', $string) !== 0)
{
$this->debug("Cron String contains invalid character");
$this->debug("e107: Cron string contains invalid character: ".$string);
return false;
}
$this->debug("<b>Working on cron schedule: $string</b>");
$this->debug("\n----- Working on cron schedule: $string ----");
$this->bits = @explode(" ", $string);
if (count($this->bits) != 5)
{
$this->debug("Cron string is invalid. Too many or too little sections after explode");
$this->debug("e107: Cron string is invalid. Too many or too little sections after explode.".print_r($this->bits,true));
return false;
}
@ -656,8 +676,12 @@ class CronParser
}
else
{
$this->debug("LAST DUE: " . $this->hour . ":" . $this->minute . " on " . $this->day . "/" . $this->month . "/" . $this->year);
$lastDue = $this->year.'-'.(strlen($this->month) === 1 ? '0'.$this->month : $this->month).'-'.(strlen($this->day) === 1 ? '0'.$this->day : $this->day).'T'.(strlen($this->hour) === 1 ? '0'.$this->hour : $this->hour) . ":" . (strlen($this->minute) === 1 ? '0'.$this->minute : $this->minute);
$this->debug(__METHOD__.' ('.__LINE__.'): '.date_default_timezone_get());
$this->debug(__METHOD__.' ('.__LINE__.'): Setting lastDue to ' . $lastDue);
$this->lastDue = $lastDue;
$this->lastRan = mktime($this->hour, $this->minute, 0, $this->month, $this->day, $this->year);
$this->debug(__METHOD__.' ('.__LINE__.'): Setting lastRan to '.date('c', $this->lastRan));
return true;
}
}
@ -914,7 +938,7 @@ class CronParser
}
}
$this->debug("Days array matching weekdays for $year-$month");
$this->debug($days);
//$this->debug($days);
return $days;
}
@ -959,8 +983,8 @@ class CronParser
$hours = $this->_sanitize($hours, 0, 23);
}
$this->debug("Hour array");
$this->debug($hours);
// $this->debug("Hour array");
// $this->debug($hours);
$this->hours_arr = $hours;
}
return $this->hours_arr;
@ -987,8 +1011,8 @@ class CronParser
$minutes = $this->expand_ranges($this->bits[0]);
$minutes = $this->_sanitize($minutes, 0, 59);
}
$this->debug("Minutes array");
$this->debug($minutes);
// $this->debug("Minutes array");
// $this->debug($minutes);
$this->minutes_arr = $minutes;
}
return $this->minutes_arr;
@ -1014,8 +1038,8 @@ class CronParser
$months = $this->expand_ranges($this->bits[3]);
$months = $this->_sanitize($months, 1, 12);
}
$this->debug("Months array");
$this->debug($months);
// $this->debug("Months array");
// $this->debug($months);
$this->months_arr = $months;
}
return $this->months_arr;
@ -1067,6 +1091,7 @@ class cronScheduler
$this->cron = new CronParser();
$this->debug = $_E107['debug'];
$this->cron->setDebug($_E107['debug']);
$this->pref = e107::getPref();
}
@ -1083,24 +1108,23 @@ class cronScheduler
{
if($this->debug)
{
error_log('e107: Invalid token used for cron class');
$this->cron->debug('e107: Invalid token used for cron class');
}
return false;
}
if(!@file_put_contents(e_CACHE . 'cronLastLoad.php', time()))
{
error_log('e107: Unable to write to: '.e_CACHE . 'cronLastLoad.php. Permissions issue?');
$this->cron->debug('e107: Unable to write to: '.e_CACHE . 'cronLastLoad.php. Permissions issue?');
}
// Get active cron jobs.
$cron_jobs = $this->getCronJobs(true);
if($this->debug && $_SERVER['QUERY_STRING'])
if($this->debug)
{
echo "<h1>Cron Lists</h1>";
print_a($cron_jobs);
$this->cron->debug('e107: Cron Jobs Found: '.print_r($cron_jobs,true));
}
foreach($cron_jobs as $job)
@ -1129,8 +1153,16 @@ class cronScheduler
{
$status = false;
$this->cron->debug(__METHOD__.' ('.__LINE__.'): '.date_default_timezone_get());
$this->cron->debug(__METHOD__.' ('.__LINE__.'): Current time: '.date('c'));
if(empty($job['active']))
{
if($this->debug)
{
error_log('e107: Cron job not active: '.print_r($job,true), E_NOTICE);
}
return false;
}
@ -1138,24 +1170,33 @@ class cronScheduler
$this->cron->calcLastRan($job['tab']);
$due = $this->cron->getLastRanUnix();
if($this->debug)
{
echo "<br />Cron: " . $job['function'];
}
$this->cron->debug(__METHOD__.' ('.__LINE__.'): '.date_default_timezone_get());
$triggerTime = (time() - 45);
if($due <= (time() - 45))
$this->cron->debug(__METHOD__.' ('.__LINE__.'): Current Time (-45): '.date('c',$triggerTime));
if($due <= $triggerTime)
{
if($this->debug)
{
$job['_lastRun'] = date('c',$due);
$job['_triggerTime'] = date('c',$triggerTime);
$this->cron->debug(__METHOD__.' ('.__LINE__.'): NOT running cron method because: _lastRun < _triggerTime ');
$this->cron->debug($job);
}
return false;
}
if($job['path'] != '_system' && !is_readable(e_PLUGIN . $job['path'] . "/e_cron.php"))
{
$this->cron->debug('e107: Cron file not readable: '.e_PLUGIN . $job['path'] . "/e_cron.php", E_ERROR);
return false;
}
if($this->debug)
{
echo "<br />Running Now...<br />path: " . $job['path'];
$this->cron->debug('e107: Cron is running: '.print_r($job,true), E_NOTICE);
}
// This is correct.
@ -1170,7 +1211,7 @@ class cronScheduler
{
if($this->debug)
{
echo "<br />Couldn't find class: " . $class;
$this->cron->debug('e107: Cron could not find class: '.$class, E_ERROR);
}
return $status;
@ -1182,7 +1223,7 @@ class cronScheduler
{
if($this->debug)
{
echo "<br />Couldn't find method: " . $job['function'];
$this->cron->debug('e107: Cron could not find method: '.$job['function'], E_ERROR);
}
return $status;
@ -1190,7 +1231,7 @@ class cronScheduler
if($this->debug)
{
echo "<br />Method Found: " . $class . "::" . $job['function'] . "()";
$this->cron->debug('e107: Cron could not find method: '.$class . "::" . $job['function'].'()', E_NOTICE);
}
// Exception handling.
@ -1215,6 +1256,8 @@ class cronScheduler
'subject' => 'e107 - Cron Schedule Exception',
);
error_log('e107: Cron Exception occurred: '.$msg, E_ERROR);
$this->sendMail($mail);
}
@ -1222,13 +1265,11 @@ class cronScheduler
// message (send email, logs).
if($status && true !== $status)
{
if($this->debug)
{
echo "<br />Method returned message: [$class::" . $job['function'] . '] ' . $status;
}
$msg = 'Method returned message: [{' . $class . '}::' . $job['function'] . '] ' . $status;
error_log('e107: Cron Method returned message: '.$msg, E_NOTICE);
$mail = array(
'to_mail' => $this->pref['siteadminemail'],
'to_name' => $this->pref['siteadmin'],

View File

@ -0,0 +1,147 @@
<?php
class CronParserTest extends \Codeception\Test\Unit
{
/** @var CronParser */
protected $cp;
protected function _before()
{
require_once(e_HANDLER."cron_class.php");
try
{
$this->cp = $this->make('CronParser');
}
catch(Exception $e)
{
$this->fail($e->getMessage());
}
}
/*
public function test_getLastMinute()
{
}
public function test_getHoursArray()
{
}
public function test_prevMonth()
{
}
public function test_getLastDay()
{
}
public function test_getLastMonth()
{
}
public function testGetDays()
{
}
public function test_getMinutesArray()
{
}
public function testGetLastRanUnix()
{
}
public function testDaysinmonth()
{
}
public function test_getMonthsArray()
{
}
*/
public function testCalcLastRan()
{
$lastTimeZone = date_default_timezone_get();
date_default_timezone_set('America/Chihuahua');
$this->cp->calcLastRan('* * * * *');
$due = $this->cp->getLastDue();
$now = $this->cp->getNow();
list($date, $time) = explode('T', $due);
list($year,$month,$day) = explode('-', $date);
list($hour,$minute) = explode(':', $time);
$this->assertSame($minute, $now[0]);
$this->assertSame($hour, $now[1]);
$this->assertSame($day, $now[2]);
$this->assertSame($month, $now[3]);
$this->assertSame($year, $now[5]);
date_default_timezone_set($lastTimeZone);
}
/*
public function testGetLastRan()
{
}
public function test_prevDay()
{
}
public function testDebug()
{
}
public function test_sanitize()
{
}
public function testGetDebug()
{
}
public function test_getLastHour()
{
}
public function test_getDaysArray()
{
}
public function test_prevHour()
{
}
public function testExpand_ranges()
{
}
*/
}

View File

@ -167,7 +167,7 @@ class e_formTest extends \Codeception\Test\Unit
"id": ""
}
]',
'datestamp_001' => 1454367600,
'datestamp_001' => 1699048024,
'date_001' => '2018-08-23',
'userclass_001' => 0,
'userclasses_001' => '0,1',
@ -426,20 +426,21 @@ class e_formTest extends \Codeception\Test\Unit
public function testDatepicker()
{
$prevTimeZone = date_default_timezone_get();
date_default_timezone_set('UTC');
$time = strtotime('January 1st, 2018 1am');
$time = strtotime('January 1st, 2023 1am');
$actual = $this->_frm->datepicker('date_field',$time,'type=datetime&format=MM, dd, yyyy hh:ii');
$expected = "<input class='tbox e-datetime input-xlarge form-control' type='text' size='40' id='e-datepicker-date-field' value='January, 01, 2018 01:00' data-date-unix ='true' data-date-format='MM, dd, yyyy hh:ii' data-date-ampm='false' data-date-language='en' data-date-firstday='0' /><input type='hidden' name='date_field' id='date-field' value='1514768400' />";
$expected = "<input class='tbox e-datetime input-xlarge form-control' type='text' size='40' id='e-datepicker-date-field' value='January, 01, 2023 01:00' data-date-unix ='true' data-date-format='MM, dd, yyyy hh:ii' data-date-ampm='false' data-date-language='en' data-date-firstday='0' /><input type='hidden' name='date_field' id='date-field' value='1672534800' />";
$this->assertEquals($expected, $actual);
// test timezone change...
date_default_timezone_set('America/Los_Angeles');
$actual = $this->_frm->datepicker('date_field',$time,'type=datetime&format=MM, dd, yyyy hh:ii');
$expected = "<input class='tbox e-datetime input-xlarge form-control' type='text' size='40' id='e-datepicker-date-field' value='December, 31, 2017 17:00' data-date-unix ='true' data-date-format='MM, dd, yyyy hh:ii' data-date-ampm='false' data-date-language='en' data-date-firstday='0' /><input type='hidden' name='date_field' id='date-field' value='1514768400' />";
$expected = "<input class='tbox e-datetime input-xlarge form-control' type='text' size='40' id='e-datepicker-date-field' value='December, 31, 2022 18:00' data-date-unix ='true' data-date-format='MM, dd, yyyy hh:ii' data-date-ampm='false' data-date-language='en' data-date-firstday='0' /><input type='hidden' name='date_field' id='date-field' value='1672534800' />";
$this->assertEquals($expected, $actual);
date_default_timezone_set($prevTimeZone);
}
public function testUserlist()
@ -1143,7 +1144,7 @@ class e_formTest extends \Codeception\Test\Unit
'file_001' => '<a href="'.SITEURL.'e107_media/000000test/files/test.zip" title="Direct link to {e_MEDIA_FILE}test.zip" rel="external">{e_MEDIA_FILE}test.zip</a>',
'files_001' => '<ol><li>{e_MEDIA_FILE}test.zip</li></ol>',
'datestamp_001' => '01 Feb 2016 : 15:00',
'datestamp_001' => '03 Nov 2023 : 14:47',
'date_001' => '2018-08-23',
'userclass_001' => 'Everyone (public)',
'userclasses_001' => 'Everyone (public)<br />PRIVATEMENU',
@ -1261,7 +1262,7 @@ class e_formTest extends \Codeception\Test\Unit
'file_001' => "<input type='hidden' name='file_001' id='file-001' value='{e_MEDIA_FILE}test.zip' style='width:400px' /><a title='Media Manager : _common_file' class='e-modal' data-modal-submit='true' data-modal-caption='Media Manager' data-cache='false' data-target='#uiModal' href='/e107_admin/image.php?mode=main&amp;action=dialog&amp;for=_common_file&amp;tagid=file-001&amp;iframe=1'><span id='file-001_prev' class='btn btn-default btn-secondary btn-small'>{e_MEDIA_FILE}test.zip</span></a>",
'files_001' => "<ol><li><input type='hidden' name='files_001[0][path]' id='files-001-0-path' value='{e_MEDIA_FILE}test.zip' /><input type='hidden' name='files_001[0][name]' id='files-001-0-name' value='test.zip' /><input type='hidden' name='files_001[0][id]' id='files-001-0-id' value='171' /><a title='Media Manager : _common_file' class='e-modal' data-modal-submit='true' data-modal-caption='Media Manager' data-cache='false' data-target='#uiModal' href='/e107_admin/image.php?mode=main&amp;action=dialog&amp;for=_common_file&amp;tagid=files-001-0&amp;iframe=1'><span id='files-001-0_prev' class='btn btn-default btn-secondary btn-small'>{e_MEDIA_FILE}test.zip</span></a></li><li><input type='hidden' name='files_001[1][path]' id='files-001-1-path' value='' /><input type='hidden' name='files_001[1][name]' id='files-001-1-name' value='' /><input type='hidden' name='files_001[1][id]' id='files-001-1-id' value='' /><a title='Media Manager : _common_file' class='e-modal' data-modal-submit='true' data-modal-caption='Media Manager' data-cache='false' data-target='#uiModal' href='/e107_admin/image.php?mode=main&amp;action=dialog&amp;for=_common_file&amp;tagid=files-001-1&amp;iframe=1'><span id='files-001-1_prev' class='btn btn-default btn-secondary btn-small'>Choose a file</span></a></li><li><input type='hidden' name='files_001[2][path]' id='files-001-2-path' value='' /><input type='hidden' name='files_001[2][name]' id='files-001-2-name' value='' /><input type='hidden' name='files_001[2][id]' id='files-001-2-id' value='' /><a title='Media Manager : _common_file' class='e-modal' data-modal-submit='true' data-modal-caption='Media Manager' data-cache='false' data-target='#uiModal' href='/e107_admin/image.php?mode=main&amp;action=dialog&amp;for=_common_file&amp;tagid=files-001-2&amp;iframe=1'><span id='files-001-2_prev' class='btn btn-default btn-secondary btn-small'>Choose a file</span></a></li><li><input type='hidden' name='files_001[3][path]' id='files-001-3-path' value='' /><input type='hidden' name='files_001[3][name]' id='files-001-3-name' value='' /><input type='hidden' name='files_001[3][id]' id='files-001-3-id' value='' /><a title='Media Manager : _common_file' class='e-modal' data-modal-submit='true' data-modal-caption='Media Manager' data-cache='false' data-target='#uiModal' href='/e107_admin/image.php?mode=main&amp;action=dialog&amp;for=_common_file&amp;tagid=files-001-3&amp;iframe=1'><span id='files-001-3_prev' class='btn btn-default btn-secondary btn-small'>Choose a file</span></a></li><li><input type='hidden' name='files_001[4][path]' id='files-001-4-path' value='' /><input type='hidden' name='files_001[4][name]' id='files-001-4-name' value='' /><input type='hidden' name='files_001[4][id]' id='files-001-4-id' value='' /><a title='Media Manager : _common_file' class='e-modal' data-modal-submit='true' data-modal-caption='Media Manager' data-cache='false' data-target='#uiModal' href='/e107_admin/image.php?mode=main&amp;action=dialog&amp;for=_common_file&amp;tagid=files-001-4&amp;iframe=1'><span id='files-001-4_prev' class='btn btn-default btn-secondary btn-small'>Choose a file</span></a></li></ol>",
'datestamp_001' => "<input class='tbox e-date input-xlarge form-control' type='text' size='40' id='e-datepicker-datestamp-001' value='Monday, 01 Feb, 2016' data-date-unix ='true' data-date-format='DD, dd M, yyyy' data-date-ampm='false' data-date-language='en' data-date-firstday='0' /><input type='hidden' name='datestamp_001' id='datestamp-001' value='1454367600' />",
'datestamp_001' => "<input class='tbox e-date input-xlarge form-control' type='text' size='40' id='e-datepicker-datestamp-001' value='Friday, 03 Nov, 2023' data-date-unix ='true' data-date-format='DD, dd M, yyyy' data-date-ampm='false' data-date-language='en' data-date-firstday='0' /><input type='hidden' name='datestamp_001' id='datestamp-001' value='1699048024' />",
'date_001' => "<input class='tbox e-date input-xlarge form-control' type='text' size='40' id='e-datepicker-date-001' value='Thursday, 23 Aug, 2018' data-date-unix ='true' data-date-format='DD, dd M, yyyy' data-date-ampm='false' data-date-language='en' data-date-firstday='0' /><input type='hidden' name='date_001' id='date-001' value='1535007600' />",
'userclass_001' => "<select name='userclass_001' id='userclass-001' class='tbox select form-control' tabindex='18'><option value='0' selected='selected'>Everyone (public)</option><option value='254'>&nbsp;&nbsp;Admin</option><option value='249'>&nbsp;&nbsp;Admins and Mods</option><option value='2'>&nbsp;&nbsp;CONTACT PEOPLE</option><option value='248'>&nbsp;&nbsp;Forum Moderators</option><option value='252'>&nbsp;&nbsp;Guests</option><option value='250'>&nbsp;&nbsp;Main Admin</option><option value='253'>&nbsp;&nbsp;Members</option><option value='1'>&nbsp;&nbsp;PRIVATEMENU</option><option value='255'>No One (inactive)</option><option value='3'>&nbsp;&nbsp;NEWSLETTER</option><optgroup label='Everyone but..'><option value='-254'>&nbsp;&nbsp;Not Admin</option><option value='-249'>&nbsp;&nbsp;Not Admins and Mods</option><option value='-2'>&nbsp;&nbsp;Not CONTACT PEOPLE</option><option value='-248'>&nbsp;&nbsp;Not Forum Moderators</option><option value='-252'>&nbsp;&nbsp;Not Guests</option><option value='-250'>&nbsp;&nbsp;Not Main Admin</option><option value='-253'>&nbsp;&nbsp;Not Members</option><option value='-1'>&nbsp;&nbsp;Not PRIVATEMENU</option><option value='-3'>&nbsp;&nbsp;Not NEWSLETTER</option></optgroup></select>",
'userclasses_001' => "<select name='userclasses_001[]' id='userclasses-001' class='tbox select form-control' tabindex='19' multiple='multiple'><option value='0'>Everyone (public)</option><option value='254'>&nbsp;&nbsp;Admin</option><option value='249'>&nbsp;&nbsp;Admins and Mods</option><option value='2'>&nbsp;&nbsp;CONTACT PEOPLE</option><option value='248'>&nbsp;&nbsp;Forum Moderators</option><option value='252'>&nbsp;&nbsp;Guests</option><option value='250'>&nbsp;&nbsp;Main Admin</option><option value='253'>&nbsp;&nbsp;Members</option><option value='1' selected='selected'>&nbsp;&nbsp;PRIVATEMENU</option><option value='255'>No One (inactive)</option><option value='3'>&nbsp;&nbsp;NEWSLETTER</option><optgroup label='Everyone but..'><option value='-254'>&nbsp;&nbsp;Not Admin</option><option value='-249'>&nbsp;&nbsp;Not Admins and Mods</option><option value='-2'>&nbsp;&nbsp;Not CONTACT PEOPLE</option><option value='-248'>&nbsp;&nbsp;Not Forum Moderators</option><option value='-252'>&nbsp;&nbsp;Not Guests</option><option value='-250'>&nbsp;&nbsp;Not Main Admin</option><option value='-253'>&nbsp;&nbsp;Not Members</option><option value='-1'>&nbsp;&nbsp;Not PRIVATEMENU</option><option value='-3'>&nbsp;&nbsp;Not NEWSLETTER</option></optgroup></select>",