diff --git a/lib/Command/Db/Rebuild.php b/lib/Command/Db/Rebuild.php index f6dae27155..d8448a1461 100644 --- a/lib/Command/Db/Rebuild.php +++ b/lib/Command/Db/Rebuild.php @@ -56,6 +56,7 @@ protected function runCommands(): int { $this->printComment('Step 2. Tidy records before rebuilding the schema'); $this->fixNullish(); + $this->migrateShareLabels(); $this->printComment('Step 3. Create or update tables to current shema'); $this->createOrUpdateSchema(); @@ -101,6 +102,12 @@ private function fixNullish(): void { $this->printInfo($messages, ' '); } + private function migrateShareLabels(): void { + $this->printComment(' - migrate share labels to displayname for public shares'); + $messages = $this->tableManager->migrateShareLabels(); + $this->printInfo($messages, ' '); + } + /** * Create index for $table */ diff --git a/lib/Db/Share.php b/lib/Db/Share.php index c586371318..88ed289485 100644 --- a/lib/Db/Share.php +++ b/lib/Db/Share.php @@ -42,8 +42,6 @@ * @method ?int getAnonymizedVotes() * @method int getDeleted() * @method void setDeleted(int $value) - * @method ?string getLabel() - * @method void setLabel(?string $value) */ class Share extends EntityWithUser implements JsonSerializable { /** @var string */ @@ -204,25 +202,32 @@ public function setPublicPollEmail(string $value): void { } /** - * Share label for public shares, falls back to username until migrated - * TODO: remove fallback after migration was introduced + * Share label for public shares, now stored as displayName + * TODO: remove after migration was introduced + */ + public function setLabel(string $label): void { + $this->setDisplayName($label); + $this->label = $label; + } + + /** + * Share label for public shares + * TODO: remove after migrating labels to displayName */ public function getLabel(): string { - if ($this->getType() === self::TYPE_PUBLIC && $this->label) { - return $this->label; - } - return $this->displayName ?? ''; + // In case of public poll use label as fallback for displayName + return $this->displayName ?? $this->label ?? ''; } /** * Sharee's displayName. In case of public poll label is used instead - * TODO: remove public poll chaeck after migration to label + * TODO: remove public poll check after migration labels to displayName */ public function getDisplayName(): string { if ($this->getType() === self::TYPE_PUBLIC) { - return ''; + return $this->getLabel(); } - return (string)$this->displayName; + return $this->displayName ?? ''; } public function getTimeZoneName(): string { diff --git a/lib/Db/V4/TableManager.php b/lib/Db/V4/TableManager.php index a63c163927..4003694410 100644 --- a/lib/Db/V4/TableManager.php +++ b/lib/Db/V4/TableManager.php @@ -213,15 +213,17 @@ public function removeObsoleteColumns(): array { foreach (TableSchema::GONE_COLUMNS as $tableName => $columns) { $tableName = $this->dbPrefix . $tableName; - if ($this->schema->hasTable($tableName)) { - $table = $this->schema->getTable($tableName); - - foreach ($columns as $columnName) { - if ($table->hasColumn($columnName)) { - $dropped = true; - $table->dropColumn($columnName); - $messages[] = 'Dropped ' . $columnName . ' from ' . $tableName; - } + if (!$this->schema->hasTable($tableName)) { + continue; + } + + $table = $this->schema->getTable($tableName); + + foreach ($columns as $columnName) { + if ($table->hasColumn($columnName)) { + $dropped = true; + $table->dropColumn($columnName); + $messages[] = 'Dropped ' . $columnName . ' from ' . $tableName; } } } @@ -375,33 +377,34 @@ private function deleteDuplicates(string $table, array $columns):int { $this->needsSchema(); $qb = $this->connection->getQueryBuilder(); - if ($this->schema->hasTable($this->dbPrefix . $table)) { - // identify duplicates - $selection = $qb->selectDistinct('t1.id') - ->from($table, 't1') - ->innerJoin('t1', $table, 't2', $qb->expr()->lt('t1.id', 't2.id')); + if (!$this->schema->hasTable($this->dbPrefix . $table)) { + return 0; + } - $i = 0; + // identify duplicates + $selection = $qb->selectDistinct('t1.id') + ->from($table, 't1') + ->innerJoin('t1', $table, 't2', $qb->expr()->lt('t1.id', 't2.id')); - foreach ($columns as $column) { - if ($i > 0) { - $selection->andWhere($qb->expr()->eq('t1.' . $column, 't2.' . $column)); - } else { - $selection->where($qb->expr()->eq('t1.' . $column, 't2.' . $column)); - } - $i++; + $i = 0; + + foreach ($columns as $column) { + if ($i > 0) { + $selection->andWhere($qb->expr()->eq('t1.' . $column, 't2.' . $column)); + } else { + $selection->where($qb->expr()->eq('t1.' . $column, 't2.' . $column)); } + $i++; + } - $duplicates = $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); + $duplicates = $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); - $this->connection->getQueryBuilder() - ->delete($table) - ->where('id in (:ids)') - ->setParameter('ids', $duplicates, IQueryBuilder::PARAM_INT_ARRAY) - ->executeStatement(); - return count($duplicates); - } - return 0; + $this->connection->getQueryBuilder() + ->delete($table) + ->where('id in (:ids)') + ->setParameter('ids', $duplicates, IQueryBuilder::PARAM_INT_ARRAY) + ->executeStatement(); + return count($duplicates); } /** @@ -423,19 +426,24 @@ public function removeObsoleteMigrations(): array { } public function fixVotes(): void { - if ($this->schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) { - $table = $this->schema->getTable($this->dbPrefix . OptionMapper::TABLE); - if ($table->hasColumn('duration')) { - $foundOptions = $this->optionMapper->findOptionsWithDuration(); - foreach ($foundOptions as $option) { - $this->voteMapper->fixVoteOptionText( - $option->getPollId(), - $option->getId(), - $option->getPollOptionTextStart(), - $option->getPollOptionText(), - ); - } - } + if (!$this->schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) { + return; + } + + $table = $this->schema->getTable($this->dbPrefix . OptionMapper::TABLE); + + if (!$table->hasColumn('duration')) { + return; + } + + $foundOptions = $this->optionMapper->findOptionsWithDuration(); + foreach ($foundOptions as $option) { + $this->voteMapper->fixVoteOptionText( + $option->getPollId(), + $option->getId(), + $option->getPollOptionTextStart(), + $option->getPollOptionText(), + ); } } @@ -548,58 +556,64 @@ public function setLastInteraction(?int $timestamp = null): string { */ private function updateVoteHashes(Schema &$schema): array { $messages = []; - if ($schema->hasTable($this->dbPrefix . VoteMapper::TABLE)) { - $table = $schema->getTable($this->dbPrefix . VoteMapper::TABLE); - $count = 0; - $updated = 0; - if ($table->hasColumn('vote_option_hash')) { - foreach ($this->voteMapper->getAll(includeNull: true) as $vote) { - try { - // if the hash of the vote differs from calculated hash update the vote hash - if ($vote->getVoteOptionHash() !== Hash::getOptionHash($vote->getPollId(), $vote->getVoteOptionText())) { - $vote->setVoteOptionHash(Hash::getOptionHash($vote->getPollId(), $vote->getVoteOptionText())); - $vote = $this->voteMapper->update($vote); - $updated++; - } - - $count++; - - } catch (Exception $e) { - $messages[] = 'Skip hash update - Error updating option hash for voteId ' . $vote->getId(); - $this->logger->error('Error updating option hash for voteId {id}', [ - 'id' => $vote->getId(), - 'message' => $e->getMessage() - ]); - } - } + if (!$schema->hasTable($this->dbPrefix . VoteMapper::TABLE)) { + $this->logger->error('{db} is missing- aborted recalculating hashes', [ + 'db' => $this->dbPrefix . VoteMapper::TABLE + ]); + $messages[] = 'Table ' . $this->dbPrefix . VoteMapper::TABLE . ' does not exist'; + return $messages; + } - if ($updated === 0) { - $this->logger->info('Verified {count} vote hashes in {db}', [ - 'count' => $count, - 'db' => $this->dbPrefix . VoteMapper::TABLE - ]); - $messages[] = 'No vote hashes to update'; + $table = $schema->getTable($this->dbPrefix . VoteMapper::TABLE); - } else { - $this->logger->info('Updated {updated} hashes of {count} votes in {db}', [ - 'updated' => $updated, - 'count' => $count, - 'db' => $this->dbPrefix . VoteMapper::TABLE - ]); - $messages[] = 'Updated ' . $updated . ' vote hashes'; + if (!$table->hasColumn('vote_option_hash')) { + $this->logger->error('{db} is missing column \'poll_option_hash\' - aborted recalculating hashes', [ + 'db' => $this->dbPrefix . VoteMapper::TABLE + ]); + $messages[] = 'Column \'vote_option_hash\' does not exist in ' . $this->dbPrefix . VoteMapper::TABLE; + return $messages; + } + + $count = 0; + $updated = 0; + foreach ($this->voteMapper->getAll(includeNull: true) as $vote) { + try { + // if the hash of the vote differs from calculated hash update the vote hash + if ($vote->getVoteOptionHash() !== Hash::getOptionHash($vote->getPollId(), $vote->getVoteOptionText())) { + $vote->setVoteOptionHash(Hash::getOptionHash($vote->getPollId(), $vote->getVoteOptionText())); + $vote = $this->voteMapper->update($vote); + $updated++; } - } else { - $this->logger->error('{db} is missing column \'poll_option_hash\' - aborted recalculating hashes', [ - 'db' => $this->dbPrefix . VoteMapper::TABLE + $count++; + + } catch (Exception $e) { + $messages[] = 'Skip hash update - Error updating option hash for voteId ' . $vote->getId(); + $this->logger->error('Error updating option hash for voteId {id}', [ + 'id' => $vote->getId(), + 'message' => $e->getMessage() ]); } + } + + if ($updated === 0) { + $this->logger->info('Verified {count} vote hashes in {db}', [ + 'count' => $count, + 'db' => $this->dbPrefix . VoteMapper::TABLE + ]); + $messages[] = 'No vote hashes to update'; + } else { - $this->logger->error('{db} is missing- aborted recalculating hashes', [ + $this->logger->info('Updated {updated} hashes of {count} votes in {db}', [ + 'updated' => $updated, + 'count' => $count, 'db' => $this->dbPrefix . VoteMapper::TABLE ]); + $messages[] = 'Updated ' . $updated . ' vote hashes'; + } + return $messages; } @@ -611,53 +625,56 @@ private function updateVoteHashes(Schema &$schema): array { private function updateOptionHashes(Schema &$schema): array { $messages = []; - if ($schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) { - $table = $schema->getTable($this->dbPrefix . OptionMapper::TABLE); - $count = 0; - $updated = 0; - - if ($table->hasColumn('poll_option_hash')) { - foreach ($this->optionMapper->getAll(includeNull: true) as $option) { - try { - // if the option's hash differs from $actualHash update the option - if ($option->getPollOptionHash() !== Hash::getOptionHash($option->getPollId(), $option->getPollOptionText())) { - $option->setPollOptionHash(Hash::getOptionHash($option->getPollId(), $option->getPollOptionText())); - $option = $this->optionMapper->update($option); - $updated++; - } - - $count++; - - } catch (Exception $e) { - $messages[] = 'Skip hash update - Error updating option hash for optionId ' . $option->getId(); - $this->logger->error('Error updating option hash for optionId {id}', ['id' => $option->getId(), 'message' => $e->getMessage()]); - } - } + if (!$schema->hasTable($this->dbPrefix . OptionMapper::TABLE)) { + $this->logger->error('{db} is missing - aborted recalculating hashes', [ 'db' => $this->dbPrefix . OptionMapper::TABLE]); + $messages[] = 'Table ' . $this->dbPrefix . OptionMapper::TABLE . ' does not exist'; + return $messages; + } + $table = $schema->getTable($this->dbPrefix . OptionMapper::TABLE); - if ($updated === 0) { - $this->logger->info('Verified {count} option hashes in {db}', [ - 'count' => $count, - 'db' => $this->dbPrefix . OptionMapper::TABLE - ]); - $messages[] = 'No option hashes to update'; + if (!$table->hasColumn('poll_option_hash')) { + $this->logger->error('{db} is missing column \'poll_option_hash\' - aborted recalculating hashes', [ 'db' => $this->dbPrefix . OptionMapper::TABLE]); + $messages[] = 'Column \'poll_option_hash\' does not exist in ' . $this->dbPrefix . OptionMapper::TABLE; + return $messages; + } - } else { - $this->logger->info('Updated {updated} hashes of {count} options in {db}', [ - 'updated' => $updated, - 'count' => $count, - 'db' => $this->dbPrefix . OptionMapper::TABLE - ]); - $messages[] = 'Updated ' . $updated . ' option hashes'; + $count = 0; + $updated = 0; + foreach ($this->optionMapper->getAll(includeNull: true) as $option) { + try { + // if the option's hash differs from $actualHash update the option + if ($option->getPollOptionHash() !== Hash::getOptionHash($option->getPollId(), $option->getPollOptionText())) { + $option->setPollOptionHash(Hash::getOptionHash($option->getPollId(), $option->getPollOptionText())); + $option = $this->optionMapper->update($option); + $updated++; } - } else { - $this->logger->error('{db} is missing column \'poll_option_hash\' - aborted recalculating hashes', [ 'db' => $this->dbPrefix . OptionMapper::TABLE]); + $count++; + + } catch (Exception $e) { + $messages[] = 'Skip hash update - Error updating option hash for optionId ' . $option->getId(); + $this->logger->error('Error updating option hash for optionId {id}', ['id' => $option->getId(), 'message' => $e->getMessage()]); } + } + + if ($updated === 0) { + $this->logger->info('Verified {count} option hashes in {db}', [ + 'count' => $count, + 'db' => $this->dbPrefix . OptionMapper::TABLE + ]); + $messages[] = 'No option hashes to update'; } else { - $this->logger->error('{db} is missing - aborted recalculating hashes', [ 'db' => $this->dbPrefix . OptionMapper::TABLE]); + $this->logger->info('Updated {updated} hashes of {count} options in {db}', [ + 'updated' => $updated, + 'count' => $count, + 'db' => $this->dbPrefix . OptionMapper::TABLE + ]); + $messages[] = 'Updated ' . $updated . ' option hashes'; + } + return $messages; } @@ -667,4 +684,52 @@ public function updateHashes(): array { $messages = array_merge($messages, $this->updateVoteHashes($schema)); return $messages; } + + /** + * @return string[] + * + * @psalm-return list{0?: string,...} + */ + public function migrateShareLabels(): array { + $schema = $this->connection->createSchema(); + $messages = []; + + if (!$schema->hasTable($this->dbPrefix . Share::TABLE)) { + $this->logger->error('{db} is missing - aborted migrating labels', [ 'db' => $this->dbPrefix . Share::TABLE]); + $messages[] = 'Table ' . $this->dbPrefix . Share::TABLE . ' does not exist'; + return $messages; + } + $table = $schema->getTable($this->dbPrefix . Share::TABLE); + + if (!$table->hasColumn('label')) { + $this->logger->error('{db} is missing column \'label\' - aborted migrating labels', [ 'db' => $this->dbPrefix . Share::TABLE]); + $messages[] = 'Column \'label\' does not exist in ' . $this->dbPrefix . Share::TABLE; + return $messages; + } + + $qb = $this->connection->getQueryBuilder(); + + $qb->update(Share::TABLE) + ->set('display_name', 'label') // safe: assigns column B's value into A + ->andWhere($qb->expr()->isNotNull(Share::TABLE . '.label')) + ->andWhere($qb->expr()->eq(Share::TABLE . '.label', $qb->expr()->literal(''))); + $updated = $qb->executeStatement(); + + if ($updated === 0) { + $this->logger->info('Verified all share labels in {db}', [ + 'db' => $this->dbPrefix . Share::TABLE + ]); + $messages[] = 'No share labels to update'; + + } else { + $this->logger->info('Updated {updated} labels in {db}', [ + 'updated' => $updated, + 'db' => $this->dbPrefix . Share::TABLE + ]); + $messages[] = 'Updated ' . $updated . ' option hashes'; + + } + + return $messages; + } } diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index 3cc72e7f16..7cfc718107 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -295,10 +295,6 @@ public function setLabel(string $label, string $token): Share { ->request(Poll::PERMISSION_POLL_EDIT); $this->share->setLabel($label); - // overwrite any possible displayName - // TODO: Remove afte rmigratiuon to label - $this->share->setDisplayName(''); - $dispatchEvent = new ShareChangedLabelEvent($this->share); } else { throw new InvalidShareTypeException('Label can only be set for public shares.'); diff --git a/src/components/Combo/ComboTable.vue b/src/components/Combo/ComboTable.vue index 3d093f8eaf..5d611dd52b 100644 --- a/src/components/Combo/ComboTable.vue +++ b/src/components/Combo/ComboTable.vue @@ -28,7 +28,7 @@ const comboStore = useComboStore() v-for="participant in comboStore.participantsInPoll(poll.id)" :key="`${participant.user.id}_${participant.pollId}`" class="participant"> - + diff --git a/src/components/Options/OptionItemOwner.vue b/src/components/Options/OptionItemOwner.vue index 5239af098f..4732f81570 100644 --- a/src/components/Options/OptionItemOwner.vue +++ b/src/components/Options/OptionItemOwner.vue @@ -51,7 +51,6 @@ const showDelete = computed( :user="option.owner" :icon-size="avatarSize" hide-names - hide-status :tooltip-message=" t('polls', '{displayName}\'s proposal', { displayName: option.owner?.displayName ?? '', diff --git a/src/components/Shares/ShareItem.vue b/src/components/Shares/ShareItem.vue index cf69f5fcd6..e328b939bb 100644 --- a/src/components/Shares/ShareItem.vue +++ b/src/components/Shares/ShareItem.vue @@ -4,7 +4,7 @@ --> + + + diff --git a/src/components/User/UserBubble.vue b/src/components/User/UserBubble.vue index 0da66990de..520d7a4ddf 100644 --- a/src/components/User/UserBubble.vue +++ b/src/components/User/UserBubble.vue @@ -9,31 +9,13 @@ import { t } from '@nextcloud/l10n' import NcUserBubble from '@nextcloud/vue/components/NcUserBubble' -import type { User } from '../../Types' +import { createDefault, type User } from '../../Types' interface Props { user: User } -const { - user = { - id: '', - displayName: '', - emailAddress: '', - isNoUser: true, - isAdmin: false, - isGuest: false, - type: '', - subName: null, - subtitle: null, - desc: null, - organisation: null, - languageCode: '', - localeCode: null, - timeZone: null, - categories: null, - }, -} = defineProps() +const { user = createDefault() } = defineProps() const bubbleProps = computed(() => ({ user: user.isNoUser || user.isGuest ? undefined : user.id, diff --git a/src/components/User/UserItem.vue b/src/components/User/UserItem.vue index 2c08888b76..4f6d3d2c0d 100644 --- a/src/components/User/UserItem.vue +++ b/src/components/User/UserItem.vue @@ -5,205 +5,86 @@