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