]*)>(.*?)<\/span>/i'; const HIGHLIGHT_ATTR_KANJI = 'style="color: rgb(255, 78, 8);"'; protected ?int $id; protected ?int $mod; protected string $model; protected string $profile; protected array $cardIds = []; protected array $fields = []; protected array $tags = []; // -------------------------------------------------- Getters & setters --- public function getId(): int { return $this->id; } public function setId(int $id): static { $this->id = $id; return $this; } public function getModel(): string { return $this->model; } public function getFields(): array { return $this->fields; } public function setFields(array $fields): static { $this->fields = $fields; return $this; } /** @return list */ public function getTags(): array { return $this->tags; } // ------------------------------------------------------- Anki-related --- public static function fromAnki(array $noteInfo): static { $note = new static(); [ 'noteId' => $note->id, 'mod' => $note->mod, 'profile' => $note->profile, 'tags' => $note->tags, 'modelName' => $note->model, 'cards' => $note->cardIds, ] = $noteInfo; // the fields array key value comes with an order fields that is // already maintained by PHP since arrays are ordered dictionaries. // So we can safely just drop it. // // REVIEW: Having said that, maybe ordering the array before throwing // the order would be advisable. $note->fields = array_map(fn($x) => $x['value'], $noteInfo['fields']); return $note; } public function toAnki(): array { return [ 'id' => $this->id, ]; } // ---------------------------------------------------- Derived methods --- public function getCreatedAt(): \DateTimeImmutable { $timestamp = ceil($this->id / 1000); return \DateTimeImmutable::createFromFormat('U', $timestamp); } public function getUpdatedAt(): \DateTimeImmutable { return \DateTimeImmutable::createFromFormat('U', $this->mod); } // -------------------------------------------------- Utility functions --- protected static function stringHighlight(string $haystack, string $needle): string { $replace = sprintf( '%s', self::HIGHLIGHT_ATTR_KANJI, $needle, ); return str_replace($needle, $replace, strip_tags($haystack)); } protected static function parseMediaInfo(string $notes): ?array { $matches = null; // Parse the notes fields. It can be in the form of // series-name_S01 EP07 (11h22m33s44ms) // or // movie-name EP (11h22m33s44ms) if (1 !== preg_match( '/(?[0-9A-Za-z\-_]+)(_S)?(?\d*) EP(?\d*) \((?