Browse Source

correct old bugs

gitcitadel
Silberengel 2 weeks ago
parent
commit
e462f05d55
  1. 10
      Dockerfile
  2. 4
      assets/controllers/progress_bar_controller.js
  3. 26
      assets/controllers/user_highlight_tooltip_controller.js
  4. 5
      src/Command/ArticleHighlightsAuditCommand.php
  5. 73
      src/Command/PrewarmCommand.php
  6. 13
      src/Controller/ArticleController.php
  7. 2
      src/Controller/AuthorController.php
  8. 4
      src/Controller/CommentReplyController.php
  9. 3
      src/Controller/DefaultController.php
  10. 10
      src/Controller/SeoController.php
  11. 9
      src/Form/DataTransformer/CommaSeparatedToArrayTransformer.php
  12. 2
      src/Nostr/MagazineEventKeys.php
  13. 10
      src/Nostr/Nip10Kind1ArticleReplyTags.php
  14. 8
      src/Nostr/Nip19Codec.php
  15. 4
      src/Nostr/Nip22CommentTags.php
  16. 2
      src/Repository/EventRepository.php
  17. 13
      src/Service/ArticleBodyHighlightInjector.php
  18. 16
      src/Service/ArticleCommentThreadLoader.php
  19. 22
      src/Service/CacheService.php
  20. 2
      src/Service/CommentReplyService.php
  21. 5
      src/Service/HighlightSyncService.php
  22. 38
      src/Service/MagazineContentService.php
  23. 4
      src/Service/MagazineRefresher.php
  24. 2
      src/Service/Nip05VerificationService.php
  25. 3
      src/Service/Nip09DeletionApplier.php
  26. 27
      src/Service/NostrClient.php
  27. 2
      src/Service/NostrShareMenuBuilder.php
  28. 10
      src/Service/ProfileIdentityLinksBuilder.php
  29. 8
      src/Service/ProfilePaymentLinksBuilder.php
  30. 3
      src/Twig/Components/IndexTabs.php
  31. 2
      src/Twig/Components/Molecules/CategoryLink.php
  32. 2
      src/Twig/Components/Organisms/FeaturedList.php
  33. 6
      src/Util/NostrEventTags.php
  34. 2
      tests/Service/ArticleBodyHighlightInjectorTest.php
  35. 3
      tests/Service/ArticleHighlightCommonMarkPipelineTest.php
  36. 4
      tests/bootstrap.php

10
Dockerfile

@ -108,10 +108,12 @@ RUN set -eux; \
composer dump-env prod; \ composer dump-env prod; \
rm -f .env; \ rm -f .env; \
# Strip deployment secrets from the compiled .env.local.php so they cannot be read from the # Strip deployment secrets from the compiled .env.local.php so they cannot be read from the
# image layers. APP_SECRET, DATABASE_URL, and MYSQL_* passwords must be injected as real # image layers. The listed keys must be injected as real environment variables at runtime;
# environment variables at runtime. If they are absent at runtime Symfony will raise an error # Symfony will raise a clear error rather than silently using the public .env.dist defaults.
# rather than silently using the public .env.dist defaults. # MAINTENANCE: if a new secret is added to .env.dist, add it here too so it is not
php -r '$e=include(".env.local.php"); unset($e["APP_SECRET"],$e["DATABASE_URL"],$e["MYSQL_USER"],$e["MYSQL_PASSWORD"],$e["MYSQL_ROOT_PASSWORD"]); file_put_contents(".env.local.php","<?php return ".var_export($e,true).";".PHP_EOL);' ; \ # compiled into the image. Use array_diff_key so the strip is explicit and order-independent;
# missing keys are safely ignored (they were never compiled in and therefore never a risk).
php -r '$strip=array_flip(["APP_SECRET","DATABASE_URL","MYSQL_USER","MYSQL_PASSWORD","MYSQL_ROOT_PASSWORD"]); $e=array_diff_key(include(".env.local.php"),$strip); file_put_contents(".env.local.php","<?php return ".var_export($e,true).";".PHP_EOL);' ; \
composer run-script --no-dev post-install-cmd; \ composer run-script --no-dev post-install-cmd; \
php bin/console asset-map:compile --no-debug; \ php bin/console asset-map:compile --no-debug; \
chmod +x bin/console; sync; chmod +x bin/console; sync;

4
assets/controllers/progress_bar_controller.js

