Add the "CardDAV" word into DAViCal's description.
[davical.git] / scripts / tz-update.php
blob16b570fb730a739a08ec83caee5ab94aa9727ccb
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];
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 $new_zones = 0;
48 $modified_zones = 0;
49 $added_aliases = 0;
52 function fetch_remote_list($base_url ) {
53 global $request;
54 $result = array();
55 $list_url = $base_url . '?action=list';
56 printf( "Fetching timezone list\n", $list_url );
57 $raw_xml = file_get_contents($list_url);
58 $xml_parser = xml_parser_create_ns('UTF-8');
59 $xml_tags = array();
60 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
61 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
62 $rc = xml_parse_into_struct( $xml_parser, $raw_xml, $xml_tags );
63 if ( $rc == false ) {
64 dbg_error_log( 'ERROR', 'XML parsing error: %s at line %d, column %d', xml_error_string(xml_get_error_code($xml_parser)),
65 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
66 $request->PreconditionFailed(400, 'invalid-xml',
67 sprintf('XML parsing error: %s at line %d, column %d', xml_error_string(xml_get_error_code($xml_parser)),
68 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) ));
70 xml_parser_free($xml_parser);
71 $position = 0;
72 return BuildXMLTree( $xml_tags, $position);
75 function fetch_remote_zone( $base_url, $tzid ) {
76 $tz_url = $base_url . '?action=get&tzid=' .$tzid;
77 printf( "Fetching zone for %s from %s\n", $tzid, $tz_url );
78 dbg_error_log( 'tz/updatecheck', "Fetching zone for %s from %s\n", $tzid, $tz_url );
79 $vtimezone = file_get_contents( $tz_url );
80 return $vtimezone;
83 function fetch_db_zone( $tzid ) {
84 $tzrow = null;
85 $qry = new AwlQuery('SELECT * FROM timezones WHERE tzid=:tzid', array(':tzid' => $tzid));
86 if ( $qry->Exec('tz/update',__LINE__,__FILE__) && $qry->rows() > 0 ) {
87 $tzrow = $qry->Fetch();
89 return $tzrow;
92 function write_updated_zone( $vtimezone, $tzid ) {
93 global $new_zones, $modified_zones;
94 if ( empty($vtimezone) ) {
95 dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no data from server', $tzid );
96 return;
98 $tzrow = fetch_db_zone($tzid);
99 if ( isset($tzrow) && $vtimezone == $tzrow->vtimezone ) {
100 dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no change', $tzid );
101 return;
103 $vtz = new vCalendar($vtimezone);
104 $last_modified = $vtz->GetPValue('LAST-MODIFIED');
105 if ( empty($last_modified) ) {
106 $last_modified = gmdate('Ymd\THis\Z');
107 // Then it was probably that way when we last updated the data, too :-(
108 if ( !empty($tzrow) ) {
109 $old_vtz = new vCalendar($tzrow->vtimezone);
110 $old_vtz->ClearProperties('LAST-MODIFIED');
111 // We need to add & remove this property so the Render is equivalent.
112 $vtz->AddProperty('LAST-MODIFIED',$last_modified);
113 $vtz->ClearProperties('LAST-MODIFIED');
114 if ( $vtz->Render() == $old_vtz->Render() ) {
115 dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no change', $tzid );
116 return;
119 $vtz->AddProperty('LAST-MODIFIED',$last_modified);
121 dbg_error_log('tz/updatecheck', 'Writing %s zone for "%s"', (empty($tzrow)?"new":"updated"), $tzid );
122 printf("Writing %s zone for '%s'\n", (empty($tzrow)?"new":"updated"), $tzid );
123 $params = array(
124 ':tzid' => $tzid,
125 ':olson_name' => $tzid,
126 ':vtimezone' => $vtz->Render(),
127 ':last_modified' => $last_modified,
128 ':etag' => md5($vtz->Render())
130 if ( empty($tzrow) ) {
131 $new_zones++;
132 $sql = 'INSERT INTO timezones(tzid,active,olson_name,last_modified,etag,vtimezone) ';
133 $sql .= 'VALUES(:tzid,TRUE,:olson_name,:last_modified,:etag,:vtimezone)';
135 else {
136 $modified_zones++;
137 $sql = 'UPDATE timezones SET active=TRUE, olson_name=:olson_name, last_modified=:last_modified, ';
138 $sql .= 'etag=:etag, vtimezone=:vtimezone WHERE tzid=:tzid';
140 $qry = new AwlQuery($sql,$params);
141 $qry->Exec('tz/update',__LINE__,__FILE__);
144 function write_zone_aliases( $tzid, $aliases ) {
145 global $added_aliases;
146 foreach( $aliases AS $alias_node ) {
147 $alias = $alias_node->GetContent();
148 $params = array(':tzid' => $tzid, ':alias' => $alias );
149 $qry = new AwlQuery('SELECT * FROM tz_aliases JOIN timezones USING(our_tzno) WHERE tzid=:tzid AND tzalias=:alias', $params);
150 if ( $qry->Exec('tz/update', __LINE__, __FILE__) && $qry->rows() < 1 ) {
151 $qry->QDo('INSERT INTO tz_aliases(our_tzno,tzalias) SELECT our_tzno, :alias FROM timezones WHERE tzid = :tzid', $params);
152 $added_aliases++;
158 if ( empty($c->tzsource) ) $c->tzsource = '../zonedb/vtimezones';
159 if ( preg_match('{^http}', $c->tzsource ) ) {
161 $changesince = null;
162 $qry = new AwlQuery("SELECT tzid, to_char(last_modified,'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS last_modified FROM timezones");
163 $current_zones = array();
164 if ( $qry->Exec('tz/updatecheck',__LINE__,__FILE__) && $qry->rows() > 0 ) {
165 while( $row = $qry->Fetch() )
166 $current_zones[$row->tzid] = new RepeatRuleDateTime($row->last_modified);
169 $xmltree = fetch_remote_list($c->tzsource);
170 $zones = $xmltree->GetElements('urn:ietf:params:xml:ns:timezone-service:summary');
171 foreach( $zones AS $zone ) {
172 $elements = $zone->GetElements('urn:ietf:params:xml:ns:timezone-service:tzid');
173 $tzid = $elements[0]->GetContent();
174 $elements = $zone->GetElements('urn:ietf:params:xml:ns:timezone-service:last-modified');
175 $last_modified = new RepeatRuleDateTime($elements[0]->GetContent());
176 if ( !isset($current_zones[$tzid]) || $last_modified > $current_zones[$tzid] ) {
177 printf("Found timezone %s needs updating\n", $tzid );
178 $vtimezone = fetch_remote_zone($c->tzsource,$tzid);
179 write_updated_zone($vtimezone, $tzid);
181 $elements = $zone->GetElements('urn:ietf:params:xml:ns:timezone-service:alias');
182 write_zone_aliases($tzid, $elements);
186 else if ( file_exists($c->tzsource) ) {
188 * Find all files recursively within the diectory given.
189 * @param string $dirname The directory to find files in
190 * @return array of filenames with full path
192 function recursive_files( $dirname ) {
193 $d = opendir($dirname);
194 $result = array();
195 while( $fn = readdir($d) ) {
196 if ( substr($fn,0,1) == '.' ) continue;
197 if ( substr($fn,0,14) == 'primary-source' ) continue;
198 $fn = $dirname.'/'.$fn;
199 if ( is_dir($fn) ) {
200 $result = array_merge($result,recursive_files($fn));
202 else {
203 $result[] = $fn;
206 return $result;
209 $qry = new AwlQuery();
210 foreach( recursive_files($c->tzsource) AS $filename ) {
211 $tzid = str_replace('.ics', '', str_replace($c->tzsource.'/', '', $filename));
212 $vtimezone = file_get_contents( $filename, false );
213 write_updated_zone($vtimezone, $tzid);
216 else {
217 dbg_error_log('ERROR', '$c->tzsource is not configured to a good source of timezone data');
220 printf("Added %d new zones, updated data for %d zones and added %d new aliases\n",
221 $new_zones, $modified_zones, $added_aliases);
223 exit(0);