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