]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - scripts/upgrade.php
Type-hint is array here.
[quix0rs-gnu-social.git] / scripts / upgrade.php
1 #!/usr/bin/env php
2 <?php
3 /*
4  * StatusNet - a distributed open-source microblogging tool
5  * Copyright (C) 2008-2011 StatusNet, Inc.
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
22
23 $shortoptions = 'x::';
24 $longoptions = array('extensions=');
25
26 $helptext = <<<END_OF_UPGRADE_HELP
27 php upgrade.php [options]
28 Upgrade database schema and data to latest software
29
30 END_OF_UPGRADE_HELP;
31
32 require_once INSTALLDIR.'/scripts/commandline.inc.php';
33
34 function main()
35 {
36     if (Event::handle('StartUpgrade')) {
37         fixupConversationURIs();
38
39         updateSchemaCore();
40         updateSchemaPlugins();
41
42         // These replace old "fixup_*" scripts
43
44         fixupNoticeConversation();
45         initConversation();
46         fixupGroupURI();
47         fixupFileGeometry();
48         deleteLocalFileThumbnailsWithoutFilename();
49         deleteMissingLocalFileThumbnails();
50         setFilehashOnLocalFiles();
51
52         initGroupProfileId();
53         initLocalGroup();
54         initNoticeReshare();
55     
56         initSubscriptionURI();
57         initGroupMemberURI();
58
59         initProfileLists();
60
61         Event::handle('EndUpgrade');
62     }
63 }
64
65 function tableDefs()
66 {
67         $schema = array();
68         require INSTALLDIR.'/db/core.php';
69         return $schema;
70 }
71
72 function updateSchemaCore()
73 {
74     printfnq("Upgrading core schema...");
75
76     $schema = Schema::get();
77     $schemaUpdater = new SchemaUpdater($schema);
78     foreach (tableDefs() as $table => $def) {
79         $schemaUpdater->register($table, $def);
80     }
81     $schemaUpdater->checkSchema();
82
83     printfnq("DONE.\n");
84 }
85
86 function updateSchemaPlugins()
87 {
88     printfnq("Upgrading plugin schema...");
89
90     Event::handle('BeforePluginCheckSchema');
91     Event::handle('CheckSchema');
92
93     printfnq("DONE.\n");
94 }
95
96 function fixupNoticeConversation()
97 {
98     printfnq("Ensuring all notices have a conversation ID...");
99
100     $notice = new Notice();
101     $notice->whereAdd('conversation is null');
102     $notice->whereAdd('conversation = 0', 'OR');
103     $notice->orderBy('id'); // try to get originals before replies
104     $notice->find();
105
106     while ($notice->fetch()) {
107         try {
108             $cid = null;
109     
110             $orig = clone($notice);
111     
112             if (!empty($notice->reply_to)) {
113                 $reply = Notice::getKV('id', $notice->reply_to);
114
115                 if ($reply instanceof Notice && !empty($reply->conversation)) {
116                     $notice->conversation = $reply->conversation;
117                 }
118                 unset($reply);
119             }
120
121             // if still empty
122             if (empty($notice->conversation)) {
123                 $child = new Notice();
124                 $child->reply_to = $notice->getID();
125                 $child->limit(1);
126                 if ($child->find(true) && !empty($child->conversation)) {
127                     $notice->conversation = $child->conversation;
128                 }
129                 unset($child);
130             }
131
132             // if _still_ empty we just create our own conversation
133             if (empty($notice->conversation)) {
134                 $notice->conversation = $notice->getID();
135             }
136
137             $result = $notice->update($orig);
138
139             unset($orig);
140         } catch (Exception $e) {
141             print("Error setting conversation: " . $e->getMessage());
142         }
143     }
144
145     printfnq("DONE.\n");
146 }
147
148 function fixupGroupURI()
149 {
150     printfnq("Ensuring all groups have an URI...");
151
152     $group = new User_group();
153     $group->whereAdd('uri IS NULL');
154
155     if ($group->find()) {
156         while ($group->fetch()) {
157             $orig = User_group::getKV('id', $group->id);
158             $group->uri = $group->getUri();
159             $group->update($orig);
160         }
161     }
162
163     printfnq("DONE.\n");
164 }
165
166 function initConversation()
167 {
168     printfnq("Ensuring all conversations have a row in conversation table...");
169
170     $notice = new Notice();
171     $notice->query('select distinct notice.conversation from notice '.
172                    'where notice.conversation is not null '.
173                    'and not exists (select conversation.id from conversation where id = notice.conversation)');
174
175     while ($notice->fetch()) {
176
177         $id = $notice->conversation;
178
179         $uri = common_local_url('conversation', array('id' => $id));
180
181         // @fixme db_dataobject won't save our value for an autoincrement
182         // so we're bypassing the insert wrappers
183         $conv = new Conversation();
184         $sql = "insert into conversation (id,uri,created) values(%d,'%s','%s')";
185         $sql = sprintf($sql,
186                        $id,
187                        $conv->escape($uri),
188                        $conv->escape(common_sql_now()));
189         $conv->query($sql);
190     }
191
192     printfnq("DONE.\n");
193 }
194
195 function fixupConversationURIs()
196 {
197     printfnq("Ensuring all conversations have a URI...");
198
199     $conv = new Conversation();
200     $conv->whereAdd('uri IS NULL');
201
202     if ($conv->find()) {
203         $rounds = 0;
204         while ($conv->fetch()) {
205             $uri = common_local_url('conversation', array('id' => $conv->id));
206             $sql = sprintf('UPDATE conversation SET uri="%1$s" WHERE id="%2$d";',
207                             $conv->escape($uri), $conv->id);
208             $conv->query($sql);
209             if (($conv->N-++$rounds) % 500 == 0) {
210                 printfnq(sprintf(' %d items left...', $conv->N-$rounds));
211             }
212         }
213     }
214
215     printfnq("DONE.\n");
216 }
217
218 function initGroupProfileId()
219 {
220     printfnq("Ensuring all User_group entries have a Profile and profile_id...");
221
222     $group = new User_group();
223     $group->whereAdd('NOT EXISTS (SELECT id FROM profile WHERE id = user_group.profile_id)');
224     $group->find();
225
226     while ($group->fetch()) {
227         try {
228             // We must create a new, incrementally assigned profile_id
229             $profile = new Profile();
230             $profile->nickname   = $group->nickname;
231             $profile->fullname   = $group->fullname;
232             $profile->profileurl = $group->mainpage;
233             $profile->homepage   = $group->homepage;
234             $profile->bio        = $group->description;
235             $profile->location   = $group->location;
236             $profile->created    = $group->created;
237             $profile->modified   = $group->modified;
238
239             $profile->query('BEGIN');
240             $id = $profile->insert();
241             if (empty($id)) {
242                 $profile->query('ROLLBACK');
243                 throw new Exception('Profile insertion failed, profileurl: '.$profile->profileurl);
244             }
245             $group->query("UPDATE user_group SET profile_id={$id} WHERE id={$group->id}");
246             $profile->query('COMMIT');
247
248             $profile->free();
249         } catch (Exception $e) {
250             printfv("Error initializing Profile for group {$group->nickname}:" . $e->getMessage());
251         }
252     }
253
254     printfnq("DONE.\n");
255 }
256
257 function initLocalGroup()
258 {
259     printfnq("Ensuring all local user groups have a local_group...");
260
261     $group = new User_group();
262     $group->whereAdd('NOT EXISTS (select group_id from local_group where group_id = user_group.id)');
263     $group->find();
264
265     while ($group->fetch()) {
266         try {
267             // Hack to check for local groups
268             if ($group->getUri() == common_local_url('groupbyid', array('id' => $group->id))) {
269                 $lg = new Local_group();
270
271                 $lg->group_id = $group->id;
272                 $lg->nickname = $group->nickname;
273                 $lg->created  = $group->created; // XXX: common_sql_now() ?
274                 $lg->modified = $group->modified;
275
276                 $lg->insert();
277             }
278         } catch (Exception $e) {
279             printfv("Error initializing local group for {$group->nickname}:" . $e->getMessage());
280         }
281     }
282
283     printfnq("DONE.\n");
284 }
285
286 function initNoticeReshare()
287 {
288     printfnq("Ensuring all reshares have the correct verb and object-type...");
289     
290     $notice = new Notice();
291     $notice->whereAdd('repeat_of is not null');
292     $notice->whereAdd('(verb != "'.ActivityVerb::SHARE.'" OR object_type != "'.ActivityObject::ACTIVITY.'")');
293
294     if ($notice->find()) {
295         while ($notice->fetch()) {
296             try {
297                 $orig = Notice::getKV('id', $notice->id);
298                 $notice->verb = ActivityVerb::SHARE;
299                 $notice->object_type = ActivityObject::ACTIVITY;
300                 $notice->update($orig);
301             } catch (Exception $e) {
302                 printfv("Error updating verb and object_type for {$notice->id}:" . $e->getMessage());
303             }
304         }
305     }
306
307     printfnq("DONE.\n");
308 }
309
310 function initSubscriptionURI()
311 {
312     printfnq("Ensuring all subscriptions have a URI...");
313
314     $sub = new Subscription();
315     $sub->whereAdd('uri IS NULL');
316
317     if ($sub->find()) {
318         while ($sub->fetch()) {
319             try {
320                 $sub->decache();
321                 $sub->query(sprintf('update subscription '.
322                                     'set uri = "%s" '.
323                                     'where subscriber = %d '.
324                                     'and subscribed = %d',
325                                     $sub->escape(Subscription::newUri($sub->getSubscriber(), $sub->getSubscribed(), $sub->created)),
326                                     $sub->subscriber,
327                                     $sub->subscribed));
328             } catch (Exception $e) {
329                 common_log(LOG_ERR, "Error updated subscription URI: " . $e->getMessage());
330             }
331         }
332     }
333
334     printfnq("DONE.\n");
335 }
336
337 function initGroupMemberURI()
338 {
339     printfnq("Ensuring all group memberships have a URI...");
340
341     $mem = new Group_member();
342     $mem->whereAdd('uri IS NULL');
343
344     if ($mem->find()) {
345         while ($mem->fetch()) {
346             try {
347                 $mem->decache();
348                 $mem->query(sprintf('update group_member set uri = "%s" '.
349                                     'where profile_id = %d ' . 
350                                     'and group_id = %d ',
351                                     Group_member::newUri(Profile::getByID($mem->profile_id), User_group::getByID($mem->group_id), $mem->created),
352                                     $mem->profile_id,
353                                     $mem->group_id));
354             } catch (Exception $e) {
355                 common_log(LOG_ERR, "Error updated membership URI: " . $e->getMessage());  
356           }
357         }
358     }
359
360     printfnq("DONE.\n");
361 }
362
363 function initProfileLists()
364 {
365     printfnq("Ensuring all profile tags have a corresponding list...");
366
367     $ptag = new Profile_tag();
368     $ptag->selectAdd();
369     $ptag->selectAdd('tagger, tag, count(*) as tagged_count');
370     $ptag->whereAdd('NOT EXISTS (SELECT tagger, tagged from profile_list '.
371                     'where profile_tag.tagger = profile_list.tagger '.
372                     'and profile_tag.tag = profile_list.tag)');
373     $ptag->groupBy('tagger, tag');
374     $ptag->orderBy('tagger, tag');
375
376     if ($ptag->find()) {
377         while ($ptag->fetch()) {
378             $plist = new Profile_list();
379
380             $plist->tagger   = $ptag->tagger;
381             $plist->tag      = $ptag->tag;
382             $plist->private  = 0;
383             $plist->created  = common_sql_now();
384             $plist->modified = $plist->created;
385             $plist->mainpage = common_local_url('showprofiletag',
386                                                 array('tagger' => $plist->getTagger()->nickname,
387                                                       'tag'    => $plist->tag));;
388
389             $plist->tagged_count     = $ptag->tagged_count;
390             $plist->subscriber_count = 0;
391
392             $plist->insert();
393
394             $orig = clone($plist);
395             // After insert since it uses auto-generated ID
396             $plist->uri      = common_local_url('profiletagbyid',
397                                         array('id' => $plist->id, 'tagger_id' => $plist->tagger));
398
399             $plist->update($orig);
400         }
401     }
402
403     printfnq("DONE.\n");
404 }
405
406 /*
407  * Added as we now store interpretd width and height in File table.
408  */
409 function fixupFileGeometry()
410 {
411     printfnq("Ensuring width and height is set for supported local File objects...");
412
413     $file = new File();
414     $file->whereAdd('filename IS NOT NULL');    // local files
415     $file->whereAdd('width IS NULL OR width = 0');
416
417     if ($file->find()) {
418         while ($file->fetch()) {
419             // Set file geometrical properties if available
420             try {
421                 $image = ImageFile::fromFileObject($file);
422             } catch (ServerException $e) {
423                 // We couldn't make out an image from the file.
424                 continue;
425             }
426             $orig = clone($file);
427             $file->width = $image->width;
428             $file->height = $image->height;
429             $file->update($orig);
430
431             // FIXME: Do this more automagically inside ImageFile or so.
432             if ($image->getPath() != $file->getPath()) {
433                 $image->unlink();
434             }
435             unset($image);
436         }
437     }
438
439     printfnq("DONE.\n");
440 }
441
442 /*
443  * File_thumbnail objects for local Files store their own filenames in the database.
444  */
445 function deleteLocalFileThumbnailsWithoutFilename()
446 {
447     printfnq("Removing all local File_thumbnail entries without filename property...");
448
449     $file = new File();
450     $file->whereAdd('filename IS NOT NULL');    // local files
451
452     if ($file->find()) {
453         // Looping through local File entries
454         while ($file->fetch()) {
455             $thumbs = new File_thumbnail();
456             $thumbs->file_id = $file->id;
457             $thumbs->whereAdd('filename IS NULL');
458             // Checking if there were any File_thumbnail entries without filename
459             if (!$thumbs->find()) {
460                 continue;
461             }
462             // deleting incomplete entry to allow regeneration
463             while ($thumbs->fetch()) {
464                 $thumbs->delete();
465             }
466         }
467     }
468
469     printfnq("DONE.\n");
470 }
471
472 /*
473  * Delete File_thumbnail entries where the referenced file does not exist.
474  */
475 function deleteMissingLocalFileThumbnails()
476 {
477     printfnq("Removing all local File_thumbnail entries without existing files...");
478
479     $thumbs = new File_thumbnail();
480     $thumbs->whereAdd('filename IS NOT NULL');  // only fill in names where they're missing
481     // Checking if there were any File_thumbnail entries without filename
482     if ($thumbs->find()) {
483         while ($thumbs->fetch()) {
484             try {
485                 $thumbs->getPath();
486             } catch (FileNotFoundException $e) {
487                 $thumbs->delete();
488             }
489         }
490     }
491
492     printfnq("DONE.\n");
493 }
494
495 /*
496  * Files are now stored with their hash, so let's generate for previously uploaded files.
497  */
498 function setFilehashOnLocalFiles()
499 {
500     printfnq('Ensuring all local files have the filehash field set...');
501
502     $file = new File();
503     $file->whereAdd('filename IS NOT NULL');        // local files
504     $file->whereAdd('filehash IS NULL', 'AND');     // without filehash value
505
506     if ($file->find()) {
507         while ($file->fetch()) {
508             try {
509                 $orig = clone($file);
510                 $file->filehash = hash_file(File::FILEHASH_ALG, $file->getPath());
511                 $file->update($orig);
512             } catch (FileNotFoundException $e) {
513                 echo "\n    WARNING: file ID {$file->id} does not exist on path '{$e->path}'. If there is no file system error, run: php scripts/clean_file_table.php";
514             }
515         }
516     }
517
518     printfnq("DONE.\n");
519 }
520
521 main();