chore(dead-code): drop public methods nothing calls

Grep across src, templates, config, tests and assets (including the
getter/property forms Twig and forms resolve) found no caller for:
Account add/removeMailbox, add/removeMessageThread, isGmailWatchActive;
MailboxRepository find{Trash,Archive,Draft}MailboxForAccount,
getIdsOfActiveInboxMailboxesForUser; MessageThreadRepository
countForLabels; MessageRepository existsWithFromAddressInThread,
iterateForRecategorization; LabelRepository findVisibleTreeForUser;
ClientErrorRepository findOneByFingerprint; ImapFolderProvisioner
ensurePath; MailPresetProvider findByEmail; UserEntityModel plainPassword
(set/get and the property: every form field is mapped: false);
OnboardingStep requiresAdmin; MailProvider usesImap; ShareWindow
usesDates; ShareDetail jsCalendarProperty; HealthReport worstSeverity;
PushDeliveryOutcome reachedTheDevice; PushTransport addressedByUrl;
CategorySource helpKey; AiPrompts customisedCount; ResetReport
skippedTables; TwoFactorEnrolment qrContent; MicrosoftOAuthErrorTranslator
isAdminConsentRequired; IntegrationsGlobal forDownload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Paul LütznerandClaude Opus 5.5 committed 2026-09-23 19:25:12 +02:00
1 parent 4b885ec4b7
commit 87bc044557
22 files changed
-353

No files matched your search

