Extend $c->default_collections - adding 'calendar_components' and 'default_properties'
[davical.git] / scripts / tz-update.php
blobba665da086259c3d722c3d677187c574c3e2fcca
1 #!/usr/bin/env php
2 <?php
3 /**
4 * DAViCal Timezone Service handler - update timezones
6 * @package davical
7 * @subpackage tzservice
8 * @author Andrew McMillan <andrew@morphoss.com>
9 * @copyright Morphoss Ltd
10 * @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
12 $script_file = __FILE__;
13 if ( $argc < 2 ) {
15 echo <<<USAGE
16 Usage:
18 $script_file davical.example.com [timezone_source]
20 Where 'davical.example.com' is the hostname of your DAViCal server and the
21 optional 'timezone_source' is the source of the timezone data. If not specified
22 this will default to the value of the $$c->tzsource configuration value, with
23 a further default to the zonedb/vtimezone directory relative to the root of the
24 DAViCal installation.
26 This script can be used to initialise or update the timezone information in
27 DAViCal used for the in-built timezone service.
29 USAGE;
30 exit(1);
33 $_SERVER['SERVER_NAME'] = $argv[1];
34 $original_dir = getcwd();
35 chdir(str_replace('/scripts/tz-update.php','/htdocs',$script_file));
37 require_once("./always.php");
39 if ( isset($argv[2]) ) {
40 $c->tzsource = $argv[2];
43 require_once('vCalendar.php');
44 require_once('XMLDocument.php');
45 require_once('RRule-v2.php');
47 chdir($original_dir);
49 $new_zones = 0;
50 $modified_zones = 0;
51 $added_aliases = 0;
54 function fetch_remote_list($base_url ) {
55 global $request;
56 $result = array();
57 $list_url = $base_url . '?action=list';
58 printf( "Fetching timezone list\n", $list_url );
59 $raw_xml = file_get_contents($list_url);
60 $xml_parser = xml_parser_create_ns('UTF-8');
61 $xml_tags = array();
62 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
63 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
64 $rc = xml_parse_into_struct( $xml_parser, $raw_xml, $xml_tags );
65 if ( $rc == false ) {
66 dbg_error_log( 'ERROR', 'XML parsing error: %s at line %d, column %d', xml_error_string(xml_get_error_code($xml_parser)),
67 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
68 $request->PreconditionFailed(400, 'invalid-xml',
69 sprintf('XML parsing error: %s at line %d, column %d', xml_error_string(xml_get_error_code($xml_parser)),
70 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) ));
72 xml_parser_free($xml_parser);
73 $position = 0;
74 return BuildXMLTree( $xml_tags, $position);
77 function fetch_remote_zone( $base_url, $tzid ) {
78 $tz_url = $base_url . '?action=get&tzid=' .$tzid;
79 printf( "Fetching zone for %s from %s\n", $tzid, $tz_url );
80 dbg_error_log( 'tz/updatecheck', "Fetching zone for %s from %s\n", $tzid, $tz_url );
81 $vtimezone = file_get_contents( $tz_url );
82 return $vtimezone;
85 function fetch_db_zone( $tzid ) {
86 $tzrow = null;
87 $qry = new AwlQuery('SELECT * FROM timezones WHERE tzid=:tzid', array(':tzid' => $tzid));
88 if ( $qry->Exec('tz/update',__LINE__,__FILE__) && $qry->rows() > 0 ) {
89 $tzrow = $qry->Fetch();
91 return $tzrow;
94 function write_updated_zone( $vtimezone, $tzid ) {
95 global $new_zones, $modified_zones;
96 if ( empty($vtimezone) ) {
97 dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no data from server', $tzid );
98 return;
100 $tzrow = fetch_db_zone($tzid);
101 if ( isset($tzrow) && $vtimezone == $tzrow->vtimezone ) {
102 dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no change', $tzid );
103 return;
105 $vtz = new vCalendar($vtimezone);
106 $last_modified = $vtz->GetPValue('LAST-MODIFIED');
107 if ( empty($last_modified) ) {
108 $last_modified = gmdate('Ymd\THis\Z');
109 // Then it was probably that way when we last updated the data, too :-(
110 if ( !empty($tzrow) ) {
111 $old_vtz = new vCalendar($tzrow->vtimezone);
112 $old_vtz->ClearProperties('LAST-MODIFIED');
113 // We need to add & remove this property so the Render is equivalent.
114 $vtz->AddProperty('LAST-MODIFIED',$last_modified);
115 $vtz->ClearProperties('LAST-MODIFIED');
116 if ( $vtz->Render() == $old_vtz->Render() ) {
117 dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no change', $tzid );
118 return;
121 $vtz->AddProperty('LAST-MODIFIED',$last_modified);
123 dbg_error_log('tz/updatecheck', 'Writing %s zone for "%s"', (empty($tzrow)?"new":"updated"), $tzid );
124 printf("Writing %s zone for '%s'\n", (empty($tzrow)?"new":"updated"), $tzid );
125 $params = array(
126 ':tzid' => $tzid,
127 ':olson_name' => $tzid,
128 ':vtimezone' => $vtz->Render(),
129 ':last_modified' => $last_modified,
130 ':etag' => md5($vtz->Render())
132 if ( empty($tzrow) ) {
133 $new_zones++;
134 $sql = 'INSERT INTO timezones(tzid,active,olson_name,last_modified,etag,vtimezone) ';
135 $sql .= 'VALUES(:tzid,TRUE,:olson_name,:last_modified,:etag,:vtimezone)';
137 else {
138 $modified_zones++;
139 $sql = 'UPDATE timezones SET active=TRUE, olson_name=:olson_name, last_modified=:last_modified, ';
140 $sql .= 'etag=:etag, vtimezone=:vtimezone WHERE tzid=:tzid';
142 $qry = new AwlQuery($sql,$params);
143 $qry->Exec('tz/update',__LINE__,__FILE__);
146 function write_zone_aliases( $tzid, $aliases ) {
147 global $added_aliases;
148 foreach( $aliases AS $alias_node ) {
149 $alias = $alias_node->GetContent();
150 $params = array(':tzid' => $tzid, ':alias' => $alias );
151 $qry = new AwlQuery('SELECT * FROM tz_aliases JOIN timezones USING(our_tzno) WHERE tzid=:tzid AND tzalias=:alias', $params);
152 if ( $qry->Exec('tz/update', __LINE__, __FILE__) && $qry->rows() < 1 ) {
153 $qry->QDo('INSERT INTO tz_aliases(our_tzno,tzalias) SELECT our_tzno, :alias FROM timezones WHERE tzid = :tzid', $params);
154 $added_aliases++;
160 if ( empty($c->tzsource) ) $c->tzsource = '../zonedb/vtimezones';
161 if ( preg_match('{^http}', $c->tzsource ) ) {
163 $changesince = null;
164 $qry = new AwlQuery("SELECT tzid, to_char(last_modified,'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS last_modified FROM timezones");
165 $current_zones = array();
166 if ( $qry->Exec('tz/updatecheck',__LINE__,__FILE__) && $qry->rows() > 0 ) {
167 while( $row = $qry->Fetch() )
168 $current_zones[$row->tzid] = new RepeatRuleDateTime($row->last_modified);
171 $xmltree = fetch_remote_list($c->tzsource);
172 $zones = $xmltree->GetElements('urn:ietf:params:xml:ns:timezone-service:summary');
173 foreach( $zones AS $zone ) {
174 $elements = $zone->GetElements('urn:ietf:params:xml:ns:timezone-service:tzid');
175 $tzid = $elements[0]->GetContent();
176 $elements = $zone->GetElements('urn:ietf:params:xml:ns:timezone-service:last-modified');
177 $last_modified = new RepeatRuleDateTime($elements[0]->GetContent());
178 if ( !isset($current_zones[$tzid]) || $last_modified > $current_zones[$tzid] ) {
179 printf("Found timezone %s needs updating\n", $tzid );
180 $vtimezone = fetch_remote_zone($c->tzsource,$tzid);
181 write_updated_zone($vtimezone, $tzid);
183 $elements = $zone->GetElements('urn:ietf:params:xml:ns:timezone-service:alias');
184 write_zone_aliases($tzid, $elements);
188 else if ( file_exists($c->tzsource) ) {
190 * Find all files recursively within the diectory given.
191 * @param string $dirname The directory to find files in
192 * @return array of filenames with full path
194 function recursive_files( $dirname ) {
195 $d = opendir($dirname);
196 $result = array();
197 while( $fn = readdir($d) ) {
198 if ( substr($fn,0,1) == '.' ) continue;
199 if ( substr($fn,0,14) == 'primary-source' ) continue;
200 $fn = $dirname.'/'.$fn;
201 if ( is_dir($fn) ) {
202 $result = array_merge($result,recursive_files($fn));
204 else {
205 $result[] = $fn;
208 return $result;
211 $qry = new AwlQuery();
212 foreach( recursive_files($c->tzsource) AS $filename ) {
213 $tzid = str_replace('.ics', '', str_replace($c->tzsource.'/', '', $filename));
214 $vtimezone = file_get_contents( $filename, false );
215 write_updated_zone($vtimezone, $tzid);
218 else {
219 dbg_error_log('ERROR', '$c->tzsource is not configured to a good source of timezone data');
222 printf("Added %d new zones, updated data for %d zones and added %d new aliases\n",
223 $new_zones, $modified_zones, $added_aliases);
225 exit(0);