adminJs(); $this->adminCss(); }); add_filter('admin_body_class', [self::class, 'bodyClass']); } /** * The screens of the editor (Add new and edit). Their ids depend on the translated menu title, so WordPress * builds them. * * @return string[] */ public static function editorScreenIds(): array { return [ get_plugin_page_hookname(SubpageCreate::SLUG, SubpageMaster::SLUG), 'admin_page_' . SubpageEdit::SLUG, ]; } /** * Mark the screens of the editor with a class of our own, so the CSS does not depend on a screen id (that * changes with the language of the menu title). * * @param string $classes The classes WordPress gives the body of an admin screen. */ public static function bodyClass($classes): string { $screen = function_exists('get_current_screen') ? get_current_screen() : null; if ($screen && in_array($screen->id, self::editorScreenIds(), true)) { return $classes . ' aop-editor-screen'; } return (string) $classes; } /** * Handles all the admin js files. */ private function adminJs() { Data::adminPagesIds()->filter(function ($page) { return get_current_screen()->id === $page; })->each(function () { // window.aop.fields: the place where the built-in and add-on field types register (Field Types API). // The editor reads it, so it comes first. static::buildScript(Plugin::PREFIX . 'field-registry-js', 'field-registry'); // The handle stays 'aop-app-js': the notices and the localized data address it. static::buildScript(Plugin::PREFIX . 'app-js', 'editor', [Plugin::PREFIX . 'field-registry-js']); static::scriptTranslations(Plugin::PREFIX . 'app-js'); wp_localize_script(Plugin::PREFIX . 'app-js', Plugin::PREFIX_ . 'editor_config', static::editorConfig()); wp_localize_script(Plugin::PREFIX . 'app-js', Plugin::PREFIX_ . 'script_data_js', Data::js()->toArray()); wp_localize_script(Plugin::PREFIX . 'app-js', Plugin::PREFIX_ . 'dashicons_js', Dashicons::allIconsCollection()->toArray()); /** * Enqueue the editor scripts of your field types here. Runs on the screens of the editor after the * scripts of the plugin were enqueued. * * @since 1.0.0 */ do_action('aop_enqueue_editor_assets'); }); } /** * What the React editor needs besides the page data: the fields of the form that WordPress always posted * with a page. They travel in a script, not in an attribute named nonce (analysis S14). * * @return array{nonceName: string, nonce: string, referer: string, svgUploads: bool} */ public static function editorConfig(): array { list($nonceName, $nonce, $referer) = SubpageEdit::wpNonceFieldArray(); return [ 'nonceName' => $nonceName, 'nonce' => $nonce, 'referer' => $referer, // WordPress blocks SVG uploads by default; the image field says so (a plugin like Safe SVG allows them). 'svgUploads' => in_array('image/svg+xml', get_allowed_mime_types(), true), ]; } /** * Handles all the admin css files. */ private function adminCss() { Data::adminPagesIds()->filter(function ($page) { return get_current_screen()->id === $page && get_current_screen()->id !== 'toplevel_page_admin_options_pages_master'; })->each(function () { static::buildStyle(Plugin::PREFIX . 'app-css', 'css/aop'); }); // The list of pages has its own WordPress table; only the dialog and the row link need styles. Data::adminPagesIds()->filter(function ($page) { return get_current_screen()->id === $page && get_current_screen()->id === 'toplevel_page_admin_options_pages_master'; })->each(function () { static::buildStyle(Plugin::PREFIX . 'master-css', 'css/master'); }); wp_enqueue_style(Plugin::PREFIX . 'icon-css', Plugin::assetsUrl() . 'css/aop-icon.css', [], Plugin::releaseVersion(), 'all'); } public static function version($file) { $path = Plugin::assetsDir() . $file; return file_exists($path) ? md5(filemtime($path)) : Plugin::releaseVersion(); } /** * Dependencies and version of a wp-scripts entry, read from its generated .asset.php. * * @param string $entry Entry name, e.g. 'settings'. * * @return array{dependencies: string[], version: string} */ public static function buildAsset($entry) { $file = Plugin::buildDir() . $entry . '.asset.php'; if (is_readable($file)) { $asset = include $file; return [ 'dependencies' => (array) ($asset['dependencies'] ?? []), 'version' => (string) ($asset['version'] ?? Plugin::releaseVersion()), ]; } return ['dependencies' => [], 'version' => static::buildFileVersion($entry . '.js')]; } /** * Version for a built file that has no .asset.php (the CSS files). * * @param string $file Path relative to includes/build/. * * @return string */ public static function buildFileVersion($file) { $path = Plugin::buildDir() . $file; return file_exists($path) ? (string) filemtime($path) : Plugin::releaseVersion(); } /** * Enqueue a wp-scripts JavaScript entry with the dependencies webpack found. * * @param string $handle * @param string $entry Entry name, e.g. 'settings'. * @param string[] $extraDependencies Script handles webpack cannot detect (e.g. 'wp-api'). */ public static function buildScript($handle, $entry, array $extraDependencies = []) { $asset = static::buildAsset($entry); wp_enqueue_script( $handle, Plugin::buildUrl() . $entry . '.js', array_values(array_unique(array_merge($asset['dependencies'], $extraDependencies))), $asset['version'], true ); } /** * Load the translations of a script: a language pack of translate.wordpress.org, otherwise the .json files * that ship with the plugin. * * @param string $handle */ public static function scriptTranslations($handle): void { wp_set_script_translations($handle, 'admin-options-pages', Plugin::languagesDir()); } /** * Enqueue a wp-scripts CSS entry. * * @param string $handle * @param string $entry Entry name, e.g. 'css/settings'. * @param string[] $dependencies Style handles. */ public static function buildStyle($handle, $entry, array $dependencies = []) { wp_enqueue_style($handle, Plugin::buildUrl() . $entry . '.css', $dependencies, static::buildFileVersion($entry . '.css')); } }