add_help_tab( array( 'id' => 'flipwebp-overview', 'title' => __( 'Overview', 'flipwebp' ), 'content' => '

' . esc_html__( 'Convert JPEG, PNG, and GIF images in the Media Library to WebP. Images are processed on this server. A file is replaced only when the WebP version is smaller.', 'flipwebp' ) . '

', ) ); $screen->add_help_tab( array( 'id' => 'flipwebp-privacy', 'title' => __( 'Privacy', 'flipwebp' ), 'content' => '

' . esc_html__( 'FlipWebP does not send images, site addresses, or account data to an external service. Conversion uses the image editor installed on this server.', 'flipwebp' ) . '

', ) ); } /** * Load assets only on the converter screen. * * @param string $hook Current page hook. * @return void */ public function enqueue( $hook ) { if ( 'media_page_flipwebp' !== $hook ) { return; } wp_enqueue_style( 'flipwebp-admin', FLIPWEBP_URL . 'assets/css/admin.css', array(), FLIPWEBP_VERSION ); wp_enqueue_script( 'flipwebp-admin', FLIPWEBP_URL . 'assets/js/admin.js', array(), FLIPWEBP_VERSION, true ); $settings = Settings::all(); $stats = Stats::get(); wp_localize_script( 'flipwebp-admin', 'flipwebpAdmin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'adminBase' => admin_url(), 'nonce' => wp_create_nonce( 'flipwebp_admin' ), 'queue' => $this->pull_queue(), 'pending' => (string) $stats['pending'], 'deleteOriginals' => ! empty( $settings['delete_originals'] ) ? 'yes' : 'no', 'backups' => ! empty( $settings['create_backups'] ) ? 'yes' : 'no', 'i18n' => array( 'confirmOne' => __( 'Convert this image to WebP?', 'flipwebp' ), /* translators: %s: number of images. */ 'confirmMany' => __( 'Convert %s images to WebP?', 'flipwebp' ), 'confirmDelete' => __( 'Original files are permanently deleted when the WebP file is smaller. This cannot be undone.', 'flipwebp' ), 'confirmBackup' => __( 'Original files are removed from the Media Library after a smaller WebP file is saved. A copy is stored in a protected backup folder and can be restored here.', 'flipwebp' ), 'confirmKeep' => __( 'Original files stay on disk. Stored addresses are updated to the WebP image when it is smaller.', 'flipwebp' ), 'local' => __( 'Each image is converted on this server. Nothing is uploaded to another service.', 'flipwebp' ), 'unsaved' => __( 'There are unsaved settings. Conversion uses the last saved settings.', 'flipwebp' ), 'working' => __( 'Converting…', 'flipwebp' ), 'stopping' => __( 'Stopping after the current image…', 'flipwebp' ), 'done' => __( 'Finished', 'flipwebp' ), 'failed' => __( 'The image could not be converted.', 'flipwebp' ), 'badResponse' => __( 'The server returned an unexpected response. Reload the page and try again.', 'flipwebp' ), 'none' => __( 'Select at least one image.', 'flipwebp' ), 'cancel' => __( 'Cancel', 'flipwebp' ), 'close' => __( 'Close', 'flipwebp' ), 'stop' => __( 'Stop after this image', 'flipwebp' ), /* translators: 1: completed count, 2: total count. */ 'progress' => __( '%1$s of %2$s', 'flipwebp' ), /* translators: 1: converted count, 2: unchanged count, 3: failed count. */ 'summary' => __( '%1$s converted, %2$s unchanged, %3$s failed.', 'flipwebp' ), ), ) ); } /** * Render the screen. * * @return void */ public function render_page() { if ( ! current_user_can( 'upload_files' ) ) { wp_die( esc_html__( 'You do not have permission to convert images.', 'flipwebp' ), '', array( 'response' => 403 ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only list filters. Values are sanitized and the page is capability-checked. $status = isset( $_GET['status'] ) ? sanitize_key( wp_unslash( $_GET['status'] ) ) : 'all'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only search. The value is sanitized before use. $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only page number. $paged = isset( $_GET['paged'] ) ? absint( wp_unslash( $_GET['paged'] ) ) : 1; $filters = Library::sanitize_filters( $status, $search, $paged ); $library = Library::query( $filters ); $stats = Stats::get(); $settings = Settings::all(); $engine = Converter::engine(); $can_manage = Capabilities::can_manage(); $prompt = $this->prompt(); include FLIPWEBP_DIR . 'includes/views/admin-page.php'; } /** * Render the library region. * * @param array $library Query result. * @return string */ public static function library_html( $library ) { ob_start(); include FLIPWEBP_DIR . 'includes/views/library.php'; return (string) ob_get_clean(); } /** * Convert one image from the no-JavaScript confirmation form. * * @return void */ public function handle_convert() { $this->handle_single( 'convert' ); } /** * Restore one image from the no-JavaScript confirmation form. * * @return void */ public function handle_restore() { $this->handle_single( 'restore' ); } /** * Shared POST handler for a single attachment. * * @param string $action convert or restore. * @return void */ private function handle_single( $action ) { $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : ''; if ( 'POST' !== $method ) { wp_die( esc_html__( 'Invalid request method.', 'flipwebp' ), '', array( 'response' => 405 ) ); } $id = isset( $_POST['attachment_id'] ) ? absint( wp_unslash( $_POST['attachment_id'] ) ) : 0; $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; $name = ( 'restore' === $action ) ? 'flipwebp_restore_' . $id : 'flipwebp_convert_' . $id; if ( ! wp_verify_nonce( $nonce, $name ) ) { wp_die( esc_html__( 'The link you followed has expired. Reload the page and try again.', 'flipwebp' ), '', array( 'response' => 403 ) ); } if ( ! Capabilities::can_convert( $id ) ) { wp_die( esc_html__( 'You do not have permission to convert this image.', 'flipwebp' ), '', array( 'response' => 403 ) ); } $converter = new Converter(); $result = ( 'restore' === $action ) ? $converter->restore( $id ) : $converter->convert( $id ); if ( is_wp_error( $result ) ) { Notices::flash( 'error', $result->get_error_message() ); } else { $type = ( isset( $result['status'] ) && 'skipped' === $result['status'] ) ? 'warning' : 'success'; Notices::flash( $type, isset( $result['message'] ) ? (string) $result['message'] : '' ); } wp_safe_redirect( admin_url( 'upload.php?page=flipwebp' ) ); exit; } /** * Read a one-time bulk queue created by the Media Library bulk action. * * @return int[] */ private function pull_queue() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The queue token is a single-use secret checked with hash_equals. if ( empty( $_GET['queue'] ) ) { return array(); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- See above. $token = sanitize_text_field( wp_unslash( $_GET['queue'] ) ); $stored = get_transient( 'flipwebp_q_' . get_current_user_id() ); if ( ! is_array( $stored ) || empty( $stored['token'] ) || ! is_string( $stored['token'] ) ) { return array(); } if ( strlen( $stored['token'] ) !== strlen( $token ) || ! hash_equals( $stored['token'], $token ) ) { return array(); } delete_transient( 'flipwebp_q_' . get_current_user_id() ); $ids = array(); foreach ( (array) $stored['ids'] as $id ) { $id = absint( $id ); if ( $id && current_user_can( 'edit_post', $id ) ) { $ids[] = $id; } } return array_slice( $ids, 0, 500 ); } /** * Confirmation card for a single image opened from the Media Library. * * @return array{action: string, item: array}|null */ private function prompt() { foreach ( array( 'confirm' => 'convert', 'restore' => 'restore' ) as $query_key => $action ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified immediately below. if ( empty( $_GET[ $query_key ] ) ) { continue; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The nonce is verified on the next line. $id = absint( wp_unslash( $_GET[ $query_key ] ) ); $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; $name = ( 'confirm' === $query_key ) ? 'flipwebp_confirm_' . $id : 'flipwebp_restore_' . $id; if ( ! wp_verify_nonce( $nonce, $name ) || ! Capabilities::can_convert( $id ) ) { continue; } $item = Library::item( $id ); if ( null === $item ) { continue; } if ( 'restore' === $action && ! Backup::exists( $id ) ) { continue; } return array( 'action' => $action, 'item' => $item, ); } return null; } }