xref: /plugin/davcal/vendor/sabre/dav/bin/migrateto17.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
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