@ -25,14 +25,18 @@ export default class extends Controller {
} }
disconnect() { disconnect() {
if (this.boundHandleInteraction) {
document.removeEventListener('click', this.boundHandleInteraction); document.removeEventListener('click', this.boundHandleInteraction);
}
if (this.boundTouchStart) { if (this.boundTouchStart) {
document.removeEventListener('touchstart', this.boundTouchStart); document.removeEventListener('touchstart', this.boundTouchStart);
} }
if (this.boundTouchEnd) { if (this.boundTouchEnd) {
document.removeEventListener('touchend', this.boundTouchEnd); document.removeEventListener('touchend', this.boundTouchEnd);
} }
if (this.boundPageShow) {
window.removeEventListener('pageshow', this.boundPageShow); window.removeEventListener('pageshow', this.boundPageShow);
}
if (this.loadListener) { if (this.loadListener) {
window.removeEventListener('load', this.loadListener); window.removeEventListener('load', this.loadListener);
this.loadListener = null; this.loadListener = null;

26
assets/controllers/user_highlight_tooltip_controller.js

@ -38,10 +38,22 @@ function formatHighlightDateUtc(d) {
*/ */
export default class extends Controller { export default class extends Controller {
connect() { connect() {
// Guard against duplicate elements if connect() fires more than once without an
// intervening disconnect() (e.g. direct invocation in tests or unusual Stimulus edge
// cases). Nulled out in disconnect() so the next normal connect() creates a fresh node.
if (!this.tip) {
this.tip = el('div', 'user-highlight__tip-popover', document.body); this.tip = el('div', 'user-highlight__tip-popover', document.body);
this.tip.setAttribute('role', 'tooltip'); this.tip.setAttribute('role', 'tooltip');
this.tip.setAttribute('hidden', ''); this.tip.setAttribute('hidden', '');
// Store named references so disconnect() can remove them explicitly and the
// anonymous-function-on-element pattern does not make cleanup impossible.
this._onTipEnter ??= () => { this._inTip = true; this._cancelHide(); };
this._onTipLeave ??= () => { this._inTip = false; this._scheduleHide(); };
this.tip.addEventListener('mouseenter', this._onTipEnter);
this.tip.addEventListener('mouseleave', this._onTipLeave);
}
this.activeMark = null; this.activeMark = null;
this._hideT = 0; this._hideT = 0;
this._inTip = false; this._inTip = false;
@ -84,15 +96,6 @@ export default class extends Controller {
this._scheduleHide(); this._scheduleHide();
}; };
this.tip.addEventListener('mouseenter', () => {
this._inTip = true;
this._cancelHide();
});
this.tip.addEventListener('mouseleave', () => {
this._inTip = false;
this._scheduleHide();
});
this._onFocus = (e) => { this._onFocus = (e) => {
const t = e.target; const t = e.target;
if (!(t instanceof Element)) { if (!(t instanceof Element)) {
@ -182,7 +185,12 @@ export default class extends Controller {
window.removeEventListener('hashchange', this._onHashChange); window.removeEventListener('hashchange', this._onHashChange);
} }
this._cancelHide(); this._cancelHide();
if (this.tip) {
this.tip.removeEventListener('mouseenter', this._onTipEnter);
this.tip.removeEventListener('mouseleave', this._onTipLeave);
this.tip.remove(); this.tip.remove();
this.tip = null;
}
} }
_cancelHide() { _cancelHide() {

5
src/Command/ArticleHighlightsAuditCommand.php

@ -76,7 +76,7 @@ final class ArticleHighlightsAuditCommand extends Command
$io->title('Article highlights audit: '.$slug); $io->title('Article highlights audit: '.$slug);
$io->writeln('Author npub: <info>'.$expectedNpub.'</info>'); $io->writeln('Author npub: <info>'.$expectedNpub.'</info>');
$io->writeln('Article id: <info>'.(string) $article->getId().'</info> · kind: <info>'. $io->writeln('Article id: <info>'.(string) $article->getId().'</info> · kind: <info>'.
($article->getKind()?->value ?? 'null').'</info>'); $article->getKind()->value.'</info>');
$highlights = $this->articleHighlightRepository->findByArticle($article); $highlights = $this->articleHighlightRepository->findByArticle($article);
$io->writeln('Rows from <comment>findByArticle</comment>: <info>'.\count($highlights).'</info>'); $io->writeln('Rows from <comment>findByArticle</comment>: <info>'.\count($highlights).'</info>');
@ -99,9 +99,6 @@ final class ArticleHighlightsAuditCommand extends Command
$rows = []; $rows = [];
$isolatedOk = 0; $isolatedOk = 0;
foreach ($highlights as $h) { foreach ($highlights as $h) {
if (! $h instanceof ArticleHighlight) {
continue;
}
$eid = \strtolower($h->getEventId()); $eid = \strtolower($h->getEventId());
$one = $this->articleBodyHighlightInjector->inject($html, [$h]); $one = $this->articleBodyHighlightInjector->inject($html, [$h]);
$found = 1 === \preg_match( $found = 1 === \preg_match(

73
src/Command/PrewarmCommand.php

@ -105,29 +105,7 @@ final class PrewarmCommand extends Command
} elseif ($phase === 'after_root') { } elseif ($phase === 'after_root') {
$hb->silent = true; $hb->silent = true;
$this->cancelPcntlAlarm(); $this->cancelPcntlAlarm();
$planned = $p['slugs'] ?? null;
if (!\is_array($planned)) {
$planned = [];
}
if ($planned === []) {
$io->writeln(' <comment>Magazine root has no child <info>a</info> tag categories; only the root index was stored.</comment>'); $io->writeln(' <comment>Magazine root has no child <info>a</info> tag categories; only the root index was stored.</comment>');
} else {
$n = \count($planned);
$io->writeln(sprintf(' <comment>Magazine child categories in root</comment> <info>(%d)</info><comment>:</comment>', $n));
foreach ($planned as $slug) {
$s = (string) $slug;
if (strlen($s) > 120) {
$s = substr($s, 0, 117).'…';
}
$io->writeln(sprintf(' · <info>%s</info>', $s));
}
$io->writeln(sprintf(
' <comment>Progress bar: <info>%d</info> steps = <info>1</info> (root) + <info>%d</info> (categor%s).</comment>',
1 + $n,
$n,
$n === 1 ? 'y' : 'ies'
));
}
$bar = $this->createPrewarmProgressBar( $bar = $this->createPrewarmProgressBar(
$io, $io,
max(1, (int) ($p['total_steps'] ?? 1)), max(1, (int) ($p['total_steps'] ?? 1)),
@ -245,7 +223,7 @@ final class PrewarmCommand extends Command
$until = time(); $until = time();
$deletionPubkeys = []; $deletionPubkeys = [];
foreach ($this->articleRepository->findDistinctAuthorPubkeys() as $pk) { foreach ($this->articleRepository->findDistinctAuthorPubkeys() as $pk) {
if (\is_string($pk) && 64 === \strlen($pk)) { if (64 === \strlen($pk)) {
$deletionPubkeys[] = $pk; $deletionPubkeys[] = $pk;
} }
} }
@ -289,7 +267,7 @@ final class PrewarmCommand extends Command
$st['articles_removed'], $st['articles_removed'],
$st['magazine_roots'], $st['magazine_roots'],
$st['magazine_categories'], $st['magazine_categories'],
$st['magazine_curation_30004'] ?? 0 $st['magazine_curation_30004']
)); ));
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->logger->error('app:prewarm NIP-09 failed', ['exception' => $e]); $this->logger->error('app:prewarm NIP-09 failed', ['exception' => $e]);
@ -322,7 +300,7 @@ final class PrewarmCommand extends Command
} }
$pubkeysSeen = []; $pubkeysSeen = [];
foreach ($pubkeys as $pk) { foreach ($pubkeys as $pk) {
if (!\is_string($pk) || 64 !== \strlen($pk)) { if (64 !== \strlen($pk)) {
continue; continue;
} }
$h = strtolower($pk); $h = strtolower($pk);
@ -357,7 +335,7 @@ final class PrewarmCommand extends Command
$fetched = $this->nostrClient->fetchProfilePrewarmWireBundlesForAuthors($chunk, $batchSize); $fetched = $this->nostrClient->fetchProfilePrewarmWireBundlesForAuthors($chunk, $batchSize);
$n += $this->cacheService->putPrewarmMetadataBatch($chunk, $fetched); $n += $this->cacheService->putPrewarmMetadataBatch($chunk, $fetched);
$bar->advance(\count($chunk)); $bar->advance(\count($chunk));
$p0 = (string) ($chunk[0] ?? ''); $p0 = $chunk[0];
$bar->setMessage('Batch up to · '.substr($p0, 0, 8).'…'); $bar->setMessage('Batch up to · '.substr($p0, 0, 8).'…');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
@ -399,7 +377,7 @@ final class PrewarmCommand extends Command
$hex = strtolower($hex); $hex = strtolower($hex);
$npub = $this->nostrKeyHelper->convertPublicKeyToBech32($hex); $npub = $this->nostrKeyHelper->convertPublicKeyToBech32($hex);
$bundle = $this->cacheService->getMetadataBundle($npub); $bundle = $this->cacheService->getMetadataBundle($npub);
$rows = $this->profileIdentityLinks->buildNip05($bundle['content'], $bundle['kind0_tags'] ?? []); $rows = $this->profileIdentityLinks->buildNip05($bundle['content'], $bundle['kind0_tags']);
$fa = $this->featuredAuthorRepository->findOneByPubkeyHex($hex); $fa = $this->featuredAuthorRepository->findOneByPubkeyHex($hex);
if ($fa !== null && $fa->isListed() && $domain !== '') { if ($fa !== null && $fa->isListed() && $domain !== '') {
$rows = $this->profileIdentityLinks->mergeSiteNip05IntoList( $rows = $this->profileIdentityLinks->mergeSiteNip05IntoList(
@ -409,7 +387,7 @@ final class PrewarmCommand extends Command
} }
foreach ($rows as $r) { foreach ($rows as $r) {
++$nt; ++$nt;
$label = (string) ($r['label'] ?? ''); $label = $r['label'];
if ($this->nip05Verification->verifyAndCache($hex, $label)) { if ($this->nip05Verification->verifyAndCache($hex, $label)) {
++$nv; ++$nv;
} }
@ -466,7 +444,7 @@ final class PrewarmCommand extends Command
continue; continue;
} }
$kind = $article->getKind()?->value ?? 30023; $kind = $article->getKind()->value;
$coordinate = $kind.':'.$pubkey.':'.$slug; $coordinate = $kind.':'.$pubkey.':'.$slug;
$msg = $slug; $msg = $slug;
if (strlen($msg) > 56) { if (strlen($msg) > 56) {
@ -672,9 +650,6 @@ final class PrewarmCommand extends Command
private function createPrewarmProgressBar(SymfonyStyle $io, int $max, string $message = ''): ProgressBar private function createPrewarmProgressBar(SymfonyStyle $io, int $max, string $message = ''): ProgressBar
{ {
$bar = $io->createProgressBar($max); $bar = $io->createProgressBar($max);
if (method_exists($bar, 'setMinSecondsBetweenRedraws')) {
$bar->setMinSecondsBetweenRedraws(5.0);
}
$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%'."\n".' <comment>%message%</comment> <info>%elapsed:6s%</info> '); $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%'."\n".' <comment>%message%</comment> <info>%elapsed:6s%</info> ');
$bar->setMessage($message); $bar->setMessage($message);
@ -711,9 +686,7 @@ final class PrewarmCommand extends Command
$max = (int) $bar->getMaxSteps(); $max = (int) $bar->getMaxSteps();
$done = (int) $bar->getProgress(); $done = (int) $bar->getProgress();
if ($max > 0 && $done < $max && $done === 0) { if ($max > 0 && $done < $max && $done === 0) {
if (method_exists($bar, 'clear')) {
$bar->clear(); $bar->clear();
}
} else { } else {
if ($max > 0 && $done < $max && $done > 0) { if ($max > 0 && $done < $max && $done > 0) {
$bar->setMaxSteps($done); $bar->setMaxSteps($done);
@ -787,33 +760,33 @@ final class PrewarmCommand extends Command
private function printCategoryCoverageSummary(SymfonyStyle $io, array $report): void private function printCategoryCoverageSummary(SymfonyStyle $io, array $report): void
{ {
$io->section('Category index -> DB coverage'); $io->section('Category index -> DB coverage');
$tot = $report['totals'] ?? ['categories' => 0, 'listed' => 0, 'resolved' => 0, 'missing' => 0]; $tot = $report['totals'];
$io->writeln(sprintf( $io->writeln(sprintf(
'Categories: <info>%d</info> · listed coordinates: <info>%d</info> · in DB: <info>%d</info> · missing: <comment>%d</comment>', 'Categories: <info>%d</info> · listed coordinates: <info>%d</info> · in DB: <info>%d</info> · missing: <comment>%d</comment>',
(int) ($tot['categories'] ?? 0), (int) $tot['categories'],
(int) ($tot['listed'] ?? 0), (int) $tot['listed'],
(int) ($tot['resolved'] ?? 0), (int) $tot['resolved'],
(int) ($tot['missing'] ?? 0), (int) $tot['missing'],
)); ));
foreach ($report['categories'] ?? [] as $cat) { foreach ($report['categories'] as $cat) {
$title = trim((string) ($cat['title'] ?? '')); $title = trim((string) $cat['title']);
$slug = (string) ($cat['slug'] ?? ''); $slug = (string) $cat['slug'];
$eventId = (string) ($cat['event_id'] ?? ''); $eventId = (string) $cat['event_id'];
$io->writeln(sprintf( $io->writeln(sprintf(
' - <info>%s</info> (%s) · event <comment>%s</comment> · listed <info>%d</info>, in DB <info>%d</info>, missing <comment>%d</comment>', ' - <info>%s</info> (%s) · event <comment>%s</comment> · listed <info>%d</info>, in DB <info>%d</info>, missing <comment>%d</comment>',
$title !== '' ? $title : $slug, $title !== '' ? $title : $slug,
$slug, $slug,
$eventId !== '' ? $eventId : 'n/a', $eventId !== '' ? $eventId : 'n/a',
(int) ($cat['listed_total'] ?? 0), (int) $cat['listed_total'],
(int) ($cat['resolved_total'] ?? 0), (int) $cat['resolved_total'],
(int) ($cat['missing_total'] ?? 0), (int) $cat['missing_total'],
)); ));
foreach ($cat['entries'] ?? [] as $entry) { foreach ($cat['entries'] as $entry) {
$coord = (string) ($entry['coordinate'] ?? ''); $coord = (string) $entry['coordinate'];
if ($coord === '') { if ($coord === '') {
continue; continue;
} }
$status = (string) ($entry['status'] ?? 'missing'); $status = (string) $entry['status'];
if ($status === 'resolved') { if ($status === 'resolved') {
$titleOut = trim((string) ($entry['article_title'] ?? '')); $titleOut = trim((string) ($entry['article_title'] ?? ''));
$io->writeln(sprintf( $io->writeln(sprintf(
@ -822,7 +795,7 @@ final class PrewarmCommand extends Command
$titleOut !== '' ? ' -> '.$titleOut : '' $titleOut !== '' ? ' -> '.$titleOut : ''
)); ));
} else { } else {
$reason = (string) ($entry['reason'] ?? 'unknown'); $reason = (string) $entry['reason'];
$io->writeln(sprintf(' - <comment>MISSING</comment> %s (%s)', $coord, $reason)); $io->writeln(sprintf(' - <comment>MISSING</comment> %s (%s)', $coord, $reason));
} }
} }

13
src/Controller/ArticleController.php

@ -140,7 +140,7 @@ class ArticleController extends AbstractController
string $articleTitle string $articleTitle
): array { ): array {
$coordparts = explode(':', $coordinate, 3); $coordparts = explode(':', $coordinate, 3);
$articleKind = isset($coordparts[0]) && ctype_digit($coordparts[0]) ? (int) $coordparts[0] : 30023; $articleKind = ctype_digit($coordparts[0]) ? (int) $coordparts[0] : 30023;
$articleAuthorPubkey = strtolower(trim((string) ($coordparts[1] ?? ''))); $articleAuthorPubkey = strtolower(trim((string) ($coordparts[1] ?? '')));
$articleReplyTags = null; $articleReplyTags = null;
@ -170,11 +170,8 @@ class ArticleController extends AbstractController
if ($userMayReply) { if ($userMayReply) {
/** @var array<int, object> $list */ /** @var array<int, object> $list */
$list = $data['list'] ?? []; $list = $data['list'];
foreach ($list as $row) { foreach ($list as $row) {
if (!\is_object($row)) {
continue;
}
$k = (int) ($row->kind ?? 0); $k = (int) ($row->kind ?? 0);
if ($k !== KindsEnum::COMMENTS->value && $k !== KindsEnum::TEXT_NOTE->value) { if ($k !== KindsEnum::COMMENTS->value && $k !== KindsEnum::TEXT_NOTE->value) {
continue; continue;
@ -375,7 +372,7 @@ class ArticleController extends AbstractController
$npub = $nostrKeyHelper->convertPublicKeyToBech32($article->getPubkey()); $npub = $nostrKeyHelper->convertPublicKeyToBech32($article->getPubkey());
$author = $cacheService->getMetadata($npub); $author = $cacheService->getMetadata($npub);
$kind = $article->getKind()?->value ?? 30023; $kind = $article->getKind()->value;
$pubkey = (string) $article->getPubkey(); $pubkey = (string) $article->getPubkey();
$articleSlug = (string) $article->getSlug(); $articleSlug = (string) $article->getSlug();
$coordinate = $kind.':'.$pubkey.':'.$articleSlug; $coordinate = $kind.':'.$pubkey.':'.$articleSlug;
@ -394,7 +391,7 @@ class ArticleController extends AbstractController
$eid, $eid,
$articleTitle $articleTitle
); );
$commentReplyContext = $commentsData['comment_reply_context'] ?? $commentReplyContext; $commentReplyContext = $commentsData['comment_reply_context'];
$commentsPreloaded = true; $commentsPreloaded = true;
} }
@ -489,7 +486,7 @@ class ArticleController extends AbstractController
} }
if ($html === '' && $previewData === null) { if ($html === '' && $previewData === null) {
$html = '<span class="text-subtle">No event found on the default relay for this preview.</span>'; $html = '<span class="text-subtle">No event found on the default relay for this preview.</span>';
} elseif ($html === '' && \is_object($previewData)) { } elseif ($html === '') {
$previewData->type = $descriptor->type; $previewData->type = $descriptor->type;
$html = $this->renderView('components/Molecules/NostrPreviewContent.html.twig', [ $html = $this->renderView('components/Molecules/NostrPreviewContent.html.twig', [
'preview' => $previewData, 'preview' => $previewData,

2
src/Controller/AuthorController.php

@ -46,7 +46,7 @@ class AuthorController extends AbstractController
$bundle = $cacheService->getMetadataBundle($npub); $bundle = $cacheService->getMetadataBundle($npub);
$author = $bundle['content']; $author = $bundle['content'];
$kind0Tags = $bundle['kind0_tags']; $kind0Tags = $bundle['kind0_tags'];
$nip30Emojis = $bundle['nip30_custom_emojis'] ?? []; $nip30Emojis = $bundle['nip30_custom_emojis'];
$perPage = 25; $perPage = 25;
$page = max(1, $request->query->getInt('page', 1)); $page = max(1, $request->query->getInt('page', 1));
$total = $articleRepository->countByPubkey($pubkey); $total = $articleRepository->countByPubkey($pubkey);

4
src/Controller/CommentReplyController.php

@ -57,8 +57,8 @@ final class CommentReplyController extends AbstractController
return $this->json([ return $this->json([
'ok' => true, 'ok' => true,
'id' => $out['id'], 'id' => $out['id'],
'ok_relays' => $out['ok_relays'] ?? null, 'ok_relays' => $out['ok_relays'],
'total_relays' => $out['total_relays'] ?? null, 'total_relays' => $out['total_relays'],
]); ]);
} }

3
src/Controller/DefaultController.php

@ -64,9 +64,6 @@ class DefaultController extends AbstractController
try { try {
$embed = new \Embed\Embed(); $embed = new \Embed\Embed();
$info = $embed->get($url); $info = $embed->get($url);
if (!$info) {
throw new Exception('No OG data found');
}
return $this->render('components/Molecules/OgPreview.html.twig', [ return $this->render('components/Molecules/OgPreview.html.twig', [
'og' => [ 'og' => [

10
src/Controller/SeoController.php

@ -140,12 +140,12 @@ final class SeoController extends AbstractController
private function buildRelaysByPubkey(array $names): array private function buildRelaysByPubkey(array $names): array
{ {
$raw = $this->params->get('profile_relays'); $raw = $this->params->get('profile_relays');
if (!\is_array($raw) || $raw === []) { if ($raw === []) {
return []; return [];
} }
$urls = []; $urls = [];
foreach ($raw as $u) { foreach ($raw as $u) {
if (\is_string($u) && (str_starts_with($u, 'wss://') || str_starts_with($u, 'ws://'))) { if (str_starts_with($u, 'wss://') || str_starts_with($u, 'ws://')) {
$urls[] = $u; $urls[] = $u;
} }
} }
@ -193,9 +193,9 @@ final class SeoController extends AbstractController
} }
$site = (string) $this->params->get('name'); $site = (string) $this->params->get('name');
$data = $this->magazineContent->getCategoryPageData($slug); $data = $this->magazineContent->getCategoryPageData($slug);
$rawList = $data['list'] ?? []; $rawList = $data['list'];
$catTitle = (string) ($data['category']['title'] ?? $this->magazineContent->getCategoryDisplayTitle($slug)); $catTitle = (string) ($data['category']['title']);
$summary = (string) ($data['category']['summary'] ?? ''); $summary = (string) ($data['category']['summary']);
$list = array_values( $list = array_values(
array_filter( array_filter(

9
src/Form/DataTransformer/CommaSeparatedToArrayTransformer.php

@ -3,7 +3,6 @@
namespace App\Form\DataTransformer; namespace App\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class CommaSeparatedToArrayTransformer implements DataTransformerInterface class CommaSeparatedToArrayTransformer implements DataTransformerInterface
{ {
@ -19,10 +18,6 @@ class CommaSeparatedToArrayTransformer implements DataTransformerInterface
return ''; return '';
} }
if (!is_array($array)) {
throw new TransformationFailedException('Expected an array.');
}
return implode(', ', $array); return implode(', ', $array);
} }
@ -38,10 +33,6 @@ class CommaSeparatedToArrayTransformer implements DataTransformerInterface
return []; return [];
} }
if (!is_string($string)) {
throw new TransformationFailedException('Expected a string.');
}
// Split by commas, trim whitespace, and filter out empty values // Split by commas, trim whitespace, and filter out empty values
$items = array_filter(array_map('trim', explode(',', $string)), function ($value) { $items = array_filter(array_map('trim', explode(',', $string)), function ($value) {
return $value !== ''; return $value !== '';

2
src/Nostr/MagazineEventKeys.php

@ -81,6 +81,6 @@ final class MagazineEventKeys
$h = ''; $h = '';
} }
return (\is_string($h) && 64 === \strlen($h) && ctype_xdigit($h)) ? strtolower($h) : ''; return (64 === \strlen($h) && ctype_xdigit($h)) ? strtolower($h) : '';
} }
} }

10
src/Nostr/Nip10Kind1ArticleReplyTags.php

@ -111,7 +111,7 @@ final class Nip10Kind1ArticleReplyTags
{ {
$want = strtolower($eventIdHex); $want = strtolower($eventIdHex);
foreach ($rawTags as $row) { foreach ($rawTags as $row) {
if (!\is_array($row) || \count($row) < 2) { if (\count($row) < 2) {
continue; continue;
} }
if (strtolower((string) ($row[0] ?? '')) !== 'e') { if (strtolower((string) ($row[0] ?? '')) !== 'e') {
@ -141,7 +141,7 @@ final class Nip10Kind1ArticleReplyTags
{ {
$out = []; $out = [];
foreach ($rawTags as $row) { foreach ($rawTags as $row) {
if (!\is_array($row) || \count($row) < 2) { if (\count($row) < 2) {
continue; continue;
} }
if (strtolower((string) ($row[0] ?? '')) !== 'p') { if (strtolower((string) ($row[0] ?? '')) !== 'p') {
@ -186,7 +186,7 @@ final class Nip10Kind1ArticleReplyTags
}; };
foreach ($rawTags as $row) { foreach ($rawTags as $row) {
if (!\is_array($row) || !$isE($row) || \count($row) < 2) { if (!$isE($row) || \count($row) < 2) {
continue; continue;
} }
if (($row[3] ?? '') === 'root') { if (($row[3] ?? '') === 'root') {
@ -199,7 +199,7 @@ final class Nip10Kind1ArticleReplyTags
if ($articleHex !== null) { if ($articleHex !== null) {
foreach ($rawTags as $row) { foreach ($rawTags as $row) {
if (!\is_array($row) || !$isE($row) || \count($row) < 2) { if (!$isE($row) || \count($row) < 2) {
continue; continue;
} }
$id = self::normEventId($row[1] ?? null); $id = self::normEventId($row[1] ?? null);
@ -211,7 +211,7 @@ final class Nip10Kind1ArticleReplyTags
$eIds = []; $eIds = [];
foreach ($rawTags as $row) { foreach ($rawTags as $row) {
if (!\is_array($row) || !$isE($row) || \count($row) < 2) { if (!$isE($row) || \count($row) < 2) {
continue; continue;
} }
$id = self::normEventId($row[1] ?? null); $id = self::normEventId($row[1] ?? null);

8
src/Nostr/Nip19Codec.php

@ -32,7 +32,7 @@ final class Nip19Codec
$out = new \stdClass(); $out = new \stdClass();
if ($hrp === 'npub' || $hrp === 'nsec') { if ($hrp === 'npub' || $hrp === 'nsec') {
if (!\is_array($raw) || !isset($raw[1]) || !\is_array($raw[1])) { if (!isset($raw[1]) || !\is_array($raw[1])) {
throw new \RuntimeException('Unexpected npub/nsec decode shape'); throw new \RuntimeException('Unexpected npub/nsec decode shape');
} }
$out->type = $hrp; $out->type = $hrp;
@ -48,7 +48,7 @@ final class Nip19Codec
} }
if ($hrp === 'note') { if ($hrp === 'note') {
if (!\is_array($raw) || !isset($raw['event_id']) || !\is_string($raw['event_id'])) { if (!isset($raw['event_id']) || !\is_string($raw['event_id'])) {
throw new \RuntimeException('Unexpected note decode shape'); throw new \RuntimeException('Unexpected note decode shape');
} }
$out->type = 'note'; $out->type = 'note';
@ -60,10 +60,6 @@ final class Nip19Codec
return $out; return $out;
} }
if (!\is_array($raw)) {
throw new \RuntimeException('Unexpected NIP-19 decode shape');
}
$out->type = $hrp; $out->type = $hrp;
$d = new \stdClass(); $d = new \stdClass();
if ($hrp === 'nprofile') { if ($hrp === 'nprofile') {

4
src/Nostr/Nip22CommentTags.php

@ -19,7 +19,7 @@ final class Nip22CommentTags
public static function forReplyToArticle(string $coordinate, string $articleAuthorPubkeyHex): array public static function forReplyToArticle(string $coordinate, string $articleAuthorPubkeyHex): array
{ {
$parts = explode(':', $coordinate, 2); $parts = explode(':', $coordinate, 2);
$k = \count($parts) >= 1 && ctype_digit((string) $parts[0]) ? (string) (int) $parts[0] : '30023'; $k = ctype_digit((string) $parts[0]) ? (string) (int) $parts[0] : '30023';
return [ return [
['A', $coordinate, ''], ['A', $coordinate, ''],
@ -90,7 +90,7 @@ final class Nip22CommentTags
{ {
foreach ([$upper, $lower] as $n) { foreach ([$upper, $lower] as $n) {
foreach ($rawTags as $row) { foreach ($rawTags as $row) {
if (!\is_array($row) || ($row[0] ?? null) === null) { if (($row[0] ?? null) === null) {
continue; continue;
} }
if ((string) $row[0] === $n) { if ((string) $row[0] === $n) {

2
src/Repository/EventRepository.php

@ -32,7 +32,7 @@ class EventRepository extends ServiceEntityRepository
{ {
$keys = array_values(array_unique(array_filter( $keys = array_values(array_unique(array_filter(
$keys, $keys,
static fn (mixed $k): bool => \is_string($k) && $k !== '', static fn (mixed $k): bool => $k !== '',
))); )));
if ($keys === []) { if ($keys === []) {
return []; return [];

13
src/Service/ArticleBodyHighlightInjector.php

@ -167,9 +167,6 @@ final class ArticleBodyHighlightInjector
$stack[] = $this->dom->documentElement; $stack[] = $this->dom->documentElement;
while ($stack !== []) { while ($stack !== []) {
$el = \array_pop($stack); $el = \array_pop($stack);
if (! $el instanceof DOMElement) {
continue;
}
if ($el->getAttribute('id') === $id) { if ($el->getAttribute('id') === $id) {
return $el; return $el;
} }
@ -260,9 +257,6 @@ final class ArticleBodyHighlightInjector
continue; continue;
} }
$span = $this->dom->createElement('span'); $span = $this->dom->createElement('span');
if (false === $span) {
continue;
}
$span->setAttribute('id', 'highlight-'.$other); $span->setAttribute('id', 'highlight-'.$other);
$span->setAttribute('class', 'user-highlight__fragment-target'); $span->setAttribute('class', 'user-highlight__fragment-target');
$span->setAttribute('aria-hidden', 'true'); $span->setAttribute('aria-hidden', 'true');
@ -320,8 +314,8 @@ final class ArticleBodyHighlightInjector
\usort( \usort(
$groups, $groups,
static function (array $a, array $b): int { static function (array $a, array $b): int {
$ta = $a[0] instanceof ArticleHighlight ? $a[0]->getEventCreatedAt() : 0; $ta = $a[0]->getEventCreatedAt();
$tb = $b[0] instanceof ArticleHighlight ? $b[0]->getEventCreatedAt() : 0; $tb = $b[0]->getEventCreatedAt();
return $ta <=> $tb; return $ta <=> $tb;
} }
@ -708,9 +702,6 @@ final class ArticleBodyHighlightInjector
$parent->insertBefore($this->dom->createTextNode($before), $ref); $parent->insertBefore($this->dom->createTextNode($before), $ref);
} }
$mark = $this->dom->createElement('mark'); $mark = $this->dom->createElement('mark');
if (! $mark) {
return false;
}
$mark->setAttribute('class', 'user-highlight__marker'); $mark->setAttribute('class', 'user-highlight__marker');
if ($firstInReadingOrder) { if ($firstInReadingOrder) {
$mark->setAttribute('id', 'highlight-'.$eventId); $mark->setAttribute('id', 'highlight-'.$eventId);

16
src/Service/ArticleCommentThreadLoader.php

@ -111,8 +111,8 @@ final readonly class ArticleCommentThreadLoader
$item->expiresAfter($partial ? self::PARTIAL_THREAD_CACHE_TTL_SEC : 86400); $item->expiresAfter($partial ? self::PARTIAL_THREAD_CACHE_TTL_SEC : 86400);
$this->logger->info('comments.loader.nostr_ok', [ $this->logger->info('comments.loader.nostr_ok', [
'nostr_elapsed_ms' => (int) round((microtime(true) - $tNostr) * 1000), 'nostr_elapsed_ms' => (int) round((microtime(true) - $tNostr) * 1000),
'thread' => \count($out['thread'] ?? []), 'thread' => \count($out['thread']),
'quotes' => \count($out['quotes'] ?? []), 'quotes' => \count($out['quotes']),
'partial' => $partial, 'partial' => $partial,
]); ]);
@ -151,7 +151,7 @@ final readonly class ArticleCommentThreadLoader
} }
/** /**
* @param array{thread: array<int, object>, quotes: array<int, object>, superchats?: list<array<string,mixed>>} $discussion * @param array{thread: array<int, object>, quotes: array<int, object>, superchats?: list<array<string,mixed>>, partial?: bool} $discussion
* *
* @return array{ * @return array{
* list: array<int, object>, * list: array<int, object>,
@ -164,8 +164,8 @@ final readonly class ArticleCommentThreadLoader
*/ */
private function expandFromDiscussion(array $discussion, float $t0, ?string $articleEventHexId = null): array private function expandFromDiscussion(array $discussion, float $t0, ?string $articleEventHexId = null): array
{ {
$list = $discussion['thread'] ?? []; $list = $discussion['thread'];
$quotes = $discussion['quotes'] ?? []; $quotes = $discussion['quotes'];
$superchats = $discussion['superchats'] ?? []; $superchats = $discussion['superchats'] ?? [];
$this->logger->info('comments.loader.cache_resolved', [ $this->logger->info('comments.loader.cache_resolved', [
'elapsed_since_start_ms' => (int) round((microtime(true) - $t0) * 1000), 'elapsed_since_start_ms' => (int) round((microtime(true) - $t0) * 1000),
@ -232,16 +232,12 @@ final readonly class ArticleCommentThreadLoader
} }
}; };
foreach ($list as $ev) { foreach ($list as $ev) {
if (\is_object($ev)) {
$strip($ev); $strip($ev);
} }
}
foreach ($quotes as $ev) { foreach ($quotes as $ev) {
if (\is_object($ev)) {
$strip($ev); $strip($ev);
} }
} }
}
/** /**
* @param array<string, array<int, mixed>> $linkBucket * @param array<string, array<int, mixed>> $linkBucket
@ -368,7 +364,7 @@ final readonly class ArticleCommentThreadLoader
return ['blurb' => null, 'body' => $content]; return ['blurb' => null, 'body' => $content];
} }
$parts = explode("\n\n", $content, 2); $parts = explode("\n\n", $content, 2);
$first = trim((string) ($parts[0] ?? '')); $first = trim($parts[0]);
$rest = (string) ($parts[1] ?? ''); $rest = (string) ($parts[1] ?? '');
if ($first === '' || !str_starts_with($first, '>')) { if ($first === '' || !str_starts_with($first, '>')) {
return ['blurb' => null, 'body' => $content]; return ['blurb' => null, 'body' => $content];

22
src/Service/CacheService.php

@ -76,7 +76,7 @@ final class CacheService implements HighlightAuthorMetadataProvider, ResetInterf
public function prefetchMetadataForNpubs(array $npubs): void public function prefetchMetadataForNpubs(array $npubs): void
{ {
foreach ($npubs as $npub) { foreach ($npubs as $npub) {
if (!\is_string($npub) || $npub === '') { if ($npub === '') {
continue; continue;
} }
$authorHex = $this->npubToAuthorHex64($npub); $authorHex = $this->npubToAuthorHex64($npub);
@ -95,7 +95,7 @@ final class CacheService implements HighlightAuthorMetadataProvider, ResetInterf
{ {
$npubs = []; $npubs = [];
foreach ($pubkeyHex as $hex) { foreach ($pubkeyHex as $hex) {
if (!\is_string($hex) || 64 !== \strlen($hex) || !ctype_xdigit($hex)) { if (64 !== \strlen($hex) || !ctype_xdigit($hex)) {
continue; continue;
} }
try { try {
@ -114,9 +114,6 @@ final class CacheService implements HighlightAuthorMetadataProvider, ResetInterf
++$this->metadataBatchDepth; ++$this->metadataBatchDepth;
try { try {
do { do {
if ($this->pendingHexToNpub === []) {
break;
}
$this->flushPendingMetadataFetches(); $this->flushPendingMetadataFetches();
} while ($this->pendingHexToNpub !== []); } while ($this->pendingHexToNpub !== []);
} finally { } finally {
@ -193,7 +190,7 @@ final class CacheService implements HighlightAuthorMetadataProvider, ResetInterf
continue; continue;
} }
$h = strtolower($hex); $h = strtolower($hex);
if (!isset($bundlesByLowerHex[$h]) || !\is_array($bundlesByLowerHex[$h])) { if (!isset($bundlesByLowerHex[$h])) {
continue; continue;
} }
$bundle = $bundlesByLowerHex[$h]; $bundle = $bundlesByLowerHex[$h];
@ -239,16 +236,12 @@ final class CacheService implements HighlightAuthorMetadataProvider, ResetInterf
if ($seq === []) { if ($seq === []) {
continue; continue;
} }
$r = array_values( $r = array_map(
array_map(
static fn (mixed $v): string => (string) $v, static fn (mixed $v): string => (string) $v,
array_values($seq) array_values($seq)
)
); );
if ($r !== [] && (string) ($r[0] ?? '') !== '') {
$out[] = $r; $out[] = $r;
} }
}
return $out; return $out;
} }
@ -352,7 +345,7 @@ final class CacheService implements HighlightAuthorMetadataProvider, ResetInterf
private function bundleFromKind0EventRow(Event $row, string $npub): array private function bundleFromKind0EventRow(Event $row, string $npub): array
{ {
$content = $this->decodeKind0ContentString($row->getContent()); $content = $this->decodeKind0ContentString($row->getContent());
if (!\is_object($content) || $this->isPlaceholderContent($content, $npub)) { if ($this->isPlaceholderContent($content, $npub)) {
$content = $this->namePlaceholderNpubObject($npub); $content = $this->namePlaceholderNpubObject($npub);
} }
@ -368,11 +361,6 @@ final class CacheService implements HighlightAuthorMetadataProvider, ResetInterf
]; ];
} }
private function decodeKind0ContentObject(object $ev): \stdClass
{
return $this->decodeKind0ContentString((string) ($ev->content ?? ''));
}
private function decodeKind0ContentString(string $raw): \stdClass private function decodeKind0ContentString(string $raw): \stdClass
{ {
try { try {

2
src/Service/CommentReplyService.php

@ -91,7 +91,7 @@ final readonly class CommentReplyService
: ''; : '';
$clientParentOk = 64 === \strlen($rawParentAuthor) && ctype_xdigit($rawParentAuthor); $clientParentOk = 64 === \strlen($rawParentAuthor) && ctype_xdigit($rawParentAuthor);
$coordBits = explode(':', $expectedCoordinate, 3); $coordBits = explode(':', $expectedCoordinate, 3);
$articleAuthor = \count($coordBits) >= 2 ? strtolower((string) $coordBits[1]) : ''; $articleAuthor = strtolower((string) $coordBits[1]);
$articleAuthorOk = 64 === \strlen($articleAuthor) && ctype_xdigit($articleAuthor); $articleAuthorOk = 64 === \strlen($articleAuthor) && ctype_xdigit($articleAuthor);
if (\in_array((int) $parentKind, [KindsEnum::COMMENTS->value, KindsEnum::TEXT_NOTE->value], true)) { if (\in_array((int) $parentKind, [KindsEnum::COMMENTS->value, KindsEnum::TEXT_NOTE->value], true)) {

5
src/Service/HighlightSyncService.php

@ -41,15 +41,12 @@ final class HighlightSyncService
return 0; return 0;
} }
$kind = $article->getKind()?->value ?? 30023; $kind = $article->getKind()->value;
$coordinate = $kind.':'.$pubkey.':'.$slug; $coordinate = $kind.':'.$pubkey.':'.$slug;
$events = $this->nostrClient->fetchHighlightEventsForArticle($coordinate); $events = $this->nostrClient->fetchHighlightEventsForArticle($coordinate);
$n = 0; $n = 0;
foreach ($events as $ev) { foreach ($events as $ev) {
if (!\is_object($ev)) {
continue;
}
if ((int) ($ev->kind ?? 0) !== KindsEnum::HIGHLIGHTS->value) { if ((int) ($ev->kind ?? 0) !== KindsEnum::HIGHLIGHTS->value) {
continue; continue;
} }

38
src/Service/MagazineContentService.php

@ -91,7 +91,7 @@ final class MagazineContentService
if (\count($parts) < 3) { if (\count($parts) < 3) {
continue; continue;
} }
if ((int) ($parts[0] ?? 0) !== KindsEnum::PUBLICATION_INDEX->value) { if ((int) $parts[0] !== KindsEnum::PUBLICATION_INDEX->value) {
continue; continue;
} }
$cats[] = ['a', $coord]; $cats[] = ['a', $coord];
@ -111,8 +111,8 @@ final class MagazineContentService
$queue = []; $queue = [];
$enqueued = []; $enqueued = [];
foreach ($this->getHomeCategoryAIndexTagsFromStoreOnly() as $row) { foreach ($this->getHomeCategoryAIndexTagsFromStoreOnly() as $row) {
$coord = $row[1] ?? ''; $coord = $row[1];
if (!\is_string($coord) || $coord === '') { if ($coord === '') {
continue; continue;
} }
$parts = explode(':', $coord, 3); $parts = explode(':', $coord, 3);
@ -130,7 +130,7 @@ final class MagazineContentService
$out = []; $out = [];
while ($queue !== []) { while ($queue !== []) {
$slug = array_shift($queue); $slug = array_shift($queue);
if (!\is_string($slug) || $slug === '') { if ($slug === '') {
continue; continue;
} }
$out[] = $slug; $out[] = $slug;
@ -446,7 +446,7 @@ final class MagazineContentService
continue; continue;
} }
$kind = (int) ($parts[0] ?? 0); $kind = (int) $parts[0];
if (!\in_array($kind, KindsEnum::longformKindValues(), true)) { if (!\in_array($kind, KindsEnum::longformKindValues(), true)) {
$entries[] = ['coordinate' => $coordinate, 'status' => 'missing', 'reason' => 'unsupported_kind']; $entries[] = ['coordinate' => $coordinate, 'status' => 'missing', 'reason' => 'unsupported_kind'];
$missing++; $missing++;
@ -521,15 +521,15 @@ final class MagazineContentService
public function missingInDbCoordinatesFromCoverageReport(array $report): array public function missingInDbCoordinatesFromCoverageReport(array $report): array
{ {
$out = []; $out = [];
foreach ($report['categories'] ?? [] as $cat) { foreach ($report['categories'] as $cat) {
foreach ($cat['entries'] ?? [] as $entry) { foreach ($cat['entries'] as $entry) {
if (($entry['status'] ?? '') !== 'missing') { if ($entry['status'] !== 'missing') {
continue; continue;
} }
if (($entry['reason'] ?? '') !== 'article_not_in_db') { if ($entry['reason'] !== 'article_not_in_db') {
continue; continue;
} }
$coord = isset($entry['coordinate']) ? (string) $entry['coordinate'] : ''; $coord = (string) $entry['coordinate'];
if ($coord !== '') { if ($coord !== '') {
$out[] = $coord; $out[] = $coord;
} }
@ -562,7 +562,7 @@ final class MagazineContentService
if (\count($parts) < 3 || trim((string) $parts[2]) === '') { if (\count($parts) < 3 || trim((string) $parts[2]) === '') {
continue; continue;
} }
$kind = (int) ($parts[0] ?? 0); $kind = (int) $parts[0];
if (!\in_array($kind, KindsEnum::longformKindValues(), true)) { if (!\in_array($kind, KindsEnum::longformKindValues(), true)) {
continue; continue;
} }
@ -698,8 +698,8 @@ final class MagazineContentService
{ {
$out = []; $out = [];
foreach ($categoryATags as $row) { foreach ($categoryATags as $row) {
$coord = $row[1] ?? ''; $coord = $row[1];
if (!\is_string($coord) || $coord === '') { if ($coord === '') {
continue; continue;
} }
foreach ($this->buildFeaturedWallBlocksForCategoryTree($coord) as $b) { foreach ($this->buildFeaturedWallBlocksForCategoryTree($coord) as $b) {
@ -751,7 +751,7 @@ final class MagazineContentService
if (\count($parts) < 3) { if (\count($parts) < 3) {
continue; continue;
} }
$kind = (int) ($parts[0] ?? 0); $kind = (int) $parts[0];
if (!\in_array($kind, KindsEnum::longformKindValues(), true)) { if (!\in_array($kind, KindsEnum::longformKindValues(), true)) {
continue; continue;
} }
@ -827,8 +827,8 @@ final class MagazineContentService
{ {
$blocks = []; $blocks = [];
foreach ($categoryATags as $row) { foreach ($categoryATags as $row) {
$coord = $row[1] ?? ''; $coord = $row[1];
if (!\is_string($coord) || $coord === '') { if ($coord === '') {
continue; continue;
} }
foreach ($this->buildFeaturedWallBlocksForCategoryTree($coord) as $b) { foreach ($this->buildFeaturedWallBlocksForCategoryTree($coord) as $b) {
@ -987,7 +987,7 @@ final class MagazineContentService
if (\count($segs) < 3) { if (\count($segs) < 3) {
continue; continue;
} }
$kind = (int) ($segs[0] ?? 0); $kind = (int) $segs[0];
$identifier = trim((string) $segs[2]); $identifier = trim((string) $segs[2]);
if ($identifier === '') { if ($identifier === '') {
continue; continue;
@ -1030,7 +1030,7 @@ final class MagazineContentService
if (\count($segs) < 3) { if (\count($segs) < 3) {
continue; continue;
} }
$kind = (int) ($segs[0] ?? 0); $kind = (int) $segs[0];
$identifier = trim((string) $segs[2]); $identifier = trim((string) $segs[2]);
if ($identifier === '') { if ($identifier === '') {
continue; continue;
@ -1139,7 +1139,7 @@ final class MagazineContentService
if (\count($segs) < 3) { if (\count($segs) < 3) {
continue; continue;
} }
$kind = (int) ($segs[0] ?? 0); $kind = (int) $segs[0];
if ($kind !== KindsEnum::PUBLICATION_INDEX->value) { if ($kind !== KindsEnum::PUBLICATION_INDEX->value) {
continue; continue;
} }

4
src/Service/MagazineRefresher.php

@ -203,7 +203,7 @@ final class MagazineRefresher
if (\count($parts) < 3) { if (\count($parts) < 3) {
continue; continue;
} }
if ((int) ($parts[0] ?? 0) !== KindsEnum::PUBLICATION_INDEX->value) { if ((int) $parts[0] !== KindsEnum::PUBLICATION_INDEX->value) {
continue; continue;
} }
$s = trim((string) $parts[2]); $s = trim((string) $parts[2]);
@ -244,7 +244,7 @@ final class MagazineRefresher
$relayLabel = (string) (parse_url($defaultRelay, \PHP_URL_HOST) ?: $defaultRelay); $relayLabel = (string) (parse_url($defaultRelay, \PHP_URL_HOST) ?: $defaultRelay);
while ($queue !== [] && microtime(true) < $deadline) { while ($queue !== [] && microtime(true) < $deadline) {
$slug = array_shift($queue); $slug = array_shift($queue);
if (!\is_string($slug) || trim($slug) === '') { if (trim($slug) === '') {
continue; continue;
} }
try { try {

2
src/Service/Nip05VerificationService.php

@ -47,7 +47,7 @@ final readonly class Nip05VerificationService
$out = []; $out = [];
$coldDone = 0; $coldDone = 0;
foreach ($rows as $r) { foreach ($rows as $r) {
$label = (string) ($r['label'] ?? ''); $label = (string) $r['label'];
$n = $this->normalizeNip05($label); $n = $this->normalizeNip05($label);
if ($n === null) { if ($n === null) {
$out[] = [...$r, 'verified' => false]; $out[] = [...$r, 'verified' => false];

3
src/Service/Nip09DeletionApplier.php

@ -53,9 +53,6 @@ final class Nip09DeletionApplier
$seenArticleIds = []; $seenArticleIds = [];
foreach ($deletionEvents as $ev) { foreach ($deletionEvents as $ev) {
if (!\is_object($ev)) {
continue;
}
if ((int) ($ev->kind ?? 0) !== KindsEnum::DELETION_REQUEST->value) { if ((int) ($ev->kind ?? 0) !== KindsEnum::DELETION_REQUEST->value) {
continue; continue;
} }

27
src/Service/NostrClient.php

@ -117,7 +117,7 @@ class NostrClient
$out = $base; $out = $base;
foreach ($pubkeys as $pk) { foreach ($pubkeys as $pk) {
foreach ($this->authorRelayCache->getAuthorNip65RelaysList($pk) as $wss) { foreach ($this->authorRelayCache->getAuthorNip65RelaysList($pk) as $wss) {
if (!\is_string($wss) || $wss === '' || isset($seen[$wss])) { if ($wss === '' || isset($seen[$wss])) {
continue; continue;
} }
$seen[$wss] = true; $seen[$wss] = true;
@ -166,7 +166,7 @@ class NostrClient
{ {
$authorPubkeyHex = \array_values(\array_unique(\array_filter( $authorPubkeyHex = \array_values(\array_unique(\array_filter(
$authorPubkeyHex, $authorPubkeyHex,
static fn (mixed $h): bool => \is_string($h) && 64 === \strlen($h), static fn (string $h): bool => 64 === \strlen($h),
))); )));
if ($authorPubkeyHex === []) { if ($authorPubkeyHex === []) {
return []; return [];
@ -265,7 +265,7 @@ class NostrClient
): array { ): array {
$authorPubkeyHex = \array_values(\array_unique(\array_filter( $authorPubkeyHex = \array_values(\array_unique(\array_filter(
$authorPubkeyHex, $authorPubkeyHex,
static fn (mixed $h): bool => \is_string($h) && 64 === \strlen($h), static fn (string $h): bool => 64 === \strlen($h),
))); )));
if ($authorPubkeyHex === [] || $since >= $until) { if ($authorPubkeyHex === [] || $since >= $until) {
return []; return [];
@ -376,7 +376,7 @@ class NostrClient
$relaySet->setMessage($eventMessage); $relaySet->setMessage($eventMessage);
$this->relayRequestFactory->applySocketTimeoutToRelaySet($relaySet); $this->relayRequestFactory->applySocketTimeoutToRelaySet($relaySet);
$sent = $relaySet->send(); $sent = $relaySet->send();
if (\is_array($sent) && \array_key_exists($relayWss, $sent)) { if (\array_key_exists($relayWss, $sent)) {
$results[$relayWss] = $sent[$relayWss]; $results[$relayWss] = $sent[$relayWss];
} else { } else {
$results[$relayWss] = $sent; $results[$relayWss] = $sent;
@ -734,7 +734,7 @@ class NostrClient
$merged = $this->wireMerge->mergeNip33ParameterizedWireEvents($response); $merged = $this->wireMerge->mergeNip33ParameterizedWireEvents($response);
$k10002 = (int) KindsEnum::RELAY_LIST->value; $k10002 = (int) KindsEnum::RELAY_LIST->value;
foreach ($merged as $e) { foreach ($merged as $e) {
if (\is_object($e) && (int) ($e->kind ?? 0) === $k10002) { if ((int) ($e->kind ?? 0) === $k10002) {
return $e; return $e;
} }
} }
@ -1073,7 +1073,7 @@ class NostrClient
$seen = []; $seen = [];
$out = []; $out = [];
foreach (array_merge($this->relayListFactory->getConfiguredArticleRelayUrlList(), $relayUrls) as $relayUrl) { foreach (array_merge($this->relayListFactory->getConfiguredArticleRelayUrlList(), $relayUrls) as $relayUrl) {
if (!\is_string($relayUrl) || $relayUrl === '' || isset($seen[$relayUrl])) { if ($relayUrl === '' || isset($seen[$relayUrl])) {
continue; continue;
} }
$seen[$relayUrl] = true; $seen[$relayUrl] = true;
@ -1703,7 +1703,7 @@ class NostrClient
$this->logger->info('[longform_ingest] ingestLongform: start', [ $this->logger->info('[longform_ingest] ingestLongform: start', [
'address_count' => \count($addresses), 'address_count' => \count($addresses),
'relays' => $relaysForLog, 'relays' => $relaysForLog,
'addresses_sample' => \array_values(\array_slice($addresses, 0, 15)), 'addresses_sample' => \array_slice($addresses, 0, 15),
]); ]);
$groups = []; $groups = [];
foreach ($addresses as $c) { foreach ($addresses as $c) {
@ -1730,10 +1730,7 @@ class NostrClient
'group_count' => \count($groups), 'group_count' => \count($groups),
]); ]);
foreach ($groups as $gkey => $g) { foreach ($groups as $gkey => $g) {
$dTags = array_values(array_unique($g['dTags'] ?? [])); $dTags = array_values(array_unique($g['dTags']));
if ($dTags === [] || !isset($g['pubkey'], $g['kind'])) {
continue;
}
$kindEnum = KindsEnum::tryFrom((int) $g['kind']); $kindEnum = KindsEnum::tryFrom((int) $g['kind']);
if ($kindEnum === null) { if ($kindEnum === null) {
$this->logger->notice('[longform_ingest] skip group: unknown kind', ['kind' => $g['kind']]); $this->logger->notice('[longform_ingest] skip group: unknown kind', ['kind' => $g['kind']]);
@ -1881,9 +1878,6 @@ class NostrClient
$merged = $this->wireMerge->mergeNip33ParameterizedWireEvents($events); $merged = $this->wireMerge->mergeNip33ParameterizedWireEvents($events);
$mergedDetail = []; $mergedDetail = [];
foreach ($merged as $ev) { foreach ($merged as $ev) {
if (!\is_object($ev)) {
continue;
}
$mergedDetail[] = $this->wireMerge->longformIngestEventWireSummary($ev); $mergedDetail[] = $this->wireMerge->longformIngestEventWireSummary($ev);
} }
$this->logger->info('[longform_ingest] ingestLongform: after mergeNip33ParameterizedWireEvents', [ $this->logger->info('[longform_ingest] ingestLongform: after mergeNip33ParameterizedWireEvents', [
@ -1898,9 +1892,6 @@ class NostrClient
} }
$seenAddresses = []; $seenAddresses = [];
foreach ($merged as $event) { foreach ($merged as $event) {
if (!\is_object($event)) {
continue;
}
$addr = $this->wireMerge->nip33ParameterizedReplaceableAddress($event); $addr = $this->wireMerge->nip33ParameterizedReplaceableAddress($event);
if ($addr !== null) { if ($addr !== null) {
$seenAddresses[$addr] = true; $seenAddresses[$addr] = true;
@ -1935,7 +1926,7 @@ class NostrClient
sprintf('[longform_ingest] ingestLongform: exception in group %s: %s', (string) $gkey, $e->getMessage()), sprintf('[longform_ingest] ingestLongform: exception in group %s: %s', (string) $gkey, $e->getMessage()),
[ [
'message' => $e->getMessage(), 'message' => $e->getMessage(),
'pubkey' => $g['pubkey'] ?? null, 'pubkey' => $g['pubkey'],
'trace' => $e->getTraceAsString(), 'trace' => $e->getTraceAsString(),
'relays' => $relaysForLog, 'relays' => $relaysForLog,
], ],

2
src/Service/NostrShareMenuBuilder.php

@ -184,7 +184,7 @@ final class NostrShareMenuBuilder
private function fromArticle(Article $article): NostrShareMenuContext private function fromArticle(Article $article): NostrShareMenuContext
{ {
$npub = $this->nostrKeyHelper->convertPublicKeyToBech32((string) $article->getPubkey()); $npub = $this->nostrKeyHelper->convertPublicKeyToBech32((string) $article->getPubkey());
$kind = (int) ($article->getKind()?->value ?? 30023); $kind = $article->getKind()->value;
$d = (string) ($article->getSlug() ?? ''); $d = (string) ($article->getSlug() ?? '');
if ($d === '') { if ($d === '') {
return new NostrShareMenuContext( return new NostrShareMenuContext(

10
src/Service/ProfileIdentityLinksBuilder.php

@ -78,8 +78,8 @@ final class ProfileIdentityLinksBuilder
} }
$seen[$id] = true; $seen[$id] = true;
$parts = explode('@', $id, 2); $parts = explode('@', $id, 2);
$local = $parts[0] ?? ''; $local = $parts[0];
$domain = $parts[1] ?? ''; $domain = $parts[1];
if ($local === '' || $domain === '' || str_contains($domain, ' ')) { if ($local === '' || $domain === '' || str_contains($domain, ' ')) {
continue; continue;
} }
@ -110,14 +110,14 @@ final class ProfileIdentityLinksBuilder
} }
$seen = []; $seen = [];
foreach ($rows as $r) { foreach ($rows as $r) {
$seen[strtolower((string) ($r['label'] ?? ''))] = true; $seen[strtolower((string) $r['label'])] = true;
} }
if (isset($seen[$siteNip05])) { if (isset($seen[$siteNip05])) {
return $rows; return $rows;
} }
$parts = explode('@', $siteNip05, 2); $parts = explode('@', $siteNip05, 2);
$local = $parts[0] ?? ''; $local = $parts[0];
$domain = $parts[1] ?? ''; $domain = $parts[1];
if ($local === '' || $domain === '' || str_contains($domain, ' ')) { if ($local === '' || $domain === '' || str_contains($domain, ' ')) {
return $rows; return $rows;
} }

8
src/Service/ProfilePaymentLinksBuilder.php

@ -48,7 +48,6 @@ final class ProfilePaymentLinksBuilder
if ($resolved['lightning_address'] !== null) { if ($resolved['lightning_address'] !== null) {
$addr = $resolved['lightning_address']; $addr = $resolved['lightning_address'];
$norm = 'la:'.strtolower($addr); $norm = 'la:'.strtolower($addr);
if (!isset($seen[$norm])) {
$seen[$norm] = true; $seen[$norm] = true;
$rows[] = [ $rows[] = [
'type' => self::TYPE_LIGHTNING_ADDRESS, 'type' => self::TYPE_LIGHTNING_ADDRESS,
@ -58,7 +57,6 @@ final class ProfilePaymentLinksBuilder
'sort' => 0, 'sort' => 0,
]; ];
} }
}
if ($resolved['lnurl_pay'] !== null) { if ($resolved['lnurl_pay'] !== null) {
$ln = $resolved['lnurl_pay']; $ln = $resolved['lnurl_pay'];
@ -207,7 +205,7 @@ final class ProfilePaymentLinksBuilder
{ {
$out = []; $out = [];
foreach ($kind10133Events as $ev) { foreach ($kind10133Events as $ev) {
if (!\is_object($ev) || (int) ($ev->kind ?? 0) !== KindsEnum::PAYMENT_TARGETS->value) { if ((int) ($ev->kind ?? 0) !== KindsEnum::PAYMENT_TARGETS->value) {
continue; continue;
} }
$tags = self::normalizeTagsArray($ev->tags ?? null); $tags = self::normalizeTagsArray($ev->tags ?? null);
@ -296,13 +294,11 @@ final class ProfilePaymentLinksBuilder
$r = array_values( $r = array_values(
array_map( array_map(
static fn (mixed $v): string => (string) $v, static fn (mixed $v): string => (string) $v,
array_values($seq) $seq
) )
); );
if ($r !== []) {
$out[] = $r; $out[] = $r;
} }
}
return $out; return $out;
} }

3
src/Twig/Components/IndexTabs.php

@ -14,8 +14,6 @@ class IndexTabs
{ {
use DefaultActionTrait; use DefaultActionTrait;
private $index;
#[LiveProp(writable: true)] #[LiveProp(writable: true)]
public int $activeTab = 1; // Default active tab public int $activeTab = 1; // Default active tab
@ -35,7 +33,6 @@ class IndexTabs
public function mount(EventEntity $index): void public function mount(EventEntity $index): void
{ {
$this->index = $index;
foreach ($index->getTags() as $tag) { foreach ($index->getTags() as $tag) {
if (array_key_first($tag) === 'a') { if (array_key_first($tag) === 'a') {
$ref = $tag[1]; $ref = $tag[1];

2
src/Twig/Components/Molecules/CategoryLink.php

@ -36,7 +36,7 @@ final class CategoryLink
$this->title = $this->slug; $this->title = $this->slug;
$this->magazineContent->warmCategoryIndexIfMissing($this->slug); $this->magazineContent->warmCategoryIndexIfMissing($this->slug);
$cat = $this->store->getCategory($this->slug); $cat = $this->store->getCategory($this->slug);
if (!\is_object($cat) || !\method_exists($cat, 'getTags')) { if (!\is_object($cat)) {
return; return;
} }

2
src/Twig/Components/Organisms/FeaturedList.php

@ -44,7 +44,7 @@ final class FeaturedList
$this->magazineContent->warmCategoryIndexIfMissing($slug); $this->magazineContent->warmCategoryIndexIfMissing($slug);
$catIndex = $this->store->getCategory($slug); $catIndex = $this->store->getCategory($slug);
if (!\is_object($catIndex) || !\method_exists($catIndex, 'getTags')) { if (!\is_object($catIndex)) {
return; return;
} }

6
src/Util/NostrEventTags.php

@ -42,7 +42,7 @@ final class NostrEventTags
return false; return false;
} }
return strtolower($seq[0] ?? '') === strtolower($name); return strtolower($seq[0]) === strtolower($name);
} }
/** /**
@ -69,7 +69,7 @@ final class NostrEventTags
if (\count($parts) < 3) { if (\count($parts) < 3) {
continue; continue;
} }
if ((int) ($parts[0] ?? 0) !== KindsEnum::PUBLICATION_INDEX->value) { if ((int) $parts[0] !== KindsEnum::PUBLICATION_INDEX->value) {
continue; continue;
} }
$d = trim((string) $parts[2]); $d = trim((string) $parts[2]);
@ -108,7 +108,7 @@ final class NostrEventTags
if (\count($parts) < 3) { if (\count($parts) < 3) {
continue; continue;
} }
if ((int) ($parts[0] ?? 0) !== KindsEnum::PUBLICATION_INDEX->value) { if ((int) $parts[0] !== KindsEnum::PUBLICATION_INDEX->value) {
continue; continue;
} }
$pk = strtolower(trim((string) $parts[1])); $pk = strtolower(trim((string) $parts[1]));

2
tests/Service/ArticleBodyHighlightInjectorTest.php

@ -147,7 +147,7 @@ final class ArticleBodyHighlightInjectorTest extends TestCase
} }
/** /**
* @param list<string> $eventIdsLowerOrMixed 64-char hex event ids * @param list<string> $eventIds 64-char hex event ids
*/ */
private function assertHighlightFragmentsPresent(string $html, array $eventIds): void private function assertHighlightFragmentsPresent(string $html, array $eventIds): void
{ {

3
tests/Service/ArticleHighlightCommonMarkPipelineTest.php

@ -26,9 +26,6 @@ final class ArticleHighlightCommonMarkPipelineTest extends KernelTestCase
private function getConverter(): Converter private function getConverter(): Converter
{ {
$container = static::getContainer(); $container = static::getContainer();
if (!$container->has(Converter::class)) {
self::fail('Converter service must be registered in the test kernel.');
}
/** @var Converter $c */ /** @var Converter $c */
$c = $container->get(Converter::class); $c = $container->get(Converter::class);

4
tests/bootstrap.php

@ -4,9 +4,7 @@ use Symfony\Component\Dotenv\Dotenv;
require dirname(__DIR__).'/vendor/autoload.php'; require dirname(__DIR__).'/vendor/autoload.php';
if (method_exists(Dotenv::class, 'bootEnv')) { (new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
}
if (!empty($_SERVER['APP_DEBUG'])) { if (!empty($_SERVER['APP_DEBUG'])) {
umask(0000); umask(0000);

Loading…
Cancel
Save