fixup_deletions.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/usr/bin/env php
  2. <?php
  3. // This file is part of GNU social - https://www.gnu.org/software/social
  4. //
  5. // GNU social is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // GNU social is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  17. /*
  18. * @copyright 2010 StatusNet, Inc.
  19. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
  20. */
  21. define('INSTALLDIR', dirname(__DIR__));
  22. define('PUBLICDIR', INSTALLDIR . DIRECTORY_SEPARATOR . 'public');
  23. $longoptions = array('dry-run', 'start=', 'end=');
  24. $helptext = <<<END_OF_USERROLE_HELP
  25. fixup_deletions.php [options]
  26. Finds notices posted by deleted users and cleans them up.
  27. Stray incompletely deleted items cause various fun problems!
  28. --dry-run look but don't touch
  29. --start=N start looking at profile_id N instead of 1
  30. --end=N end looking at profile_id N instead of the max
  31. END_OF_USERROLE_HELP;
  32. require_once INSTALLDIR.'/scripts/commandline.inc';
  33. /**
  34. * Find the highest profile_id currently listed in the notice table;
  35. * this field is indexed and should return very quickly.
  36. *
  37. * We check notice.profile_id rather than profile.id because we're
  38. * looking for notices left behind after deletion; if the most recent
  39. * accounts were deleted, we wouldn't have them from profile.
  40. *
  41. * @return int
  42. * @access private
  43. */
  44. function get_max_profile_id()
  45. {
  46. $query = 'SELECT MAX(profile_id) AS id FROM notice';
  47. $profile = new Profile();
  48. $profile->query($query);
  49. if ($profile->fetch()) {
  50. return intval($profile->id);
  51. } else {
  52. die("Something went awry; could not look up max used profile_id.");
  53. }
  54. }
  55. /**
  56. * Check for profiles in the given id range that are missing, presumed deleted.
  57. *
  58. * @param int $start beginning profile.id, inclusive
  59. * @param int $end final profile.id, inclusive
  60. * @return array of integer profile.ids
  61. * @access private
  62. */
  63. function get_missing_profiles($start, $end)
  64. {
  65. $query = sprintf(
  66. 'SELECT id FROM profile WHERE id BETWEEN %d AND %d',
  67. $start,
  68. $end
  69. );
  70. $profile = new Profile();
  71. $profile->query($query);
  72. $all = range($start, $end);
  73. $known = [];
  74. while ($profile->fetch()) {
  75. $known[] = (int) $profile->id;
  76. }
  77. unset($profile);
  78. $missing = array_diff($all, $known);
  79. return $missing;
  80. }
  81. /**
  82. * Look for stray notices from this profile and, if present, kill them.
  83. *
  84. * @param int $profile_id
  85. * @param bool $dry if true, we won't delete anything
  86. */
  87. function cleanup_missing_profile($profile_id, $dry)
  88. {
  89. $notice = new Notice();
  90. $notice->profile_id = $profile_id;
  91. $notice->find();
  92. if ($notice->N == 0) {
  93. return;
  94. }
  95. $s = ($notice->N == 1) ? '' : 's';
  96. print "Deleted profile $profile_id has $notice->N stray notice$s:\n";
  97. while ($notice->fetch()) {
  98. print " notice $notice->id";
  99. if ($dry) {
  100. print " (skipped; dry run)\n";
  101. } else {
  102. $victim = clone($notice);
  103. try {
  104. $victim->delete();
  105. print " (deleted)\n";
  106. } catch (Exception $e) {
  107. print " FAILED: ";
  108. print $e->getMessage();
  109. print "\n";
  110. }
  111. }
  112. }
  113. }
  114. $dry = have_option('dry-run');
  115. $max_profile_id = get_max_profile_id();
  116. $chunk = 1000;
  117. if (have_option('start')) {
  118. $begin = intval(get_option_value('start'));
  119. } else {
  120. $begin = 1;
  121. }
  122. if (have_option('end')) {
  123. $final = min($max_profile_id, intval(get_option_value('end')));
  124. } else {
  125. $final = $max_profile_id;
  126. }
  127. if ($begin < 1) {
  128. die("Silly human, you can't begin before profile number 1!\n");
  129. }
  130. if ($final < $begin) {
  131. die("Silly human, you can't end at $final if it's before $begin!\n");
  132. }
  133. // Identify missing profiles...
  134. for ($start = $begin; $start <= $final; $start += $chunk) {
  135. $end = min($start + $chunk - 1, $final);
  136. print "Checking for missing profiles between id $start and $end";
  137. if ($dry) {
  138. print " (dry run)";
  139. }
  140. print "...\n";
  141. $missing = get_missing_profiles($start, $end);
  142. foreach ($missing as $profile_id) {
  143. cleanup_missing_profile($profile_id, $dry);
  144. }
  145. }
  146. echo "done.\n";