empty( $event_times['plugin_installed'] ) ? $event_times['plugin_installed'] : ''; if ( $installed_at && is_array( $configs ) ) { foreach ( $configs as $index => $config ) { if ( ! empty( $config['default'] ) && empty( $config['date'] ) ) { $configs[ $index ]['date'] = $installed_at; } } } $script = ( new Script() ) ->set_handle( 'smush-ui-settings' ) ->set_source( WP_SMUSH_URL . 'app/assets/js/smush-ui-settings.min.js' ) ->set_dependencies( array( 'smush-ui-vendor', 'wp-i18n', 'media-editor', 'clipboard' ) ) ->set_version( WP_SMUSH_VERSION ) ->set_in_footer( true ) ->set_localization_data( array( 'activeSettingsTab' => $current_tab, 'links' => array(), 'requestsData' => array( 'ImageRestoreData' => array( 'restore_ids' => ( new Backups() )->get_attachments_with_backups(), 'smush_bulk_restore' => wp_create_nonce( 'smush_bulk_restore' ), ), ), 'bulkSmushMetaData' => array( 'imageSizes' => array( 'allSelected' => $all_selected, 'sizes' => $sizes, 'selected' => is_array( $image_sizes ) ? $image_sizes : array(), ), ), 'configsData' => array( 'nonce' => wp_create_nonce( 'smush_handle_config' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'restUrl' => rest_url( 'wp-smush/v1/preset_configs' ), // Initial configs list (avoids a separate REST fetch on page load). 'configs' => $configs, 'configTipsDismissed' => ! empty( get_option( 'wp-smush-dismissed-notices', array() )['config_tips_toast'] ), ), ) ); $style = ( new Style() ) ->set_handle( 'smush-ui-settings-style' ) ->set_source( WP_SMUSH_URL . 'app/assets/css/smush-ui-settings.min.css' ) ->set_dependencies( array() ) ->set_version( WP_SMUSH_VERSION ); return ( new Admin_Page() ) ->set_slug( self::PAGE_SETTINGS ) ->set_title( __( 'Settings', 'wp-smushit' ) ) ->set_scripts( array( $script ) ) ->set_styles( array( $style ) ); } public function smush_body_classes( $classes ) { // Exit if function doesn't exists. if ( ! function_exists( 'get_current_screen' ) ) { return $classes; } $current_screen_id = get_current_screen()->id; // If not on plugin page. if ( ! in_array( $current_screen_id, Admin::$plugin_pages, true ) && false === strpos( $current_screen_id, 'page_smush' ) ) { return $classes; } // Remove old wpmud class from body of smush page to avoid style conflict. $classes = str_replace( 'wpmud ', '', $classes ); $classes .= ' wpmudev-core-ui'; if ( $this->show_onboarding_wizard() ) { $classes .= ' smush-onboarding-wizard smush-onboarding-wizard--fullscreen'; } if ( Settings::get_instance()->get( 'accessible_colors' ) ) { $classes .= ' wpmudev-core-ui__mono'; } $classes .= Membership::get_instance()->get_guest_value( ' smush-plan-free', ' smush-plan-pro' ); return $classes; } /** * Get user avatar URL. * * @return string|bool Avatar URL or false on failure. * @since 3.24.0 */ protected function get_user_avatar() { $current_user = wp_get_current_user(); if ( ! $this->has_real_gravatar( $current_user->user_email ) ) { return false; } $avatar_url = get_avatar_url( $current_user->ID ); return $avatar_url; } /** * Checks if the current user has a real Gravatar image set. * * @return bool True if they have a custom photo, false if they are using a default. */ protected function has_real_gravatar( $user_email = null ) { $email = trim( strtolower( $user_email ) ); $hash = md5( $email ); $cache_key = 'smush_user_gravatar_' . $hash; $has_gravatar = get_transient( $cache_key ); if ( $has_gravatar ) { return (bool) $has_gravatar; } // Build a URL that asks Gravatar to return a 404 error if no image exists. $check_url = "https://www.gravatar.com/avatar/$hash?d=404"; // Use WordPress remote_head to check just the headers (and save some bandwidth). $response = wp_remote_head( $check_url ); // If the response code is 200, the image exists. If it is 404, it doesn't. $has_gravatar = wp_remote_retrieve_response_code( $response ) === 200; set_transient( $cache_key, $has_gravatar, DAY_IN_SECONDS ); return $has_gravatar; } /** * Get user initials from first and last name. * * @return string User initials. * @since 3.24.0 */ protected function get_user_initials() { $current_user = wp_get_current_user(); $first = $current_user->user_firstname; $last = $current_user->user_lastname; if ( ! empty( $first ) || ! empty( $last ) ) { $first_initial = ! empty( $first ) ? substr( $first, 0, 1 ) : ''; $last_initial = ! empty( $last ) ? substr( $last, 0, 1 ) : ''; return strtoupper( $first_initial . $last_initial ); } // Fallback: Use the first letter of the Display Name if no first or last name is set. return strtoupper( substr( $current_user->display_name, 0, 1 ) ); } /** * Get current user email address. * * @return string User email or empty string if not available. * @since 3.24.0 */ protected function get_user_email() { $current_user = wp_get_current_user(); if ( ! $current_user->exists() ) { return ''; } return $current_user->user_email ?? ''; } /** * Get current user display name. * * @return string User display name or empty string if not available. * @since 4.0 */ protected function get_user_display_name() { $current_user = wp_get_current_user(); if ( ! $current_user->exists() ) { return ''; } return $current_user->display_name ?? ''; } private function show_onboarding_wizard() { if ( is_multisite() && ! is_network_admin() ) { return false; } $skip_quick_setup = ! empty( get_option( 'skip-smush-setup' ) ); $smush_action = isset( $_GET['smush_action'] ) ? sanitize_text_field( $_GET['smush_action'] ) : ''; $should_skip_wizard = Hub_Connector::is_syncing() || ( 'skip-wizard' === $smush_action ); if ( $should_skip_wizard ) { if ( ! $skip_quick_setup ) { // If syncing, and not skipped, we consider the setup is done. update_option( 'skip-smush-setup', true ); } return false; } $is_our_page = false; foreach ( $this->get_admin_pages() as $admin_page ) { if ( $admin_page->get_slug() === $this->get_current_page() ) { $is_our_page = true; break; } } if ( ! $is_our_page ) { return false; } // Keep users on the login flow when Hub returns an auth error. if ( Hub_Connector::has_error_in_login() ) { return true; } $settings = Settings::get_instance(); if ( $skip_quick_setup || ! $settings->has_bulk_smush_page() ) { return false; } return true; } private function enqueue_onboarding_wizard_script( $page ) { wp_enqueue_script( 'smush-ui-onboarding-wizard', WP_SMUSH_URL . 'app/assets/js/smush-ui-onboarding-wizard.min.js', array( 'smush-ui-vendor', 'wp-i18n' ), WP_SMUSH_VERSION, true ); wp_set_script_translations( 'smush-ui-onboarding-wizard', 'wp-smushit', WP_SMUSH_DIR . 'languages' ); $this->localize_script( 'smush-ui-onboarding-wizard', array( 'smushSetupNonce' => wp_create_nonce( 'smush_quick_setup' ), 'showOnboardingWizard' => true, ), $page ); wp_enqueue_style( 'smush-ui-onboarding-wizard-style', WP_SMUSH_URL . 'app/assets/css/smush-ui-onboarding-wizard.min.css', array(), WP_SMUSH_VERSION ); } /** * @return string */ private function get_plugin_update_url() { return add_query_arg( array( 'action' => 'upgrade-plugin', 'plugin' => WP_SMUSH_BASENAME, '_wpnonce' => wp_create_nonce( 'upgrade-plugin_' . WP_SMUSH_BASENAME ), ), self_admin_url( 'update.php' ) ); } private function get_current_page() { return isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : ''; } /** * Handle a React UI error event and forward it to product analytics. * * Accepts: * message (string) – error.message * code (string) – error.name (e.g. "TypeError") * stack (string) – error.stack, truncated to 2000 chars by the client * component_stack (string) – React component hierarchy from ErrorInfo * page (string) – window.location.pathname at the time of the error * * @return void */ public function ajax_ui_error() { check_ajax_referer( 'wp-smush-ajax' ); if ( ! Helper::is_user_allowed() ) { wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'wp-smushit' ) ), 403 ); } $message = isset( $_POST['message'] ) ? sanitize_text_field( wp_unslash( $_POST['message'] ) ) : ''; $code = isset( $_POST['code'] ) ? sanitize_text_field( wp_unslash( $_POST['code'] ) ) : 'UnknownError'; $stack = isset( $_POST['stack'] ) ? sanitize_textarea_field( wp_unslash( $_POST['stack'] ) ) : ''; $component_stack = isset( $_POST['component_stack'] ) ? sanitize_textarea_field( wp_unslash( $_POST['component_stack'] ) ) : ''; $page = isset( $_POST['page'] ) ? sanitize_text_field( wp_unslash( $_POST['page'] ) ) : ''; $extra_properties = array_filter( array( 'Stack Trace' => $stack, 'Component Stack' => $component_stack, 'Page' => $page, ) ); Product_Analytics::get_instance()->maybe_track_error( 'react_ui', $code, $message, $extra_properties ); wp_send_json_success(); } /** * Render the deactivation survey modal. * * @return void */ public function render_deactivate_survey_modal() { $current_screen = get_current_screen(); if ( ! $current_screen || 'plugins' !== $current_screen->id ) { return; } wp_enqueue_script( 'smush-shared-ui' ); wp_enqueue_style( 'smush-shared-ui' ); $this->view( 'deactivation-survey' ); } /** * Load an admin view. * * @param string $name View name = file name. * @param array $args Arguments. * @param string $dir Directory for the views. Default: views. */ private function view( $name, $args = array(), $dir = 'views' ) { $file = WP_SMUSH_DIR . "app/{$dir}/{$name}.php"; $content = ''; if ( is_file( $file ) ) { ob_start(); if ( isset( $args['id'] ) ) { $args['orig_id'] = $args['id']; $args['id'] = str_replace( '/', '-', $args['id'] ); } extract( $args );//phpcs:ignore include $file; $content = ob_get_clean(); } // Everything escaped in all template files. echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Check if the upgrade modal should be shown. * * @return bool */ private function should_show_upgrade_modal() { return get_site_option( 'wp-smush-show_upgrade_modal' ); } /** * Hide the new features modal */ public function hide_new_features_modal() { check_ajax_referer( 'wp-smush-ajax' ); // Check for permission. if ( ! Helper::is_user_allowed( 'manage_options' ) ) { wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 ); } delete_site_option( 'wp-smush-show_upgrade_modal' ); wp_send_json_success(); } /** * Provide translations for JavaScript files. * * @param mixed $translations * @param mixed $file * @param mixed $handle * @param mixed $domain */ public function provide_script_translations( $translations, $file, $handle, $domain ) { if ( 'wp-smushit' !== $domain ) { return $translations; } static $served = false; static $cached_json = null; if ( $served ) { return wp_json_encode( array( 'locale_data' => array( 'wp-smushit' => array( '' => array( 'domain' => 'wp-smushit', ), ), ), ) ); } if ( null !== $cached_json ) { return $cached_json; } $locale = is_admin() ? get_user_locale() : get_locale(); $mo_file = WP_LANG_DIR . "/plugins/{$domain}-{$locale}.mo"; if ( ! file_exists( $mo_file ) ) { return $translations; } $mo = new \MO(); if ( ! $mo->import_from_file( $mo_file ) ) { return $translations; } $locale_data = array( '' => array( 'domain' => $domain, 'lang' => $locale, ), ); if ( ! empty( $mo->headers['Plural-Forms'] ) ) { $locale_data['']['plural-forms'] = $mo->headers['Plural-Forms']; } foreach ( $mo->entries as $msgid => $entry ) { $locale_data[ $msgid ] = $entry->translations; } $cached_json = wp_json_encode( array( 'locale_data' => array( $domain => $locale_data, ), ) ); $served = true; return $cached_json; } /** * White-label the plugin name as displayed on the Plugins screen (`plugins.php`). * * This only affects display (the plugin file/slug stays the same). * * @param array $plugins All plugins. * * @return array */ public function maybe_whitelabel_plugin_name_in_plugins_list( $plugins ) { if ( ! is_array( $plugins ) || ! defined( 'WP_SMUSH_BASENAME' ) || empty( $plugins[ WP_SMUSH_BASENAME ] ) ) { return $plugins; } if ( ! $this->whitelabel->is_whitelabel_enabled() ) { return $plugins; } $whitelabel_keys = array( 'Name', 'Title', ); // Whitelabel the plugin name and title. foreach ( $whitelabel_keys as $key ) { if ( isset( $plugins[ WP_SMUSH_BASENAME ][ $key ] ) ) { $plugins[ WP_SMUSH_BASENAME ][ $key ] = $this->whitelabel->replace_branding_terms( $plugins[ WP_SMUSH_BASENAME ][ $key ] ); } } // Whitelabel the plugin description. $whitelabel_description = $this->whitelabel->replace_branding_terms( $this->whitelabel->remove_brand_links( $plugins[ WP_SMUSH_BASENAME ]['Description'] ) ); $plugins[ WP_SMUSH_BASENAME ]['Description'] = $whitelabel_description; // Whitelabel the plugin author. if ( $this->whitelabel->hide_doc_link() ) { $plugins[ WP_SMUSH_BASENAME ]['Author'] = ''; $plugins[ WP_SMUSH_BASENAME ]['PluginURI'] = ''; } return $plugins; } /** * Adds action links on plugin page. * * @param array $links Current links. * * @return array|string */ public function plugin_action_links( $links ) { // Upgrade link. if ( Membership::get_instance()->get_guest_value( true, false ) ) { $upgrade_url = add_query_arg( array( 'utm_source' => 'smush', 'utm_medium' => 'plugin', 'utm_campaign' => 'wp-smush-pro/wp-smush.php' !== WP_SMUSH_BASENAME ? 'smush_pluginlist_upgrade' : 'smush_pluginlist_renew', ), esc_url( 'https://wpmudev.com/project/wp-smush-pro/' ) ); $using_free_version = 'wp-smush-pro/wp-smush.php' !== WP_SMUSH_BASENAME; if ( $using_free_version ) { $label = __( 'Upgrade to Smush Pro', 'wp-smushit' ); $text = __( 'Get Smush Pro', 'wp-smushit' ); } else { $label = __( 'Renew Membership', 'wp-smushit' ); $text = __( 'Renew Membership', 'wp-smushit' ); } if ( isset( $text ) ) { $links['smush_upgrade'] = '' . esc_html( $text ) . ''; } } if ( ! $this->whitelabel->should_hide_doc_link() ) { // Documentation link. $docs_link = Helper::get_utm_link( array( 'utm_campaign' => 'smush_pluginlist_docs' ), 'https://wpmudev.com/docs/wpmu-dev-plugins/smush/' ); $links['smush_docs'] = '' . esc_html__( 'Docs', 'wp-smushit' ) . ''; } // Dashboard link. $dashboard_page = is_multisite() && is_network_admin() ? network_admin_url( 'admin.php?page=smush' ) : menu_page_url( 'smush', false ); $links['smush_dashboard'] = '' . esc_html__( 'Dashboard', 'wp-smushit' ) . ''; $access = get_site_option( 'wp-smush-networkwide' ); if ( ! is_network_admin() && is_plugin_active_for_network( WP_SMUSH_BASENAME ) && ! $access ) { // Remove settings link for subsites if Subsite Controls is not set on network permissions tab. unset( $links['smush_dashboard'] ); } return array_reverse( $links ); } /** * Add additional links next to the plugin version. * * @param array $links Links array. * @param string $file Plugin basename. * * @return array */ public function add_plugin_meta_links( $links, $file ) { if ( ! defined( 'WP_SMUSH_BASENAME' ) || WP_SMUSH_BASENAME !== $file ) { return $links; } if ( 'wp-smush-pro/wp-smush.php' !== WP_SMUSH_BASENAME ) { $links[] = '' . esc_html__( 'Rate Smush', 'wp-smushit' ) . ''; $links[] = '' . esc_html__( 'Support', 'wp-smushit' ) . ''; } elseif ( ! $this->whitelabel->should_hide_doc_link() ) { if ( isset( $links[2] ) && false !== strpos( $links[2], 'project/wp-smush-pro' ) ) { $links[2] = sprintf( '%s', __( 'View details', 'wp-smushit' ) ); } $links[] = '' . esc_html__( 'Premium Support', 'wp-smushit' ) . ''; } if ( $this->whitelabel->should_hide_doc_link() ) { return $links; } $roadmap_link = Helper::get_utm_link( array( 'utm_campaign' => 'smush_pluginlist_roadmap', ), 'https://wpmudev.com/roadmap/' ); $links[] = '' . esc_html__( 'Roadmap', 'wp-smushit' ) . ''; $links[] = ' '; echo ''; return $links; } /** * Dismiss the plugin conflicts notice. */ public function dismiss_notice() { check_ajax_referer( 'wp-smush-ajax' ); // Check capability. if ( ! Helper::is_user_allowed( 'manage_options' ) ) { wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 ); } if ( empty( $_REQUEST['key'] ) ) { wp_send_json_error(); } $this->set_notice_dismissed( sanitize_key( $_REQUEST['key'] ) ); wp_send_json_success(); } private function set_notice_dismissed( $notice ) { $option_id = 'wp-smush-dismissed-notices'; $dismissed_notices = get_option( $option_id, array() ); $dismissed_notices[ $notice ] = true; update_option( $option_id, $dismissed_notices ); } /** * Hide API Message */ public function hide_api_message() { check_ajax_referer( 'wp-smush-ajax' ); // Check capability. if ( ! Helper::is_user_allowed( 'manage_options' ) ) { wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 ); } $api_message = get_site_option( 'wp-smush-api_message', array() ); if ( ! empty( $api_message ) && is_array( $api_message ) ) { $api_message[ key( $api_message ) ]['status'] = 'hide'; update_site_option( 'wp-smush-api_message', $api_message ); } wp_send_json_success(); } public function print_upgrade_submenu_script() { } /** * @return void */ public function add_upgrade_submenu_page() { add_submenu_page( self::PAGE_DASHBOARD, __( 'Upgrade to Smush Pro', 'wp-smushit' ), sprintf( '%1$s%2$s', __( 'Upgrade', 'wp-smushit' ), __( 'Pro', 'wp-smushit' ) ), $this->get_permission_level_for_menus(), esc_url( 'https://wpmudev.com/project/wp-smush-pro/?utm_source=smush&utm_medium=plugin&utm_campaign=smush_new-submenu_upsell#dev-pricing' ) ); } }