*/ function adminight_areas() { $areas = array( 'core' => array( 'label' => __( 'Dashboard and content', 'adminight' ), 'description' => __( 'Dashboard, posts, pages, media, comments, users, tools, settings, plugins and updates.', 'adminight' ), 'url' => admin_url( 'index.php' ), ), 'editor' => array( 'label' => __( 'Editors', 'adminight' ), 'description' => __( 'The block editor around the writing area, the classic editor and the code editors.', 'adminight' ), 'url' => admin_url( 'post-new.php' ), ), 'appearance' => array( 'label' => __( 'Appearance', 'adminight' ), 'description' => __( 'Themes, the Customizer, the Site Editor, widgets and menus.', 'adminight' ), 'url' => admin_url( 'themes.php' ), ), 'login' => array( 'label' => __( 'Login screen', 'adminight' ), 'description' => __( 'Follows the default mode, since nobody is signed in there yet.', 'adminight' ), 'url' => wp_login_url(), ), ); if ( class_exists( 'WooCommerce' ) ) { $areas['woocommerce'] = array( 'label' => __( 'WooCommerce', 'adminight' ), 'description' => __( 'Products, orders, coupons and the WooCommerce settings.', 'adminight' ), 'url' => admin_url( 'edit.php?post_type=product' ), ); } return $areas; } /** * Plugin modes: off, the standard dark styles, or inverted colours. * * @return array */ function adminight_plugin_modes() { return array( 'off' => __( 'Off', 'adminight' ), 'dark' => __( 'Dark', 'adminight' ), 'invert' => __( 'Inverted colours (experimental)', 'adminight' ), ); } /** * Areas the administrator has turned on. Every area is on until it is turned off. * * @return array */ function adminight_site_areas() { $saved = get_option( ADMINIGHT_AREAS_OPTION, array() ); $saved = is_array( $saved ) ? $saved : array(); $out = array(); foreach ( array_keys( adminight_areas() ) as $key ) { $out[ $key ] = isset( $saved[ $key ] ) ? (bool) $saved[ $key ] : true; } return $out; } /** * The administrator's choice for each plugin, keyed by plugin folder. Plugins not listed are off. * * @return array */ function adminight_site_plugins() { $saved = get_option( ADMINIGHT_PLUGINS_OPTION, array() ); return is_array( $saved ) ? $saved : array(); } /** * The places a user has turned off for themselves ("core", "editor", … or "p-"). * * @param int $user_id User ID; the current user when 0. * @return string[] */ function adminight_user_off( $user_id = 0 ) { $user_id = $user_id ? $user_id : get_current_user_id(); $off = $user_id ? get_user_meta( $user_id, ADMINIGHT_USER_OFF_META, true ) : array(); return is_array( $off ) ? $off : array(); } /** * Whether a post type or taxonomy belongs to WooCommerce. * * @param string $post_type Post type of the screen. * @param string $taxonomy Taxonomy of the screen. * @return bool */ function adminight_is_woo_type( $post_type, $taxonomy ) { if ( in_array( $post_type, array( 'product', 'product_variation', 'shop_order', 'shop_order_refund', 'shop_coupon', 'shop_order_placehold' ), true ) ) { return true; } return in_array( $taxonomy, array( 'product_cat', 'product_tag', 'product_shipping_class', 'product_brand' ), true ) || 0 === strpos( (string) $taxonomy, 'pa_' ); } /** * Whether a plugin folder is WooCommerce (or one of its own bundled parts), which has its own area. * * @param string $folder Plugin folder. * @return bool */ function adminight_is_woo_folder( $folder ) { return class_exists( 'WooCommerce' ) && 0 === strpos( $folder, 'woocommerce' ); } /** * The plugin folder a PHP file belongs to, or "" when it is not inside a plugin. * * @param string $file Absolute file path. * @return string */ function adminight_file_plugin( $file ) { if ( '' === (string) $file ) { return ''; } $base = plugin_basename( $file ); if ( $base === wp_normalize_path( $file ) || 0 === strpos( $base, '/' ) || preg_match( '#^[a-z]:#i', $base ) ) { return ''; } $folder = false !== strpos( $base, '/' ) ? strtok( $base, '/' ) : preg_replace( '/\.php$/', '', $base ); return sanitize_key( (string) $folder ); } /** * The file a callback is defined in. * * @param mixed $callback A hook callback. * @return string */ function adminight_callback_file( $callback ) { try { if ( is_string( $callback ) && false !== strpos( $callback, '::' ) ) { $callback = explode( '::', $callback, 2 ); } if ( is_array( $callback ) && 2 === count( $callback ) ) { $ref = new ReflectionMethod( $callback[0], $callback[1] ); } elseif ( $callback instanceof Closure || ( is_string( $callback ) && function_exists( $callback ) ) ) { $ref = new ReflectionFunction( $callback ); } elseif ( is_object( $callback ) && method_exists( $callback, '__invoke' ) ) { $ref = new ReflectionMethod( $callback, '__invoke' ); } else { return ''; } return (string) $ref->getFileName(); } catch ( ReflectionException $e ) { return ''; } } /** * The plugin folder that draws an admin page (page=$page under $parent), or "" when that is not known. * * @param string $page Page slug. * @param string $parent Parent menu slug ("" for a top-level page). * @return string */ function adminight_page_owner( $page, $parent ) { global $wp_filter; $hook = get_plugin_page_hookname( $page, $parent ); if ( empty( $wp_filter[ $hook ] ) ) { return ''; } foreach ( $wp_filter[ $hook ]->callbacks as $callbacks ) { foreach ( $callbacks as $callback ) { $folder = adminight_file_plugin( adminight_callback_file( $callback['function'] ) ); if ( '' !== $folder ) { return $folder; } } } return ''; } /** * The place the current admin screen belongs to: an area key, or "p-" for another plugin's screen. * * @return string */ function adminight_current_scope() { global $pagenow, $plugin_page, $typenow, $taxnow; $post_type = isset( $typenow ) ? (string) $typenow : ''; $taxonomy = isset( $taxnow ) ? (string) $taxnow : ''; if ( isset( $pagenow ) && 'customize.php' === $pagenow ) { return 'appearance'; } if ( ! empty( $plugin_page ) ) { if ( in_array( $plugin_page, array( 'custom-header', 'custom-background' ), true ) ) { return 'appearance'; } $owner = adminight_page_owner( $plugin_page, (string) get_admin_page_parent() ); if ( 'adminight' === $owner || 'adminight' === $plugin_page ) { return 'core'; } if ( adminight_is_woo_folder( $owner ) ) { return 'woocommerce'; } return '' !== $owner ? 'p-' . $owner : 'p-' . sanitize_key( $plugin_page ); } if ( class_exists( 'WooCommerce' ) && adminight_is_woo_type( $post_type, $taxonomy ) ) { return 'woocommerce'; } // Page builders open their own editor through post.php?action=; that editor belongs to the builder. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- only reads which screen this is. $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : ''; if ( isset( $pagenow ) && 'post.php' === $pagenow && '' !== $action && 'edit' !== $action ) { return 'p-' . $action; } switch ( isset( $pagenow ) ? $pagenow : '' ) { case 'post.php': case 'post-new.php': case 'theme-editor.php': case 'plugin-editor.php': return 'editor'; case 'themes.php': case 'theme-install.php': case 'site-editor.php': case 'widgets.php': case 'nav-menus.php': return 'appearance'; } return 'core'; } /** * How dark mode applies to a place for a user: "dark", "invert" or "" (not here). * * @param string $scope An area key or "p-". * @param int $user_id User ID; the current user when 0. * @return string */ function adminight_scope_style( $scope, $user_id = 0 ) { if ( in_array( $scope, adminight_user_off( $user_id ), true ) ) { return ''; } if ( 0 === strpos( $scope, 'p-' ) ) { $plugins = adminight_site_plugins(); $folder = substr( $scope, 2 ); $mode = isset( $plugins[ $folder ] ) ? $plugins[ $folder ] : 'off'; return in_array( $mode, array( 'dark', 'invert' ), true ) ? $mode : ''; } $areas = adminight_site_areas(); return ! empty( $areas[ $scope ] ) ? 'dark' : ''; } /** * Whether "?adminight=off" asks for this one page without dark mode (a way back when a screen looks wrong). * * @return bool */ function adminight_paused() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display switch for the current page; nothing is saved. return isset( $_GET['adminight'] ) && 'off' === sanitize_key( wp_unslash( $_GET['adminight'] ) ); } /** * How dark mode applies to the current screen for the current user. * * @return string "dark", "invert" or "". */ function adminight_current_style() { if ( adminight_paused() ) { return ''; } return adminight_scope_style( adminight_current_scope() ); } /** * Other plugins that add pages to the admin menu, as plugin folder => [label, url]. WooCommerce has its own area. * * @return array */ function adminight_detected_plugins() { global $menu, $submenu; if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $names = array(); foreach ( get_plugins() as $file => $data ) { $folder = sanitize_key( false !== strpos( $file, '/' ) ? strtok( $file, '/' ) : preg_replace( '/\.php$/', '', $file ) ); if ( empty( $names[ $folder ] ) ) { $names[ $folder ] = $data['Name']; } } $pages = array(); foreach ( (array) $menu as $item ) { if ( ! empty( $item[2] ) && false === strpos( $item[2], '.php' ) ) { $pages[] = array( (string) $item[2], '', admin_url( 'admin.php?page=' . rawurlencode( $item[2] ) ) ); } } foreach ( (array) $submenu as $parent => $items ) { foreach ( (array) $items as $item ) { if ( empty( $item[2] ) || false !== strpos( $item[2], '.php' ) ) { continue; } $url = false !== strpos( $parent, '.php' ) ? add_query_arg( 'page', rawurlencode( $item[2] ), admin_url( $parent ) ) : admin_url( 'admin.php?page=' . rawurlencode( $item[2] ) ); $pages[] = array( (string) $item[2], (string) $parent, $url ); } } $found = array(); foreach ( $pages as $page ) { $folder = adminight_page_owner( $page[0], $page[1] ); if ( '' === $folder || 'adminight' === $folder || adminight_is_woo_folder( $folder ) || isset( $found[ $folder ] ) ) { continue; } $found[ $folder ] = array( 'label' => isset( $names[ $folder ] ) ? $names[ $folder ] : $folder, 'url' => $page[2], ); } uasort( $found, function ( $a, $b ) { return strnatcasecmp( $a['label'], $b['label'] ); } ); return $found; }