mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-25 16:19:00 +01:00
WIP: periodic running admin-commands
This commit is contained in:
parent
cdae6c4b01
commit
c1316beda5
@ -45,6 +45,10 @@ use EGroupware\Api\Acl;
|
|||||||
* foreign key into egw_admin_remote (table of remote systems administrated by this one)
|
* foreign key into egw_admin_remote (table of remote systems administrated by this one)
|
||||||
* @property-read int $account account_id of user affected by this cmd or NULL
|
* @property-read int $account account_id of user affected by this cmd or NULL
|
||||||
* @property-read string $app app-name affected by this cmd or NULL
|
* @property-read string $app app-name affected by this cmd or NULL
|
||||||
|
* @property-read string $parent parent cmd (with rrule) of single periodic execution
|
||||||
|
* @property-read string $rrule rrule for periodic execution
|
||||||
|
* @property int $rrule_start optional start timestamp for rrule, default $created time
|
||||||
|
* @property string async_job_id optional name of async job for periodic-run, default "admin-cmd-$id"
|
||||||
*/
|
*/
|
||||||
abstract class admin_cmd
|
abstract class admin_cmd
|
||||||
{
|
{
|
||||||
@ -67,7 +71,7 @@ abstract class admin_cmd
|
|||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
protected $status;
|
protected $status = self::successful;
|
||||||
|
|
||||||
static $stati = array(
|
static $stati = array(
|
||||||
admin_cmd::scheduled => 'scheduled',
|
admin_cmd::scheduled => 'scheduled',
|
||||||
@ -96,6 +100,8 @@ abstract class admin_cmd
|
|||||||
public $remote_id;
|
public $remote_id;
|
||||||
protected $account;
|
protected $account;
|
||||||
protected $app;
|
protected $app;
|
||||||
|
protected $rrule;
|
||||||
|
protected $parent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display name of command, default ucfirst(str_replace(['_cmd_', '_'], ' ', __CLASS__))
|
* Display name of command, default ucfirst(str_replace(['_cmd_', '_'], ' ', __CLASS__))
|
||||||
@ -387,6 +393,16 @@ abstract class admin_cmd
|
|||||||
{
|
{
|
||||||
admin_cmd::_set_async_job();
|
admin_cmd::_set_async_job();
|
||||||
}
|
}
|
||||||
|
// schedule periodic execution, if we have an rrule
|
||||||
|
elseif (!empty($this->rrule))
|
||||||
|
{
|
||||||
|
$this->set_periodic_job();
|
||||||
|
}
|
||||||
|
// existing object with no rrule, cancle evtl. running periodic job
|
||||||
|
elseif($vars['id'])
|
||||||
|
{
|
||||||
|
$this->cancel_periodic_job();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,12 +434,12 @@ abstract class admin_cmd
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reading a command from the queue returning the comand object
|
* Reading a command from the queue returning the comand object
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @param int|string $id id or uid of the command
|
* @param int|string $id id or uid of the command
|
||||||
* @return admin_cmd or null if record not found
|
* @return admin_cmd or null if record not found
|
||||||
* @throws Exception(lang('Unknown command %1!',$class),0);
|
* @throws Api\Exception\WrongParameter if class does not exist or is no instance of admin_cmd
|
||||||
*/
|
*/
|
||||||
static function read($id)
|
static function read($id)
|
||||||
{
|
{
|
||||||
@ -977,6 +993,72 @@ abstract class admin_cmd
|
|||||||
return admin_cmd::_set_async_job();
|
return admin_cmd::_set_async_job();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PERIOD_ASYNC_ID_PREFIX = 'admin-cmd-';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule next execution of a periodic job
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function set_periodic_job()
|
||||||
|
{
|
||||||
|
if (empty($this->rrule)) return false;
|
||||||
|
|
||||||
|
// parse rrule and calculate next execution time
|
||||||
|
$event = calendar_rrule::parseRrule($this->rrule, true); // true: allow HOURLY or MINUTELY
|
||||||
|
// rrule can depend on start-time, use policy creation time by default, if rrule_start is not set
|
||||||
|
$event['start'] = empty($this->rrule_start) ? $this->created : $this->rrule_start;
|
||||||
|
$event['tzid'] = Api\DateTime::$server_timezone->getName();
|
||||||
|
$rrule = calendar_rrule::event2rrule($event, false); // false = server timezone
|
||||||
|
$rrule->rewind();
|
||||||
|
while((($time = $rrule->current()->format('ts'))) <= time())
|
||||||
|
{
|
||||||
|
$rrule->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// schedule run_periodic_job to run at that time
|
||||||
|
$async = new Api\Asyncservice();
|
||||||
|
$job_id = empty($this->async_job_id) ? self::PERIOD_ASYNC_ID_PREFIX.$this->id : $this->async_job_id;
|
||||||
|
$async->cancel_timer($job_id); // we delete it in case a job already exists
|
||||||
|
return $async->set_timer($time, $job_id, __CLASS__.'::run_periodic_job', $this->as_array(), $this->creator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel evtl. existing periodic job
|
||||||
|
*
|
||||||
|
* @return boolean true if job was canceled, false otherwise
|
||||||
|
*/
|
||||||
|
protected function cancel_periodic_job()
|
||||||
|
{
|
||||||
|
$async = new Api\Asyncservice();
|
||||||
|
$job_id = empty($this->async_job_id) ? self::PERIOD_ASYNC_ID_PREFIX.$this->id : $this->async_job_id;
|
||||||
|
$async->cancel_timer($job_id); // we delete it in case a job already exists
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a periodic job, record it's result and schedule next run
|
||||||
|
*/
|
||||||
|
static function run_periodic_job($data)
|
||||||
|
{
|
||||||
|
$cmd = admin_cmd::read($data['id']);
|
||||||
|
|
||||||
|
// schedule next execution
|
||||||
|
$cmd->set_periodic_job();
|
||||||
|
|
||||||
|
// instanciate single periodic execution object
|
||||||
|
$single = $cmd->as_array();
|
||||||
|
$single['parent'] = $single['id'];
|
||||||
|
unset($single['id'], $single['uid'], $single['rrule'], $single['created'], $single['modified'], $single['modifier']);
|
||||||
|
$periodic = admin_cmd::instanciate($single);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$periodic->run(null, false);
|
||||||
|
}
|
||||||
|
catch (Exception $ex) {
|
||||||
|
error_log(__METHOD__."(".array2string($data).") periodic execution failed: ".$ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of defined remote instances
|
* Return a list of defined remote instances
|
||||||
*
|
*
|
||||||
|
@ -48,7 +48,8 @@ class admin_cmds
|
|||||||
$row['title'] = $e->getMessage();
|
$row['title'] = $e->getMessage();
|
||||||
}
|
}
|
||||||
$row['data'] = !($data = json_php_unserialize($row['data'])) ? '' :
|
$row['data'] = !($data = json_php_unserialize($row['data'])) ? '' :
|
||||||
json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
json_encode($data+(empty($row['rrule'])?array():array('rrule' => $row['rrule'])),
|
||||||
|
JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||||
|
|
||||||
if ($row['status'] == admin_cmd::scheduled)
|
if ($row['status'] == admin_cmd::scheduled)
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
* @package calendar
|
* @package calendar
|
||||||
* @subpackage export
|
* @subpackage export
|
||||||
* @version $Id$
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use EGroupware\Api;
|
use EGroupware\Api;
|
||||||
@ -28,15 +27,6 @@ class calendar_ical extends calendar_boupdate
|
|||||||
*/
|
*/
|
||||||
var $supportedFields;
|
var $supportedFields;
|
||||||
|
|
||||||
var $recur_days_1_0 = array(
|
|
||||||
MCAL_M_MONDAY => 'MO',
|
|
||||||
MCAL_M_TUESDAY => 'TU',
|
|
||||||
MCAL_M_WEDNESDAY => 'WE',
|
|
||||||
MCAL_M_THURSDAY => 'TH',
|
|
||||||
MCAL_M_FRIDAY => 'FR',
|
|
||||||
MCAL_M_SATURDAY => 'SA',
|
|
||||||
MCAL_M_SUNDAY => 'SU',
|
|
||||||
);
|
|
||||||
/**
|
/**
|
||||||
* @var array $status_egw2ical conversation of the participant status egw => ical
|
* @var array $status_egw2ical conversation of the participant status egw => ical
|
||||||
*/
|
*/
|
||||||
@ -2640,160 +2630,8 @@ class calendar_ical extends calendar_boupdate
|
|||||||
$vcardData['location'] = str_replace("\r\n", "\n", $attributes['value']);
|
$vcardData['location'] = str_replace("\r\n", "\n", $attributes['value']);
|
||||||
break;
|
break;
|
||||||
case 'RRULE':
|
case 'RRULE':
|
||||||
$recurence = $attributes['value'];
|
$vcardData += calendar_rrule::parseRrule($attributes['value']);
|
||||||
$vcardData['recur_interval'] = 1;
|
if (!empty($vcardData['recur_enddate'])) self::check_fix_endate ($vcardData);
|
||||||
$type = preg_match('/FREQ=([^;: ]+)/i',$recurence,$matches) ? $matches[1] : $recurence[0];
|
|
||||||
// vCard 2.0 values for all types
|
|
||||||
if (preg_match('/UNTIL=([0-9TZ]+)/',$recurence,$matches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($matches[1]);
|
|
||||||
// If it couldn't be parsed, treat it as not set
|
|
||||||
if(is_string($vcardData['recur_enddate']))
|
|
||||||
{
|
|
||||||
unset($vcardData['recur_enddate']);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// iCal defines enddate to be a time and eg. Apple sends 1s less then next recurance, if they split events
|
|
||||||
self::check_fix_endate($vcardData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseif (preg_match('/COUNT=([0-9]+)/',$recurence,$matches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_count'] = (int)$matches[1];
|
|
||||||
}
|
|
||||||
if (preg_match('/INTERVAL=([0-9]+)/',$recurence,$matches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_interval'] = (int) $matches[1] ? (int) $matches[1] : 1;
|
|
||||||
}
|
|
||||||
$vcardData['recur_data'] = 0;
|
|
||||||
switch($type)
|
|
||||||
{
|
|
||||||
case 'D': // 1.0
|
|
||||||
$recurenceMatches = null;
|
|
||||||
if (preg_match('/D(\d+) #(\d+)/', $recurence, $recurenceMatches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_interval'] = $recurenceMatches[1];
|
|
||||||
$vcardData['recur_count'] = $recurenceMatches[2];
|
|
||||||
}
|
|
||||||
elseif (preg_match('/D(\d+) (.*)/', $recurence, $recurenceMatches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_interval'] = $recurenceMatches[1];
|
|
||||||
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime(trim($recurenceMatches[2]));
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
// fall-through
|
|
||||||
case 'DAILY': // 2.0
|
|
||||||
$vcardData['recur_type'] = MCAL_RECUR_DAILY;
|
|
||||||
if (stripos($recurence, 'BYDAY') === false) break;
|
|
||||||
// hack to handle TYPE=DAILY;BYDAY= as WEEKLY, which is true as long as there's no interval
|
|
||||||
// fall-through
|
|
||||||
case 'W':
|
|
||||||
case 'WEEKLY':
|
|
||||||
$days = array();
|
|
||||||
if (preg_match('/W(\d+) *((?i: [AEFHMORSTUW]{2})+)?( +([^ ]*))$/',$recurence, $recurenceMatches)) // 1.0
|
|
||||||
{
|
|
||||||
$vcardData['recur_interval'] = $recurenceMatches[1];
|
|
||||||
if (empty($recurenceMatches[2]))
|
|
||||||
{
|
|
||||||
$days[0] = strtoupper(substr(date('D', $vcardData['start']),0,2));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$days = explode(' ',trim($recurenceMatches[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
$repeatMatches = null;
|
|
||||||
if (preg_match('/#(\d+)/',$recurenceMatches[4],$repeatMatches))
|
|
||||||
{
|
|
||||||
if ($repeatMatches[1]) $vcardData['recur_count'] = $repeatMatches[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$recur_days = $this->recur_days_1_0;
|
|
||||||
}
|
|
||||||
elseif (preg_match('/BYDAY=([^;: ]+)/',$recurence,$recurenceMatches)) // 2.0
|
|
||||||
{
|
|
||||||
$days = explode(',',$recurenceMatches[1]);
|
|
||||||
$recur_days = $this->recur_days;
|
|
||||||
}
|
|
||||||
else // no day given, use the day of dtstart
|
|
||||||
{
|
|
||||||
$vcardData['recur_data'] |= 1 << (int)date('w',$vcardData['start']);
|
|
||||||
$vcardData['recur_type'] = MCAL_RECUR_WEEKLY;
|
|
||||||
}
|
|
||||||
if ($days)
|
|
||||||
{
|
|
||||||
foreach ($recur_days as $id => $day)
|
|
||||||
{
|
|
||||||
if (in_array(strtoupper(substr($day,0,2)),$days))
|
|
||||||
{
|
|
||||||
$vcardData['recur_data'] |= $id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$vcardData['recur_type'] = MCAL_RECUR_WEEKLY;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'M':
|
|
||||||
if (preg_match('/MD(\d+)(?: [^ ]+)? #(\d+)/', $recurence, $recurenceMatches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_type'] = MCAL_RECUR_MONTHLY_MDAY;
|
|
||||||
$vcardData['recur_interval'] = $recurenceMatches[1];
|
|
||||||
$vcardData['recur_count'] = $recurenceMatches[2];
|
|
||||||
}
|
|
||||||
elseif (preg_match('/MD(\d+)(?: [^ ]+)? ([0-9TZ]+)/',$recurence, $recurenceMatches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_type'] = MCAL_RECUR_MONTHLY_MDAY;
|
|
||||||
$vcardData['recur_interval'] = $recurenceMatches[1];
|
|
||||||
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[2]);
|
|
||||||
}
|
|
||||||
elseif (preg_match('/MP(\d+) (.*) (.*) (.*)/',$recurence, $recurenceMatches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_type'] = MCAL_RECUR_MONTHLY_WDAY;
|
|
||||||
$vcardData['recur_interval'] = $recurenceMatches[1];
|
|
||||||
if (preg_match('/#(\d+)/',$recurenceMatches[4],$recurenceMatches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_count'] = $recurenceMatches[1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime(trim($recurenceMatches[4]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Y': // 1.0
|
|
||||||
if (preg_match('/YM(\d+)(?: [^ ]+)? #(\d+)/', $recurence, $recurenceMatches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_interval'] = $recurenceMatches[1];
|
|
||||||
$vcardData['recur_count'] = $recurenceMatches[2];
|
|
||||||
}
|
|
||||||
elseif (preg_match('/YM(\d+)(?: [^ ]+)? ([0-9TZ]+)/',$recurence, $recurenceMatches))
|
|
||||||
{
|
|
||||||
$vcardData['recur_interval'] = $recurenceMatches[1];
|
|
||||||
$vcardData['recur_enddate'] = $this->vCalendar->_parseDateTime($recurenceMatches[2]);
|
|
||||||
} else break;
|
|
||||||
// fall-through
|
|
||||||
case 'YEARLY': // 2.0
|
|
||||||
if (strpos($recurence, 'BYDAY') === false)
|
|
||||||
{
|
|
||||||
$vcardData['recur_type'] = MCAL_RECUR_YEARLY;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// handle FREQ=YEARLY;BYDAY= as FREQ=MONTHLY;BYDAY= with 12*INTERVAL
|
|
||||||
$vcardData['recur_interval'] = $vcardData['recur_interval'] ?
|
|
||||||
12*$vcardData['recur_interval'] : 12;
|
|
||||||
// fall-through
|
|
||||||
case 'MONTHLY':
|
|
||||||
// does currently NOT parse BYDAY or BYMONTH, it has to be specified/identical to DTSTART
|
|
||||||
$vcardData['recur_type'] = strpos($recurence,'BYDAY') !== false ?
|
|
||||||
MCAL_RECUR_MONTHLY_WDAY : MCAL_RECUR_MONTHLY_MDAY;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'EXDATE': // current Horde_Icalendar returns dates, no timestamps
|
case 'EXDATE': // current Horde_Icalendar returns dates, no timestamps
|
||||||
if ($attributes['values'])
|
if ($attributes['values'])
|
||||||
|
@ -56,6 +56,15 @@ class calendar_rrule implements Iterator
|
|||||||
* Yearly recurrance
|
* Yearly recurrance
|
||||||
*/
|
*/
|
||||||
const YEARLY = 5;
|
const YEARLY = 5;
|
||||||
|
/**
|
||||||
|
* Hourly recurrance
|
||||||
|
*/
|
||||||
|
const HOURLY = 8;
|
||||||
|
/**
|
||||||
|
* Minutely recurrance
|
||||||
|
*/
|
||||||
|
const MINUTELY = 7;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate recure types to labels
|
* Translate recure types to labels
|
||||||
*
|
*
|
||||||
@ -67,7 +76,7 @@ class calendar_rrule implements Iterator
|
|||||||
self::WEEKLY => 'Weekly',
|
self::WEEKLY => 'Weekly',
|
||||||
self::MONTHLY_WDAY => 'Monthly (by day)',
|
self::MONTHLY_WDAY => 'Monthly (by day)',
|
||||||
self::MONTHLY_MDAY => 'Monthly (by date)',
|
self::MONTHLY_MDAY => 'Monthly (by date)',
|
||||||
self::YEARLY => 'Yearly'
|
self::YEARLY => 'Yearly',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +88,8 @@ class calendar_rrule implements Iterator
|
|||||||
self::MONTHLY_WDAY => 'MONTHLY', // BYDAY={1..7, -1}{MO..SO, last workday}
|
self::MONTHLY_WDAY => 'MONTHLY', // BYDAY={1..7, -1}{MO..SO, last workday}
|
||||||
self::MONTHLY_MDAY => 'MONTHLY', // BYMONHTDAY={1..31, -1 for last day of month}
|
self::MONTHLY_MDAY => 'MONTHLY', // BYMONHTDAY={1..31, -1 for last day of month}
|
||||||
self::YEARLY => 'YEARLY',
|
self::YEARLY => 'YEARLY',
|
||||||
|
self::HOURLY => 'HOURLY',
|
||||||
|
self::MINUTELY => 'MINUTELY',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -250,7 +261,7 @@ class calendar_rrule implements Iterator
|
|||||||
|
|
||||||
$this->time = $time instanceof Api\DateTime ? $time : new Api\DateTime($time);
|
$this->time = $time instanceof Api\DateTime ? $time : new Api\DateTime($time);
|
||||||
|
|
||||||
if (!in_array($type,array(self::NONE, self::DAILY, self::WEEKLY, self::MONTHLY_MDAY, self::MONTHLY_WDAY, self::YEARLY)))
|
if (!in_array($type,array(self::NONE, self::DAILY, self::WEEKLY, self::MONTHLY_MDAY, self::MONTHLY_WDAY, self::YEARLY, self::HOURLY, self::MINUTELY)))
|
||||||
{
|
{
|
||||||
throw new Api\Exception\WrongParameter(__METHOD__."($time,$type,$interval,$enddate,$weekdays,...) type $type is NOT valid!");
|
throw new Api\Exception\WrongParameter(__METHOD__."($time,$type,$interval,$enddate,$weekdays,...) type $type is NOT valid!");
|
||||||
}
|
}
|
||||||
@ -451,6 +462,14 @@ class calendar_rrule implements Iterator
|
|||||||
$this->current->modify($this->interval.' year');
|
$this->current->modify($this->interval.' year');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case self::HOURLY:
|
||||||
|
$this->current->modify($this->interval.' hour');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::MINUTELY:
|
||||||
|
$this->current->modify($this->interval.' minute');
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Api\Exception\AssertionFailed(__METHOD__."() invalid type #$this->type !");
|
throw new Api\Exception\AssertionFailed(__METHOD__."() invalid type #$this->type !");
|
||||||
}
|
}
|
||||||
@ -787,6 +806,7 @@ class calendar_rrule implements Iterator
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a rrule from a string generated by __toString().
|
* Generate a rrule from a string generated by __toString().
|
||||||
|
*
|
||||||
* @param String $rrule Recurrence rule in string format, as generated by __toString()
|
* @param String $rrule Recurrence rule in string format, as generated by __toString()
|
||||||
* @param DateTime date Optional date to work from, defaults to today
|
* @param DateTime date Optional date to work from, defaults to today
|
||||||
*/
|
*/
|
||||||
@ -799,16 +819,16 @@ class calendar_rrule implements Iterator
|
|||||||
$weekdays = 0;
|
$weekdays = 0;
|
||||||
$exceptions = array();
|
$exceptions = array();
|
||||||
|
|
||||||
list($type, $sub, $conditions) = explode(' (', $rrule);
|
list($type, $sub, $conds) = explode(' (', $rrule);
|
||||||
if(!$conditions)
|
if(!$conds)
|
||||||
{
|
{
|
||||||
$conditions = $sub;
|
$conds = $sub;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$type .= " ($sub";
|
$type .= " ($sub";
|
||||||
}
|
}
|
||||||
$conditions = explode(', ', substr($conditions, 0, -1));
|
$conditions = explode(', ', substr($conds, 0, -1));
|
||||||
|
|
||||||
foreach(static::$types as $id => $type_name)
|
foreach(static::$types as $id => $type_name)
|
||||||
{
|
{
|
||||||
@ -859,13 +879,14 @@ class calendar_rrule implements Iterator
|
|||||||
}
|
}
|
||||||
else if ($condition_name == lang('ends'))
|
else if ($condition_name == lang('ends'))
|
||||||
{
|
{
|
||||||
list($dow, $date) = explode(', ', $value);
|
list(, $date) = explode(', ', $value);
|
||||||
$enddate = new DateTime($date);
|
$enddate = new DateTime($date);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new calendar_rrule($time,$type_id,$interval,$enddate,$weekdays,$exceptions);
|
return new calendar_rrule($time,$type_id,$interval,$enddate,$weekdays,$exceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get recurrence data (keys 'recur_*') to merge into an event
|
* Get recurrence data (keys 'recur_*') to merge into an event
|
||||||
*
|
*
|
||||||
@ -939,6 +960,189 @@ class calendar_rrule implements Iterator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a DateTime field and returns a unix timestamp. If the
|
||||||
|
* field cannot be parsed then the original text is returned
|
||||||
|
* unmodified.
|
||||||
|
*
|
||||||
|
* @param string $text The Icalendar datetime field value.
|
||||||
|
* @param string $tzid =null A timezone identifier.
|
||||||
|
*
|
||||||
|
* @return integer A unix timestamp.
|
||||||
|
*/
|
||||||
|
private static function parseIcalDateTime($text, $tzid=null)
|
||||||
|
{
|
||||||
|
static $vcal = null;
|
||||||
|
if (!isset($vcal)) $vcal = new Horde_Icalendar;
|
||||||
|
|
||||||
|
return $vcal->_parseDateTime($text, $tzid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an iCal recurrence-rule string
|
||||||
|
*
|
||||||
|
* @param type $recurence
|
||||||
|
* @param bool $support_below_daily =false true: support FREQ=HOURLY|MINUTELY
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
public static function parseRrule($recurence, $support_below_daily=false)
|
||||||
|
{
|
||||||
|
$vcardData = array();
|
||||||
|
$vcardData['recur_interval'] = 1;
|
||||||
|
$matches = null;
|
||||||
|
$type = preg_match('/FREQ=([^;: ]+)/i',$recurence,$matches) ? $matches[1] : $recurence[0];
|
||||||
|
// vCard 2.0 values for all types
|
||||||
|
if (preg_match('/UNTIL=([0-9TZ]+)/',$recurence,$matches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_enddate'] = self::parseIcalDateTime($matches[1]);
|
||||||
|
// If it couldn't be parsed, treat it as not set
|
||||||
|
if(is_string($vcardData['recur_enddate']))
|
||||||
|
{
|
||||||
|
unset($vcardData['recur_enddate']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (preg_match('/COUNT=([0-9]+)/',$recurence,$matches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_count'] = (int)$matches[1];
|
||||||
|
}
|
||||||
|
if (preg_match('/INTERVAL=([0-9]+)/',$recurence,$matches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_interval'] = (int) $matches[1] ? (int) $matches[1] : 1;
|
||||||
|
}
|
||||||
|
$vcardData['recur_data'] = 0;
|
||||||
|
switch($type)
|
||||||
|
{
|
||||||
|
case 'D': // 1.0
|
||||||
|
$recurenceMatches = null;
|
||||||
|
if (preg_match('/D(\d+) #(\d+)/', $recurence, $recurenceMatches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_interval'] = $recurenceMatches[1];
|
||||||
|
$vcardData['recur_count'] = $recurenceMatches[2];
|
||||||
|
}
|
||||||
|
elseif (preg_match('/D(\d+) (.*)/', $recurence, $recurenceMatches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_interval'] = $recurenceMatches[1];
|
||||||
|
$vcardData['recur_enddate'] = self::parseIcalDateTime(trim($recurenceMatches[2]));
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
// fall-through
|
||||||
|
case 'DAILY': // 2.0
|
||||||
|
$vcardData['recur_type'] = self::DAILY;
|
||||||
|
if (stripos($recurence, 'BYDAY') === false) break;
|
||||||
|
// hack to handle TYPE=DAILY;BYDAY= as WEEKLY, which is true as long as there's no interval
|
||||||
|
// fall-through
|
||||||
|
case 'W':
|
||||||
|
case 'WEEKLY':
|
||||||
|
$days = array();
|
||||||
|
if (preg_match('/W(\d+) *((?i: [AEFHMORSTUW]{2})+)?( +([^ ]*))$/',$recurence, $recurenceMatches)) // 1.0
|
||||||
|
{
|
||||||
|
$vcardData['recur_interval'] = $recurenceMatches[1];
|
||||||
|
if (empty($recurenceMatches[2]))
|
||||||
|
{
|
||||||
|
$days[0] = strtoupper(substr(date('D', $vcardData['start']),0,2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$days = explode(' ',trim($recurenceMatches[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$repeatMatches = null;
|
||||||
|
if (preg_match('/#(\d+)/',$recurenceMatches[4],$repeatMatches))
|
||||||
|
{
|
||||||
|
if ($repeatMatches[1]) $vcardData['recur_count'] = $repeatMatches[1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$vcardData['recur_enddate'] = self::parseIcalDateTime($recurenceMatches[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (preg_match('/BYDAY=([^;: ]+)/',$recurence,$recurenceMatches)) // 2.0
|
||||||
|
{
|
||||||
|
$days = explode(',',$recurenceMatches[1]);
|
||||||
|
}
|
||||||
|
else // no day given, use the day of dtstart
|
||||||
|
{
|
||||||
|
$vcardData['recur_data'] |= 1 << (int)date('w',$vcardData['start']);
|
||||||
|
$vcardData['recur_type'] = self::WEEKLY;
|
||||||
|
}
|
||||||
|
if ($days)
|
||||||
|
{
|
||||||
|
foreach (self::$days as $id => $day)
|
||||||
|
{
|
||||||
|
if (in_array(strtoupper(substr($day,0,2)),$days))
|
||||||
|
{
|
||||||
|
$vcardData['recur_data'] |= $id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$vcardData['recur_type'] = self::WEEKLY;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'M':
|
||||||
|
if (preg_match('/MD(\d+)(?: [^ ]+)? #(\d+)/', $recurence, $recurenceMatches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_type'] = self::MONTHLY_MDAY;
|
||||||
|
$vcardData['recur_interval'] = $recurenceMatches[1];
|
||||||
|
$vcardData['recur_count'] = $recurenceMatches[2];
|
||||||
|
}
|
||||||
|
elseif (preg_match('/MD(\d+)(?: [^ ]+)? ([0-9TZ]+)/',$recurence, $recurenceMatches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_type'] = self::MONTHLY_MDAY;
|
||||||
|
$vcardData['recur_interval'] = $recurenceMatches[1];
|
||||||
|
$vcardData['recur_enddate'] = self::parseIcalDateTime($recurenceMatches[2]);
|
||||||
|
}
|
||||||
|
elseif (preg_match('/MP(\d+) (.*) (.*) (.*)/',$recurence, $recurenceMatches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_type'] = self::MONTHLY_WDAY;
|
||||||
|
$vcardData['recur_interval'] = $recurenceMatches[1];
|
||||||
|
if (preg_match('/#(\d+)/',$recurenceMatches[4],$recurenceMatches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_count'] = $recurenceMatches[1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$vcardData['recur_enddate'] = self::parseIcalDateTime(trim($recurenceMatches[4]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Y': // 1.0
|
||||||
|
if (preg_match('/YM(\d+)(?: [^ ]+)? #(\d+)/', $recurence, $recurenceMatches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_interval'] = $recurenceMatches[1];
|
||||||
|
$vcardData['recur_count'] = $recurenceMatches[2];
|
||||||
|
}
|
||||||
|
elseif (preg_match('/YM(\d+)(?: [^ ]+)? ([0-9TZ]+)/',$recurence, $recurenceMatches))
|
||||||
|
{
|
||||||
|
$vcardData['recur_interval'] = $recurenceMatches[1];
|
||||||
|
$vcardData['recur_enddate'] = self::parseIcalDateTime($recurenceMatches[2]);
|
||||||
|
} else break;
|
||||||
|
// fall-through
|
||||||
|
case 'YEARLY': // 2.0
|
||||||
|
if (strpos($recurence, 'BYDAY') === false)
|
||||||
|
{
|
||||||
|
$vcardData['recur_type'] = self::YEARLY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// handle FREQ=YEARLY;BYDAY= as FREQ=MONTHLY;BYDAY= with 12*INTERVAL
|
||||||
|
$vcardData['recur_interval'] = $vcardData['recur_interval'] ?
|
||||||
|
12*$vcardData['recur_interval'] : 12;
|
||||||
|
// fall-through
|
||||||
|
case 'MONTHLY':
|
||||||
|
// does currently NOT parse BYDAY or BYMONTH, it has to be specified/identical to DTSTART
|
||||||
|
$vcardData['recur_type'] = strpos($recurence,'BYDAY') !== false ?
|
||||||
|
self::MONTHLY_WDAY : self::MONTHLY_MDAY;
|
||||||
|
break;
|
||||||
|
case 'HOURLY':
|
||||||
|
if ($support_below_daily) $vcardData['recur_type'] = self::HOURLY;
|
||||||
|
break;
|
||||||
|
case 'MINUTELY':
|
||||||
|
if ($support_below_daily) $vcardData['recur_type'] = self::MINUTELY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $vcardData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) // some tests
|
if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) // some tests
|
||||||
|
Loading…
Reference in New Issue
Block a user