upgrade.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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. define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
  21. $shortoptions = 'x::';
  22. $longoptions = array('extensions=');
  23. $helptext = <<<END_OF_UPGRADE_HELP
  24. php upgrade.php [options]
  25. Upgrade database schema and data to latest software
  26. END_OF_UPGRADE_HELP;
  27. require_once INSTALLDIR.'/scripts/commandline.inc';
  28. function main()
  29. {
  30. if (Event::handle('StartUpgrade')) {
  31. fixupConversationURIs();
  32. updateSchemaCore();
  33. updateSchemaPlugins();
  34. // These replace old "fixup_*" scripts
  35. fixupNoticeRendered();
  36. fixupNoticeConversation();
  37. initConversation();
  38. fixupGroupURI();
  39. fixupFileGeometry();
  40. deleteLocalFileThumbnailsWithoutFilename();
  41. deleteMissingLocalFileThumbnails();
  42. initGroupProfileId();
  43. initLocalGroup();
  44. initNoticeReshare();
  45. initSubscriptionURI();
  46. initGroupMemberURI();
  47. initProfileLists();
  48. Event::handle('EndUpgrade');
  49. }
  50. }
  51. function tableDefs()
  52. {
  53. $schema = array();
  54. require INSTALLDIR.'/db/core.php';
  55. return $schema;
  56. }
  57. function updateSchemaCore()
  58. {
  59. printfnq("Upgrading core schema...");
  60. $schema = Schema::get();
  61. $schemaUpdater = new SchemaUpdater($schema);
  62. foreach (tableDefs() as $table => $def) {
  63. $schemaUpdater->register($table, $def);
  64. }
  65. $schemaUpdater->checkSchema();
  66. printfnq("DONE.\n");
  67. }
  68. function updateSchemaPlugins()
  69. {
  70. printfnq("Upgrading plugin schema...");
  71. Event::handle('CheckSchema');
  72. printfnq("DONE.\n");
  73. }
  74. function fixupNoticeRendered()
  75. {
  76. printfnq("Ensuring all notices have rendered HTML...");
  77. $notice = new Notice();
  78. $notice->whereAdd('rendered IS NULL');
  79. $notice->find();
  80. while ($notice->fetch()) {
  81. $original = clone($notice);
  82. $notice->rendered = common_render_content($notice->content, $notice);
  83. $notice->update($original);
  84. }
  85. printfnq("DONE.\n");
  86. }
  87. function fixupNoticeConversation()
  88. {
  89. printfnq("Ensuring all notices have a conversation ID...");
  90. $notice = new Notice();
  91. $notice->whereAdd('conversation is null');
  92. $notice->orderBy('id'); // try to get originals before replies
  93. $notice->find();
  94. while ($notice->fetch()) {
  95. try {
  96. $cid = null;
  97. $orig = clone($notice);
  98. if (empty($notice->reply_to)) {
  99. $notice->conversation = $notice->id;
  100. } else {
  101. $reply = Notice::getKV('id', $notice->reply_to);
  102. if (empty($reply)) {
  103. $notice->conversation = $notice->id;
  104. } else if (empty($reply->conversation)) {
  105. $notice->conversation = $notice->id;
  106. } else {
  107. $notice->conversation = $reply->conversation;
  108. }
  109. unset($reply);
  110. $reply = null;
  111. }
  112. $result = $notice->update($orig);
  113. $orig = null;
  114. unset($orig);
  115. } catch (Exception $e) {
  116. printv("Error setting conversation: " . $e->getMessage());
  117. }
  118. }
  119. printfnq("DONE.\n");
  120. }
  121. function fixupGroupURI()
  122. {
  123. printfnq("Ensuring all groups have an URI...");
  124. $group = new User_group();
  125. $group->whereAdd('uri IS NULL');
  126. if ($group->find()) {
  127. while ($group->fetch()) {
  128. $orig = User_group::getKV('id', $group->id);
  129. $group->uri = $group->getUri();
  130. $group->update($orig);
  131. }
  132. }
  133. printfnq("DONE.\n");
  134. }
  135. function initConversation()
  136. {
  137. printfnq("Ensuring all conversations have a row in conversation table...");
  138. $notice = new Notice();
  139. $notice->query('select distinct notice.conversation from notice '.
  140. 'where notice.conversation is not null '.
  141. 'and not exists (select conversation.id from conversation where id = notice.conversation)');
  142. while ($notice->fetch()) {
  143. $id = $notice->conversation;
  144. $uri = common_local_url('conversation', array('id' => $id));
  145. // @fixme db_dataobject won't save our value for an autoincrement
  146. // so we're bypassing the insert wrappers
  147. $conv = new Conversation();
  148. $sql = "insert into conversation (id,uri,created) values(%d,'%s','%s')";
  149. $sql = sprintf($sql,
  150. $id,
  151. $conv->escape($uri),
  152. $conv->escape(common_sql_now()));
  153. $conv->query($sql);
  154. }
  155. printfnq("DONE.\n");
  156. }
  157. function fixupConversationURIs()
  158. {
  159. printfnq("Ensuring all conversations have a URI...");
  160. $conv = new Conversation();
  161. $conv->whereAdd('uri IS NULL');
  162. if ($conv->find()) {
  163. $rounds = 0;
  164. while ($conv->fetch()) {
  165. $uri = common_local_url('conversation', array('id' => $conv->id));
  166. $sql = sprintf('UPDATE conversation SET uri="%1$s" WHERE id="%2$d";',
  167. $conv->escape($uri), $conv->id);
  168. $conv->query($sql);
  169. if (($conv->N-++$rounds) % 500 == 0) {
  170. printfnq(sprintf(' %d items left...', $conv->N-$rounds));
  171. }
  172. }
  173. }
  174. printfnq("DONE.\n");
  175. }
  176. function initGroupProfileId()
  177. {
  178. printfnq("Ensuring all User_group entries have a Profile and profile_id...");
  179. $group = new User_group();
  180. $group->whereAdd('NOT EXISTS (SELECT id FROM profile WHERE id = user_group.profile_id)');
  181. $group->find();
  182. while ($group->fetch()) {
  183. try {
  184. // We must create a new, incrementally assigned profile_id
  185. $profile = new Profile();
  186. $profile->nickname = $group->nickname;
  187. $profile->fullname = $group->fullname;
  188. $profile->profileurl = $group->mainpage;
  189. $profile->homepage = $group->homepage;
  190. $profile->bio = $group->description;
  191. $profile->location = $group->location;
  192. $profile->created = $group->created;
  193. $profile->modified = $group->modified;
  194. $profile->query('BEGIN');
  195. $id = $profile->insert();
  196. if (empty($id)) {
  197. $profile->query('ROLLBACK');
  198. throw new Exception('Profile insertion failed, profileurl: '.$profile->profileurl);
  199. }
  200. $group->query("UPDATE user_group SET profile_id={$id} WHERE id={$group->id}");
  201. $profile->query('COMMIT');
  202. $profile->free();
  203. } catch (Exception $e) {
  204. printfv("Error initializing Profile for group {$group->nickname}:" . $e->getMessage());
  205. }
  206. }
  207. printfnq("DONE.\n");
  208. }
  209. function initLocalGroup()
  210. {
  211. printfnq("Ensuring all local user groups have a local_group...");
  212. $group = new User_group();
  213. $group->whereAdd('NOT EXISTS (select group_id from local_group where group_id = user_group.id)');
  214. $group->find();
  215. while ($group->fetch()) {
  216. try {
  217. // Hack to check for local groups
  218. if ($group->getUri() == common_local_url('groupbyid', array('id' => $group->id))) {
  219. $lg = new Local_group();
  220. $lg->group_id = $group->id;
  221. $lg->nickname = $group->nickname;
  222. $lg->created = $group->created; // XXX: common_sql_now() ?
  223. $lg->modified = $group->modified;
  224. $lg->insert();
  225. }
  226. } catch (Exception $e) {
  227. printfv("Error initializing local group for {$group->nickname}:" . $e->getMessage());
  228. }
  229. }
  230. printfnq("DONE.\n");
  231. }
  232. function initNoticeReshare()
  233. {
  234. printfnq("Ensuring all reshares have the correct verb and object-type...");
  235. $notice = new Notice();
  236. $notice->whereAdd('repeat_of is not null');
  237. $notice->whereAdd('(verb != "'.ActivityVerb::SHARE.'" OR object_type != "'.ActivityObject::ACTIVITY.'")');
  238. if ($notice->find()) {
  239. while ($notice->fetch()) {
  240. try {
  241. $orig = Notice::getKV('id', $notice->id);
  242. $notice->verb = ActivityVerb::SHARE;
  243. $notice->object_type = ActivityObject::ACTIVITY;
  244. $notice->update($orig);
  245. } catch (Exception $e) {
  246. printfv("Error updating verb and object_type for {$notice->id}:" . $e->getMessage());
  247. }
  248. }
  249. }
  250. printfnq("DONE.\n");
  251. }
  252. function initSubscriptionURI()
  253. {
  254. printfnq("Ensuring all subscriptions have a URI...");
  255. $sub = new Subscription();
  256. $sub->whereAdd('uri IS NULL');
  257. if ($sub->find()) {
  258. while ($sub->fetch()) {
  259. try {
  260. $sub->decache();
  261. $sub->query(sprintf('update subscription '.
  262. 'set uri = "%s" '.
  263. 'where subscriber = %d '.
  264. 'and subscribed = %d',
  265. Subscription::newURI($sub->subscriber, $sub->subscribed, $sub->created),
  266. $sub->subscriber,
  267. $sub->subscribed));
  268. } catch (Exception $e) {
  269. common_log(LOG_ERR, "Error updated subscription URI: " . $e->getMessage());
  270. }
  271. }
  272. }
  273. printfnq("DONE.\n");
  274. }
  275. function initGroupMemberURI()
  276. {
  277. printfnq("Ensuring all group memberships have a URI...");
  278. $mem = new Group_member();
  279. $mem->whereAdd('uri IS NULL');
  280. if ($mem->find()) {
  281. while ($mem->fetch()) {
  282. try {
  283. $mem->decache();
  284. $mem->query(sprintf('update group_member set uri = "%s" '.
  285. 'where profile_id = %d ' .
  286. 'and group_id = %d ',
  287. Group_member::newURI($mem->profile_id, $mem->group_id, $mem->created),
  288. $mem->profile_id,
  289. $mem->group_id));
  290. } catch (Exception $e) {
  291. common_log(LOG_ERR, "Error updated membership URI: " . $e->getMessage());
  292. }
  293. }
  294. }
  295. printfnq("DONE.\n");
  296. }
  297. function initProfileLists()
  298. {
  299. printfnq("Ensuring all profile tags have a corresponding list...");
  300. $ptag = new Profile_tag();
  301. $ptag->selectAdd();
  302. $ptag->selectAdd('tagger, tag, count(*) as tagged_count');
  303. $ptag->whereAdd('NOT EXISTS (SELECT tagger, tagged from profile_list '.
  304. 'where profile_tag.tagger = profile_list.tagger '.
  305. 'and profile_tag.tag = profile_list.tag)');
  306. $ptag->groupBy('tagger, tag');
  307. $ptag->orderBy('tagger, tag');
  308. if ($ptag->find()) {
  309. while ($ptag->fetch()) {
  310. $plist = new Profile_list();
  311. $plist->tagger = $ptag->tagger;
  312. $plist->tag = $ptag->tag;
  313. $plist->private = 0;
  314. $plist->created = common_sql_now();
  315. $plist->modified = $plist->created;
  316. $plist->mainpage = common_local_url('showprofiletag',
  317. array('tagger' => $plist->getTagger()->nickname,
  318. 'tag' => $plist->tag));;
  319. $plist->tagged_count = $ptag->tagged_count;
  320. $plist->subscriber_count = 0;
  321. $plist->insert();
  322. $orig = clone($plist);
  323. // After insert since it uses auto-generated ID
  324. $plist->uri = common_local_url('profiletagbyid',
  325. array('id' => $plist->id, 'tagger_id' => $plist->tagger));
  326. $plist->update($orig);
  327. }
  328. }
  329. printfnq("DONE.\n");
  330. }
  331. /*
  332. * Added as we now store interpretd width and height in File table.
  333. */
  334. function fixupFileGeometry()
  335. {
  336. printfnq("Ensuring width and height is set for supported local File objects...");
  337. $file = new File();
  338. $file->whereAdd('filename IS NOT NULL'); // local files
  339. $file->whereAdd('width IS NULL OR width = 0');
  340. if ($file->find()) {
  341. while ($file->fetch()) {
  342. // Set file geometrical properties if available
  343. try {
  344. $image = ImageFile::fromFileObject($file);
  345. } catch (ServerException $e) {
  346. // We couldn't make out an image from the file.
  347. continue;
  348. }
  349. $orig = clone($file);
  350. $file->width = $image->width;
  351. $file->height = $image->height;
  352. $file->update($orig);
  353. // FIXME: Do this more automagically inside ImageFile or so.
  354. if ($image->getPath() != $file->getPath()) {
  355. $image->unlink();
  356. }
  357. unset($image);
  358. }
  359. }
  360. printfnq("DONE.\n");
  361. }
  362. /*
  363. * File_thumbnail objects for local Files store their own filenames in the database.
  364. */
  365. function deleteLocalFileThumbnailsWithoutFilename()
  366. {
  367. printfnq("Removing all local File_thumbnail entries without filename property...");
  368. $file = new File();
  369. $file->whereAdd('filename IS NOT NULL'); // local files
  370. if ($file->find()) {
  371. // Looping through local File entries
  372. while ($file->fetch()) {
  373. $thumbs = new File_thumbnail();
  374. $thumbs->file_id = $file->id;
  375. $thumbs->whereAdd('filename IS NULL');
  376. // Checking if there were any File_thumbnail entries without filename
  377. if (!$thumbs->find()) {
  378. continue;
  379. }
  380. // deleting incomplete entry to allow regeneration
  381. while ($thumbs->fetch()) {
  382. $thumbs->delete();
  383. }
  384. }
  385. }
  386. printfnq("DONE.\n");
  387. }
  388. /*
  389. * Delete File_thumbnail entries where the referenced file does not exist.
  390. */
  391. function deleteMissingLocalFileThumbnails()
  392. {
  393. printfnq("Removing all local File_thumbnail entries without existing files...");
  394. $thumbs = new File_thumbnail();
  395. $thumbs->whereAdd('filename IS NOT NULL'); // only fill in names where they're missing
  396. // Checking if there were any File_thumbnail entries without filename
  397. if ($thumbs->find()) {
  398. while ($thumbs->fetch()) {
  399. if (!file_exists(File_thumbnail::path($thumbs->filename))) {
  400. $thumbs->delete();
  401. }
  402. }
  403. }
  404. printfnq("DONE.\n");
  405. }
  406. main();