loadManifest($pluginDirectory . '/manifest.json'); foreach ($manifest as $key => $value) { $this->{$key} = $value; } // check that plugin is active $this->active = get_plugin_option($this->key, 'active') ?? false; $this->iconSrc = $this->loadIcon($pluginDirectory . '/icon.svg'); } /** * @param list|string $value */ public function __set(string $name, array|string $value): void { $this->{$name} = $name === 'releaseDate' ? Time::createFromFormat('Y-m-d', $value) : $value; } public function init(): void { // add to admin navigation // TODO: setup navigation and views? } public function channelTag(Podcast $podcast, SimpleRSSElement $channel): void { } public function itemTag(Episode $episode, SimpleRSSElement $item): void { } public function siteHead(): void { } final public function isActive(): bool { return $this->active; } final public function isHookDeclared(string $name): bool { return in_array($name, $this->hooks, true); } final public function getKey(): string { return $this->key; } final public function getName(): string { $key = sprintf('Plugin.%s.name', $this->key); /** @var string $name */ $name = lang($key); if ($name === $key) { return $this->name; } return $name; } final public function getDescription(): string { $key = sprintf('Plugin.%s.description', $this->key); /** @var string $description */ $description = lang($key); if ($description === $key) { return $this->description; } return $description; } final protected function getOption(string $option): mixed { return get_plugin_option($this->key, $option); } final protected function setOption(string $option, mixed $value = null): void { set_plugin_option($this->key, $option, $value); } /** * @return array> */ private function loadManifest(string $path): array { // TODO: cache manifest data $manifestContents = file_get_contents($path); if (! $manifestContents) { throw new RuntimeException('manifest file not found!'); } /** @var array|null $manifest */ $manifest = json_decode($manifestContents, true); if ($manifest === null) { throw new RuntimeException('manifest.json is not a valid JSON', 1); } $validation = service('validation'); if (array_key_exists('options', $manifest)) { $optionRules = [ 'key' => 'required|alpha_numeric', 'name' => 'required|max_length[32]', 'description' => 'permit_empty|max_length[128]', ]; $defaultOption = [ 'key' => '', 'name' => '', 'description' => '', ]; $validation->setRules($optionRules); foreach ($manifest['options'] as $key => $options) { foreach ($options as $key2 => $option) { $manifest['options'][$key][$key2] = array_merge($defaultOption, $option); if (! $validation->run($manifest['options'][$key][$key2])) { dd($this->key, $manifest['options'][$key][$key2], $validation->getErrors()); } } } } $rules = [ 'name' => 'required|max_length[32]', 'version' => 'required|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]', 'compatible' => 'required|in_list[1.0]', 'description' => 'max_length[128]', 'releaseDate' => 'valid_date[Y-m-d]', 'license' => 'in_list[MIT]', 'author.name' => 'permit_empty|max_length[32]', 'author.email' => 'permit_empty|valid_email', 'author.url' => 'permit_empty|valid_url_strict', 'website' => 'valid_url_strict', 'keywords.*' => 'permit_empty|in_list[seo,podcasting20,analytics]', 'hooks.*' => 'permit_empty|in_list[' . implode(',', Plugins::HOOKS) . ']', 'options' => 'permit_empty', ]; $validation->setRules($rules); if (! $validation->run($manifest)) { dd($this->key, $manifest, $validation->getErrors()); } $defaultAttributes = [ 'description' => '', 'releaseDate' => '', 'license' => '', 'author' => [], 'website' => '', 'hooks' => [], 'keywords' => [], 'options' => [ 'settings' => [], 'podcast' => [], 'episode' => [], ], ]; $validated = $validation->getValidated(); return array_merge_recursive_distinct($defaultAttributes, $validated); } 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 ); } }