-15
View File
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Domain\DTO\Health;
use App\Domain\Enum\Health\HealthSeverity;
/**
* Everything wrong with one user's accounts, at one moment.
@@ -39,20 +38,6 @@ final readonly class HealthReport
return [] === $this->issues;
}
/** The worst severity present, or null when there is nothing wrong. */
public function worstSeverity(): ?HealthSeverity
{
$worst = null;
foreach ($this->issues as $issue) {
if (null === $worst || $issue->severity->rank() < $worst->rank()) {
$worst = $issue->severity;
}
}
return $worst;
}
/**
* What the topbar counts.
*
-11
View File
@@ -317,17 +317,6 @@ enum MailProvider: string
};
}
/**
* Whether this provider is synced over IMAP at all.
*
* Both current providers are API-synced; the method exists so the IMAP
* syncer can ask the question instead of hard-coding provider checks.
*/
public function usesImap(): bool
{
return false;
}
public function imapHost(): ?string
{
return match ($this) {
-19
View File
@@ -34,25 +34,6 @@ enum ShareDetail: string
case Description = 'description';
case Participants = 'participants';
/**
* The JSCalendar property this case unlocks (RFC 8984 §4.2).
*
* Exhaustive with no default, so a fifth detail cannot be added without
* somebody deciding where in the canonical object it comes from — the one
* question that has to be answered before it can be revealed at all.
* Locations and participants are maps in JSCalendar rather than scalars;
* the reader flattens them, and the name here is the key it reaches for.
*/
public function jsCalendarProperty(): string
{
return match ($this) {
self::Title => 'title',
self::Location => 'locations',
self::Description => 'description',
self::Participants => 'participants',
};
}
/** Translation key for the checkbox label. */
public function transKey(): string
{
-16
View File
@@ -29,22 +29,6 @@ enum ShareWindow: string
/** Two dates the owner named, unchanging. */
case Fixed = 'fixed';
/**
* Whether the link's start and end dates are the ones that matter.
*
* Exhaustive with no default: a third shape — "this month", "the next
* quarter" — would need the form, the validation and the reader to agree
* about which columns it reads, and a fallthrough here would make it agree
* silently with whichever branch came last.
*/
public function usesDates(): bool
{
return match ($this) {
self::Rolling => false,
self::Fixed => true,
};
}
/** Translation key for the radio label. */
public function transKey(): string
{
-6
View File
@@ -59,12 +59,6 @@ enum CategorySource: string
return 'settings.sorting.source.' . $this->value;
}
/** Translation key for the line under it. */
public function helpKey(): string
{
return 'settings.sorting.source.' . $this->value . '_help';
}
/**
* Whatever was stored or posted, read charitably.
*
@@ -60,16 +60,4 @@ enum OnboardingStep: string
self::Integrations => 'fa-solid fa-cloud',
};
}
/**
* Whether the step configures the whole install rather than one person's
* mailbox. Only ROLE_ADMIN is offered these.
*/
public function requiresAdmin(): bool
{
return match ($this) {
self::AdminMailCredentials, self::AdminIntegrationCredentials, self::AdminAi => true,
default => false,
};
}
}
-13
View File
@@ -40,19 +40,6 @@ enum PushDeliveryOutcome: string
/** Nothing was sent: the transport is unconfigured, or the row cannot address it. */
case Skipped = 'skipped';
/**
* Whether this outcome means the device was reached.
*
* Stays a predicate rather than a `self::Accepted === $x` at each call
* site, because two of the three failures are not the caller's business
* to enumerate — a reader wants "did it arrive", and adding a fifth case
* would otherwise have to be found in every comparison.
*/
public function reachedTheDevice(): bool
{
return self::Accepted === $this;
}
/**
* The semantic colour this outcome is drawn in, named rather than spelled
* as classes: the admin table and the settings pane show the same four
-16
View File
@@ -33,20 +33,4 @@ enum PushTransport: string
/** FCM HTTP v1 — Google owns the device token. */
case Fcm = 'fcm';
/**
* Whether a subscription of this kind is addressed by a URL this server
* POSTs to, as opposed to a token handed to somebody else's gateway.
*
* Stays a method rather than a `url !== null` test on the entity: it is a
* property of the transport, and asking the row would answer "no" for a
* Web Push subscription that is merely half-built.
*/
public function addressedByUrl(): bool
{
return match ($this) {
self::WebPush => true,
self::Fcm => false,
};
}
}
-14
View File
@@ -11,8 +11,6 @@ class UserEntityModel
User::ROLE_ADMIN,
];
private ?string $plainPassword = null;
public function addRole(string $role): User
{
if ($this instanceof User) {
@@ -45,11 +43,6 @@ class UserEntityModel
throw new LogicException();
}
public function getPlainPassword(): ?string
{
return $this->plainPassword;
}
public function getName(): string
{
if (true === $this instanceof User) {
@@ -59,13 +52,6 @@ class UserEntityModel
throw new LogicException('Not a User');
}
public function setPlainPassword(?string $plainPassword): self
{
$this->plainPassword = $plainPassword;
return $this;
}
public function isDeleted(): ?bool
{
if (true === $this instanceof User) {
-14
View File
@@ -142,20 +142,6 @@ final class AiPrompts
};
}
/** Whether anything at all has been overridden. For the chip on the card. */
public function customisedCount(): int
{
$count = 0;
foreach (PromptSlot::cases() as $slot) {
if (null !== $this->of($slot)) {
++$count;
}
}
return $count;
}
/**
* One prompt, as it is worth storing: trimmed, capped, and null when empty.
*
-60
View File
@@ -414,50 +414,6 @@ class Account extends AccountModel
$this->updatedAt = new DateTimeImmutable();
}
public function addMailbox(Mailbox $mailbox): static
{
if (!$this->mailboxes->contains($mailbox)) {
$this->mailboxes->add($mailbox);
$mailbox->account = $this;
}
return $this;
}
public function removeMailbox(Mailbox $mailbox): static
{
if ($this->mailboxes->removeElement($mailbox)) {
// set the owning side to null (unless already changed)
if ($mailbox->account === $this) {
$mailbox->account = null;
}
}
return $this;
}
public function addMessageThread(MessageThread $messageThread): static
{
if (!$this->messageThreads->contains($messageThread)) {
$this->messageThreads->add($messageThread);
$messageThread->account = $this;
}
return $this;
}
public function removeMessageThread(MessageThread $messageThread): static
{
if ($this->messageThreads->removeElement($messageThread)) {
// set the owning side to null (unless already changed)
if ($messageThread->account === $this) {
$messageThread->account = null;
}
}
return $this;
}
/**
* Which calendar events extracted from this account's mail land on.
*
@@ -710,22 +666,6 @@ class Account extends AccountModel
return true === $this->isGmail() || true === $this->isMicrosoft();
}
/**
* Stays a method rather than becoming a property: this reads a timestamp
* and answers a question about it against the clock, which is an
* interpretation, not the mapped boolean column that $isActive is.
*/
public function isGmailWatchActive(): bool
{
$expiry = $this->gmailWatchExpiry;
if (null === $expiry) {
return false;
}
return $expiry > new DateTimeImmutable();
}
public function addAlias(EmailAlias $alias): static
{
if (false === $this->aliases->contains($alias)) {
-10
View File
@@ -164,16 +164,6 @@ class LabelRepository extends ServiceEntityRepository
->getResult();
}
/**
* Visible custom labels of a user in sidebar tree order.
*
* @return Label[]
*/
public function findVisibleTreeForUser(UserInterface $user): array
{
return $this->treeOrder($this->findVisibleForUser($user));
}
/**
* Resolve a sidebar path ("Work/Invoices") to the user's custom Label at
* that path. Candidates are narrowed by leaf name in SQL, the full parent
-35
View File
@@ -7,7 +7,6 @@ use App\Entity\Mail\Account;
use App\Entity\Mail\Mailbox;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @extends ServiceEntityRepository<Mailbox>
@@ -32,26 +31,11 @@ class MailboxRepository extends ServiceEntityRepository
return $indexed;
}
public function findTrashMailboxForAccount(Account $account): ?Mailbox
{
return $this->findOneBy(['account' => $account, 'specialUse' => MailboxSpecialUse::TRASH]);
}
public function findArchiveMailboxForAccount(Account $account): ?Mailbox
{
return $this->findOneBy(['account' => $account, 'specialUse' => MailboxSpecialUse::ARCHIVE]);
}
public function findSentMailboxForAccount(Account $account): ?Mailbox
{
return $this->findOneBy(['account' => $account, 'specialUse' => MailboxSpecialUse::SENT]);
}
public function findDraftMailboxForAccount(Account $account): ?Mailbox
{
return $this->findOneBy(['account' => $account, 'specialUse' => MailboxSpecialUse::DRAFTS]);
}
/**
* The oldest full-listing this account's folders have between them, or null
* if any of them has never had one.
@@ -130,25 +114,6 @@ class MailboxRepository extends ServiceEntityRepository
return $queryBuilder->getQuery()->getResult();
}
/**
* QueryBuilder for the join to Account and for the id-only projection —
* the caller compares sets of ids and has no use for a Mailbox.
*/
public function getIdsOfActiveInboxMailboxesForUser(UserInterface $user): array
{
return $this->createQueryBuilder('mailbox')
->select('mailbox.id')
->leftJoin('mailbox.account', 'account')
->where('account.isActive = :isActive')
->andWhere('account.usr = :usr')
->andWhere('mailbox.specialUse = :inbox')
->setParameter('isActive', true)
->setParameter('usr', $user)
->setParameter('inbox', MailboxSpecialUse::INBOX)
->getQuery()
->getSingleColumnResult();
}
/**
* Make a folder's deletion sweep due right now, without lying about what
* has been listed.
-24
View File
@@ -1696,11 +1696,6 @@ class MessageRepository extends ServiceEntityRepository
->getOneOrNullResult();
}
public function existsWithFromAddressInThread(string $fromAddress, MessageThread $thread): bool
{
return $this->existsWithAnyFromAddressInThread([$fromAddress], $thread);
}
/**
* Does the thread contain a message sent by any of these addresses?
*
@@ -2104,25 +2099,6 @@ class MessageRepository extends ServiceEntityRepository
return array_values(array_map(static fn ($id): string => (string) $id, $rows));
}
/**
* Streamed, so recategorising a large account does not load it into memory
* — the reason this cannot be findBy().
*
* @return iterable<Message>
*/
public function iterateForRecategorization(Account $account, bool $includeCategorized): iterable
{
$qb = $this->createQueryBuilder('m')
->where('m.account = :account')
->setParameter('account', $account);
if (false === $includeCategorized) {
$qb->andWhere('m.category IS NULL');
}
return $qb->getQuery()->toIterable();
}
/**
* QueryBuilder for the join to the owning user and for the single-column
* projection, as with findSyncedGmailIdsForUser().
@@ -1123,22 +1123,6 @@ class MessageThreadRepository extends ServiceEntityRepository
->getResult();
}
/**
* Same to-many membership test as findForLabels().
*
* @param Label[] $labels
*/
public function countForLabels(array $labels): int
{
return (int) $this->createQueryBuilder('thread')
->select('COUNT(DISTINCT thread.id)')
->innerJoin('thread.labels', 'label')
->where('label IN (:labels)')
->setParameter('labels', $labels)
->getQuery()
->getSingleScalarResult();
}
/**
* Initialize the labels collection of every given thread in ONE query so
* the list view's label chips don't lazy-load per row. Fetch-joining onto
@@ -40,11 +40,6 @@ final class ClientErrorRepository extends ServiceEntityRepository
return $rows;
}
public function findOneByFingerprint(string $fingerprint): ?ClientError
{
return $this->findOneBy(['fingerprint' => $fingerprint]);
}
public function countAll(): int
{
return (int) $this->createQueryBuilder('e')
@@ -130,34 +130,6 @@ readonly class ImapFolderProvisioner
return $this->create($account, $client, $path, $known?->specialUse, record: null === $known);
}
/**
* The full path for an arbitrary folder name — a custom label being pushed
* — creating it if need be.
*
* The name is a plMail label's full name, so its own "/" separators become
* the server's, which is what makes a nested label arrive as a nested
* folder rather than one with a slash in its name.
*/
public function ensurePath(Account $account, Client $client, string $labelFullName): ?string
{
$separator = $this->separatorFor($account);
$segments = array_values(array_filter(explode('/', $labelFullName), static fn (string $s): bool => '' !== $s));
if (0 === count($segments)) {
return null;
}
$path = $this->pathFor($account, implode($separator, $segments));
$existing = $this->mailboxes->findOneBy(['account' => $account, 'fullPath' => $path]);
if (null !== $existing) {
return $path;
}
return $this->create($account, $client, $path, null);
}
/**
* Issue the CREATE, subscribe, and record the folder locally.
*
-11
View File
@@ -59,17 +59,6 @@ final class MailPresetProvider
return $this->all()[$key] ?? null;
}
public function findByEmail(string $email): ?MailPreset
{
$at = strrpos($email, '@');
if (false === $at) {
return null;
}
return $this->findByDomain(substr($email, $at + 1));
}
public function findByDomain(string $domain): ?MailPreset
{
foreach ($this->all() as $preset) {
-8
View File
@@ -43,12 +43,4 @@ final readonly class ResetReport
{
return array_keys(array_filter($this->tables));
}
/**
* @return list<string>
*/
public function skippedTables(): array
{
return array_keys(array_filter($this->tables, static fn (bool $truncated): bool => false === $truncated));
}
}
@@ -65,11 +65,6 @@ final readonly class MicrosoftOAuthErrorTranslator
];
}
public function isAdminConsentRequired(?string $rawError): bool
{
return true === $this->translate($rawError)['adminActionable'];
}
private function extractCode(?string $rawError): ?string
{
if (null === $rawError || '' === $rawError) {
@@ -61,11 +61,6 @@ final readonly class TwoFactorEnrolment
return $this->totpAuthenticator->getQRContent($user);
}
public function qrContent(User $user): string
{
return $this->totpAuthenticator->getQRContent($user);
}
public function verifyCode(User $user, string $code): bool
{
if (null === $user->totpSecret) {
-10
View File
@@ -73,16 +73,6 @@ class IntegrationsGlobal implements ResetInterface
));
}
/**
* Connections a file can be pulled out of.
*
* @return list<Integration>
*/
public function forDownload(): array
{
return $this->supporting(Capability::Download);
}
/**
* @return list<Integration>
*/