settings_page = new SettingsPage(); } /** * Register admin hooks. * * @return void */ public function boot() { add_action( 'admin_menu', array( $this, 'register_menu' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_post_akava_btt_reset_settings', array( $this, 'handle_reset' ) ); add_action( 'admin_post_akava_btt_review_prompt', array( $this, 'handle_review_prompt' ) ); add_filter( 'plugin_action_links_' . plugin_basename( AKAVA_BTT_FILE ), array( $this, 'action_links' ) ); add_filter( 'plugin_row_meta', array( $this, 'row_meta' ), 10, 2 ); } /** * Register the compact settings array with one sanitization boundary. * * @return void */ public function register_settings() { register_setting( Config::SETTINGS_GROUP, Config::OPTION, array( 'type' => 'array', 'sanitize_callback' => array( Config::class, 'sanitize' ), 'default' => Config::defaults(), ) ); } /** * Add the page under Settings to avoid another top-level admin menu. * * @return void */ public function register_menu() { add_options_page( __( 'AKAVA ScrollTop Settings', 'akava-back-to-top' ), __( 'AKAVA ScrollTop', 'akava-back-to-top' ), self::CAPABILITY, self::PAGE_SLUG, array( $this->settings_page, 'render' ) ); } /** * Load admin assets only on this plugin screen. * * @param string $hook Current admin page hook. * @return void */ public function enqueue_assets( $hook ) { if ( 'settings_page_' . self::PAGE_SLUG !== $hook ) { return; } wp_enqueue_media(); wp_enqueue_style( 'akava-btt-admin', AKAVA_BTT_URL . 'assets/css/admin.css', array(), AKAVA_BTT_VERSION ); wp_enqueue_script( 'akava-btt-admin', AKAVA_BTT_URL . 'assets/js/admin.js', array(), AKAVA_BTT_VERSION, array( 'in_footer' => true, 'strategy' => 'defer', ) ); wp_add_inline_script( 'akava-btt-admin', 'window.AkavaBttAdmin=' . wp_json_encode( array( 'defaults' => Config::defaults(), 'strings' => array( 'chooseIcon' => __( 'Choose a button icon', 'akava-back-to-top' ), 'useIcon' => __( 'Use this image', 'akava-back-to-top' ), 'confirmReset' => __( 'Reset all settings to defaults?', 'akava-back-to-top' ), 'invalidImage' => __( 'Choose an image allowed by your WordPress media library.', 'akava-back-to-top' ), /* translators: %s: currently previewed device name, e.g. Desktop, Tablet or Mobile. */ 'previewDevice' => __( 'Preview device: %s.', 'akava-back-to-top' ), 'defaultLabel' => __( 'Back to top', 'akava-back-to-top' ), ), ) ) . ';', 'before' ); } /** * Reset settings through POST with capability and nonce checks. * * @return void */ public function handle_reset() { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You are not allowed to change these settings.', 'akava-back-to-top' ), esc_html__( 'Access denied', 'akava-back-to-top' ), array( 'response' => 403 ) ); } check_admin_referer( 'akava_btt_reset_settings' ); update_option( Config::OPTION, Config::defaults(), true ); Config::flush_cache(); wp_safe_redirect( add_query_arg( array( 'page' => self::PAGE_SLUG, 'akava-btt' => 'reset', ), admin_url( 'options-general.php' ) ) ); exit; } /** * Handle the optional review reminder without collecting user data. * * @return void */ public function handle_review_prompt() { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You are not allowed to change these settings.', 'akava-back-to-top' ), esc_html__( 'Access denied', 'akava-back-to-top' ), array( 'response' => 403 ) ); } check_admin_referer( 'akava_btt_review_prompt' ); $choice = isset( $_POST['choice'] ) ? sanitize_key( wp_unslash( $_POST['choice'] ) ) : 'later'; $state = get_option( Config::REVIEW_PROMPT_OPTION, array() ); $state = is_array( $state ) ? $state : array(); if ( 'dismiss' === $choice ) { $state['dismissed'] = true; $state['next_at'] = 0; } else { $state['dismissed'] = false; $state['next_at'] = time() + ( 30 * DAY_IN_SECONDS ); } update_option( Config::REVIEW_PROMPT_OPTION, $state, true ); wp_safe_redirect( admin_url( 'options-general.php?page=' . self::PAGE_SLUG ) ); exit; } /** * Show the review reminder at a deliberately low frequency. * * @return bool */ public static function should_show_review_prompt() { $state = get_option( Config::REVIEW_PROMPT_OPTION, array() ); $state = is_array( $state ) ? $state : array(); if ( ! empty( $state['dismissed'] ) || ( ! empty( $state['next_at'] ) && time() < absint( $state['next_at'] ) ) ) { return false; } $state['next_at'] = time() + ( 30 * DAY_IN_SECONDS ); update_option( Config::REVIEW_PROMPT_OPTION, $state, true ); return true; } /** * Add product and documentation links on the Plugins screen. * * @param array $links Existing metadata links. * @param string $file Plugin basename. * @return array */ public function row_meta( $links, $file ) { if ( plugin_basename( AKAVA_BTT_FILE ) !== $file ) { return $links; } $links[] = '' . esc_html__( 'Plugin page', 'akava-back-to-top' ) . ''; $links[] = '' . esc_html__( 'Documentation', 'akava-back-to-top' ) . ''; return $links; } /** * Add a single useful quick link on the Plugins screen. * * @param array $links Existing links. * @return array */ public function action_links( $links ) { $settings_link = '' . esc_html__( 'Settings', 'akava-back-to-top' ) . ''; array_unshift( $links, $settings_link ); return $links; } }