You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CleanTags.php 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Joas Schilling <coding@schilljs.com>
  6. * @author Lukas Reschke <lukas@statuscode.ch>
  7. * @author Morris Jobke <hey@morrisjobke.de>
  8. * @author Robin Appelman <robin@icewind.nl>
  9. * @author Roeland Jago Douma <roeland@famdouma.nl>
  10. * @author Thomas Müller <thomas.mueller@tmit.eu>
  11. *
  12. * @license AGPL-3.0
  13. *
  14. * This code is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License, version 3,
  16. * as published by the Free Software Foundation.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License, version 3,
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>
  25. *
  26. */
  27. namespace OC\Repair;
  28. use OCP\DB\QueryBuilder\IQueryBuilder;
  29. use OCP\IDBConnection;
  30. use OCP\IUserManager;
  31. use OCP\Migration\IOutput;
  32. use OCP\Migration\IRepairStep;
  33. /**
  34. * Class RepairConfig
  35. *
  36. * @package OC\Repair
  37. */
  38. class CleanTags implements IRepairStep {
  39. /** @var IDBConnection */
  40. protected $connection;
  41. /** @var IUserManager */
  42. protected $userManager;
  43. protected $deletedTags = 0;
  44. /**
  45. * @param IDBConnection $connection
  46. * @param IUserManager $userManager
  47. */
  48. public function __construct(IDBConnection $connection, IUserManager $userManager) {
  49. $this->connection = $connection;
  50. $this->userManager = $userManager;
  51. }
  52. /**
  53. * @return string
  54. */
  55. public function getName() {
  56. return 'Clean tags and favorites';
  57. }
  58. /**
  59. * Updates the configuration after running an update
  60. */
  61. public function run(IOutput $output) {
  62. $this->deleteOrphanTags($output);
  63. $this->deleteOrphanFileEntries($output);
  64. $this->deleteOrphanTagEntries($output);
  65. $this->deleteOrphanCategoryEntries($output);
  66. }
  67. /**
  68. * Delete tags for deleted users
  69. */
  70. protected function deleteOrphanTags(IOutput $output) {
  71. $offset = 0;
  72. while ($this->checkTags($offset)) {
  73. $offset += 50;
  74. }
  75. $output->info(sprintf('%d tags of deleted users have been removed.', $this->deletedTags));
  76. }
  77. protected function checkTags($offset) {
  78. $query = $this->connection->getQueryBuilder();
  79. $query->select('uid')
  80. ->from('vcategory')
  81. ->groupBy('uid')
  82. ->orderBy('uid')
  83. ->setMaxResults(50)
  84. ->setFirstResult($offset);
  85. $result = $query->execute();
  86. $users = [];
  87. $hadResults = false;
  88. while ($row = $result->fetch()) {
  89. $hadResults = true;
  90. if (!$this->userManager->userExists($row['uid'])) {
  91. $users[] = $row['uid'];
  92. }
  93. }
  94. $result->closeCursor();
  95. if (!$hadResults) {
  96. // No more tags, stop looping
  97. return false;
  98. }
  99. if (!empty($users)) {
  100. $query = $this->connection->getQueryBuilder();
  101. $query->delete('vcategory')
  102. ->where($query->expr()->in('uid', $query->createNamedParameter($users, IQueryBuilder::PARAM_STR_ARRAY)));
  103. $this->deletedTags += $query->execute();
  104. }
  105. return true;
  106. }
  107. /**
  108. * Delete tag entries for deleted files
  109. */
  110. protected function deleteOrphanFileEntries(IOutput $output) {
  111. $this->deleteOrphanEntries(
  112. $output,
  113. '%d tags for delete files have been removed.',
  114. 'vcategory_to_object', 'objid',
  115. 'filecache', 'fileid', 'path_hash'
  116. );
  117. }
  118. /**
  119. * Delete tag entries for deleted tags
  120. */
  121. protected function deleteOrphanTagEntries(IOutput $output) {
  122. $this->deleteOrphanEntries(
  123. $output,
  124. '%d tag entries for deleted tags have been removed.',
  125. 'vcategory_to_object', 'categoryid',
  126. 'vcategory', 'id', 'uid'
  127. );
  128. }
  129. /**
  130. * Delete tags that have no entries
  131. */
  132. protected function deleteOrphanCategoryEntries(IOutput $output) {
  133. $this->deleteOrphanEntries(
  134. $output,
  135. '%d tags with no entries have been removed.',
  136. 'vcategory', 'id',
  137. 'vcategory_to_object', 'categoryid', 'type'
  138. );
  139. }
  140. /**
  141. * Deletes all entries from $deleteTable that do not have a matching entry in $sourceTable
  142. *
  143. * A query joins $deleteTable.$deleteId = $sourceTable.$sourceId and checks
  144. * whether $sourceNullColumn is null. If it is null, the entry in $deleteTable
  145. * is being deleted.
  146. *
  147. * @param string $repairInfo
  148. * @param string $deleteTable
  149. * @param string $deleteId
  150. * @param string $sourceTable
  151. * @param string $sourceId
  152. * @param string $sourceNullColumn If this column is null in the source table,
  153. * the entry is deleted in the $deleteTable
  154. * @suppress SqlInjectionChecker
  155. */
  156. protected function deleteOrphanEntries(IOutput $output, $repairInfo, $deleteTable, $deleteId, $sourceTable, $sourceId, $sourceNullColumn) {
  157. $qb = $this->connection->getQueryBuilder();
  158. $qb->select('d.' . $deleteId)
  159. ->from($deleteTable, 'd')
  160. ->leftJoin('d', $sourceTable, 's', $qb->expr()->eq('d.' . $deleteId, 's.' . $sourceId))
  161. ->where(
  162. $qb->expr()->eq('d.type', $qb->expr()->literal('files'))
  163. )
  164. ->andWhere(
  165. $qb->expr()->isNull('s.' . $sourceNullColumn)
  166. );
  167. $result = $qb->execute();
  168. $orphanItems = [];
  169. while ($row = $result->fetch()) {
  170. $orphanItems[] = (int) $row[$deleteId];
  171. }
  172. if (!empty($orphanItems)) {
  173. $orphanItemsBatch = array_chunk($orphanItems, 200);
  174. foreach ($orphanItemsBatch as $items) {
  175. $qb->delete($deleteTable)
  176. ->where(
  177. $qb->expr()->eq('type', $qb->expr()->literal('files'))
  178. )
  179. ->andWhere($qb->expr()->in($deleteId, $qb->createParameter('ids')));
  180. $qb->setParameter('ids', $items, IQueryBuilder::PARAM_INT_ARRAY);
  181. $qb->execute();
  182. }
  183. }
  184. if ($repairInfo) {
  185. $output->info(sprintf($repairInfo, count($orphanItems)));
  186. }
  187. }
  188. }