PHP Version Scan screen (Network Admin > Settings on multisite). */ final class Hxpc_Admin { const SLUG = 'hexloom-php-version-scan'; const NONCE = 'hxpc'; const REVIEW_META = 'hxpc_review_notice_dismissed'; const REVIEW_URL = 'https://wordpress.org/support/plugin/hexloom-php-version-scan/reviews/#new-post'; const PRO_URL = 'https://hexloomlabs.com/php-scan/'; const ACTIONS = array( 'start', 'step', 'cancel', 'reset', 'report', 'dismiss' ); /** * Hook suffixes of the registered screens. * * @var string[] */ private static $screens = array(); /** * Register the hooks. * * @return void */ public static function init() { add_action( 'admin_menu', array( __CLASS__, 'menu' ) ); add_action( 'network_admin_menu', array( __CLASS__, 'menu' ) ); add_action( 'admin_enqueue_scripts', array( __CLASS__, 'assets' ) ); add_filter( 'plugin_action_links_' . plugin_basename( HXPC_FILE ), array( __CLASS__, 'action_links' ) ); foreach ( self::ACTIONS as $action ) { add_action( 'wp_ajax_hxpc_' . $action, array( __CLASS__, 'ajax' ) ); } } /** * Capability needed for the screen and every AJAX action. * * @return string */ private static function capability() { return is_multisite() ? 'manage_network_options' : 'manage_options'; } /** * Add the menu entry: Tools on a single site, Settings in the Network Admin. * * @return void */ public static function menu() { $parent = is_network_admin() ? 'settings.php' : 'tools.php'; $hook = add_submenu_page( $parent, __( 'PHP Version Scan', 'hexloom-php-version-scan' ), __( 'PHP Version Scan', 'hexloom-php-version-scan' ), self::capability(), self::SLUG, array( __CLASS__, 'render' ) ); if ( is_string( $hook ) ) { self::$screens[] = $hook; } } /** * Add a Scan link on the Plugins screen. * * @param string[] $links Existing action links. * @return string[] */ public static function action_links( $links ) { if ( current_user_can( self::capability() ) ) { $base = is_network_admin() ? network_admin_url( 'settings.php' ) : admin_url( 'tools.php' ); array_unshift( $links, '' . esc_html__( 'Scan', 'hexloom-php-version-scan' ) . '' ); } return $links; } /** * Load the stylesheet, the script and its data on our screen only. * * @param string $hook Current screen hook suffix. * @return void */ public static function assets( $hook ) { if ( ! in_array( $hook, self::$screens, true ) ) { return; } wp_enqueue_style( 'hxpc-admin', plugins_url( 'assets/admin.css', HXPC_FILE ), array( 'dashicons' ), HXPC_VERSION ); wp_enqueue_script( 'hxpc-admin', plugins_url( 'assets/admin.js', HXPC_FILE ), array( 'wp-i18n' ), HXPC_VERSION, true ); wp_set_script_translations( 'hxpc-admin', 'hexloom-php-version-scan' ); $data = array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE ), 'state' => Hxpc_Runner::state(), 'hasReport' => null !== Hxpc_Runner::report(), 'reviewUrl' => self::REVIEW_URL, 'showReview' => ! get_user_meta( get_current_user_id(), self::REVIEW_META, true ), 'maxFiles' => Hxpc_Runner::MAX_FILES, ); wp_add_inline_script( 'hxpc-admin', 'window.hxpcData = ' . wp_json_encode( $data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';', 'before' ); } /** * Print the screen. The script fills the regions that start hidden. * * @return void */ public static function render() { if ( ! current_user_can( self::capability() ) ) { wp_die( esc_html__( 'You do not have permission to open this page.', 'hexloom-php-version-scan' ), '', array( 'response' => 403 ) ); } ?>
null ) ); break; case 'hxpc_report': $rows = Hxpc_Runner::report(); if ( null === $rows ) { self::refuse( 'no_report', __( 'There are no results yet. Run a scan first.', 'hexloom-php-version-scan' ), 200 ); } foreach ( $rows as $i => $row ) { unset( $rows[ $i ]['path'] ); } wp_send_json_success( array( 'rows' => $rows, 'state' => Hxpc_Runner::state(), ) ); break; case 'hxpc_dismiss': update_user_meta( get_current_user_id(), self::REVIEW_META, 1 ); wp_send_json_success( array() ); break; default: self::refuse( 'action', __( 'Unknown request.', 'hexloom-php-version-scan' ), 400 ); } } /** * Answer with a runner state; an error state becomes an error response. * * @param array|null $state Runner state. * @return void */ private static function answer( $state ) { if ( is_array( $state ) && 'error' === $state['status'] && '' === $state['id'] ) { self::refuse( 'scan', $state['message'], 200, $state ); } wp_send_json_success( array( 'state' => $state ) ); } /** * Send a plain-language error. Nothing here answers with a 5xx on purpose. * * @param string $code Short machine code. * @param string $message Plain-English message. * @param int $status HTTP status. * @param array|null $state Optional state for the client. * @return void */ private static function refuse( $code, $message, $status, $state = null ) { wp_send_json_error( array( 'code' => $code, 'message' => $message, 'state' => $state, ), $status ); } }