connection = $connection; $this->credentials = $credentials; $this->cache_notice = $cache_notice; } /* --------------------------------------------------------------------- * Menu * * Every item is a route of the React app. The menu is the same before * the site is connected; the app shows the setup screen until then. * ------------------------------------------------------------------ */ public function register_menu() { add_menu_page( __( 'RazorBooking', 'razorbooking' ), __( 'RazorBooking', 'razorbooking' ), 'manage_options', self::SLUG, array( $this, 'render_app' ), // Drawn by menu_icon_style() instead: a data-URI icon is painted // black first and recoloured by script a moment later. 'none', 26 ); $children = array( '' => __( 'Home', 'razorbooking' ), 'services' => __( 'Services', 'razorbooking' ), 'team' => __( 'Team', 'razorbooking' ), 'placements' => __( 'Add to site', 'razorbooking' ), ); foreach ( $children as $route => $label ) { $this->add_screen( $route, $label ); } $rest = array( 'design' => __( 'Design', 'razorbooking' ), 'seo' => __( 'SEO', 'razorbooking' ), 'support' => __( 'Support', 'razorbooking' ), 'settings' => __( 'Settings', 'razorbooking' ), ); foreach ( $rest as $route => $label ) { $this->add_screen( $route, $label ); } } /** * A submenu item that stays inside this plugin. * * @param string $route Hash fragment, empty for Home. * @param string $label */ private function add_screen( $route, $label ) { add_submenu_page( self::SLUG, $label, $label, 'manage_options', $route ? self::SLUG . '#/' . $route : self::SLUG, array( $this, 'render_app' ) ); } public function fix_submenu_highlight( $parent_file ) { return self::is_own_screen() ? self::SLUG : $parent_file; } /** Whether the current admin screen is one of this plugin's. */ private static function is_own_screen() { $screen = get_current_screen(); return $screen && false !== strpos( $screen->id, self::SLUG ); } /** * The menu icon, on every admin screen: the brandmark as a mask over the * menu's own icon colour (`currentColor`), so it follows the admin colour * scheme, hover and the current page with no script and no flash of black. * The box is WordPress's own `dashicons-before` icon, which `'none'` gives. */ public function menu_icon_style() { $mask = 'url("' . $this->menu_icon() . '") no-repeat center / 20px auto'; wp_add_inline_style( 'admin-menu', '#adminmenu #toplevel_page_' . self::SLUG . ' div.wp-menu-image::before{' . 'content:"";background-color:currentColor;' . '-webkit-mask:' . $mask . ';mask:' . $mask . ';}' ); } private function menu_icon() { // The RazorBooking brandmark (".R."), reduced to one path and two circles. // Used as a mask (menu_icon_style()), so only its shape matters. $svg = ''; return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } /* --------------------------------------------------------------------- * App mount * ------------------------------------------------------------------ */ public function render_app() { require RAZORBOOKING_PATH . 'admin/partials/razorbooking-admin-display.php'; } public function enqueue_styles( $hook ) { if ( ! $this->is_plugin_screen( $hook ) ) { return; } self::enqueue_design_system(); } /** The setup screen's picture, shipped with the plugin. */ const SETUP_IMAGE = 'admin/img/setup.svg'; /** The admin stylesheet's handle. */ const STYLE_HANDLE = 'razorbooking-admin'; /** * Enqueue the brand font and the admin stylesheet (scoped to .rb-root). * Loaded after core's `common` and `forms` so its base rules win ties. * Also used by the activation banner. */ public static function enqueue_design_system() { wp_enqueue_style( self::STYLE_HANDLE . '-fonts', RAZORBOOKING_URL . 'admin/css/razorbooking-fonts.css', array(), self::asset_version( 'admin/css/razorbooking-fonts.css' ), 'all' ); wp_enqueue_style( self::STYLE_HANDLE, RAZORBOOKING_URL . 'admin/js/razorbooking-admin.css', array( 'common', 'forms', self::STYLE_HANDLE . '-fonts' ), self::asset_version( 'admin/js/razorbooking-admin.css' ), 'all' ); } /** * Add `rb-app` to the body class on this plugin's screens. The * stylesheet's full-width layout rules apply only there. * * @param string $classes * @return string */ public function add_body_class( $classes ) { return self::is_own_screen() ? $classes . ' rb-app' : $classes; } /** * An asset's version: the plugin version plus the file's modification * time, so each build busts the browser cache. * * @param string $relative Path inside the plugin. * @return string */ private static function asset_version( $relative ) { $path = RAZORBOOKING_PATH . $relative; return file_exists( $path ) ? RAZORBOOKING_VERSION . '.' . filemtime( $path ) : RAZORBOOKING_VERSION; } public function enqueue_scripts( $hook ) { if ( ! $this->is_plugin_screen( $hook ) ) { return; } wp_enqueue_script( self::SCRIPT_HANDLE, RAZORBOOKING_URL . 'admin/js/razorbooking-admin.js', // The bundle uses WordPress's React (see docs/DEVELOPMENT.md). array( 'react', 'react-dom', 'wp-element' ), self::asset_version( 'admin/js/razorbooking-admin.js' ), true ); // What the app needs on first paint. wp_localize_script( self::SCRIPT_HANDLE, 'razorbookingBoot', array( 'root' => esc_url_raw( rest_url( Razorbooking_Rest_Controller::NS ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'adminUrl' => admin_url( 'admin.php?page=' . self::SLUG ), 'appUrl' => RAZORBOOKING_APP_URL, 'siteName' => get_bloginfo( 'name' ), // Where "Exit setup" goes. 'wpAdminUrl' => admin_url(), 'setupImage' => esc_url_raw( plugins_url( self::SETUP_IMAGE, RAZORBOOKING_FILE ) ), // The WordPress user's name, for the greeting. 'userName' => wp_get_current_user()->display_name, 'siteUrl' => home_url(), // Signs the floating button preview URL (Razorbooking_Public::floating_preview()). 'floatingPreviewNonce' => wp_create_nonce( Razorbooking_Public::FLOATING_PREVIEW ), 'timezone' => wp_timezone_string(), 'locale' => get_locale(), 'version' => RAZORBOOKING_VERSION, 'mode' => get_user_meta( get_current_user_id(), Razorbooking_Option::USER_MODE, true ), 'connection' => $this->connection->current()->to_array(), ) ); if ( function_exists( 'wp_set_script_translations' ) ) { wp_set_script_translations( self::SCRIPT_HANDLE, 'razorbooking', RAZORBOOKING_PATH . 'languages' ); } } private function is_plugin_screen( $hook ) { return is_string( $hook ) && false !== strpos( $hook, self::SLUG ); } /* --------------------------------------------------------------------- * Chrome * ------------------------------------------------------------------ */ /** The most important connection notice, if any. */ public function render_notices() { if ( ! current_user_can( 'manage_options' ) ) { return; } $this->cache_notice->render(); $connection = $this->connection->current(); if ( $connection->is_clone ) { printf( '
%s %s
%s %s %s