Icons form submissions. */ class AdminActions { /** * Collection state service. * * @var CollectionRegistry */ private $collection_registry; /** * Custom icon repository. * * @var CustomIconRepository */ private $custom_icons; /** * Constructor. * * @param CollectionRegistry $collection_registry Collection state service. * @param CustomIconRepository $custom_icons Custom icon repository. */ public function __construct( CollectionRegistry $collection_registry, CustomIconRepository $custom_icons ) { $this->collection_registry = $collection_registry; $this->custom_icons = $custom_icons; } /** Registers fallback form handlers. */ public function register() { add_action( 'admin_post_icon_library_toggle_collection', array( $this, 'toggle_collection' ) ); add_action( 'admin_post_icon_library_select_collection', array( $this, 'select_collection' ) ); add_action( 'admin_post_icon_library_toggle_variant', array( $this, 'toggle_variant' ) ); add_action( 'admin_post_icon_library_upload_custom_icon', array( $this, 'upload_custom_icon' ) ); } /** Handles local bundle selection with a non-JavaScript fallback. */ public function select_collection() { $this->authorize(); check_admin_referer( 'icon_library_select_collection' ); $collection = isset( $_POST['collection'] ) && is_string( $_POST['collection'] ) ? sanitize_key( wp_unslash( $_POST['collection'] ) ) : ''; $state = isset( $_POST['state'] ) && is_string( $_POST['state'] ) ? sanitize_key( wp_unslash( $_POST['state'] ) ) : ''; $result = in_array( $state, array( 'install', 'remove' ), true ) && $this->collection_registry->set_collection_installed( $collection, 'install' === $state ); $this->redirect( array( 'tab' => 'install', 'collection' => $collection, 'icon-library-updated' => $result ? 1 : 0, ) ); } /** Handles a collection activation toggle. */ public function toggle_collection() { $this->authorize(); check_admin_referer( 'icon_library_toggle_collection' ); $collection = isset( $_POST['collection'] ) && is_string( $_POST['collection'] ) ? sanitize_key( wp_unslash( $_POST['collection'] ) ) : ''; $state = isset( $_POST['state'] ) && is_string( $_POST['state'] ) ? sanitize_key( wp_unslash( $_POST['state'] ) ) : ''; $result = in_array( $state, array( 'activate', 'deactivate' ), true ) && $this->collection_registry->set_collection_enabled( $collection, 'activate' === $state ); $this->redirect( array( 'icon-library-updated' => $result ? 1 : 0 ) ); } /** Handles a collection variant activation toggle. */ public function toggle_variant() { $this->authorize(); check_admin_referer( 'icon_library_toggle_variant' ); $collection = isset( $_POST['collection'] ) && is_string( $_POST['collection'] ) ? sanitize_key( wp_unslash( $_POST['collection'] ) ) : ''; $variant = isset( $_POST['variant'] ) && is_string( $_POST['variant'] ) ? sanitize_key( wp_unslash( $_POST['variant'] ) ) : ''; $state = isset( $_POST['state'] ) && is_string( $_POST['state'] ) ? sanitize_key( wp_unslash( $_POST['state'] ) ) : ''; $result = in_array( $state, array( 'activate', 'deactivate' ), true ) && $this->collection_registry->set_variant_enabled( $collection, $variant, 'activate' === $state ); $this->redirect( array( 'tab' => 'library', 'collection' => $collection, 'icon-library-updated' => $result ? 1 : 0, ) ); } /** Handles a custom SVG upload when JavaScript is unavailable. */ public function upload_custom_icon() { $this->authorize(); check_admin_referer( 'icon_library_upload_custom_icon' ); // File metadata is validated below with is_uploaded_file(), an extension allowlist, a size limit, and SVG sanitization. $file = isset( $_FILES['svg'] ) && is_array( $_FILES['svg'] ) ? $_FILES['svg'] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File upload metadata is validated before use. $name = isset( $_POST['name'] ) && is_string( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : ''; $label = isset( $_POST['label'] ) && is_string( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : ''; $error = isset( $file['error'] ) ? absint( $file['error'] ) : UPLOAD_ERR_NO_FILE; $tmp_name = isset( $file['tmp_name'] ) && is_string( $file['tmp_name'] ) ? $file['tmp_name'] : ''; $file_name = isset( $file['name'] ) && is_string( $file['name'] ) ? $file['name'] : ''; $valid = UPLOAD_ERR_OK === $error && '' !== $tmp_name && is_uploaded_file( $tmp_name ) && is_readable( $tmp_name ); $failure = UPLOAD_ERR_NO_FILE === $error ? 'file_missing' : ( in_array( $error, array( UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE ), true ) ? 'file_size' : 'file_failed' ); if ( $valid && ( '' === $file_name || 'svg' !== strtolower( pathinfo( sanitize_file_name( $file_name ), PATHINFO_EXTENSION ) ) ) ) { $valid = false; $failure = 'file_type'; } $size = $valid ? filesize( $tmp_name ) : false; if ( $valid && ( false === $size || SvgSanitizer::MAX_FILE_SIZE < $size ) ) { $valid = false; $failure = 'file_size'; } $result = $valid ? $this->custom_icons->create( $name, $label, (string) file_get_contents( $tmp_name ) ) : false; if ( is_wp_error( $result ) ) { $failure = $result->get_error_code(); } $this->redirect( array( 'tab' => 'custom', ( $valid && ! is_wp_error( $result ) && false !== $result ? 'icon-library-updated' : 'icon-library-upload-error' ) => $valid && ! is_wp_error( $result ) && false !== $result ? 1 : $failure, ) ); } /** * Enforces the capability check shared by mutation handlers. */ private function authorize() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to manage icon libraries.', 'aculect-icon-library' ) ); } } /** * Redirects to the plugin screen. * * @param array $args Query arguments. */ private function redirect( $args ) { $args['page'] = AdminPage::MENU_SLUG; wp_safe_redirect( add_query_arg( $args, admin_url( 'themes.php' ) ) ); exit; } }