summaryrefslogtreecommitdiffstats
path: root/lib/private/repair
diff options
context:
space:
mode:
authorMorris Jobke <hey@morrisjobke.de>2016-01-07 09:14:35 +0100
committerMorris Jobke <hey@morrisjobke.de>2016-01-07 09:14:35 +0100
commit604897945b01a368893661c0455072c0b390f7e8 (patch)
treed7adcb3122ad799ad60fb8999841b4d5b024dfa4 /lib/private/repair
parent470bf234c545ae5d0ad9c73834f8a596593791d6 (diff)
downloadnextcloud-server-604897945b01a368893661c0455072c0b390f7e8.tar.gz
nextcloud-server-604897945b01a368893661c0455072c0b390f7e8.zip
Move lib/repair to lib/private/repair
Diffstat (limited to 'lib/private/repair')
-rw-r--r--lib/private/repair/assetcache.php45
-rw-r--r--lib/private/repair/cleantags.php146
-rw-r--r--lib/private/repair/collation.php90
-rw-r--r--lib/private/repair/dropoldjobs.php78
-rw-r--r--lib/private/repair/dropoldtables.php96
-rw-r--r--lib/private/repair/filletags.php55
-rw-r--r--lib/private/repair/innodb.php69
-rw-r--r--lib/private/repair/oldgroupmembershipshares.php117
-rw-r--r--lib/private/repair/preview.php45
-rw-r--r--lib/private/repair/removegetetagentries.php59
-rw-r--r--lib/private/repair/repairinvalidshares.php112
-rw-r--r--lib/private/repair/repairlegacystorages.php263
-rw-r--r--lib/private/repair/repairmimetypes.php360
-rw-r--r--lib/private/repair/searchlucenetables.php77
-rw-r--r--lib/private/repair/sqliteautoincrement.php98
-rw-r--r--lib/private/repair/updatecertificatestore.php88
-rw-r--r--lib/private/repair/updateoutdatedocsids.php108
17 files changed, 1906 insertions, 0 deletions
diff --git a/lib/private/repair/assetcache.php b/lib/private/repair/assetcache.php
new file mode 100644
index 00000000000..c46aa63a3e4
--- /dev/null
+++ b/lib/private/repair/assetcache.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @author Adam Williamson <awilliam@redhat.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use Doctrine\DBAL\Platforms\MySqlPlatform;
+use OC\Hooks\BasicEmitter;
+
+class AssetCache extends BasicEmitter implements \OC\RepairStep {
+
+ public function getName() {
+ return 'Clear asset cache after upgrade';
+ }
+
+ public function run() {
+ if (!\OC_Template::isAssetPipelineEnabled()) {
+ $this->emit('\OC\Repair', 'info', array('Asset pipeline disabled -> nothing to do'));
+ return;
+ }
+ $assetDir = \OC::$server->getConfig()->getSystemValue('assetdirectory', \OC::$SERVERROOT) . '/assets';
+ \OC_Helper::rmdirr($assetDir, false);
+ $this->emit('\OC\Repair', 'info', array('Asset cache cleared.'));
+ }
+}
+
diff --git a/lib/private/repair/cleantags.php b/lib/private/repair/cleantags.php
new file mode 100644
index 00000000000..d16a49fbca7
--- /dev/null
+++ b/lib/private/repair/cleantags.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Hooks\BasicEmitter;
+use OC\RepairStep;
+use OCP\IDBConnection;
+
+/**
+ * Class RepairConfig
+ *
+ * @package OC\Repair
+ */
+class CleanTags extends BasicEmitter implements RepairStep {
+
+ /** @var IDBConnection */
+ protected $connection;
+
+ /**
+ * @param IDBConnection $connection
+ */
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName() {
+ return 'Clean tags and favorites';
+ }
+
+ /**
+ * Updates the configuration after running an update
+ */
+ public function run() {
+ $this->deleteOrphanFileEntries();
+ $this->deleteOrphanTagEntries();
+ $this->deleteOrphanCategoryEntries();
+ }
+
+ /**
+ * Delete tag entries for deleted files
+ */
+ protected function deleteOrphanFileEntries() {
+ $this->deleteOrphanEntries(
+ '%d tags for delete files have been removed.',
+ 'vcategory_to_object', 'objid',
+ 'filecache', 'fileid', 'path_hash'
+ );
+ }
+
+ /**
+ * Delete tag entries for deleted tags
+ */
+ protected function deleteOrphanTagEntries() {
+ $this->deleteOrphanEntries(
+ '%d tag entries for deleted tags have been removed.',
+ 'vcategory_to_object', 'categoryid',
+ 'vcategory', 'id', 'uid'
+ );
+ }
+
+ /**
+ * Delete tags that have no entries
+ */
+ protected function deleteOrphanCategoryEntries() {
+ $this->deleteOrphanEntries(
+ '%d tags with no entries have been removed.',
+ 'vcategory', 'id',
+ 'vcategory_to_object', 'categoryid', 'type'
+ );
+ }
+
+ /**
+ * Deletes all entries from $deleteTable that do not have a matching entry in $sourceTable
+ *
+ * A query joins $deleteTable.$deleteId = $sourceTable.$sourceId and checks
+ * whether $sourceNullColumn is null. If it is null, the entry in $deleteTable
+ * is being deleted.
+ *
+ * @param string $repairInfo
+ * @param string $deleteTable
+ * @param string $deleteId
+ * @param string $sourceTable
+ * @param string $sourceId
+ * @param string $sourceNullColumn If this column is null in the source table,
+ * the entry is deleted in the $deleteTable
+ */
+ protected function deleteOrphanEntries($repairInfo, $deleteTable, $deleteId, $sourceTable, $sourceId, $sourceNullColumn) {
+ $qb = $this->connection->getQueryBuilder();
+
+ $qb->select('d.' . $deleteId)
+ ->from($deleteTable, 'd')
+ ->leftJoin('d', $sourceTable, 's', $qb->expr()->eq('d.' . $deleteId, ' s.' . $sourceId))
+ ->where(
+ $qb->expr()->eq('d.type', $qb->expr()->literal('files'))
+ )
+ ->andWhere(
+ $qb->expr()->isNull('s.' . $sourceNullColumn)
+ );
+ $result = $qb->execute();
+
+ $orphanItems = array();
+ while ($row = $result->fetch()) {
+ $orphanItems[] = (int) $row[$deleteId];
+ }
+
+ if (!empty($orphanItems)) {
+ $orphanItemsBatch = array_chunk($orphanItems, 200);
+ foreach ($orphanItemsBatch as $items) {
+ $qb->delete($deleteTable)
+ ->where(
+ $qb->expr()->eq('type', $qb->expr()->literal('files'))
+ )
+ ->andWhere($qb->expr()->in($deleteId, $qb->createParameter('ids')));
+ $qb->setParameter('ids', $items, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY);
+ $qb->execute();
+ }
+ }
+
+ if ($repairInfo) {
+ $this->emit('\OC\Repair', 'info', array(sprintf($repairInfo, sizeof($orphanItems))));
+ }
+ }
+}
diff --git a/lib/private/repair/collation.php b/lib/private/repair/collation.php
new file mode 100644
index 00000000000..7eb14f0ded2
--- /dev/null
+++ b/lib/private/repair/collation.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <icewind@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use Doctrine\DBAL\Platforms\MySqlPlatform;
+use OC\Hooks\BasicEmitter;
+
+class Collation extends BasicEmitter implements \OC\RepairStep {
+ /**
+ * @var \OCP\IConfig
+ */
+ protected $config;
+
+ /**
+ * @var \OC\DB\Connection
+ */
+ protected $connection;
+
+ /**
+ * @param \OCP\IConfig $config
+ * @param \OC\DB\Connection $connection
+ */
+ public function __construct($config, $connection) {
+ $this->connection = $connection;
+ $this->config = $config;
+ }
+
+ public function getName() {
+ return 'Repair MySQL collation';
+ }
+
+ /**
+ * Fix mime types
+ */
+ public function run() {
+ if (!$this->connection->getDatabasePlatform() instanceof MySqlPlatform) {
+ $this->emit('\OC\Repair', 'info', array('Not a mysql database -> nothing to no'));
+ return;
+ }
+
+ $tables = $this->getAllNonUTF8BinTables($this->connection);
+ foreach ($tables as $table) {
+ $this->emit('\OC\Repair', 'info', array("Change collation for $table ..."));
+ $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;');
+ $query->execute();
+ }
+ }
+
+ /**
+ * @param \Doctrine\DBAL\Connection $connection
+ * @return string[]
+ */
+ protected function getAllNonUTF8BinTables($connection) {
+ $dbName = $this->config->getSystemValue("dbname");
+ $rows = $connection->fetchAll(
+ "SELECT DISTINCT(TABLE_NAME) AS `table`" .
+ " FROM INFORMATION_SCHEMA . COLUMNS" .
+ " WHERE TABLE_SCHEMA = ?" .
+ " AND (COLLATION_NAME <> 'utf8_bin' OR CHARACTER_SET_NAME <> 'utf8')" .
+ " AND TABLE_NAME LIKE \"*PREFIX*%\"",
+ array($dbName)
+ );
+ $result = array();
+ foreach ($rows as $row) {
+ $result[] = $row['table'];
+ }
+ return $result;
+ }
+}
+
diff --git a/lib/private/repair/dropoldjobs.php b/lib/private/repair/dropoldjobs.php
new file mode 100644
index 00000000000..89d7f96a144
--- /dev/null
+++ b/lib/private/repair/dropoldjobs.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @author Arthur Schiwon <blizzz@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Hooks\BasicEmitter;
+use OC\RepairStep;
+use OCP\BackgroundJob\IJobList;
+
+class DropOldJobs extends BasicEmitter implements RepairStep {
+
+ /** @var IJobList */
+ protected $jobList;
+
+ /**
+ * @param IJobList $jobList
+ */
+ public function __construct(IJobList $jobList) {
+ $this->jobList = $jobList;
+ }
+
+ /**
+ * Returns the step's name
+ *
+ * @return string
+ */
+ public function getName() {
+ return 'Drop old background jobs';
+ }
+
+ /**
+ * Run repair step.
+ * Must throw exception on error.
+ *
+ * @throws \Exception in case of failure
+ */
+ public function run() {
+ $oldJobs = $this->oldJobs();
+ foreach($oldJobs as $job) {
+ if($this->jobList->has($job['class'], $job['arguments'])) {
+ $this->jobList->remove($job['class'], $job['arguments']);
+ }
+ }
+ }
+
+ /**
+ * returns a list of old jobs as an associative array with keys 'class' and
+ * 'arguments'.
+ *
+ * @return array
+ */
+ public function oldJobs() {
+ return [
+ ['class' => 'OC_Cache_FileGlobalGC', 'arguments' => null],
+ ['class' => 'OC\Cache\FileGlobalGC', 'arguments' => null],
+ ];
+ }
+
+
+}
diff --git a/lib/private/repair/dropoldtables.php b/lib/private/repair/dropoldtables.php
new file mode 100644
index 00000000000..2d7fc8376b3
--- /dev/null
+++ b/lib/private/repair/dropoldtables.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+
+use OC\Hooks\BasicEmitter;
+use OC\RepairStep;
+use OCP\IDBConnection;
+
+class DropOldTables extends BasicEmitter implements RepairStep {
+
+ /** @var IDBConnection */
+ protected $connection;
+
+ /**
+ * @param IDBConnection $connection
+ */
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ }
+
+ /**
+ * Returns the step's name
+ *
+ * @return string
+ */
+ public function getName() {
+ return 'Drop old database tables';
+ }
+
+ /**
+ * Run repair step.
+ * Must throw exception on error.
+ *
+ * @throws \Exception in case of failure
+ */
+ public function run() {
+ foreach ($this->oldDatabaseTables() as $tableName) {
+ if ($this->connection->tableExists($tableName)){
+ $this->emit('\OC\Repair', 'info', [
+ sprintf('Table %s has been deleted', $tableName)
+ ]);
+ $this->connection->dropTable($tableName);
+ }
+ }
+ }
+
+ /**
+ * Returns a list of outdated tables which are not used anymore
+ * @return array
+ */
+ protected function oldDatabaseTables() {
+ return [
+ 'calendar_calendars',
+ 'calendar_objects',
+ 'calendar_share_calendar',
+ 'calendar_share_event',
+ 'file_map',
+ 'foldersize',
+ 'fscache',
+ 'gallery_sharing',
+ 'locks',
+ 'log',
+ 'media_albums',
+ 'media_artists',
+ 'media_sessions',
+ 'media_songs',
+ 'media_users',
+ 'permissions',
+ 'pictures_images_cache',
+ 'principalgroups',
+ 'principals',
+ 'queuedtasks',
+ 'sharing',
+ ];
+ }
+}
diff --git a/lib/private/repair/filletags.php b/lib/private/repair/filletags.php
new file mode 100644
index 00000000000..8cfc4a7c258
--- /dev/null
+++ b/lib/private/repair/filletags.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Hooks\BasicEmitter;
+
+class FillETags extends BasicEmitter implements \OC\RepairStep {
+
+ /** @var \OCP\IDBConnection */
+ protected $connection;
+
+ /**
+ * @param \OCP\IDBConnection $connection
+ */
+ public function __construct($connection) {
+ $this->connection = $connection;
+ }
+
+ public function getName() {
+ return 'Generate ETags for file where no ETag is present.';
+ }
+
+ public function run() {
+ $qb = $this->connection->getQueryBuilder();
+ $qb->update('filecache')
+ ->set('etag', $qb->expr()->literal('xxx'))
+ ->where($qb->expr()->eq('etag', $qb->expr()->literal('')))
+ ->orWhere($qb->expr()->isNull('etag'));
+
+ $result = $qb->execute();
+ $this->emit('\OC\Repair', 'info', array("ETags have been fixed for $result files/folders."));
+ }
+}
+
diff --git a/lib/private/repair/innodb.php b/lib/private/repair/innodb.php
new file mode 100644
index 00000000000..ab94c79468d
--- /dev/null
+++ b/lib/private/repair/innodb.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <icewind@owncloud.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use Doctrine\DBAL\Platforms\MySqlPlatform;
+use OC\Hooks\BasicEmitter;
+
+class InnoDB extends BasicEmitter implements \OC\RepairStep {
+
+ public function getName() {
+ return 'Repair MySQL database engine';
+ }
+
+ /**
+ * Fix mime types
+ */
+ public function run() {
+ $connection = \OC_DB::getConnection();
+ if (!$connection->getDatabasePlatform() instanceof MySqlPlatform) {
+ $this->emit('\OC\Repair', 'info', array('Not a mysql database -> nothing to do'));
+ return;
+ }
+
+ $tables = $this->getAllMyIsamTables($connection);
+ if (is_array($tables)) {
+ foreach ($tables as $table) {
+ $connection->exec("ALTER TABLE $table ENGINE=InnoDB;");
+ $this->emit('\OC\Repair', 'info', array("Fixed $table"));
+ }
+ }
+ }
+
+ /**
+ * @param \Doctrine\DBAL\Connection $connection
+ * @return string[]
+ */
+ private function getAllMyIsamTables($connection) {
+ $dbName = \OC::$server->getConfig()->getSystemValue("dbname");
+ $result = $connection->fetchArray(
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND engine = 'MyISAM' AND TABLE_NAME LIKE \"*PREFIX*%\"",
+ array($dbName)
+ );
+
+ return $result;
+ }
+}
+
diff --git a/lib/private/repair/oldgroupmembershipshares.php b/lib/private/repair/oldgroupmembershipshares.php
new file mode 100644
index 00000000000..2d701ac9fb7
--- /dev/null
+++ b/lib/private/repair/oldgroupmembershipshares.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+
+use OC\Hooks\BasicEmitter;
+use OC\RepairStep;
+use OCP\IDBConnection;
+use OCP\IGroupManager;
+use OCP\Share;
+
+class OldGroupMembershipShares extends BasicEmitter implements RepairStep {
+
+ /** @var \OCP\IDBConnection */
+ protected $connection;
+
+ /** @var \OCP\IGroupManager */
+ protected $groupManager;
+
+ /**
+ * @var array [gid => [uid => (bool)]]
+ */
+ protected $memberships;
+
+ /**
+ * @param IDBConnection $connection
+ * @param IGroupManager $groupManager
+ */
+ public function __construct(IDBConnection $connection, IGroupManager $groupManager) {
+ $this->connection = $connection;
+ $this->groupManager = $groupManager;
+ }
+
+ /**
+ * Returns the step's name
+ *
+ * @return string
+ */
+ public function getName() {
+ return 'Remove shares of old group memberships';
+ }
+
+ /**
+ * Run repair step.
+ * Must throw exception on error.
+ *
+ * @throws \Exception in case of failure
+ */
+ public function run() {
+ $deletedEntries = 0;
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select(['s1.id', $query->createFunction('s1.`share_with` AS `user`'), $query->createFunction('s2.`share_with` AS `group`')])
+ ->from('share', 's1')
+ ->where($query->expr()->isNotNull('s1.parent'))
+ // \OC\Share\Constant::$shareTypeGroupUserUnique === 2
+ ->andWhere($query->expr()->eq('s1.share_type', $query->expr()->literal(2)))
+ ->andWhere($query->expr()->isNotNull('s2.id'))
+ ->andWhere($query->expr()->eq('s2.share_type', $query->expr()->literal(Share::SHARE_TYPE_GROUP)))
+ ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'));
+
+ $deleteQuery = $this->connection->getQueryBuilder();
+ $deleteQuery->delete('share')
+ ->where($query->expr()->eq('id', $deleteQuery->createParameter('share')));
+
+ $result = $query->execute();
+ while ($row = $result->fetch()) {
+ if (!$this->isMember($row['group'], $row['user'])) {
+ $deletedEntries += $deleteQuery->setParameter('share', (int) $row['id'])
+ ->execute();
+ }
+ }
+ $result->closeCursor();
+
+ if ($deletedEntries) {
+ $this->emit('\OC\Repair', 'info', array('Removed ' . $deletedEntries . ' shares where user is not a member of the group anymore'));
+ }
+ }
+
+ /**
+ * @param string $gid
+ * @param string $uid
+ * @return bool
+ */
+ protected function isMember($gid, $uid) {
+ if (isset($this->memberships[$gid][$uid])) {
+ return $this->memberships[$gid][$uid];
+ }
+
+ $isMember = $this->groupManager->isInGroup($uid, $gid);
+ if (!isset($this->memberships[$gid])) {
+ $this->memberships[$gid] = [];
+ }
+ $this->memberships[$gid][$uid] = $isMember;
+
+ return $isMember;
+ }
+}
diff --git a/lib/private/repair/preview.php b/lib/private/repair/preview.php
new file mode 100644
index 00000000000..2284da93734
--- /dev/null
+++ b/lib/private/repair/preview.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @author Georg Ehrke <georg@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OC\Repair;
+
+use OC\Files\View;
+use OC\Hooks\BasicEmitter;
+
+class Preview extends BasicEmitter implements \OC\RepairStep {
+
+ public function getName() {
+ return 'Cleaning-up broken previews';
+ }
+
+ public function run() {
+ $view = new View('/');
+ $children = $view->getDirectoryContent('/');
+
+ foreach ($children as $child) {
+ if ($view->is_dir($child->getPath())) {
+ $thumbnailsFolder = $child->getPath() . '/thumbnails';
+ if ($view->is_dir($thumbnailsFolder)) {
+ $view->rmdir($thumbnailsFolder);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/private/repair/removegetetagentries.php b/lib/private/repair/removegetetagentries.php
new file mode 100644
index 00000000000..40040763654
--- /dev/null
+++ b/lib/private/repair/removegetetagentries.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Hooks\BasicEmitter;
+use OCP\IDBConnection;
+
+class RemoveGetETagEntries extends BasicEmitter {
+
+ /**
+ * @var IDBConnection
+ */
+ protected $connection;
+
+ /**
+ * @param IDBConnection $connection
+ */
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ }
+
+ public function getName() {
+ return 'Remove getetag entries in properties table';
+ }
+
+ /**
+ * Removes all entries with the key "{DAV:}getetag" from the table properties
+ */
+ public function run() {
+ $sql = 'DELETE FROM `*PREFIX*properties`'
+ . ' WHERE `propertyname` = ?';
+ $deletedRows = $this->connection->executeUpdate($sql, ['{DAV:}getetag']);
+
+ $this->emit(
+ '\OC\Repair',
+ 'info',
+ ['Removed ' . $deletedRows . ' unneeded "{DAV:}getetag" entries from properties table.']
+ );
+ }
+}
diff --git a/lib/private/repair/repairinvalidshares.php b/lib/private/repair/repairinvalidshares.php
new file mode 100644
index 00000000000..5a4cb445ce9
--- /dev/null
+++ b/lib/private/repair/repairinvalidshares.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Hooks\BasicEmitter;
+
+/**
+ * Repairs shares with invalid data
+ */
+class RepairInvalidShares extends BasicEmitter implements \OC\RepairStep {
+
+ /**
+ * @var \OCP\IConfig
+ */
+ protected $config;
+
+ /**
+ * @var \OCP\IDBConnection
+ */
+ protected $connection;
+
+ /**
+ * @param \OCP\IConfig $config
+ * @param \OCP\IDBConnection $connection
+ */
+ public function __construct($config, $connection) {
+ $this->connection = $connection;
+ $this->config = $config;
+ }
+
+ public function getName() {
+ return 'Repair invalid shares';
+ }
+
+ /**
+ * Past bugs would make it possible to set an expiration date on user shares even
+ * though it is not supported. This functions removes the expiration date from such entries.
+ */
+ private function removeExpirationDateFromNonLinkShares() {
+ $builder = $this->connection->getQueryBuilder();
+ $builder
+ ->update('share')
+ ->set('expiration', 'null')
+ ->where($builder->expr()->isNotNull('expiration'))
+ ->andWhere($builder->expr()->neq('share_type', $builder->expr()->literal(\OC\Share\Constants::SHARE_TYPE_LINK)));
+
+ $updatedEntries = $builder->execute();
+ if ($updatedEntries > 0) {
+ $this->emit('\OC\Repair', 'info', array('Removed invalid expiration date from ' . $updatedEntries . ' shares'));
+ }
+ }
+
+ /**
+ * Remove shares where the parent share does not exist anymore
+ */
+ private function removeSharesNonExistingParent() {
+ $deletedEntries = 0;
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select('s1.parent')
+ ->from('share', 's1')
+ ->where($query->expr()->isNotNull('s1.parent'))
+ ->andWhere($query->expr()->isNull('s2.id'))
+ ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id'))
+ ->groupBy('s1.parent');
+
+ $deleteQuery = $this->connection->getQueryBuilder();
+ $deleteQuery->delete('share')
+ ->where($query->expr()->eq('parent', $deleteQuery->createParameter('parent')));
+
+ $result = $query->execute();
+ while ($row = $result->fetch()) {
+ $deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent'])
+ ->execute();
+ }
+ $result->closeCursor();
+
+ if ($deletedEntries) {
+ $this->emit('\OC\Repair', 'info', array('Removed ' . $deletedEntries . ' shares where the parent did not exist'));
+ }
+ }
+
+ public function run() {
+ $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
+ if (version_compare($ocVersionFromBeforeUpdate, '8.2.0.7', '<')) {
+ // this situation was only possible before 8.2
+ $this->removeExpirationDateFromNonLinkShares();
+ }
+
+ $this->removeSharesNonExistingParent();
+ }
+}
diff --git a/lib/private/repair/repairlegacystorages.php b/lib/private/repair/repairlegacystorages.php
new file mode 100644
index 00000000000..5ba452cbbc6
--- /dev/null
+++ b/lib/private/repair/repairlegacystorages.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Files\Cache\Storage;
+use OC\Hooks\BasicEmitter;
+use OC\RepairException;
+
+class RepairLegacyStorages extends BasicEmitter {
+ /**
+ * @var \OCP\IConfig
+ */
+ protected $config;
+
+ /**
+ * @var \OCP\IDBConnection
+ */
+ protected $connection;
+
+ protected $findStorageInCacheStatement;
+ protected $renameStorageStatement;
+
+ /**
+ * @param \OCP\IConfig $config
+ * @param \OCP\IDBConnection $connection
+ */
+ public function __construct($config, $connection) {
+ $this->connection = $connection;
+ $this->config = $config;
+
+ $this->findStorageInCacheStatement = $this->connection->prepare(
+ 'SELECT DISTINCT `storage` FROM `*PREFIX*filecache`'
+ . ' WHERE `storage` in (?, ?)'
+ );
+ $this->renameStorageStatement = $this->connection->prepare(
+ 'UPDATE `*PREFIX*storages`'
+ . ' SET `id` = ?'
+ . ' WHERE `id` = ?'
+ );
+ }
+
+ public function getName() {
+ return 'Repair legacy storages';
+ }
+
+ /**
+ * Extracts the user id from a legacy storage id
+ *
+ * @param string $storageId legacy storage id in the
+ * format "local::/path/to/datadir/userid"
+ * @return string user id extracted from the storage id
+ */
+ private function extractUserId($storageId) {
+ $storageId = rtrim($storageId, '/');
+ $pos = strrpos($storageId, '/');
+ return substr($storageId, $pos + 1);
+ }
+
+ /**
+ * Fix the given legacy storage by renaming the old id
+ * to the new id. If the new id already exists, whichever
+ * storage that has data in the file cache will be used.
+ * If both have data, nothing will be done and false is
+ * returned.
+ *
+ * @param string $oldId old storage id
+ * @param int $oldNumericId old storage numeric id
+ * @param string $userId
+ * @return bool true if fixed, false otherwise
+ * @throws RepairException
+ */
+ private function fixLegacyStorage($oldId, $oldNumericId, $userId = null) {
+ // check whether the new storage already exists
+ if (is_null($userId)) {
+ $userId = $this->extractUserId($oldId);
+ }
+ $newId = 'home::' . $userId;
+
+ // check if target id already exists
+ $newNumericId = Storage::getNumericStorageId($newId);
+ if (!is_null($newNumericId)) {
+ $newNumericId = (int)$newNumericId;
+ // try and resolve the conflict
+ // check which one of "local::" or "home::" needs to be kept
+ $this->findStorageInCacheStatement->execute(array($oldNumericId, $newNumericId));
+ $row1 = $this->findStorageInCacheStatement->fetch();
+ $row2 = $this->findStorageInCacheStatement->fetch();
+ $this->findStorageInCacheStatement->closeCursor();
+ if ($row2 !== false) {
+ // two results means both storages have data, not auto-fixable
+ throw new RepairException(
+ 'Could not automatically fix legacy storage '
+ . '"' . $oldId . '" => "' . $newId . '"'
+ . ' because they both have data.'
+ );
+ }
+ if ($row1 === false || (int)$row1['storage'] === $oldNumericId) {
+ // old storage has data, then delete the empty new id
+ $toDelete = $newId;
+ } else if ((int)$row1['storage'] === $newNumericId) {
+ // new storage has data, then delete the empty old id
+ $toDelete = $oldId;
+ } else {
+ // unknown case, do not continue
+ return false;
+ }
+
+ // delete storage including file cache
+ Storage::remove($toDelete);
+
+ // if we deleted the old id, the new id will be used
+ // automatically
+ if ($toDelete === $oldId) {
+ // nothing more to do
+ return true;
+ }
+ }
+
+ // rename old id to new id
+ $newId = Storage::adjustStorageId($newId);
+ $oldId = Storage::adjustStorageId($oldId);
+ $rowCount = $this->renameStorageStatement->execute(array($newId, $oldId));
+ $this->renameStorageStatement->closeCursor();
+ return ($rowCount === 1);
+ }
+
+ /**
+ * Converts legacy home storage ids in the format
+ * "local::/data/dir/path/userid/" to the new format "home::userid"
+ */
+ public function run() {
+ // only run once
+ if ($this->config->getAppValue('core', 'repairlegacystoragesdone') === 'yes') {
+ return;
+ }
+
+ $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/');
+ $dataDir = rtrim($dataDir, '/') . '/';
+ $dataDirId = 'local::' . $dataDir;
+
+ $count = 0;
+ $hasWarnings = false;
+
+ $this->connection->beginTransaction();
+
+ // note: not doing a direct UPDATE with the REPLACE function
+ // because regexp search/extract is needed and it is not guaranteed
+ // to work on all database types
+ $sql = 'SELECT `id`, `numeric_id` FROM `*PREFIX*storages`'
+ . ' WHERE `id` LIKE ?'
+ . ' ORDER BY `id`';
+ $result = $this->connection->executeQuery($sql, array($dataDirId . '%'));
+
+ while ($row = $result->fetch()) {
+ $currentId = $row['id'];
+ // one entry is the datadir itself
+ if ($currentId === $dataDirId) {
+ continue;
+ }
+
+ try {
+ if ($this->fixLegacyStorage($currentId, (int)$row['numeric_id'])) {
+ $count++;
+ }
+ }
+ catch (RepairException $e) {
+ $hasWarnings = true;
+ $this->emit(
+ '\OC\Repair',
+ 'warning',
+ array('Could not repair legacy storage ' . $currentId . ' automatically.')
+ );
+ }
+ }
+
+ // check for md5 ids, not in the format "prefix::"
+ $sql = 'SELECT COUNT(*) AS "c" FROM `*PREFIX*storages`'
+ . ' WHERE `id` NOT LIKE \'%::%\'';
+ $result = $this->connection->executeQuery($sql);
+ $row = $result->fetch();
+
+ // find at least one to make sure it's worth
+ // querying the user list
+ if ((int)$row['c'] > 0) {
+ $userManager = \OC::$server->getUserManager();
+
+ // use chunks to avoid caching too many users in memory
+ $limit = 30;
+ $offset = 0;
+
+ do {
+ // query the next page of users
+ $results = $userManager->search('', $limit, $offset);
+ $storageIds = array();
+ foreach ($results as $uid => $userObject) {
+ $storageId = $dataDirId . $uid . '/';
+ if (strlen($storageId) <= 64) {
+ // skip short storage ids as they were handled in the previous section
+ continue;
+ }
+ $storageIds[$uid] = $storageId;
+ }
+
+ if (count($storageIds) > 0) {
+ // update the storages of these users
+ foreach ($storageIds as $uid => $storageId) {
+ $numericId = Storage::getNumericStorageId($storageId);
+ try {
+ if (!is_null($numericId) && $this->fixLegacyStorage($storageId, (int)$numericId)) {
+ $count++;
+ }
+ }
+ catch (RepairException $e) {
+ $hasWarnings = true;
+ $this->emit(
+ '\OC\Repair',
+ 'warning',
+ array('Could not repair legacy storage ' . $storageId . ' automatically.')
+ );
+ }
+ }
+ }
+ $offset += $limit;
+ } while (count($results) >= $limit);
+ }
+
+ $this->emit('\OC\Repair', 'info', array('Updated ' . $count . ' legacy home storage ids'));
+
+ $this->connection->commit();
+
+ if ($hasWarnings) {
+ $this->emit(
+ '\OC\Repair',
+ 'warning',
+ array('Some legacy storages could not be repaired. Please manually fix them then re-run ./occ maintenance:repair')
+ );
+ } else {
+ // if all were done, no need to redo the repair during next upgrade
+ $this->config->setAppValue('core', 'repairlegacystoragesdone', 'yes');
+ }
+ }
+}
diff --git a/lib/private/repair/repairmimetypes.php b/lib/private/repair/repairmimetypes.php
new file mode 100644
index 00000000000..e687dbde688
--- /dev/null
+++ b/lib/private/repair/repairmimetypes.php
@@ -0,0 +1,360 @@
+<?php
+/**
+ * @author Joas Schilling <nickvergessen@owncloud.com>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Normal Ra <normalraw@gmail.com>
+ * @author Olivier Paroz <github@oparoz.com>
+ * @author Roeland Jago Douma <rullzer@owncloud.com>
+ * @author Victor Dubiniuk <dubiniuk@owncloud.com>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Hooks\BasicEmitter;
+
+class RepairMimeTypes extends BasicEmitter implements \OC\RepairStep {
+ /**
+ * @var \OCP\IConfig
+ */
+ protected $config;
+
+ /**
+ * @var int
+ */
+ protected $folderMimeTypeId;
+
+ /**
+ * @param \OCP\IConfig $config
+ */
+ public function __construct($config) {
+ $this->config = $config;
+ }
+
+ public function getName() {
+ return 'Repair mime types';
+ }
+
+ private static function existsStmt() {
+ return \OC_DB::prepare('
+ SELECT count(`mimetype`)
+ FROM `*PREFIX*mimetypes`
+ WHERE `mimetype` = ?
+ ');
+ }
+
+ private static function getIdStmt() {
+ return \OC_DB::prepare('
+ SELECT `id`
+ FROM `*PREFIX*mimetypes`
+ WHERE `mimetype` = ?
+ ');
+ }
+
+ private static function insertStmt() {
+ return \OC_DB::prepare('
+ INSERT INTO `*PREFIX*mimetypes` ( `mimetype` )
+ VALUES ( ? )
+ ');
+ }
+
+ private static function updateWrongStmt() {
+ return \OC_DB::prepare('
+ UPDATE `*PREFIX*filecache`
+ SET `mimetype` = (
+ SELECT `id`
+ FROM `*PREFIX*mimetypes`
+ WHERE `mimetype` = ?
+ ) WHERE `mimetype` = ?
+ ');
+ }
+
+ private static function deleteStmt() {
+ return \OC_DB::prepare('
+ DELETE FROM `*PREFIX*mimetypes`
+ WHERE `id` = ?
+ ');
+ }
+
+ private static function updateByNameStmt() {
+ return \OC_DB::prepare('
+ UPDATE `*PREFIX*filecache`
+ SET `mimetype` = ?
+ WHERE `mimetype` <> ? AND `mimetype` <> ? AND `name` ILIKE ?
+ ');
+ }
+
+ private function repairMimetypes($wrongMimetypes) {
+ foreach ($wrongMimetypes as $wrong => $correct) {
+ // do we need to remove a wrong mimetype?
+ $result = \OC_DB::executeAudited(self::getIdStmt(), array($wrong));
+ $wrongId = $result->fetchOne();
+
+ if ($wrongId !== false) {
+ // do we need to insert the correct mimetype?
+ $result = \OC_DB::executeAudited(self::existsStmt(), array($correct));
+ $exists = $result->fetchOne();
+
+ if (!is_null($correct)) {
+ if (!$exists) {
+ // insert mimetype
+ \OC_DB::executeAudited(self::insertStmt(), array($correct));
+ }
+
+ // change wrong mimetype to correct mimetype in filecache
+ \OC_DB::executeAudited(self::updateWrongStmt(), array($correct, $wrongId));
+ }
+
+ // delete wrong mimetype
+ \OC_DB::executeAudited(self::deleteStmt(), array($wrongId));
+
+ }
+ }
+ }
+
+ private function updateMimetypes($updatedMimetypes) {
+ if (empty($this->folderMimeTypeId)) {
+ $result = \OC_DB::executeAudited(self::getIdStmt(), array('httpd/unix-directory'));
+ $this->folderMimeTypeId = (int)$result->fetchOne();
+ }
+
+ foreach ($updatedMimetypes as $extension => $mimetype) {
+ $result = \OC_DB::executeAudited(self::existsStmt(), array($mimetype));
+ $exists = $result->fetchOne();
+
+ if (!$exists) {
+ // insert mimetype
+ \OC_DB::executeAudited(self::insertStmt(), array($mimetype));
+ }
+
+ // get target mimetype id
+ $result = \OC_DB::executeAudited(self::getIdStmt(), array($mimetype));
+ $mimetypeId = $result->fetchOne();
+
+ // change mimetype for files with x extension
+ \OC_DB::executeAudited(self::updateByNameStmt(), array($mimetypeId, $this->folderMimeTypeId, $mimetypeId, '%.' . $extension));
+ }
+ }
+
+ private function fixOfficeMimeTypes() {
+ // update wrong mimetypes
+ $wrongMimetypes = array(
+ 'application/mspowerpoint' => 'application/vnd.ms-powerpoint',
+ 'application/msexcel' => 'application/vnd.ms-excel',
+ );
+
+ self::repairMimetypes($wrongMimetypes);
+
+ $updatedMimetypes = array(
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ );
+
+
+ // separate doc from docx etc
+ self::updateMimetypes($updatedMimetypes);
+
+ }
+
+ private function fixApkMimeType() {
+ $updatedMimetypes = array(
+ 'apk' => 'application/vnd.android.package-archive',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function fixFontsMimeTypes() {
+ // update wrong mimetypes
+ $wrongMimetypes = array(
+ 'font' => null,
+ 'font/opentype' => 'application/font-sfnt',
+ 'application/x-font-ttf' => 'application/font-sfnt',
+ );
+
+ self::repairMimetypes($wrongMimetypes);
+
+ $updatedMimetypes = array(
+ 'ttf' => 'application/font-sfnt',
+ 'otf' => 'application/font-sfnt',
+ 'pfb' => 'application/x-font',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function fixPostscriptMimeType() {
+ $updatedMimetypes = array(
+ 'eps' => 'application/postscript',
+ 'ps' => 'application/postscript',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceRawMimeType() {
+ $updatedMimetypes = array(
+ 'arw' => 'image/x-dcraw',
+ 'cr2' => 'image/x-dcraw',
+ 'dcr' => 'image/x-dcraw',
+ 'dng' => 'image/x-dcraw',
+ 'erf' => 'image/x-dcraw',
+ 'iiq' => 'image/x-dcraw',
+ 'k25' => 'image/x-dcraw',
+ 'kdc' => 'image/x-dcraw',
+ 'mef' => 'image/x-dcraw',
+ 'nef' => 'image/x-dcraw',
+ 'orf' => 'image/x-dcraw',
+ 'pef' => 'image/x-dcraw',
+ 'raf' => 'image/x-dcraw',
+ 'rw2' => 'image/x-dcraw',
+ 'srf' => 'image/x-dcraw',
+ 'sr2' => 'image/x-dcraw',
+ 'xrf' => 'image/x-dcraw',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduce3dImagesMimeType() {
+ $updatedMimetypes = array(
+ 'jps' => 'image/jpeg',
+ 'mpo' => 'image/jpeg',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceConfMimeType() {
+ $updatedMimetypes = array(
+ 'conf' => 'text/plain',
+ 'cnf' => 'text/plain',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceYamlMimeType() {
+ $updatedMimetypes = array(
+ 'yaml' => 'application/yaml',
+ 'yml' => 'application/yaml',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceJavaMimeType() {
+ $updatedMimetypes = array(
+ 'class' => 'application/java',
+ 'java' => 'text/x-java-source',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceHppMimeType() {
+ $updatedMimetypes = array(
+ 'hpp' => 'text/x-h',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceRssMimeType() {
+ $updatedMimetypes = array(
+ 'rss' => 'application/rss+xml',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ private function introduceRtfMimeType() {
+ $updatedMimetypes = array(
+ 'rtf' => 'text/rtf',
+ );
+
+ self::updateMimetypes($updatedMimetypes);
+ }
+
+ /**
+ * Fix mime types
+ */
+ public function run() {
+
+ $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
+
+ // NOTE TO DEVELOPERS: when adding new mime types, please make sure to
+ // add a version comparison to avoid doing it every time
+
+ // only update mime types if necessary as it can be expensive
+ if (version_compare($ocVersionFromBeforeUpdate, '8.2.0', '<')) {
+ if ($this->fixOfficeMimeTypes()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed office mime types'));
+ }
+
+ if ($this->fixApkMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed APK mime type'));
+ }
+
+ if ($this->fixFontsMimeTypes()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed fonts mime types'));
+ }
+
+ if ($this->fixPostscriptMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed Postscript mime types'));
+ }
+
+ if ($this->introduceRawMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed Raw mime types'));
+ }
+
+ if ($this->introduce3dImagesMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed 3D images mime types'));
+ }
+
+ if ($this->introduceConfMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed Conf/cnf mime types'));
+ }
+
+ if ($this->introduceYamlMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed Yaml/Yml mime types'));
+ }
+ }
+
+ // Mimetype updates from #19272
+ if (version_compare($ocVersionFromBeforeUpdate, '8.2.0.8', '<')) {
+ if ($this->introduceJavaMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed java/class mime types'));
+ }
+
+ if ($this->introduceHppMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed hpp mime type'));
+ }
+
+ if ($this->introduceRssMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed rss mime type'));
+ }
+
+ if ($this->introduceRtfMimeType()) {
+ $this->emit('\OC\Repair', 'info', array('Fixed rtf mime type'));
+ }
+ }
+ }
+}
diff --git a/lib/private/repair/searchlucenetables.php b/lib/private/repair/searchlucenetables.php
new file mode 100644
index 00000000000..5ae8a300246
--- /dev/null
+++ b/lib/private/repair/searchlucenetables.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Hooks\BasicEmitter;
+
+class SearchLuceneTables extends BasicEmitter implements \OC\RepairStep {
+
+ public function getName() {
+ return 'Repair duplicate entries in oc_lucene_status';
+ }
+
+ /**
+ * Fix duplicate entries in oc_lucene_status
+ *
+ * search_lucene prior to v0.5.0 did not have a primary key on the lucene_status table. Newer versions do, which
+ * causes the migration check to fail because it tries to insert duplicate rows into the new schema.
+ *
+ * FIXME Currently, apps don't have a way of repairing anything before the migration check:
+ * @link https://github.com/owncloud/core/issues/10980
+ *
+ * As a result this repair step needs to live in the core repo, although it belongs into search_lucene:
+ * @link https://github.com/owncloud/core/issues/10205#issuecomment-54957557
+ *
+ * It will completely remove any rows that make a file id have more than one status:
+ * fileid | status fileid | status
+ * --------+-------- will become --------+--------
+ * 2 | E 3 | E
+ * 2 | I
+ * 3 | E
+ *
+ * search_lucene will then reindex the fileids without a status when the next indexing job is executed
+ */
+ public function run() {
+ if (\OC_DB::tableExists('lucene_status')) {
+ $this->emit('\OC\Repair', 'info', array('removing duplicate entries from lucene_status'));
+
+ $connection = \OC_DB::getConnection();
+ $query = $connection->prepare('
+ DELETE FROM `*PREFIX*lucene_status`
+ WHERE `fileid` IN (
+ SELECT `fileid`
+ FROM (
+ SELECT `fileid`
+ FROM `*PREFIX*lucene_status`
+ GROUP BY `fileid`
+ HAVING count(`fileid`) > 1
+ ) AS `mysqlerr1093hack`
+ )');
+ $query->execute();
+ } else {
+ $this->emit('\OC\Repair', 'info', array('lucene_status table does not exist -> nothing to do'));
+ }
+ }
+
+}
+
diff --git a/lib/private/repair/sqliteautoincrement.php b/lib/private/repair/sqliteautoincrement.php
new file mode 100644
index 00000000000..70d0adae5d7
--- /dev/null
+++ b/lib/private/repair/sqliteautoincrement.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use Doctrine\DBAL\Platforms\SqlitePlatform;
+use Doctrine\DBAL\Schema\SchemaException;
+use Doctrine\DBAL\Schema\SchemaDiff;
+use Doctrine\DBAL\Schema\TableDiff;
+use Doctrine\DBAL\Schema\ColumnDiff;
+use OC\Hooks\BasicEmitter;
+
+/**
+ * Fixes Sqlite autoincrement by forcing the SQLite table schemas to be
+ * altered in order to retrigger SQL schema generation through OCSqlitePlatform.
+ */
+class SqliteAutoincrement extends BasicEmitter implements \OC\RepairStep {
+ /**
+ * @var \OC\DB\Connection
+ */
+ protected $connection;
+
+ /**
+ * @param \OC\DB\Connection $connection
+ */
+ public function __construct($connection) {
+ $this->connection = $connection;
+ }
+
+ public function getName() {
+ return 'Repair SQLite autoincrement';
+ }
+
+ /**
+ * Fix mime types
+ */
+ public function run() {
+ if (!$this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
+ return;
+ }
+
+ $sourceSchema = $this->connection->getSchemaManager()->createSchema();
+
+ $schemaDiff = new SchemaDiff();
+
+ foreach ($sourceSchema->getTables() as $tableSchema) {
+ $primaryKey = $tableSchema->getPrimaryKey();
+ if (!$primaryKey) {
+ continue;
+ }
+
+ $columnNames = $primaryKey->getColumns();
+
+ // add a column diff for every primary key column,
+ // but do not actually change anything, this will
+ // force the generation of SQL statements to alter
+ // those tables, which will then trigger the
+ // specific SQL code from OCSqlitePlatform
+ try {
+ $tableDiff = new TableDiff($tableSchema->getName());
+ $tableDiff->fromTable = $tableSchema;
+ foreach ($columnNames as $columnName) {
+ $columnSchema = $tableSchema->getColumn($columnName);
+ $columnDiff = new ColumnDiff($columnSchema->getName(), $columnSchema);
+ $tableDiff->changedColumns[] = $columnDiff;
+ $schemaDiff->changedTables[] = $tableDiff;
+ }
+ } catch (SchemaException $e) {
+ // ignore
+ }
+ }
+
+ $this->connection->beginTransaction();
+ foreach ($schemaDiff->toSql($this->connection->getDatabasePlatform()) as $sql) {
+ $this->connection->query($sql);
+ }
+ $this->connection->commit();
+ }
+}
+
diff --git a/lib/private/repair/updatecertificatestore.php b/lib/private/repair/updatecertificatestore.php
new file mode 100644
index 00000000000..5fad309a959
--- /dev/null
+++ b/lib/private/repair/updatecertificatestore.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Files\View;
+use OC\Hooks\BasicEmitter;
+use OC\RepairStep;
+use OC\Server;
+use OCP\IConfig;
+
+/**
+ * Class UpdateCertificateStore rewrites the user specific certificate store after
+ * an update has been performed. This is done because a new root certificate file
+ * might have been added.
+ *
+ * @package OC\Repair
+ */
+class UpdateCertificateStore extends BasicEmitter implements RepairStep {
+ /**
+ * FIXME: The certificate manager does only allow specifying the user
+ * within the constructor. This makes DI impossible.
+ * @var Server
+ */
+ protected $server;
+ /** @var IConfig */
+ protected $config;
+
+ /**
+ * @param Server $server
+ * @param IConfig $config
+ */
+ public function __construct(Server $server,
+ IConfig $config) {
+ $this->server = $server;
+ $this->config = $config;
+ }
+
+ /** {@inheritDoc} */
+ public function getName() {
+ return 'Update user certificate stores with new root certificates';
+ }
+
+ /** {@inheritDoc} */
+ public function run() {
+ $rootView = new View();
+ $dataDirectory = $this->config->getSystemValue('datadirectory', null);
+ if(is_null($dataDirectory)) {
+ throw new \Exception('No data directory specified');
+ }
+
+ $pathToRootCerts = '/files_external/rootcerts.crt';
+
+ foreach($rootView->getDirectoryContent('', 'httpd/unix-directory') as $fileInfo) {
+ $uid = trim($fileInfo->getPath(), '/');
+ if($rootView->file_exists($uid . $pathToRootCerts)) {
+ // Delete the existing root certificate
+ $rootView->unlink($uid . $pathToRootCerts);
+
+ /**
+ * FIXME: The certificate manager does only allow specifying the user
+ * within the constructor. This makes DI impossible.
+ */
+ // Regenerate the certificates
+ $certificateManager = $this->server->getCertificateManager($uid);
+ $certificateManager->createCertificateBundle();
+ }
+ }
+ }
+}
diff --git a/lib/private/repair/updateoutdatedocsids.php b/lib/private/repair/updateoutdatedocsids.php
new file mode 100644
index 00000000000..5f6ee029536
--- /dev/null
+++ b/lib/private/repair/updateoutdatedocsids.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\Repair;
+
+use OC\Hooks\BasicEmitter;
+use OC\RepairStep;
+use OCP\IConfig;
+
+/**
+ * Class UpdateOutdatedOcsIds is used to update invalid outdated OCS IDs, this is
+ * for example the case when an application has had another OCS ID in the past such
+ * as for contacts and calendar when apps.owncloud.com migrated to a unified identifier
+ * for multiple versions.
+ *
+ * @package OC\Repair
+ */
+class UpdateOutdatedOcsIds extends BasicEmitter implements RepairStep {
+ /** @var IConfig */
+ private $config;
+
+ /**
+ * @param IConfig $config
+ */
+ public function __construct(IConfig $config) {
+ $this->config = $config;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName() {
+ return 'Repair outdated OCS IDs';
+ }
+
+ /**
+ * @param string $appName
+ * @param string $oldId
+ * @param string $newId
+ * @return bool True if updated, false otherwise
+ */
+ public function fixOcsId($appName, $oldId, $newId) {
+ $existingId = $this->config->getAppValue($appName, 'ocsid');
+
+ if($existingId === $oldId) {
+ $this->config->setAppValue($appName, 'ocsid', $newId);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function run() {
+ $appsToUpdate = [
+ 'contacts' => [
+ 'old' => '166044',
+ 'new' => '168708',
+ ],
+ 'calendar' => [
+ 'old' => '166043',
+ 'new' => '168707',
+ ],
+ 'bookmarks' => [
+ 'old' => '166042',
+ 'new' => '168710',
+ ],
+ 'search_lucene' => [
+ 'old' => '166057',
+ 'new' => '168709',
+ ],
+ 'documents' => [
+ 'old' => '166045',
+ 'new' => '168711',
+ ]
+ ];
+
+ foreach($appsToUpdate as $appName => $ids) {
+ if ($this->fixOcsId($appName, $ids['old'], $ids['new'])) {
+ $this->emit(
+ '\OC\Repair',
+ 'info',
+ [sprintf('Fixed invalid %s OCS id', $appName)]
+ );
+ }
+ }
+ }
+}