PageRenderTime 32ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/scripts/refresh-alarms.php

https://gitlab.com/tiggerben/davical
PHP | 182 lines | 123 code | 27 blank | 32 comment | 53 complexity | fbf5787f95f303703be88deae6483466 MD5 | raw file
  1. #!/usr/bin/env php
  2. <?php
  3. /**
  4. * Script to refresh the pending alarm times for the next alarm instance.
  5. *
  6. * @package davical
  7. * @subpackage alarms
  8. * @author Andrew McMillan <andrew@morphoss.com>
  9. * @copyright Morphoss Ltd
  10. * @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
  11. */
  12. $script_file = __FILE__;
  13. chdir(str_replace('/scripts/refresh-alarms.php','/htdocs',$script_file));
  14. $_SERVER['SERVER_NAME'] = 'localhost';
  15. /**
  16. * Call with something like e.g.:
  17. *
  18. * scripts/refresh_alarms.php -p P1800D -f P1D
  19. *
  20. */
  21. $args = (object) array();
  22. $args->debug = false;
  23. $args->set_last = false;
  24. $args->slow_query_threshold = false; // Won't set higher threshold by default
  25. $args->future = 'P400D';
  26. $args->near_past = 'P1D';
  27. $debugging = null;
  28. function parse_arguments() {
  29. global $args;
  30. $opts = getopt( 'f:p:q:s:d:lh' );
  31. foreach( $opts AS $k => $v ) {
  32. switch( $k ) {
  33. case 'f': $args->future = $v; break;
  34. case 'p': $args->near_past = $v; break;
  35. case 'q': $args->slow_query_threshold = $v; break;
  36. case 's': $_SERVER['SERVER_NAME'] = $v; break;
  37. case 'd': $args->debug = true; $debugging = explode(',',$v); break;
  38. case 'l': $args->set_last = true; break;
  39. case 'h': usage(); break;
  40. default: $args->{$k} = $v;
  41. }
  42. }
  43. }
  44. function usage() {
  45. echo <<<USAGE
  46. Usage:
  47. refresh-alarms.php [-s server.domain.tld] [other options]
  48. -s <server> The servername to be used to identify the DAViCal configuration file.
  49. -p <duration> Near past period to review for finding recently last instances: default 1 days ('P1D')
  50. -f <duration> Future period to consider for finding future alarms: default ~5 years ('P2000D')
  51. -l Try to set the 'last' alarm date in historical alarms
  52. -q <secs> Warn about slow queries which take longer than this many seconds (use AWL default).
  53. -d xxx Enable debugging where 'xxx' is a comma-separated list of debug subsystems
  54. USAGE;
  55. exit(0);
  56. }
  57. parse_arguments();
  58. if ( $args->debug && is_array($debugging )) {
  59. foreach( $debugging AS $v ) {
  60. $c->dbg[$v] = 1;
  61. }
  62. }
  63. $args->near_past = '-' . $args->near_past;
  64. require_once("./always.php");
  65. if ( $args->slow_query_threshold !== false ) {
  66. $c->default_query_warning_threshold = $args->slow_query_threshold;
  67. }
  68. require_once('AwlQuery.php');
  69. require_once('RRule-v2.php');
  70. require_once('vCalendar.php');
  71. /**
  72. * Essentially what we are doing is:
  73. *
  74. UPDATE calendar_alarm
  75. SET next_trigger = (SELECT rrule_event_instances_range(
  76. dtstart + icalendar_interval_to_SQL(replace(trigger,'TRIGGER:','')),
  77. rrule,
  78. current_timestamp, current_timestamp + '2 days'::interval,
  79. 1)
  80. LIMIT 1)
  81. FROM calendar_item
  82. WHERE calendar_alarm.dav_id = calendar_item.dav_id
  83. AND next_trigger is null
  84. AND rrule IS NOT NULL
  85. */
  86. $expand_range_start = new RepeatRuleDateTime(gmdate('Ymd\THis\Z'));
  87. $expand_range_end = new RepeatRuleDateTime(gmdate('Ymd\THis\Z'));
  88. $expand_range_end->modify( $args->future );
  89. $earliest = clone($expand_range_start);
  90. $earliest->modify( $args->near_past );
  91. if ( $args->debug ) printf( "Looking for event instances between '%s' and '%s'\n", $earliest->UTC(), $expand_range_end->UTC() );
  92. $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';
  93. if ( $args->debug ) printf( "%s\n", $sql );
  94. $qry = new AwlQuery( $sql );
  95. if ( $qry->Exec() && $qry->rows() ) {
  96. while( $alarm = $qry->Fetch() ) {
  97. if ( $args->debug ) printf( "refresh: Processing alarm for '%s' based on '%s','%s', '%s'\n",
  98. $alarm->dav_name, $alarm->dtstart, $alarm->rrule, $alarm->trigger );
  99. $ic = new vComponent( $alarm->caldav_data );
  100. $expanded = expand_event_instances( $ic, $earliest, $expand_range_end );
  101. $expanded->MaskComponents( array( 'VEVENT'=>1, 'VTODO'=>1, 'VJOURNAL'=>1 ) );
  102. $instances = $expanded->GetComponents();
  103. $trigger = new vProperty( $alarm->trigger );
  104. $related = $trigger->GetParameterValue('RELATED');
  105. $first = new RepeatRuleDateTime($alarm->dtstart);
  106. $first->modify( $trigger->Value() );
  107. $next = null;
  108. $last = null;
  109. foreach( $instances AS $k => $component ) {
  110. $when = new RepeatRuleDateTime( $component->GetPValue('DTSTART') ); // a UTC value
  111. if ( $args->debug ) printf( "refresh: Looking at event instance on '%s'\n", $when->UTC() );
  112. if ( $related == 'END' ) {
  113. $when->modify( $component->GetPValue('DURATION') );
  114. }
  115. $when->modify( $trigger->Value() );
  116. if ( $when > $expand_range_start && $when < $expand_range_end && (!isset($next) || $when < $next) ) {
  117. $next = clone($when);
  118. }
  119. if ( $args->set_last && (!isset($last) || $when > $last) ) {
  120. $last = clone($when);
  121. }
  122. }
  123. $trigger_type = $trigger->GetParameterValue('VALUE');
  124. if ( $trigger_type == 'DATE' || $trigger_type == 'DATE-TIME' || preg_match('{^\d{8}T\d{6}Z?$}', $trigger->Value()) ) {
  125. $first = new RepeatRuleDateTime($trigger);
  126. if ( $first > $expand_range_start && (empty($next) || $first < $next ) )
  127. $next = $first;
  128. else if ( empty($next) ) {
  129. if ( $args->set_last && (empty($last) || $first > $last) )
  130. $last = $first;
  131. }
  132. }
  133. if ( $args->set_last && !isset($last) && (!isset($next) || $next < $expand_range_Start) ) {
  134. $vc = new vCalendar( $alarm->caldav_data );
  135. $range = getVCalendarRange($vc);
  136. if ( isset($range->until) && $range->until < $earliest ) $last = $range->until;
  137. }
  138. if ( isset($next) && $next < $expand_range_end ) {
  139. if ( $args->debug ) printf( "refresh: Found next alarm instance on '%s'\n", $next->UTC() );
  140. $sql = 'UPDATE calendar_alarm SET next_trigger = :next WHERE dav_id = :id AND component = :component';
  141. $update = new AwlQuery( $sql, array( ':next' => $next->UTC(), ':id' => $alarm->dav_id, ':component' => $alarm->component ) );
  142. $update->Exec('refresh-alarms', __LINE__, __FILE__ );
  143. }
  144. else if ( $args->set_last && isset($last) && $last < $earliest ) {
  145. if ( $args->debug ) printf( "refresh: Found past final alarm instance on '%s'\n", $last->UTC() );
  146. $sql = 'UPDATE calendar_alarm SET next_trigger = :last WHERE dav_id = :id AND component = :component';
  147. $update = new AwlQuery( $sql, array( ':last' => $last->UTC(), ':id' => $alarm->dav_id, ':component' => $alarm->component ) );
  148. $update->Exec('refresh-alarms', __LINE__, __FILE__ );
  149. }
  150. else if ( $args->debug && isset($next) && $next < $expand_range_end ) {
  151. printf( "refresh: Found next alarm instance on '%s' after '%s'\n", $next->UTC(), $expand_range_end->UTC() );
  152. }
  153. }
  154. }