<?php /* * * * * * * * * * * * * * * * * * * * * * * ██████╗ ███╗ ███╗ ██████╗ ███████╗ * ██╔═══██╗████╗ ████║██╔════╝ ██╔════╝ * ██║ ██║██╔████╔██║██║ ███╗█████╗ * ██║ ██║██║╚██╔╝██║██║ ██║██╔══╝ * ╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██║ * ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ * * @package : OMGF * @author : Daan van den Bergh * @copyright: © 2017 - 2025 Daan van den Bergh * @url : https://daan.dev * * * * * * * * * * * * * * * * * * * */ namespace OMGF\Admin; use OMGF\Helper as OMGF; class Actions { /** * Execute all actions required in wp-admin. * * @return void */ public function __construct() { add_action( '_admin_menu', [ $this, 'init_admin' ] ); add_action( 'admin_init', [ $this, 'do_optimize' ] ); add_action( 'admin_init', [ $this, 'update_settings' ] ); add_action( 'in_plugin_update_message-' . OMGF_PLUGIN_BASENAME, [ $this, 'render_update_notice' ], 11 ); add_action( 'omgf_pre_update_setting_cache_keys', [ $this, 'clean_stale_cache' ], 10, 2 ); } /** * Cleans up the old (unused) cache directories. */ public function clean_stale_cache( $option_name, $option_value ) { $old_keys = OMGF::cache_keys(); $new_keys = explode( ',', $option_value ); $diff = array_diff( $new_keys, $old_keys ); foreach ( $diff as $new_cache_key ) { $dir_to_remove = ''; $base_key = preg_replace( '/-mod.*?$/', '', $new_cache_key ); foreach ( $old_keys as $old_cache_key ) { if ( str_contains( $old_cache_key, $base_key ) ) { $dir_to_remove = $old_cache_key; break; } } if ( ! $dir_to_remove ) { continue; // @codeCoverageIgnore } $dir = OMGF_UPLOAD_DIR . '/' . $dir_to_remove; if ( $dir !== realpath( $dir ) ) { continue; // @codeCoverageIgnore } $this->delete_files( $dir ); if ( $this->dir_is_empty( $dir ) ) { rmdir( OMGF_UPLOAD_DIR . '/' . $dir_to_remove ); } } } /** * Delete files from $dir. * * @param mixed $dir * * @return void */ private function delete_files( $dir ) { array_map( 'unlink', glob( $dir . '/*.*' ) ); } /** * Check if directory is empty. * This works because a new FilesystemIterator will initially point to the first file in the folder - * if there are no files in the folder, valid() will return false * * @param mixed $dir * * @return bool */ private function dir_is_empty( $dir ) { if ( ! file_exists( $dir ) ) { return false; // @codeCoverageIgnore } $iterator = new \FilesystemIterator( $dir ); return ! $iterator->valid(); } /** * Initialize the Save & Optimize routine. * * @return void * * @codeCoverageIgnore */ public function do_optimize() { new Optimize(); } /** * Needs to run before admin_menu and admin_init. * * @action _admin_menu * * @codeCoverageIgnore */ public function init_admin() { new Settings(); } /** * Render update notices if available. * * @param mixed $plugin * @param mixed $response * * @return void * * @codeCoverageIgnore */ public function render_update_notice( $plugin ) { $current_version = $plugin['Version']; $new_version = $plugin['new_version']; if ( version_compare( $current_version, $new_version, '<' ) ) { $response = wp_remote_get( 'https://daan.dev/omgf-update-notices.json?' . substr( uniqid( '', true ), - 5 ) ); if ( is_wp_error( $response ) ) { return; } $update_notices = (array) json_decode( wp_remote_retrieve_body( $response ) ); if ( ! isset( $update_notices[ $new_version ] ) ) { return; } echo wp_kses_post( sprintf( ' <strong>' . __( 'This update includes major changes, please <a href="%s" target="_blank">read this</a> before continuing.', 'host-webfonts-local' ) . '</strong>', $update_notices[ $new_version ]->url ) ); } } /** * We use a custom update action, because we're storing multidimensional arrays upon form submit. * This prevents us from having to use AJAX, serialize(), stringify() and eventually having to json_decode() it, i.e. * a lot of headaches. * * @since v5.6.0 */ public function update_settings() { if ( wp_doing_cron() || wp_doing_ajax() || empty( $_POST['action'] ) || $_POST['action'] !== 'omgf-update' ) { return; // @codeCoverageIgnore } $action = array_key_exists( 'tab', $_GET ) ? $_GET['tab'] . '-options' : 'omgf-optimize-settings-options'; $nonce = $_POST['_wpnonce'] ?? ''; if ( wp_verify_nonce( $nonce, $action ) < 1 ) { return; // @codeCoverageIgnore } if ( ! defined( 'DAAN_DOING_TESTS' ) && ! current_user_can( 'manage_options' ) ) { return; // @codeCoverageIgnore } $updated_settings = $this->clean( $_POST ); /** * These options are always stored as (nested) arrays, keyed by the font handle. A scalar value for * any of them means the request was malformed, e.g., a proxy or security plugin that flattened * the array parameters (foo[a][b]=x => foo=x). Persisting the scalar corrupts the option and, * because the read side subscripts it by handle, triggers a fatal * "Cannot access offset of type string on string" on PHP 8+ the next time the settings screen * is rendered. A reset ('0') is handled separately below and is always allowed. * * @filter omgf_settings_array_options */ $array_options = apply_filters( 'omgf_settings_array_options', [ Settings::OMGF_OPTIMIZE_SETTING_PRELOAD_FONTS, Settings::OMGF_OPTIMIZE_SETTING_UNLOAD_FONTS, ] ); foreach ( $updated_settings as $option_name => $option_value ) { if ( ! str_starts_with( $option_name, 'omgf_' ) || ( empty( $option_value ) && $option_value !== '0' ) ) { continue; } // Ignore a malformed (non-array, non-reset) value for an option that must be an array, // rather than overwriting the existing, valid setting with corrupt data. if ( $option_value !== '0' && ! is_array( $option_value ) && in_array( $option_name, $array_options, true ) ) { continue; } if ( is_array( $option_value ) ) { foreach ( $option_value as $setting_name => $setting_value ) { do_action( "omgf_pre_update_setting_$setting_name", $setting_name, $setting_value ); } } if ( $option_value === '0' ) { $merged = []; } elseif ( is_string( $option_value ) ) { $merged = $option_value; } else { $current_options = OMGF::get_option( $option_name, [] ); // Coerce a previously corrupted (non-array) value back to an array, so a valid save // self-heals the option instead of throwing a TypeError in array_replace(). if ( ! is_array( $current_options ) ) { $current_options = []; } $merged = array_replace( $current_options, $option_value ); } OMGF::update_option( $option_name, $merged ); } /** * Additional update actions can be added here. * * @since v5.6.0 */ do_action( 'omgf_update_settings', $updated_settings ); // Display settings errors. set_transient( 'settings_errors', get_settings_errors(), 30 ); // Redirect back to the settings page that was submitted. $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() ); if ( ! defined( 'DAAN_DOING_TESTS' ) ) { wp_redirect( $goback ); // @codeCoverageIgnore exit; // @codeCoverageIgnore } } /** * Clean variables using `sanitize_text_field`. * Arrays are cleaned recursively. Non-scalar values are ignored. * * @since 5.5.7 * * @param string|array $var Sanitize the variable. * * @return string|array */ private function clean( $var ) { if ( is_array( $var ) ) { return array_map( [ $this, 'clean' ], $var ); } return is_scalar( $var ) ? sanitize_text_field( wp_unslash( $var ) ) : $var; } }