1*a1a3b679SAndreas Boehler#!/usr/bin/env php 2*a1a3b679SAndreas Boehler<?php 3*a1a3b679SAndreas Boehler 4*a1a3b679SAndreas Boehlerecho "SabreDAV migrate script for version 1.7\n"; 5*a1a3b679SAndreas Boehler 6*a1a3b679SAndreas Boehlerif ($argc<2) { 7*a1a3b679SAndreas Boehler 8*a1a3b679SAndreas Boehler echo <<<HELLO 9*a1a3b679SAndreas Boehler 10*a1a3b679SAndreas BoehlerThis script help you migrate from a pre-1.7 database to 1.7 and later\n 11*a1a3b679SAndreas BoehlerBoth the 'calendarobjects' and 'calendars' tables will be upgraded. 12*a1a3b679SAndreas Boehler 13*a1a3b679SAndreas BoehlerIf you do not have this table, or don't use the default PDO CalDAV backend 14*a1a3b679SAndreas Boehlerit's pointless to run this script. 15*a1a3b679SAndreas Boehler 16*a1a3b679SAndreas BoehlerKeep in mind that some processing will be done on every single record of this 17*a1a3b679SAndreas Boehlertable and in addition, ALTER TABLE commands will be executed. 18*a1a3b679SAndreas BoehlerIf you have a large calendarobjects table, this may mean that this process 19*a1a3b679SAndreas Boehlertakes a while. 20*a1a3b679SAndreas Boehler 21*a1a3b679SAndreas BoehlerUsage: 22*a1a3b679SAndreas Boehler 23*a1a3b679SAndreas Boehlerphp {$argv[0]} [pdo-dsn] [username] [password] 24*a1a3b679SAndreas Boehler 25*a1a3b679SAndreas BoehlerFor example: 26*a1a3b679SAndreas Boehler 27*a1a3b679SAndreas Boehlerphp {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password 28*a1a3b679SAndreas Boehlerphp {$argv[0]} sqlite:data/sabredav.db 29*a1a3b679SAndreas Boehler 30*a1a3b679SAndreas BoehlerHELLO; 31*a1a3b679SAndreas Boehler 32*a1a3b679SAndreas Boehler exit(); 33*a1a3b679SAndreas Boehler 34*a1a3b679SAndreas Boehler} 35*a1a3b679SAndreas Boehler 36*a1a3b679SAndreas Boehler// There's a bunch of places where the autoloader could be, so we'll try all of 37*a1a3b679SAndreas Boehler// them. 38*a1a3b679SAndreas Boehler$paths = array( 39*a1a3b679SAndreas Boehler __DIR__ . '/../vendor/autoload.php', 40*a1a3b679SAndreas Boehler __DIR__ . '/../../../autoload.php', 41*a1a3b679SAndreas Boehler); 42*a1a3b679SAndreas Boehler 43*a1a3b679SAndreas Boehlerforeach($paths as $path) { 44*a1a3b679SAndreas Boehler if (file_exists($path)) { 45*a1a3b679SAndreas Boehler include $path; 46*a1a3b679SAndreas Boehler break; 47*a1a3b679SAndreas Boehler } 48*a1a3b679SAndreas Boehler} 49*a1a3b679SAndreas Boehler 50*a1a3b679SAndreas Boehler$dsn = $argv[1]; 51*a1a3b679SAndreas Boehler$user = isset($argv[2])?$argv[2]:null; 52*a1a3b679SAndreas Boehler$pass = isset($argv[3])?$argv[3]:null; 53*a1a3b679SAndreas Boehler 54*a1a3b679SAndreas Boehlerecho "Connecting to database: " . $dsn . "\n"; 55*a1a3b679SAndreas Boehler 56*a1a3b679SAndreas Boehler$pdo = new PDO($dsn, $user, $pass); 57*a1a3b679SAndreas Boehler$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 58*a1a3b679SAndreas Boehler$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 59*a1a3b679SAndreas Boehler 60*a1a3b679SAndreas Boehlerecho "Validating existing table layout\n"; 61*a1a3b679SAndreas Boehler 62*a1a3b679SAndreas Boehler// The only cross-db way to do this, is to just fetch a single record. 63*a1a3b679SAndreas Boehler$row = $pdo->query("SELECT * FROM calendarobjects LIMIT 1")->fetch(); 64*a1a3b679SAndreas Boehler 65*a1a3b679SAndreas Boehlerif (!$row) { 66*a1a3b679SAndreas Boehler echo "Error: This database did not have any records in the calendarobjects table, you should just recreate the table.\n"; 67*a1a3b679SAndreas Boehler exit(-1); 68*a1a3b679SAndreas Boehler} 69*a1a3b679SAndreas Boehler 70*a1a3b679SAndreas Boehler$requiredFields = array( 71*a1a3b679SAndreas Boehler 'id', 72*a1a3b679SAndreas Boehler 'calendardata', 73*a1a3b679SAndreas Boehler 'uri', 74*a1a3b679SAndreas Boehler 'calendarid', 75*a1a3b679SAndreas Boehler 'lastmodified', 76*a1a3b679SAndreas Boehler); 77*a1a3b679SAndreas Boehler 78*a1a3b679SAndreas Boehlerforeach($requiredFields as $requiredField) { 79*a1a3b679SAndreas Boehler if (!array_key_exists($requiredField,$row)) { 80*a1a3b679SAndreas Boehler echo "Error: The current 'calendarobjects' table was missing a field we expected to exist.\n"; 81*a1a3b679SAndreas Boehler echo "For safety reasons, this process is stopped.\n"; 82*a1a3b679SAndreas Boehler exit(-1); 83*a1a3b679SAndreas Boehler } 84*a1a3b679SAndreas Boehler} 85*a1a3b679SAndreas Boehler 86*a1a3b679SAndreas Boehler$fields17 = array( 87*a1a3b679SAndreas Boehler 'etag', 88*a1a3b679SAndreas Boehler 'size', 89*a1a3b679SAndreas Boehler 'componenttype', 90*a1a3b679SAndreas Boehler 'firstoccurence', 91*a1a3b679SAndreas Boehler 'lastoccurence', 92*a1a3b679SAndreas Boehler); 93*a1a3b679SAndreas Boehler 94*a1a3b679SAndreas Boehler$found = 0; 95*a1a3b679SAndreas Boehlerforeach($fields17 as $field) { 96*a1a3b679SAndreas Boehler if (array_key_exists($field, $row)) { 97*a1a3b679SAndreas Boehler $found++; 98*a1a3b679SAndreas Boehler } 99*a1a3b679SAndreas Boehler} 100*a1a3b679SAndreas Boehler 101*a1a3b679SAndreas Boehlerif ($found === 0) { 102*a1a3b679SAndreas Boehler echo "The database had the 1.6 schema. Table will now be altered.\n"; 103*a1a3b679SAndreas Boehler echo "This may take some time for large tables\n"; 104*a1a3b679SAndreas Boehler 105*a1a3b679SAndreas Boehler switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) { 106*a1a3b679SAndreas Boehler 107*a1a3b679SAndreas Boehler case 'mysql' : 108*a1a3b679SAndreas Boehler 109*a1a3b679SAndreas Boehler $pdo->exec(<<<SQL 110*a1a3b679SAndreas BoehlerALTER TABLE calendarobjects 111*a1a3b679SAndreas BoehlerADD etag VARCHAR(32), 112*a1a3b679SAndreas BoehlerADD size INT(11) UNSIGNED, 113*a1a3b679SAndreas BoehlerADD componenttype VARCHAR(8), 114*a1a3b679SAndreas BoehlerADD firstoccurence INT(11) UNSIGNED, 115*a1a3b679SAndreas BoehlerADD lastoccurence INT(11) UNSIGNED 116*a1a3b679SAndreas BoehlerSQL 117*a1a3b679SAndreas Boehler ); 118*a1a3b679SAndreas Boehler break; 119*a1a3b679SAndreas Boehler case 'sqlite' : 120*a1a3b679SAndreas Boehler $pdo->exec('ALTER TABLE calendarobjects ADD etag text'); 121*a1a3b679SAndreas Boehler $pdo->exec('ALTER TABLE calendarobjects ADD size integer'); 122*a1a3b679SAndreas Boehler $pdo->exec('ALTER TABLE calendarobjects ADD componenttype TEXT'); 123*a1a3b679SAndreas Boehler $pdo->exec('ALTER TABLE calendarobjects ADD firstoccurence integer'); 124*a1a3b679SAndreas Boehler $pdo->exec('ALTER TABLE calendarobjects ADD lastoccurence integer'); 125*a1a3b679SAndreas Boehler break; 126*a1a3b679SAndreas Boehler 127*a1a3b679SAndreas Boehler default : 128*a1a3b679SAndreas Boehler die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n"); 129*a1a3b679SAndreas Boehler 130*a1a3b679SAndreas Boehler } 131*a1a3b679SAndreas Boehler echo "Database schema upgraded.\n"; 132*a1a3b679SAndreas Boehler 133*a1a3b679SAndreas Boehler} elseif ($found === 5) { 134*a1a3b679SAndreas Boehler 135*a1a3b679SAndreas Boehler echo "Database already had the 1.7 schema\n"; 136*a1a3b679SAndreas Boehler 137*a1a3b679SAndreas Boehler} else { 138*a1a3b679SAndreas Boehler 139*a1a3b679SAndreas Boehler echo "The database had $found out of 5 from the changes for 1.7. This is scary and unusual, so we have to abort.\n"; 140*a1a3b679SAndreas Boehler echo "You can manually try to upgrade the schema, and then run this script again.\n"; 141*a1a3b679SAndreas Boehler exit(-1); 142*a1a3b679SAndreas Boehler 143*a1a3b679SAndreas Boehler} 144*a1a3b679SAndreas Boehler 145*a1a3b679SAndreas Boehlerecho "Now, we need to parse every record and pull out some information.\n"; 146*a1a3b679SAndreas Boehler 147*a1a3b679SAndreas Boehler$result = $pdo->query('SELECT id, calendardata FROM calendarobjects'); 148*a1a3b679SAndreas Boehler$stmt = $pdo->prepare('UPDATE calendarobjects SET etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE id = ?'); 149*a1a3b679SAndreas Boehler 150*a1a3b679SAndreas Boehlerecho "Total records found: " . $result->rowCount() . "\n"; 151*a1a3b679SAndreas Boehler$done = 0; 152*a1a3b679SAndreas Boehler$total = $result->rowCount(); 153*a1a3b679SAndreas Boehlerwhile($row = $result->fetch()) { 154*a1a3b679SAndreas Boehler 155*a1a3b679SAndreas Boehler try { 156*a1a3b679SAndreas Boehler $newData = getDenormalizedData($row['calendardata']); 157*a1a3b679SAndreas Boehler } catch (Exception $e) { 158*a1a3b679SAndreas Boehler echo "===\nException caught will trying to parser calendarobject.\n"; 159*a1a3b679SAndreas Boehler echo "Error message: " . $e->getMessage() . "\n"; 160*a1a3b679SAndreas Boehler echo "Record id: " . $row['id'] . "\n"; 161*a1a3b679SAndreas Boehler echo "This record is ignored, you should inspect it to see if there's anything wrong.\n===\n"; 162*a1a3b679SAndreas Boehler continue; 163*a1a3b679SAndreas Boehler } 164*a1a3b679SAndreas Boehler $stmt->execute(array( 165*a1a3b679SAndreas Boehler $newData['etag'], 166*a1a3b679SAndreas Boehler $newData['size'], 167*a1a3b679SAndreas Boehler $newData['componentType'], 168*a1a3b679SAndreas Boehler $newData['firstOccurence'], 169*a1a3b679SAndreas Boehler $newData['lastOccurence'], 170*a1a3b679SAndreas Boehler $row['id'], 171*a1a3b679SAndreas Boehler )); 172*a1a3b679SAndreas Boehler $done++; 173*a1a3b679SAndreas Boehler 174*a1a3b679SAndreas Boehler if ($done % 500 === 0) { 175*a1a3b679SAndreas Boehler echo "Completed: $done / $total\n"; 176*a1a3b679SAndreas Boehler } 177*a1a3b679SAndreas Boehler} 178*a1a3b679SAndreas Boehlerecho "Completed: $done / $total\n"; 179*a1a3b679SAndreas Boehler 180*a1a3b679SAndreas Boehlerecho "Checking the calendars table needs changes.\n"; 181*a1a3b679SAndreas Boehler$row = $pdo->query("SELECT * FROM calendars LIMIT 1")->fetch(); 182*a1a3b679SAndreas Boehler 183*a1a3b679SAndreas Boehlerif (array_key_exists('transparent', $row)) { 184*a1a3b679SAndreas Boehler 185*a1a3b679SAndreas Boehler echo "The calendars table is already up to date\n"; 186*a1a3b679SAndreas Boehler 187*a1a3b679SAndreas Boehler} else { 188*a1a3b679SAndreas Boehler 189*a1a3b679SAndreas Boehler echo "Adding the 'transparent' field to the calendars table\n"; 190*a1a3b679SAndreas Boehler 191*a1a3b679SAndreas Boehler switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) { 192*a1a3b679SAndreas Boehler 193*a1a3b679SAndreas Boehler case 'mysql' : 194*a1a3b679SAndreas Boehler $pdo->exec("ALTER TABLE calendars ADD transparent TINYINT(1) NOT NULL DEFAULT '0'"); 195*a1a3b679SAndreas Boehler break; 196*a1a3b679SAndreas Boehler case 'sqlite' : 197*a1a3b679SAndreas Boehler $pdo->exec("ALTER TABLE calendars ADD transparent bool"); 198*a1a3b679SAndreas Boehler break; 199*a1a3b679SAndreas Boehler 200*a1a3b679SAndreas Boehler default : 201*a1a3b679SAndreas Boehler die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n"); 202*a1a3b679SAndreas Boehler 203*a1a3b679SAndreas Boehler } 204*a1a3b679SAndreas Boehler 205*a1a3b679SAndreas Boehler} 206*a1a3b679SAndreas Boehler 207*a1a3b679SAndreas Boehlerecho "Process completed!\n"; 208*a1a3b679SAndreas Boehler 209*a1a3b679SAndreas Boehler/** 210*a1a3b679SAndreas Boehler * Parses some information from calendar objects, used for optimized 211*a1a3b679SAndreas Boehler * calendar-queries. 212*a1a3b679SAndreas Boehler * 213*a1a3b679SAndreas Boehler * Blantently copied from Sabre\CalDAV\Backend\PDO 214*a1a3b679SAndreas Boehler * 215*a1a3b679SAndreas Boehler * Returns an array with the following keys: 216*a1a3b679SAndreas Boehler * * etag 217*a1a3b679SAndreas Boehler * * size 218*a1a3b679SAndreas Boehler * * componentType 219*a1a3b679SAndreas Boehler * * firstOccurence 220*a1a3b679SAndreas Boehler * * lastOccurence 221*a1a3b679SAndreas Boehler * 222*a1a3b679SAndreas Boehler * @param string $calendarData 223*a1a3b679SAndreas Boehler * @return array 224*a1a3b679SAndreas Boehler */ 225*a1a3b679SAndreas Boehlerfunction getDenormalizedData($calendarData) { 226*a1a3b679SAndreas Boehler 227*a1a3b679SAndreas Boehler $vObject = \Sabre\VObject\Reader::read($calendarData); 228*a1a3b679SAndreas Boehler $componentType = null; 229*a1a3b679SAndreas Boehler $component = null; 230*a1a3b679SAndreas Boehler $firstOccurence = null; 231*a1a3b679SAndreas Boehler $lastOccurence = null; 232*a1a3b679SAndreas Boehler foreach($vObject->getComponents() as $component) { 233*a1a3b679SAndreas Boehler if ($component->name!=='VTIMEZONE') { 234*a1a3b679SAndreas Boehler $componentType = $component->name; 235*a1a3b679SAndreas Boehler break; 236*a1a3b679SAndreas Boehler } 237*a1a3b679SAndreas Boehler } 238*a1a3b679SAndreas Boehler if (!$componentType) { 239*a1a3b679SAndreas Boehler throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); 240*a1a3b679SAndreas Boehler } 241*a1a3b679SAndreas Boehler if ($componentType === 'VEVENT') { 242*a1a3b679SAndreas Boehler $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); 243*a1a3b679SAndreas Boehler // Finding the last occurence is a bit harder 244*a1a3b679SAndreas Boehler if (!isset($component->RRULE)) { 245*a1a3b679SAndreas Boehler if (isset($component->DTEND)) { 246*a1a3b679SAndreas Boehler $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); 247*a1a3b679SAndreas Boehler } elseif (isset($component->DURATION)) { 248*a1a3b679SAndreas Boehler $endDate = clone $component->DTSTART->getDateTime(); 249*a1a3b679SAndreas Boehler $endDate->add(\Sabre\VObject\DateTimeParser::parse($component->DURATION->value)); 250*a1a3b679SAndreas Boehler $lastOccurence = $endDate->getTimeStamp(); 251*a1a3b679SAndreas Boehler } elseif (!$component->DTSTART->hasTime()) { 252*a1a3b679SAndreas Boehler $endDate = clone $component->DTSTART->getDateTime(); 253*a1a3b679SAndreas Boehler $endDate->modify('+1 day'); 254*a1a3b679SAndreas Boehler $lastOccurence = $endDate->getTimeStamp(); 255*a1a3b679SAndreas Boehler } else { 256*a1a3b679SAndreas Boehler $lastOccurence = $firstOccurence; 257*a1a3b679SAndreas Boehler } 258*a1a3b679SAndreas Boehler } else { 259*a1a3b679SAndreas Boehler $it = new \Sabre\VObject\Recur\EventIterator($vObject, (string)$component->UID); 260*a1a3b679SAndreas Boehler $maxDate = new DateTime(\Sabre\CalDAV\Backend\PDO::MAX_DATE); 261*a1a3b679SAndreas Boehler if ($it->isInfinite()) { 262*a1a3b679SAndreas Boehler $lastOccurence = $maxDate->getTimeStamp(); 263*a1a3b679SAndreas Boehler } else { 264*a1a3b679SAndreas Boehler $end = $it->getDtEnd(); 265*a1a3b679SAndreas Boehler while($it->valid() && $end < $maxDate) { 266*a1a3b679SAndreas Boehler $end = $it->getDtEnd(); 267*a1a3b679SAndreas Boehler $it->next(); 268*a1a3b679SAndreas Boehler 269*a1a3b679SAndreas Boehler } 270*a1a3b679SAndreas Boehler $lastOccurence = $end->getTimeStamp(); 271*a1a3b679SAndreas Boehler } 272*a1a3b679SAndreas Boehler 273*a1a3b679SAndreas Boehler } 274*a1a3b679SAndreas Boehler } 275*a1a3b679SAndreas Boehler 276*a1a3b679SAndreas Boehler return array( 277*a1a3b679SAndreas Boehler 'etag' => md5($calendarData), 278*a1a3b679SAndreas Boehler 'size' => strlen($calendarData), 279*a1a3b679SAndreas Boehler 'componentType' => $componentType, 280*a1a3b679SAndreas Boehler 'firstOccurence' => $firstOccurence, 281*a1a3b679SAndreas Boehler 'lastOccurence' => $lastOccurence, 282*a1a3b679SAndreas Boehler ); 283*a1a3b679SAndreas Boehler 284*a1a3b679SAndreas Boehler} 285