Add back default relationships, which still have some utility in complex environments.
[davical.git] / scripts / refresh-alarms.php
blob1845819209a8d5b8bdb30be8bc8c44659f15c7c1
1 #!/usr/bin/php
2 <?php
4 set_include_path('.');
6 function add_path_for( $include, $paths ) {
7 foreach( $paths AS $test_path ) {
8 if ( @file_exists($test_path.'/'.$include) ) {
9 set_include_path( $test_path. PATH_SEPARATOR. get_include_path());
10 return;
15 add_path_for('AWLUtilities.php', array( '../../awl/inc' , '../awl/inc'
16 , '../../../awl/inc' , '/usr/share/awl/inc' , '/usr/local/share/awl/inc') );
17 add_path_for('caldav-client-v2.php', array( '../../davical/inc' , '../davical/inc'
18 , '../../../davical/inc' , '/usr/share/davical/inc' , '/usr/local/share/davical/inc') );
19 add_path_for('always.php', array( 'scripts' ) );
20 add_path_for('sync-config.php', array( 'config' ) );
23 require('always.php');
24 require_once('AwlQuery.php');
25 require_once('RRule-v2.php');
26 require_once('vComponent.php');
28 /**
29 * Call with something like e.g.:
31 * scripts/refresh_alarms.php -p P1800D -f P1D
35 $args = (object) array();
36 $args->debug = false;
37 $args->set_last = false;
39 $args->future = 'P2000D';
40 $args->near_past = 'P1D';
41 $args->far_past = 'P1200D';
42 $debugging = null;
44 function parse_arguments() {
45 global $args;
47 $opts = getopt( 'f:p:n:d:lh' );
48 foreach( $opts AS $k => $v ) {
49 switch( $k ) {
50 case 'f': $args->future = $v; break;
51 case 'n': $args->near_past = $v; break;
52 case 'p': $args->far_past = $v; break;
53 case 'd': $args->debug = true; $debugging = explode(',',$v); break;
54 case 'l': $args->set_last = true; break;
55 case 'h': usage(); break;
56 default: $args->{$k} = $v;
61 function usage() {
62 echo <<<EOUSAGE
63 Usage:
64 refresh-alarms.php [-d]
66 -n <duration> Near past period to skip for finding last instances: default 1 days ('P1D')
67 -p <duration> Far past period to examine for finding last instances: default ~3 years ('P1200D')
68 -f <duration> Future period to consider for finding future alarms: default ~5 years ('P2000D')
70 -l Try to set the 'last' alarm date in historical alarms
72 -d Enable debugging
74 EOUSAGE;
76 exit(0);
79 parse_arguments();
81 if ( $args->debug && is_array($debugging )) {
82 foreach( $debugging AS $v ) {
83 $c->dbg[$v] = 1;
86 $args->near_past = '-' . $args->near_past;
87 $args->far_past = '-' . $args->far_past;
90 /**
91 * Essentially what we are doing is:
93 UPDATE calendar_alarm
94 SET next_trigger = (SELECT rrule_event_instances_range(
95 dtstart + icalendar_interval_to_SQL(replace(trigger,'TRIGGER:','')),
96 rrule,
97 current_timestamp, current_timestamp + '2 days'::interval,
99 LIMIT 1)
100 FROM calendar_item
101 WHERE calendar_alarm.dav_id = calendar_item.dav_id
102 AND next_trigger is null
103 AND rrule IS NOT NULL
106 $expand_range_start = new RepeatRuleDateTime(gmdate('Ymd\THis\Z'));
107 $expand_range_end = new RepeatRuleDateTime(gmdate('Ymd\THis\Z'));
108 $expand_range_end->modify( $args->future );
112 $earliest = clone($expand_range_start);
113 $earliest->modify( $args->near_past );
115 $sql = 'SELECT * FROM calendar_alarm JOIN calendar_item USING (dav_id) JOIN caldav_data USING (dav_id) WHERE rrule IS NOT NULL AND next_trigger IS NULL';
116 if ( $args->debug ) printf( "%s\n", $sql );
117 $qry = new AwlQuery( $sql );
118 if ( $qry->Exec() && $qry->rows() ) {
119 while( $alarm = $qry->Fetch() ) {
120 if ( $args->debug ) printf( "Processing alarm for '%s' based on '%s','%s', '%s'\n",
121 $alarm->dav_name, $alarm->dtstart, $alarm->rrule, $alarm->trigger );
122 $ic = new vComponent( $alarm->caldav_data );
123 $expanded = expand_event_instances( $ic, $earliest, $expand_range_end );
124 $expanded->MaskComponents( array( 'VEVENT', 'VTODO', 'VJOURNAL' ) );
125 $instances = $expanded->GetComponents();
127 $trigger = new vProperty( $alarm->trigger );
128 $related = $trigger->GetParameterValue('RELATED');
130 $first = new RepeatRuleDateTime($alarm->dtstart);
131 $first->modify( $trigger->Value() );
132 $next = null;
133 $last = null;
134 foreach( $instances AS $k => $component ) {
135 $when = new RepeatRuleDateTime( $component->GetPValue('DTSTART') ); // a UTC value
136 if ( $related == 'END' ) {
137 $when->modify( $component->GetPValue('DURATION') );
139 $when->modify( $trigger->Value() );
140 if ( $when > $expand_range_start && $when < $expand_range_end && (!isset($next) || $when < $next) ) {
141 $next = clone($when);
143 if ( $args->set_last && (!isset($last) || $when > $last) ) {
144 $last = clone($when);
147 if ( isset($next) && $next < $expand_range_end ) {
148 if ( $args->debug ) printf( "Found next alarm instance on '%s'\n", $next->UTC() );
149 $sql = 'UPDATE calendar_alarm SET next_trigger = :next WHERE dav_id = :id AND component = :component';
150 $update = new AwlQuery( $sql, array( ':next' => $next->UTC(), ':id' => $alarm->dav_id, ':component' => $alarm->component ) );
151 $update->Exec('refresh-alarms', __LINE__, __FILE__ );
153 else if ( $args->set_last && isset($last) && $last < $earliest ) {
154 if ( $args->debug ) printf( "Found past final alarm instance on '%s'\n", $last->UTC() );
155 $sql = 'UPDATE calendar_alarm SET next_trigger = :last WHERE dav_id = :id AND component = :component';
156 $update = new AwlQuery( $sql, array( ':last' => $last->UTC(), ':id' => $alarm->dav_id, ':component' => $alarm->component ) );
157 $update->Exec('refresh-alarms', __LINE__, __FILE__ );