davical/scripts/archive-old-events.php
2016-12-30 08:54:14 +01:00

212 lines
7.2 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
/**
* Script to move old events into an archive collection
*
* @package davical
* @subpackage alarms
* @author Andrew McMillan <andrew@morphoss.com>
* @copyright Morphoss Ltd
* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$script_file = __FILE__;
chdir(str_replace('/scripts/archive-old-events.php','/htdocs',$script_file));
$_SERVER['SERVER_NAME'] = 'localhost';
/**
* Call with something like e.g.:
*
* scripts/archive-old-events.php -a archive -p karora -c calendar -o P-93D
*
*/
$args = (object) array();
$args->debug = false;
$args->principal = false;
$args->collection = false;
$args->older = 'P-190D';
$args->delete = false;
$args->archive_suffix = 'archive';
$debugging = null;
function parse_arguments() {
global $args;
$opts = getopt( 'a:c:o:p:s:dh' );
foreach( $opts AS $k => $v ) {
switch( $k ) {
case 'a': $args->archive_suffix = $v; break;
case 'c': $args->collection = $v; break;
case 'o': $args->older = $v; break;
case 'p': $args->principal = $v; break;
case 's': $_SERVER['SERVER_NAME'] = $v; break;
case 'd': $args->debug = true; $debugging = explode(',',$v); break;
case 'h': usage(); break;
default: $args->{$k} = $v;
}
}
$bad = false;
if ( $args->principal === false ) {
echo "You must supply a principal.\n";
$bad = true;
}
if ( $args->collection === false ) {
echo "You must supply a collection.\n";
$bad = true;
}
if ( $bad ) {
usage();
}
}
function usage() {
echo <<<USAGE
Usage:
archive-old-events.php [-s server.domain.tld] -p principal [other options]
-a <archive_suffix> Appendeded (after a '-') to the name of the original calendar to give
the archive calendar name. Default 'archive'.
-o <duration> Archive events completed this much prior to the current
date. Default 'P-190D'
-p <principal> The name of the principal to do the archiving for (required).
-c <collection> The name of the collection to do the archiving for (required).
-s <server> The servername to be used to identify the DAViCal configuration file.
-d xxx Enable debugging where 'xxx' is a comma-separated list of debug subsystems
USAGE;
exit(0);
}
parse_arguments();
if ( $args->debug && is_array($debugging )) {
foreach( $debugging AS $v ) {
$c->dbg[$v] = 1;
}
}
require_once("./always.php");
require_once('AwlQuery.php');
require_once('AwlCache.php');
require_once('RRule.php');
require_once('vCalendar.php');
/**
* Essentially what we are doing is:
*
DELETE FROM caldav_data JOIN calendar_item USING(dav_id)
WHERE dav_name LIKE '/someprincipal/%'
AND (
(rrule IS NULL AND dtend < archive_before_date)
OR (last_instance_end < archive_before_date)
)
*/
$recent = new RepeatRuleDateTime(gmdate('Ymd\THis\Z'));
$recent->modify('P-6D');
$archive_before_date = new RepeatRuleDateTime(gmdate('Ymd\THis\Z'));
$archive_before_date->modify( $args->older );
if ( $archive_before_date > $recent ) {
echo "Cowardly refusing to archive events before " . $archive_before_date->format('Y-m-d H:i:s') . "\n";
exit(1);
}
if ( $args->debug ) printf( "Archiving event instances finished before '%s'\n", $archive_before_date->UTC() );
// SQL to create an archive collection but only if it doesn't exist already.
$archive_collection_sql = <<<EOSQL
INSERT INTO collection (user_no, parent_container, dav_name, dav_etag, dav_displayname, is_calendar,
created, modified, public_events_only, publicly_readable, is_addressbook,
resourcetypes, schedule_transp, timezone, description)
SELECT user_no, parent_container, :archive_dav_name, random(),
'Archive of ' || dav_displayname, true, current_timestamp, current_timestamp, false, false, false,
resourcetypes, schedule_transp, timezone,
'Archive of ' || CASE WHEN description IS NULL OR description = '' THEN dav_name ELSE description END
FROM collection
WHERE dav_name = :collection_dav_name
AND NOT EXISTS(SELECT 1 FROM collection c2 WHERE c2.dav_name = :archive_dav_name)
EOSQL;
$collection_dav_name = sprintf( '/%s/%s/', $args->principal, $args->collection );
$collection_archive = sprintf( '/%s/%s-%s/', $args->principal, $args->collection, $args->archive_suffix );
$sqlargs = array(
':collection_dav_name' => $collection_dav_name,
':archive_dav_name' => $collection_archive
);
$qry = new AwlQuery($archive_collection_sql, $sqlargs);
$qry->query_time_warning = 5; // Don't warn on queries unless they take more than 5 seconds.
if ( $qry->Exec(__CLASS__, __LINE__, __FILE__) ) {
$qry->QDo('SELECT collection_id FROM collection WHERE dav_name = ?', $collection_dav_name );
if ( $qry->rows() != 1 ) {
printf( "Could not find source collection '%s'\n", $collection_dav_name);
exit(1);
}
$row = $qry->Fetch();
$source_collection_id = $row->collection_id;
$qry->QDo('SELECT collection_id FROM collection WHERE dav_name = ?', $collection_archive );
if ( $qry->rows() != 1 ) {
printf( "Could not create archive collection '%s'!\n", $collection_archive);
exit(2);
}
$row = $qry->Fetch();
$archive_collection_id = $row->collection_id;
$archive_sql = <<<EOSQL
UPDATE caldav_data
SET dav_name = replace( caldav_data.dav_name, :collection_dav_name, :archive_dav_name),
collection_id = :archive_collection_id
FROM calendar_item
WHERE caldav_data.collection_id = :source_collection_id
AND caldav_data.caldav_type = 'VEVENT'
AND caldav_data.dav_id = calendar_item.dav_id
AND (
(rrule IS NULL AND dtend < :archive_before_date)
OR (last_instance_end is not null AND last_instance_end < :archive_before_date)
)
EOSQL;
if ( $args->debug ) printf( "%s\n", $archive_sql );
$sqlargs[':archive_before_date'] = $archive_before_date->FloatOrUTC();
$sqlargs[':source_collection_id'] = $source_collection_id;
$sqlargs[':archive_collection_id'] = $archive_collection_id;
$qry->QDo($archive_sql, $sqlargs);
/**
* At this point we've done all the work, we just have to inform the rest of the world that
* everything has changed underneath it.
*/
// Now ensure the collection tag changes...
$sql = 'UPDATE collection SET dav_etag = random(), modified = current_timestamp WHERE collection_id IN (:source_id, :archive_id)';
$sqlargs = array(
':source_id' => $source_collection_id,
':archive_id' => $archive_collection_id
);
$qry->QDo($sql, $sqlargs);
// Delete the sync tokens...
$sql = 'DELETE FROM sync_tokens WHERE collection_id IN (:source_id, :archive_id)';
$qry->QDo($sql, $sqlargs);
// Delete the sync_changes...
$sql = 'DELETE FROM sync_changes WHERE collection_id IN (:source_id, :archive_id)';
$qry->QDo($sql, $sqlargs);
// Uncache anything to do with the collection, or the archive
$cache = getCacheInstance();
$cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $collection_dav_name);
$cache->delete( $cache_ns, null );
$cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $collection_archive);
$cache->delete( $cache_ns, null );
}