SaveAccountsTableData.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace OC\Repair\Owncloud;
  7. use OCP\DB\QueryBuilder\IQueryBuilder;
  8. use OCP\IConfig;
  9. use OCP\IDBConnection;
  10. use OCP\Migration\IOutput;
  11. use OCP\Migration\IRepairStep;
  12. use OCP\PreConditionNotMetException;
  13. /**
  14. * Copies the email address from the accounts table to the preference table,
  15. * before the data structure is changed and the information is gone
  16. */
  17. class SaveAccountsTableData implements IRepairStep {
  18. public const BATCH_SIZE = 75;
  19. /** @var IDBConnection */
  20. protected $db;
  21. /** @var IConfig */
  22. protected $config;
  23. protected $hasForeignKeyOnPersistentLocks = false;
  24. /**
  25. * @param IDBConnection $db
  26. * @param IConfig $config
  27. */
  28. public function __construct(IDBConnection $db, IConfig $config) {
  29. $this->db = $db;
  30. $this->config = $config;
  31. }
  32. /**
  33. * @return string
  34. */
  35. public function getName() {
  36. return 'Copy data from accounts table when migrating from ownCloud';
  37. }
  38. /**
  39. * @param IOutput $output
  40. */
  41. public function run(IOutput $output) {
  42. if (!$this->shouldRun()) {
  43. return;
  44. }
  45. $offset = 0;
  46. $numUsers = $this->runStep($offset);
  47. while ($numUsers === self::BATCH_SIZE) {
  48. $offset += $numUsers;
  49. $numUsers = $this->runStep($offset);
  50. }
  51. // oc_persistent_locks will be removed later on anyways so we can just drop and ignore any foreign key constraints here
  52. $tableName = $this->config->getSystemValueString('dbtableprefix', 'oc_') . 'persistent_locks';
  53. $schema = $this->db->createSchema();
  54. $table = $schema->getTable($tableName);
  55. foreach ($table->getForeignKeys() as $foreignKey) {
  56. $table->removeForeignKey($foreignKey->getName());
  57. }
  58. $this->db->migrateToSchema($schema);
  59. // Remove the table
  60. if ($this->hasForeignKeyOnPersistentLocks) {
  61. $this->db->dropTable('persistent_locks');
  62. }
  63. $this->db->dropTable('accounts');
  64. }
  65. /**
  66. * @return bool
  67. */
  68. protected function shouldRun() {
  69. $schema = $this->db->createSchema();
  70. $prefix = $this->config->getSystemValueString('dbtableprefix', 'oc_');
  71. $tableName = $prefix . 'accounts';
  72. if (!$schema->hasTable($tableName)) {
  73. return false;
  74. }
  75. $table = $schema->getTable($tableName);
  76. if (!$table->hasColumn('user_id')) {
  77. return false;
  78. }
  79. if ($schema->hasTable($prefix . 'persistent_locks')) {
  80. $locksTable = $schema->getTable($prefix . 'persistent_locks');
  81. $foreignKeys = $locksTable->getForeignKeys();
  82. foreach ($foreignKeys as $foreignKey) {
  83. if ($tableName === $foreignKey->getForeignTableName()) {
  84. $this->hasForeignKeyOnPersistentLocks = true;
  85. }
  86. }
  87. }
  88. return true;
  89. }
  90. /**
  91. * @param int $offset
  92. * @return int Number of copied users
  93. */
  94. protected function runStep($offset) {
  95. $query = $this->db->getQueryBuilder();
  96. $query->select('*')
  97. ->from('accounts')
  98. ->orderBy('id')
  99. ->setMaxResults(self::BATCH_SIZE);
  100. if ($offset > 0) {
  101. $query->setFirstResult($offset);
  102. }
  103. $result = $query->execute();
  104. $update = $this->db->getQueryBuilder();
  105. $update->update('users')
  106. ->set('displayname', $update->createParameter('displayname'))
  107. ->where($update->expr()->eq('uid', $update->createParameter('userid')));
  108. $updatedUsers = 0;
  109. while ($row = $result->fetch()) {
  110. try {
  111. $this->migrateUserInfo($update, $row);
  112. } catch (PreConditionNotMetException $e) {
  113. // Ignore and continue
  114. } catch (\UnexpectedValueException $e) {
  115. // Ignore and continue
  116. }
  117. $updatedUsers++;
  118. }
  119. $result->closeCursor();
  120. return $updatedUsers;
  121. }
  122. /**
  123. * @param IQueryBuilder $update
  124. * @param array $userdata
  125. * @throws PreConditionNotMetException
  126. * @throws \UnexpectedValueException
  127. */
  128. protected function migrateUserInfo(IQueryBuilder $update, $userdata) {
  129. $state = (int)$userdata['state'];
  130. if ($state === 3) {
  131. // Deleted user, ignore
  132. return;
  133. }
  134. if ($userdata['email'] !== null) {
  135. $this->config->setUserValue($userdata['user_id'], 'settings', 'email', $userdata['email']);
  136. }
  137. if ($userdata['quota'] !== null) {
  138. $this->config->setUserValue($userdata['user_id'], 'files', 'quota', $userdata['quota']);
  139. }
  140. if ($userdata['last_login'] !== null) {
  141. $this->config->setUserValue($userdata['user_id'], 'login', 'lastLogin', $userdata['last_login']);
  142. }
  143. if ($state === 1) {
  144. $this->config->setUserValue($userdata['user_id'], 'core', 'enabled', 'true');
  145. } elseif ($state === 2) {
  146. $this->config->setUserValue($userdata['user_id'], 'core', 'enabled', 'false');
  147. }
  148. if ($userdata['display_name'] !== null) {
  149. $update->setParameter('displayname', $userdata['display_name'])
  150. ->setParameter('userid', $userdata['user_id']);
  151. $update->execute();
  152. }
  153. }
  154. }