key = sprintf('%s/%s', $vendor, $package); // TODO: cache manifest data $manifestPath = $directory . '/manifest.json'; $this->manifest = new Manifest($this->key); $this->manifest->loadFromFile($manifestPath); $this->status = get_plugin_setting($this->key, 'active') ? PluginStatus::ACTIVE : PluginStatus::INACTIVE; $this->iconSrc = $this->loadIcon($directory . '/icon.svg'); $this->readmeHTML = $this->loadReadme($directory . '/README.md'); } /** * @param list|string $value */ public function __set(string $name, array|string $value): void { $this->{$name} = $value; } #[Override] public function rssBeforeChannel(Podcast $podcast): void { } #[Override] public function rssAfterChannel(Podcast $podcast, SimpleRSSElement $channel): void { } #[Override] public function rssBeforeItem(Episode $episode): void { } #[Override] public function rssAfterItem(Episode $episode, SimpleRSSElement $item): void { } #[Override] public function siteHead(): void { } final public function getGeneralSetting(string $key): mixed { return get_plugin_setting($this->key, $key); } final public function getPodcastSetting(int $podcastId, string $key): mixed { return get_plugin_setting($this->key, $key, ['podcast', $podcastId]); } final public function getEpisodeSetting(int $episodeId, string $key): mixed { return get_plugin_setting($this->key, $key, ['episode', $episodeId]); } /** * @return bool true on success, false on failure */ final public function activate(): bool { if ($this->status === PluginStatus::ACTIVE) { return false; } $this->setStatus(PluginStatus::ACTIVE); if ($this->status === PluginStatus::INACTIVE) { return false; } set_plugin_setting($this->key, 'active', true); return true; } final public function deactivate(): bool { if ($this->status !== PluginStatus::ACTIVE) { return false; } $this->setStatus(PluginStatus::INACTIVE); set_plugin_setting($this->key, 'active', false); return true; } final public function getStatus(): PluginStatus { return $this->status; } final public function getDirectory(): string { return $this->directory; } /** * @return array */ final public function getManifestErrors(): array { return Manifest::getPluginErrors($this->key); } final public function isHookDeclared(string $name): bool { return in_array($name, $this->manifest->hooks, true); } final public function getVersion(): string { return $this->manifest->version; } final public function getHomepage(): ?URI { return $this->manifest->homepage; } final public function getRepository(): ?Repository { return $this->manifest->repository; } /** * @return list */ final public function getKeywords(): array { return $this->manifest->keywords; } /** * @return Person[] */ final public function getAuthors(): array { return $this->manifest->authors; } final public function getIconSrc(): string { return $this->iconSrc; } /** * @return Field[] */ final public function getSettingsFields(string $type): array { $settings = $this->manifest->settings; if (! $settings instanceof Settings) { return []; } return $settings->{$type}; } /** * @return list */ final public function getHooks(): array { return $this->manifest->hooks; } final public function getKey(): string { return $this->key; } final public function getVendor(): string { return $this->vendor; } final public function getPackage(): string { return $this->package; } final public function getTitle(): string { $key = sprintf('Plugin.%s.title', $this->key); /** @var string $title */ $title = lang($key); if ($title === $key) { return $this->manifest->name; } return $title; } final public function getDescription(): ?string { $key = sprintf('Plugin.%s.description', $this->key); /** @var string $description */ $description = lang($key); if ($description === $key) { return $this->manifest->description; } return $description; } final public function getReadmeHTML(): ?string { return $this->readmeHTML; } final public function getLicense(): string { return $this->manifest->license ?? 'UNLICENSED'; } /** * @param PluginStatus::ACTIVE|PluginStatus::INACTIVE $value */ final protected function setStatus(PluginStatus $value): self { if ($this->getManifestErrors() !== []) { $this->status = PluginStatus::INVALID; return $this; } $this->status = $value; return $this; } final protected function getOption(string $option): mixed { return get_plugin_setting($this->key, $option); } final protected function setOption(string $option, mixed $value = null): void { set_plugin_setting($this->key, $option, $value); } private function loadIcon(string $path): string { // TODO: cache icon $svgIcon = @file_get_contents($path); if (! $svgIcon) { return "data:image/svg+xml;utf8,%3Csvg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' viewBox='0 0 64 64'%3E%3Cpath fill='%2300564A' d='M0 0h64v64H0z'%2F%3E%3Cpath fill='%23E7F9E4' d='M25.3 18.7a5 5 0 1 1 9.7 1.6h7c1 0 1.7.8 1.7 1.7v7a5 5 0 1 1 0 9.4v7c0 .9-.8 1.6-1.7 1.6H18.7c-1 0-1.7-.7-1.7-1.7V22c0-1 .7-1.7 1.7-1.7h7a5 5 0 0 1-.4-1.6Z'%2F%3E%3C%2Fsvg%3E"; } $encodedIcon = rawurlencode(str_replace(["\r", "\n"], ' ', $svgIcon)); return 'data:image/svg+xml;utf8,' . str_replace( ['%20', '%22', '%27', '%3D'], [' ', "'", "'", '='], $encodedIcon ); } private function loadReadme(string $path): ?string { // TODO: cache readme $readmeMD = @file_get_contents($path); if (! $readmeMD) { return null; } $environment = new Environment([ 'html_input' => 'escape', 'allow_unsafe_links' => false, 'host' => (new URI(base_url()))->getHost(), ]); $environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new GithubFlavoredMarkdownExtension()); $environment->addExtension(new SmartPunctExtension()); $environment->addEventListener( DocumentParsedEvent::class, static function (DocumentParsedEvent $event): void { (new ExternalLinkProcessor())->onDocumentParsed($event); } ); $environment->addEventListener( DocumentParsedEvent::class, static function (DocumentParsedEvent $event): void { (new ExternalImageProcessor())->onDocumentParsed($event); } ); $converter = new MarkdownConverter($environment); return $converter->convert($readmeMD) ->getContent(); } }