src/Entity/Artikel/Artikel.php line 23

Open in your IDE?
  1. <?php
  2. namespace App\Entity\Artikel;
  3. use App\Entity\Artikel\Exceptions\ArtikelNotValidForAGalleryException;
  4. use App\Entity\Artikel\Exceptions\ArtikelSelfReferenzException;
  5. use App\Entity\Dossier\Dossier;
  6. use App\Entity\EntityId;
  7. use App\Entity\ObjectFileHandlingInterface;
  8. use App\Entity\Redaktor\Redaktor;
  9. use App\Util\UrlSlug;
  10. use Doctrine\Common\Collections\ArrayCollection;
  11. use Doctrine\Common\Collections\Collection;
  12. use Doctrine\ORM\Mapping as ORM;
  13. use Symfony\Component\Filesystem\Filesystem;
  14. use Symfony\Component\Finder\Finder;
  15. use Symfony\Component\OptionsResolver\OptionsResolver;
  16. use Symfony\Component\Validator\Constraints;
  17. use Webmozart\Assert\Assert;
  18. #[ORM\Entity]
  19. #[ORM\Table(name: 'artikel')]
  20. class Artikel implements ObjectFileHandlingInterface
  21. {
  22. public $redaktorAlternative;
  23. public const int MAX_LENGTH_TITEL = 80;
  24. public const int MAX_LENGTH_LEAD = 180;
  25. public const int MAX_LENGTH_HINWEIS = 80;
  26. public const int MAX_LENGTH_REDAKTOR_ALTERNATIVE = 64;
  27. public const int MAX_LENGTH_SPITZMARKE = 64;
  28. public const int MAX_LENGTH_URLSLUG = 255;
  29. public const int MAX_WIDTH_IMAGE = 1024;
  30. public const int MAX_WIDTH_PR_LOGO = 350;
  31. public const float MIN_IMAGE_RATIO = 1.65;
  32. public const float MAX_IMAGE_RATIO = 1.85;
  33. public const string REGEX_VIDEO_URL_PATTERN = '#^(|https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9\-\_]{1,})$#';
  34. public const int MAX_CONCURRENT_LEAD_GALLERY_ARTIKEL = 3;
  35. #[ORM\Column(name: 'titel', type: 'string', length: 128, nullable: true)]
  36. private ?string $titel = null;
  37. #[ORM\Column(name: 'spitzmarke', type: 'string', length: 64, nullable: true)]
  38. private ?string $spitzmarke = null;
  39. #[ORM\Column(name: '`lead`', type: 'text', length: 300, nullable: true)]
  40. private ?string $lead = null;
  41. #[ORM\Column(name: 'contentHtml', type: 'text', nullable: true)]
  42. private ?string $contentHtml = null;
  43. #[ORM\Column(name: 'video_url', type: 'text', length: 255, nullable: true)]
  44. #[Constraints\Regex(pattern: '#^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9\-\_]{1,}$#', message: 'Die URL entspricht nicht der Formatvorgaben für eine YouTube URL.', match: true)]
  45. private ?string $videoUrl = null;
  46. /**
  47. * ArtikelRubrik[]
  48. */
  49. #[ORM\Column(name: 'rubrik_ids', type: 'json', nullable: true)]
  50. private ?array $rubrikIds = [];
  51. /**
  52. * ArtikelKanal[]
  53. */
  54. #[ORM\Column(name: 'kanal_ids', type: 'json', nullable: true)]
  55. private ?array $kanalIds = [];
  56. #[ORM\ManyToOne(targetEntity: Dossier::class)]
  57. #[ORM\JoinColumn(name: 'dossier_id', referencedColumnName: 'id', nullable: true)]
  58. private ?Dossier $dossier = null;
  59. /**
  60. * ArtikelStatus
  61. */
  62. #[ORM\Column(name: 'status_id', type: 'string', length: 16)]
  63. private ?string $statusId = null;
  64. /**
  65. * ArtikelTyp
  66. */
  67. #[ORM\Column(name: 'typ_id', type: 'string', length: 16)]
  68. private ?string $typId = null;
  69. /**
  70. * ArtikelAssettyp
  71. */
  72. #[ORM\Column(name: 'assettyp_id', type: 'string', length: 16)]
  73. private ?string $assettypId = null;
  74. #[ORM\ManyToOne(targetEntity: Redaktor::class)]
  75. #[ORM\JoinColumn(name: 'redaktor_id', referencedColumnName: 'id', nullable: true)]
  76. private ?Redaktor $redaktor = null;
  77. #[ORM\ManyToMany(targetEntity: Artikel::class)]
  78. #[ORM\JoinTable(name: 'lnk_artikel_artikel', joinColumns: [new ORM\JoinColumn(name: 'artikel_id', referencedColumnName: 'id')], inverseJoinColumns: [new ORM\JoinColumn(name: 'verwandter_artikel_id', referencedColumnName: 'id')])]
  79. private Collection $verwandteArtikel;
  80. /**
  81. * Artikel Datum, falls leer, erhält es das Publikationsdatum.
  82. */
  83. #[ORM\Column(name: 'authored_at', type: 'datetime', nullable: true)]
  84. private ?\DateTimeInterface $authoredAt = null;
  85. /**
  86. * Definitives Publikationsdatum.
  87. */
  88. #[ORM\Column(name: 'publish_at', type: 'datetime', nullable: true)]
  89. private ?\DateTimeInterface $publishAt = null;
  90. #[ORM\Column(name: 'edit_at', type: 'datetime', nullable: false)]
  91. private ?\DateTimeInterface $editAt = null;
  92. #[ORM\Column(name: 'storage_contents', type: 'json', nullable: true)]
  93. private array $storageContents = [];
  94. #[ORM\Column(name: 'url_slug', type: 'string', length: 255, unique: true, nullable: true)]
  95. private ?string $urlSlug = null;
  96. #[ORM\Column(name: 'is_lead_artikel', type: 'boolean', nullable: true)]
  97. private bool $isLeadArtikel = false;
  98. #[ORM\Column(name: 'is_lead_gallery_artikel', type: 'boolean', nullable: true)]
  99. private bool $isLeadGalleryArtikel = false;
  100. #[ORM\Column(name: 'pushed_to_twitter_at', type: 'datetime', nullable: true)]
  101. private ?\DateTimeInterface $pushedToTwitterAt = null;
  102. /**
  103. * Publireportage Anzeige in Feed angepinnt auf Position 3 bis zu diesem Datum.
  104. */
  105. #[ORM\Column(name: 'pr_pinned_until', type: 'date', nullable: true)]
  106. private ?\DateTimeInterface $prPinnedUntil = null;
  107. public function __construct(
  108. #[ORM\Id]
  109. #[ORM\Column(name: "id", type: "entityId")]
  110. #[ORM\GeneratedValue(strategy: "NONE")]
  111. private EntityId $id,
  112. string $titel,
  113. ?array $artikelKanale = null
  114. )
  115. {
  116. $this->setTitel($titel);
  117. // Default Values
  118. $this->setStatus(ArtikelStatus::fromString(ArtikelStatus::ID_AS_2)); // Draft Status
  119. $this->setTyp(ArtikelTyp::fromString(ArtikelTyp::ID_AT_1)); // Free
  120. $this->setAssettyp(); // Asset Typ None
  121. $this->verwandteArtikel = new ArrayCollection();
  122. $this->setArtikelKanale($artikelKanale);
  123. $this->setEditAtNow();
  124. $this->setHinweis('Mehr dazu in der gedruckten Ausgabe oder im E-Paper. Kein Abo? Hier bestellen!'); // ACHTUNG: Text "Hier bestellen!" (nicht ändern oder JS-Logik erweitern) wird automatisch im Frontend mit dem ABO Link verlinkt
  125. }
  126. public function change(array $options): void
  127. {
  128. $resolver = new OptionsResolver();
  129. $resolver->setRequired([
  130. 'titel',
  131. 'rubriken',
  132. 'status',
  133. 'typ',
  134. 'lead',
  135. 'hinweis',
  136. 'dossier',
  137. 'content_html',
  138. 'content_html_media_code',
  139. 'video_url',
  140. 'redaktor',
  141. 'redaktor_alternative',
  142. 'spitzmarke',
  143. 'verwandte_artikel',
  144. 'artikel_kanale',
  145. 'publish_at',
  146. 'authored_at',
  147. 'images',
  148. 'pr_content_html',
  149. 'pr_logo',
  150. 'pr_pinned_until',
  151. ]);
  152. $options = $resolver->resolve($options);
  153. $this->setTitel($options['titel']);
  154. $this->setHinweis($options['hinweis']);
  155. $this->setRubriken($options['rubriken']);
  156. $this->setStatus($options['status']);
  157. $this->setTyp($options['typ']);
  158. $this->setLead($options['lead']);
  159. $this->setDossier($options['dossier']);
  160. $this->setContentHtml($options['content_html']);
  161. $this->setContentHtmlMediaCode($options['content_html_media_code']);
  162. $this->setVideoUrl($options['video_url']);
  163. $this->setRedaktor($options['redaktor'], $options['redaktor_alternative']);
  164. $this->setSpitzmarke($options['spitzmarke']);
  165. $this->setVerwandteArtikel($options['verwandte_artikel']);
  166. $this->setArtikelKanale($options['artikel_kanale']);
  167. $this->setAuthoredAt($options['authored_at']);
  168. $this->setImages($options['images']); // setze auch den Assettyp
  169. $this->setPrContentHtml($options['pr_content_html']);
  170. $this->setPrLogo($options['pr_logo']);
  171. // --- Domain Logiken
  172. $this->setAssettyp($this->images());
  173. $this->setPublishAtAndPrPinnedUntil($options['status'], $options['publish_at'], $options['pr_pinned_until']);
  174. // Falls LeadGalleryArtikel aktiv, diesen überprüfen ob noch valid()
  175. if ($this->isLeadGalleryArtikel()) {
  176. $this->autoConfigureLeadArtikelGallery();
  177. }
  178. // Ende
  179. $this->setEditAtNow();
  180. }
  181. /**
  182. * Spez function, welche die Flags der Frontendanzeige regelt!
  183. */
  184. public function changeFrontendConfig(array $options): void
  185. {
  186. // Validierung input
  187. $resolver = new OptionsResolver();
  188. $resolver->setDefaults(
  189. [
  190. 'is_lead_artikel' => null,
  191. 'is_lead_gallery_artikel' => null,
  192. ]
  193. );
  194. $options = $resolver->resolve($options);
  195. // End
  196. if (null !== $options['is_lead_artikel']) {
  197. $this->setIsLeadArtikel($options['is_lead_artikel']);
  198. }
  199. if (null !== $options['is_lead_gallery_artikel']) {
  200. $this->setIsLeadArtikelGallery($options['is_lead_gallery_artikel']);
  201. }
  202. }
  203. /**
  204. * Setzt die Zeit, wann die Mitteilung auf Twitter gepushed wurde.
  205. */
  206. public function setPushedToTwitterNow(): void
  207. {
  208. $this->setPushedToTwitterAt(new \DateTime());
  209. }
  210. public function setPublishAtAndPrPinnedUntil(ArtikelStatus $status, ?\DateTime $publishAt = null, ?\DateTime $prPinnedUntil = null): void
  211. {
  212. // Berechne und setze das Publikationsdatum
  213. if ($status->isPublish() && !$publishAt) {
  214. $this->publishAt = new \DateTime('now'); // umgehend publizieren
  215. } else {
  216. $this->publishAt = $publishAt;
  217. }
  218. // Berechne und setze das Publireportage pinned Datum
  219. if (!$this->typ()->isPublireportage()) {
  220. $this->prPinnedUntil = null;
  221. } elseif ($status->isPublish() && !$prPinnedUntil) {
  222. $dateTmp = clone $this->publishAt;
  223. $this->prPinnedUntil = $dateTmp->add(new \DateInterval('P10D'));
  224. // Standard
  225. } else {
  226. $this->prPinnedUntil = $prPinnedUntil;
  227. }
  228. // end
  229. }
  230. // ----- Ende Domain Functions --------------------------
  231. public function id(): EntityId
  232. {
  233. return $this->id;
  234. }
  235. public function titel(): ?string
  236. {
  237. return $this->titel;
  238. }
  239. public function spitzmarke(): ?string
  240. {
  241. return $this->spitzmarke;
  242. }
  243. /**
  244. * @return ArtikelRubrik[]
  245. */
  246. public function rubriken(): array
  247. {
  248. $artikelRubriken = [];
  249. if ($this->rubrikIds) {
  250. foreach ($this->rubrikIds as $rubrikId) {
  251. $artikelRubriken[] = ArtikelRubrik::fromString($rubrikId);
  252. }
  253. }
  254. return $artikelRubriken;
  255. }
  256. /**
  257. * @return ArtikelKanal[]
  258. */
  259. public function artikelKanale(): array
  260. {
  261. $artikelKanale = [];
  262. if ($this->kanalIds) {
  263. foreach ($this->kanalIds as $kanalId) {
  264. $artikelKanale[] = ArtikelKanal::fromString($kanalId);
  265. }
  266. }
  267. return $artikelKanale;
  268. }
  269. /**
  270. * @return ArtikelStatus
  271. */
  272. public function status(): ArtikelStatus
  273. {
  274. return ArtikelStatus::fromString($this->statusId);
  275. }
  276. /**
  277. * @return ArtikelTyp
  278. */
  279. public function typ(): ArtikelTyp
  280. {
  281. return ArtikelTyp::fromString($this->typId);
  282. }
  283. /**
  284. * @return ArtikelAssettyp
  285. */
  286. public function assettyp(): ArtikelAssettyp
  287. {
  288. return ArtikelAssettyp::fromString($this->assettypId);
  289. }
  290. public function lead(): ?string
  291. {
  292. return $this->lead;
  293. }
  294. private function setLead(?string $lead): void
  295. {
  296. Assert::maxLength($lead, self::MAX_LENGTH_LEAD);
  297. $this->lead = $lead;
  298. }
  299. public function dossier(): ?Dossier
  300. {
  301. return $this->dossier;
  302. }
  303. public function videoUrl(bool $extractYouTubeIdOnly = false): ?string
  304. {
  305. if (true === $extractYouTubeIdOnly && $this->videoUrl) {
  306. return explode('v=', $this->videoUrl)[1];
  307. }
  308. return $this->videoUrl;
  309. }
  310. public function redaktor(): ?Redaktor
  311. {
  312. return $this->redaktor;
  313. }
  314. public function authoredAt(bool $rawData = false): ?\DateTimeInterface
  315. {
  316. if ($rawData || $this->authoredAt) {
  317. return $this->authoredAt;
  318. }
  319. return $this->publishAt();
  320. }
  321. public function publishAt(): ?\DateTimeInterface
  322. {
  323. return $this->publishAt;
  324. }
  325. public function pushedToTwitterAt(): ?\DateTimeInterface
  326. {
  327. return $this->pushedToTwitterAt;
  328. }
  329. public function isPublished(): bool
  330. {
  331. return $this->status()->isPublish() && $this->publishAt() < new \DateTime('now');
  332. }
  333. public function verwandteArtikel(): ArrayCollection|Collection
  334. {
  335. return $this->verwandteArtikel;
  336. }
  337. protected function setStatus(ArtikelStatus $status): static
  338. {
  339. $this->statusId = $status->id();
  340. return $this;
  341. }
  342. protected function setTyp(ArtikelTyp $typ): static
  343. {
  344. $this->typId = $typ->id();
  345. return $this;
  346. }
  347. protected function setAssettyp(?ArtikelImageCollection $artikelImageCollection = null): static
  348. {
  349. $this->assettypId = ArtikelAssettyp::ID_AAT_1;
  350. if ($artikelImageCollection instanceof ArtikelImageCollection) {
  351. if ($artikelImageCollection->count() > 1) {
  352. $this->assettypId = ArtikelAssettyp::ID_AAT_3;
  353. } elseif ($artikelImageCollection->count() === 1) {
  354. $this->assettypId = ArtikelAssettyp::ID_AAT_2;
  355. }
  356. }
  357. return $this;
  358. }
  359. protected function setRubriken(array $rubriken): static
  360. {
  361. $this->rubrikIds = [];
  362. if ($rubriken) {
  363. Assert::allIsInstanceOf($rubriken, ArtikelRubrik::class);
  364. foreach ($rubriken as $item) {
  365. /*
  366. * @var ArtikelKanal $item
  367. */
  368. $this->rubrikIds[] = $item->id();
  369. }
  370. }
  371. return $this;
  372. }
  373. protected function setDossier(?Dossier $dossier = null): static
  374. {
  375. $this->dossier = $dossier;
  376. return $this;
  377. }
  378. protected function setVideoUrl(?string $url): static
  379. {
  380. Assert::regex($url, self::REGEX_VIDEO_URL_PATTERN);
  381. $this->videoUrl = $url;
  382. return $this;
  383. }
  384. private function setTitel(?string $titel): static
  385. {
  386. Assert::minLength($titel, 1);
  387. Assert::maxLength($titel, self::MAX_LENGTH_TITEL);
  388. // Urlslug generieren und setzen
  389. $this->setUrlSlug($titel);
  390. $this->titel = $titel;
  391. return $this;
  392. }
  393. private function setSpitzmarke(?string $spitzmarke): static
  394. {
  395. Assert::maxLength($spitzmarke, self::MAX_LENGTH_SPITZMARKE);
  396. $this->spitzmarke = $spitzmarke;
  397. return $this;
  398. }
  399. private function setRedaktor(?Redaktor $redaktor = null, $redaktorAlternative = null): static
  400. {
  401. $redaktorAlternative = trim((string) $redaktorAlternative);
  402. if ($redaktor && strlen($redaktorAlternative) > 0) {
  403. throw new \InvalidArgumentException('Gleichzeitige Setzung eines Redaktors und einer Redaktor-Alternative ist nicht erlaubt!');
  404. }
  405. $this->redaktor = null;
  406. $this->redaktorAlternative = null;
  407. if ($redaktor instanceof Redaktor) {
  408. $this->redaktor = $redaktor;
  409. } elseif (strlen($redaktorAlternative) > 0) {
  410. $this->storageContents['redaktor_alternative'] = $redaktorAlternative;
  411. }
  412. return $this;
  413. }
  414. public function isLeadArtikel(): bool
  415. {
  416. return $this->isLeadArtikel;
  417. }
  418. private function setIsLeadArtikel(bool $isLeadArtikel): static
  419. {
  420. Assert::boolean($isLeadArtikel);
  421. $this->isLeadArtikel = $isLeadArtikel;
  422. return $this;
  423. }
  424. public function isLeadGalleryArtikel(): bool
  425. {
  426. return $this->isLeadGalleryArtikel;
  427. }
  428. private function setIsLeadArtikelGallery(bool $boolean): static
  429. {
  430. Assert::boolean($boolean);
  431. // Validierung
  432. if ($boolean) {
  433. if ($this->hasGalleryAssets()) {
  434. $this->isLeadGalleryArtikel = true;
  435. } else {
  436. throw new ArtikelNotValidForAGalleryException();
  437. }
  438. } else {
  439. $this->isLeadGalleryArtikel = false;
  440. }
  441. return $this;
  442. }
  443. /**
  444. * Wert wird automatisch gemäss Artikel Daten gesetzt.
  445. */
  446. private function autoConfigureLeadArtikelGallery(): void
  447. {
  448. $this->isLeadGalleryArtikel = $this->hasGalleryAssets();
  449. }
  450. /**
  451. * Kann dieser Artikel in einer Gallery angezeigt werden?
  452. */
  453. public function hasGalleryAssets(): bool
  454. {
  455. return $this->assettyp()->id() === ArtikelAssettyp::ID_AAT_3 || $this->videoUrl();
  456. }
  457. /* --- Publireportage Felder --- */
  458. private function setPrContentHtml(?string $prContentHtml = null): void
  459. {
  460. $prContentHtml = (string) $prContentHtml;
  461. $this->storageContents['pr_content_html'] = $prContentHtml;
  462. }
  463. public function prContentHtml(): ?string
  464. {
  465. return $this->storageContents('pr_content_html');
  466. }
  467. private function setPrLogo(?ArtikelImage $image = null): void
  468. {
  469. if (!$image instanceof ArtikelImage) {
  470. $image = ArtikelImage::createNonExistImage();
  471. }
  472. $this->storageContents['pr_logo'] = [
  473. 'filename' => $image->getFilename(),
  474. 'alt' => $image->getAlt(),
  475. 'caption' => $image->getCaption(),
  476. ];
  477. }
  478. public function prLogo(): ArtikelImage
  479. {
  480. $imageData = $this->storageContents('pr_logo');
  481. if (is_array($imageData)) {
  482. return ArtikelImage::create($imageData['filename'], $imageData['alt'], $imageData['caption'], '', $this->baseObjectServerDirPath(), $this->baseObjectWebDirPath());
  483. }
  484. return ArtikelImage::createNonExistImage();
  485. }
  486. public function prPinnedUntil(): ?\DateTimeInterface
  487. {
  488. return $this->prPinnedUntil;
  489. }
  490. /* --- Ende Publireportage Felder --- */
  491. public function contentHtml(): ?string
  492. {
  493. return $this->contentHtml;
  494. }
  495. private function setContentHtml(?string $contentHtml): void
  496. {
  497. $this->contentHtml = $contentHtml;
  498. }
  499. private function setAuthoredAt(?\DateTime $authoredAt = null): void
  500. {
  501. $this->authoredAt = $authoredAt;
  502. }
  503. private function setPushedToTwitterAt(?\DateTime $pushedToTwitterAt = null): void
  504. {
  505. $this->pushedToTwitterAt = $pushedToTwitterAt;
  506. }
  507. /**
  508. * Zusätzlicher Media HTML-Code welcher zum ContentHtml Inhalt dazu generiert wird
  509. * z.B. ein fertiger IFrame Code oder ein HTML Image Code usw. oder auch ein Javascript.
  510. *
  511. * ACHTUNG: Ausgabe wird nicht escaped!
  512. */
  513. public function contentHtmlMediaCode()
  514. {
  515. return $this->storageContents('content_html_media_code');
  516. }
  517. private function setContentHtmlMediaCode($string): void
  518. {
  519. $this->storageContents['content_html_media_code'] = $string;
  520. }
  521. /**
  522. * Hinweis Text.
  523. */
  524. public function hinweis()
  525. {
  526. return $this->storageContents('hinweis');
  527. }
  528. private function setHinweis($string): void
  529. {
  530. $this->storageContents['hinweis'] = $string;
  531. }
  532. private function setImages(ArtikelImageCollection|array $images = null): void
  533. {
  534. $this->storageContents['images'] = []; // resetten
  535. if ($images) {
  536. Assert::isInstanceOf($images, ArtikelImageCollection::class);
  537. foreach ($images as $image) {
  538. $this->storageContents['images'][] = $image->toArray();
  539. }
  540. }
  541. }
  542. public function images(): ?ArtikelImageCollection
  543. {
  544. $imageDataRaw = $this->storageContents('images');
  545. if ($imageDataRaw) {
  546. // Filepath wieder gemäss $path Parameter wieder dazu bilden hinzufügen
  547. $imageCollection = new ArtikelImageCollection();
  548. foreach ($imageDataRaw as $data) {
  549. $fileName = $data['filename'];
  550. $alt = $data['alt'];
  551. $caption = $data['caption'];
  552. $rightholder = $data['rightholder'];
  553. $imageCollection->add(ArtikelImage::create($fileName, $alt, $caption, $rightholder, $this->baseObjectServerDirPath(), $this->baseObjectWebDirPath()));
  554. }
  555. // Ende
  556. return $imageCollection;
  557. }
  558. return null;
  559. }
  560. private function setEditAtNow(): void
  561. {
  562. $this->editAt = new \DateTime();
  563. }
  564. public function editAt(): ?\DateTimeInterface
  565. {
  566. return $this->editAt;
  567. }
  568. public function redaktorAlternativ()
  569. {
  570. return $this->storageContents('redaktor_alternative');
  571. }
  572. // Hier muss array der Typ sein, weil es als Array aus dem Form kommt und nicht als Collection, evtl, weil der ChoiceType verwendet wird anstatt EntityType.
  573. // Habe ehrlich gesagt langsam die Schnauze voll von dieser Typisierung. SB/7.3.2024
  574. private function setVerwandteArtikel(?array $verwandteArtikel = null): void
  575. {
  576. // Verwandte Artikel nullen
  577. $this->verwandteArtikel = new ArrayCollection();
  578. if ($verwandteArtikel) {
  579. Assert::allIsInstanceOf($verwandteArtikel, Artikel::class);
  580. // Entferne verwandter Artikel, welcher er selbst ist
  581. foreach ($verwandteArtikel as $artikel) {
  582. if ($artikel->id != $this->id) {
  583. $this->verwandteArtikel[] = $artikel;
  584. } else {
  585. throw new ArtikelSelfReferenzException();
  586. }
  587. }
  588. }
  589. }
  590. private function setArtikelKanale($artikelKanale = null): void
  591. {
  592. $this->kanalIds = [];
  593. if ($artikelKanale) {
  594. Assert::allIsInstanceOf($artikelKanale, ArtikelKanal::class);
  595. foreach ($artikelKanale as $item) {
  596. /*
  597. * @var ArtikelKanal $item
  598. */
  599. $this->kanalIds[] = $item->id();
  600. }
  601. foreach ($this->kanalIds as $kanalId) {
  602. // Falls nicht als Lead Artikel verwendet werden darf isLeadArtikel Flag zurücksetzen
  603. if (in_array($kanalId, [ArtikelKanal::ID_AK_2, ArtikelKanal::ID_AK_3], true)) {
  604. $this->setIsLeadArtikel(false);
  605. }
  606. }
  607. }
  608. }
  609. private function storageContents($key)
  610. {
  611. $storage = $this->storageContents ?: [];
  612. if (array_key_exists($key, $storage)) {
  613. return $storage[$key];
  614. }
  615. return null;
  616. }
  617. #[\Override]
  618. public function baseObjectServerDirPath(): string
  619. {
  620. return __DIR__.'/../../../var/data/public/artikel/'.substr($this->id, 0, 2).'/'.substr($this->id, 2, 2).'/'.$this->id;
  621. }
  622. #[\Override]
  623. public function baseObjectWebDirPath(): string
  624. {
  625. return '/data/artikel/'.substr($this->id, 0, 2).'/'.substr($this->id, 2, 2).'/'.$this->id;
  626. }
  627. #[\Override]
  628. public function removeUnusedFiles(): void
  629. {
  630. $serverPath = $this->baseObjectServerDirPath();
  631. // Auflistung aller effektiv benötigter Filenamen dieses Objects, damit diese erhalten bleiben
  632. $usedFiles_ = [];
  633. if ($this->images()) {
  634. foreach ($this->images() as $image) {
  635. $usedFiles_[] = $image->getFilename();
  636. }
  637. }
  638. if ($this->prLogo()->doesExist()) {
  639. $usedFiles_[] = $this->prLogo()->getFilename();
  640. }
  641. // Ende
  642. // Lösche alle Files aus dem Objektverzeichnis, welche nicht im Objekt vorhanden sind
  643. $fs = new Filesystem();
  644. if ($fs->exists($serverPath)) {
  645. $finder = new Finder();
  646. $finder->files()->in($serverPath);
  647. foreach ($finder as $file) {
  648. if (!in_array($file->getFilename(), $usedFiles_)) {
  649. $fs->remove($file->getRealPath());
  650. }
  651. }
  652. }
  653. // Ende
  654. }
  655. public function urlSlug(): ?string
  656. {
  657. return $this->urlSlug;
  658. }
  659. /**
  660. * Generiert einen Spez. Slug mit Suffix -p{Nummer}.
  661. */
  662. public function generateNextUrlSlug(): void
  663. {
  664. // Filtere eine bestehende UrlSlug Nummer raus und erhöhe diese!
  665. $result = [];
  666. preg_match('/.*-p(\\d+)$/', $this->urlSlug, $result);
  667. if ($result) {
  668. $pageNrSuffix = ($result[1] + 1); // inkrementiere die bestehende p Nummer um 1
  669. } else {
  670. $pageNrSuffix = 1;
  671. }
  672. // end
  673. $this->setUrlSlug($this->titel.'-p'.$pageNrSuffix);
  674. }
  675. private function setUrlSlug($string): void
  676. {
  677. // all chars lowercase
  678. $urlSlug = self::extractUrlSlug($string);
  679. Assert::notEmpty($urlSlug);
  680. Assert::maxLength($urlSlug, self::MAX_LENGTH_URLSLUG);
  681. $this->urlSlug = $urlSlug;
  682. }
  683. public static function extractUrlSlug($string): array|string|null
  684. {
  685. return UrlSlug::fromString($string);
  686. }
  687. }