true, 'show_ui' => true ), 'names' ); unset( $types['attachment'] ); return array_values( array_filter( (array) apply_filters( 'ocssjs_supported_post_types', $types ), 'post_type_exists' ) ); } public function add_meta_boxes(): void { foreach ( $this->post_types() as $type ) { add_meta_box( 'ocssjs-page-assets', __( 'Page CSS & JavaScript', 'page-asset-manager' ), array( $this, 'meta_box' ), $type, 'advanced', 'default' ); } } public function admin_assets( string $hook ): void { if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) { return; } $screen = get_current_screen(); if ( ! $screen || ! in_array( $screen->post_type, $this->post_types(), true ) ) { return; } wp_enqueue_script( 'jquery-ui-sortable' ); wp_enqueue_script( 'ocssjs-admin', plugins_url( 'js/reorder-admin.js', __FILE__ ), array( 'jquery', 'jquery-ui-sortable' ), self::VERSION, true ); wp_enqueue_style( 'ocssjs-admin', plugins_url( 'css/reorder-admin.css', __FILE__ ), array( 'dashicons' ), self::VERSION ); wp_localize_script( 'ocssjs-admin', 'ocssjsAdmin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE ), 'messages' => array( 'saving' => __( 'Saving…', 'page-asset-manager' ), 'scanning' => __( 'Scanning the front end…', 'page-asset-manager' ), 'reset' => __( 'Reset ordering and enable all discovered assets?', 'page-asset-manager' ), 'error' => __( 'The request failed. Please try again.', 'page-asset-manager' ), ), ) ); } public function meta_box( WP_Post $post ): void { $settings = $this->settings( $post->ID ); $assets = $this->saved_assets( $post->ID, $settings ); ?>

asset_column( 'css', __( 'Stylesheets', 'page-asset-manager' ), $assets['css'], $settings['css_disabled'] ); ?> asset_column( 'js', __( 'JavaScript', 'page-asset-manager' ), $assets['js'], $settings['js_disabled'] ); ?>
$this->post_types(), 'post_status' => array( 'publish', 'draft', 'private' ), 'posts_per_page' => 100, 'orderby' => 'modified', 'order' => 'DESC', 'post__not_in' => array( $current ), 'meta_key' => self::META, ) ); foreach ( $posts as $post ) { printf( '', (int) $post->ID, esc_html( get_the_title( $post ) ?: __( '(no title)', 'page-asset-manager' ) ) ); } } private function asset_column( string $type, string $title, array $assets, array $disabled ): void { ?>

>

authorize(); $settings = array( 'css_order' => $this->handles( $this->posted_array( 'css_order' ) ), 'js_order' => $this->handles( $this->posted_array( 'js_order' ) ), 'css_disabled' => $this->handles( $this->posted_array( 'css_disabled' ) ), 'js_disabled' => array_values( array_diff( $this->handles( $this->posted_array( 'js_disabled' ) ), array( 'jquery', 'jquery-core', 'jquery-migrate' ) ) ), ); $this->store_settings( $post_id, $settings ); wp_send_json_success( array( 'message' => __( 'Asset settings saved.', 'page-asset-manager' ) ) ); } public function ajax_copy(): void { $post_id = $this->authorize(); $source = isset( $_POST['source_id'] ) ? absint( wp_unslash( $_POST['source_id'] ) ) : 0; if ( ! $source || ! current_user_can( 'edit_post', $source ) ) { wp_send_json_error( array( 'message' => __( 'Invalid source item.', 'page-asset-manager' ) ), 403 ); } $this->store_settings( $post_id, $this->settings( $source ) ); wp_send_json_success( array( 'message' => __( 'Settings copied. Reloading…', 'page-asset-manager' ) ) ); } public function ajax_scan(): void { $post_id = $this->authorize(); $url = get_permalink( $post_id ); if ( ! $url ) { wp_send_json_error( array( 'message' => __( 'This item has no front-end URL.', 'page-asset-manager' ) ), 400 ); } $token = wp_generate_password( 32, false, false ); set_transient( self::SCAN_TOKEN . $post_id, hash( 'sha256', $token ), MINUTE_IN_SECONDS ); $cookies = array(); foreach ( $_COOKIE as $name => $value ) { if ( is_string( $name ) && is_string( $value ) ) { $cookies[] = new WP_Http_Cookie( array( 'name' => sanitize_key( $name ), 'value' => $value, ) ); } } $scan_url = add_query_arg( array( 'ocssjs_scan' => $post_id, 'ocssjs_token' => $token, 'cache_bust' => time() ), $url ); $request_args = array( 'timeout' => 20, 'redirection' => 3, 'headers' => array( 'Cache-Control' => 'no-cache' ), 'cookies' => $cookies, 'sslverify' => true, ); $response = wp_remote_get( $scan_url, $request_args ); /* * Local development tools commonly use a self-signed certificate. * Retry only this specific failure and only for the site's own host. */ if ( is_wp_error( $response ) && $this->is_same_site_certificate_error( $response, $scan_url ) ) { $request_args['sslverify'] = false; $response = wp_remote_get( $scan_url, $request_args ); } delete_transient( self::SCAN_TOKEN . $post_id ); if ( is_wp_error( $response ) ) { wp_send_json_error( array( 'message' => sprintf( __( 'Front-end scan failed: %s', 'page-asset-manager' ), $response->get_error_message() ) ), 500 ); } $assets = get_transient( self::SCAN_RESULT . $post_id ); delete_transient( self::SCAN_RESULT . $post_id ); if ( ! is_array( $assets ) ) { wp_send_json_error( array( 'message' => __( 'No scan result was returned. A cache or loopback restriction may be blocking discovery.', 'page-asset-manager' ) ), 500 ); } update_post_meta( $post_id, self::ASSETS, $assets ); wp_send_json_success( array( 'message' => __( 'Assets refreshed. Reloading…', 'page-asset-manager' ) ) ); } private function is_same_site_certificate_error( WP_Error $error, string $url ): bool { $message = strtolower( $error->get_error_message() ); if ( false === strpos( $message, 'curl error 60' ) && false === strpos( $message, 'certificate' ) ) { return false; } $scan_host = wp_parse_url( $url, PHP_URL_HOST ); $site_host = wp_parse_url( home_url( '/' ), PHP_URL_HOST ); return is_string( $scan_host ) && is_string( $site_host ) && 0 === strcasecmp( $scan_host, $site_host ); } public function capture_scan(): void { if ( is_admin() || ! is_singular() || empty( $_GET['ocssjs_scan'] ) || empty( $_GET['ocssjs_token'] ) ) { return; } $post_id = absint( wp_unslash( $_GET['ocssjs_scan'] ) ); $token = sanitize_text_field( wp_unslash( $_GET['ocssjs_token'] ) ); $expected = get_transient( self::SCAN_TOKEN . $post_id ); if ( get_queried_object_id() !== $post_id || ! is_string( $expected ) || ! hash_equals( $expected, hash( 'sha256', $token ) ) ) { return; } set_transient( self::SCAN_RESULT . $post_id, array( 'css' => $this->collect( wp_styles() ), 'js' => $this->collect( wp_scripts() ) ), MINUTE_IN_SECONDS ); } private function collect( WP_Dependencies $registry ): array { $assets = array(); foreach ( array_values( array_unique( $registry->queue ) ) as $handle ) { if ( ! isset( $registry->registered[ $handle ] ) ) { continue; } $item = $registry->registered[ $handle ]; $assets[] = array( 'handle' => sanitize_key( $handle ), 'src' => is_string( $item->src ) ? $item->src : '', 'deps' => $this->handles( (array) $item->deps ) ); } return $assets; } public function apply_settings(): void { if ( is_admin() || ! is_singular() ) { return; } $settings = $this->settings( get_queried_object_id() ); $this->reorder( wp_styles(), $settings['css_order'] ); $this->reorder( wp_scripts(), $settings['js_order'] ); foreach ( $settings['css_disabled'] as $handle ) { wp_dequeue_style( $handle ); } foreach ( $settings['js_disabled'] as $handle ) { if ( ! in_array( $handle, array( 'jquery', 'jquery-core', 'jquery-migrate' ), true ) ) { wp_dequeue_script( $handle ); } } } private function reorder( WP_Dependencies $registry, array $preferred ): void { $queue = array_values( array_unique( $registry->queue ) ); $front = array_values( array_intersect( $preferred, $queue ) ); $registry->queue = array_merge( $front, array_values( array_diff( $queue, $front ) ) ); } private function authorize(): int { check_ajax_referer( self::NONCE, 'nonce' ); $post_id = isset( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : 0; if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) { wp_send_json_error( array( 'message' => __( 'You are not allowed to edit this item.', 'page-asset-manager' ) ), 403 ); } if ( ! in_array( get_post_type( $post_id ), $this->post_types(), true ) ) { wp_send_json_error( array( 'message' => __( 'This post type is not supported.', 'page-asset-manager' ) ), 400 ); } return $post_id; } private function posted_array( string $key ): array { return isset( $_POST[ $key ] ) && is_array( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : array(); } private function handles( array $handles ): array { return array_values( array_unique( array_filter( array_map( 'sanitize_key', $handles ) ) ) ); } private function store_settings( int $post_id, array $settings ): void { update_post_meta( $post_id, self::META, $settings ); update_post_meta( $post_id, self::LEGACY_CSS, $settings['css_order'] ); update_post_meta( $post_id, self::LEGACY_JS, $settings['js_order'] ); } private function settings( int $post_id ): array { $defaults = array( 'css_order' => array(), 'js_order' => array(), 'css_disabled' => array(), 'js_disabled' => array() ); $saved = get_post_meta( $post_id, self::META, true ); if ( ! is_array( $saved ) ) { $saved = array( 'css_order' => (array) get_post_meta( $post_id, self::LEGACY_CSS, true ), 'js_order' => (array) get_post_meta( $post_id, self::LEGACY_JS, true ) ); } foreach ( $defaults as $key => $default ) { $defaults[ $key ] = $this->handles( isset( $saved[ $key ] ) && is_array( $saved[ $key ] ) ? $saved[ $key ] : $default ); } return $defaults; } private function saved_assets( int $post_id, array $settings ): array { $assets = get_post_meta( $post_id, self::ASSETS, true ); $assets = is_array( $assets ) ? $assets : array( 'css' => array(), 'js' => array() ); foreach ( array( 'css', 'js' ) as $type ) { $assets[ $type ] = isset( $assets[ $type ] ) && is_array( $assets[ $type ] ) ? $assets[ $type ] : array(); $known = wp_list_pluck( $assets[ $type ], 'handle' ); foreach ( $settings[ $type . '_order' ] as $handle ) { if ( ! in_array( $handle, $known, true ) ) { $assets[ $type ][] = array( 'handle' => $handle, 'src' => '', 'deps' => array() ); } } $order = array_flip( $settings[ $type . '_order' ] ); usort( $assets[ $type ], static fn( array $a, array $b ): int => ( $order[ $a['handle'] ] ?? PHP_INT_MAX ) <=> ( $order[ $b['handle'] ] ?? PHP_INT_MAX ) ); } return $assets; } } OCSSJS_Page_Asset_Manager::instance();