* * @see http://wcpos.com * @package WCPOS\WooCommercePOS */ namespace WCPOS\WooCommercePOS\Admin; use WCPOS\WooCommercePOS\API\V1\Logs; use WCPOS\WooCommercePOS\Services\Cloudflare_Detector; use WCPOS\WooCommercePOS\Services\Extensions as ExtensionsService; use WCPOS\WooCommercePOS\Services\Settings as SettingsService; use const WCPOS\WooCommercePOS\PLUGIN_NAME; use const WCPOS\WooCommercePOS\PLUGIN_URL; use const WCPOS\WooCommercePOS\TRANSLATION_VERSION; use const WCPOS\WooCommercePOS\VERSION; /** * Class Settings. */ class Settings { /** * Settings constructor. */ public function __construct() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_head', array( $this, 'override_wpcontent_padding' ) ); add_action( 'in_admin_header', array( $this, 'remove_admin_notices' ) ); /* * Initializes the settings for WCPOS in the admin panel. * * This action hook can be used to run additional initialization routines * when the WCPOS settings are being set up in the admin panel. * * @since 1.0.0 * * @param Settings $this Settings class instance for the current admin context. */ do_action( 'woocommerce_pos_admin_settings_init', $this ); } /** * Output the settings pages. */ public static function display_settings_page(): void { printf( '

%s

%s support@wcpos.com

', /* translators: Short WCPOS UI label; keep concise. */ esc_html__( 'Error', 'woocommerce-pos' ), esc_html__( 'Settings failed to load, please contact support', 'woocommerce-pos' ) ); } /** * Zero out #wpcontent padding so the settings page fills edge-to-edge. * * WordPress sets padding-left on #wpcontent, but the value varies across * themes, plugins, and screen sizes. Rather than guessing the value with * a negative margin hack, we remove it entirely on our settings page. * * @return void */ public function override_wpcontent_padding(): void { echo ''; } /** * Remove all admin notices on our settings page. * * @return void */ public function remove_admin_notices(): void { remove_all_actions( 'admin_notices' ); remove_all_actions( 'all_admin_notices' ); } /** * Enqueue assets. * * Note: SCRIPT_DEBUG should be set in the wp-config.php file for debugging * * @return void */ public function enqueue_assets(): void { $is_development = isset( $_ENV['DEVELOPMENT'] ) && wp_validate_boolean( sanitize_text_field( wp_unslash( $_ENV['DEVELOPMENT'] ) ) ); $dir = $is_development ? 'build' : 'assets'; wp_enqueue_style( PLUGIN_NAME . '-settings-styles', PLUGIN_URL . $dir . '/css/settings.css', array(), VERSION ); wp_enqueue_script( PLUGIN_NAME . '-settings', PLUGIN_URL . $dir . '/js/settings.js', array( 'react', 'react-dom', 'wp-api-fetch', \WCPOS\WooCommercePOS\Admin::API_FETCH_METHOD_PARAM_HANDLE, 'wp-url', 'lodash', ), VERSION, true ); wp_add_inline_script( PLUGIN_NAME . '-settings', Menu::get_posthog_inline_script(), 'before' ); // Add inline script. wp_add_inline_script( PLUGIN_NAME . '-settings', $this->inline_script(), 'before' ); } /** * Generate the inline script for settings. * * @return string */ private function inline_script(): string { $settings_service = SettingsService::instance(); $barcodes = array_values( $settings_service->get_barcodes() ); $order_statuses = $settings_service->get_order_statuses(); $update_ext_count = $this->get_update_available_count(); $unread_logs = Logs::get_unread_counts( get_current_user_id() ); $current_user_id = get_current_user_id(); $countries = function_exists( 'WC' ) ? WC()->countries->get_countries() : array(); $store_country = function_exists( 'WC' ) ? WC()->countries->get_base_country() : ''; $store_options = apply_filters( 'woocommerce_pos_cloud_print_store_options', array() ); $anon_id = ( new \WCPOS\WooCommercePOS\Services\Anon_ID() )->get(); $site_uuid = get_option( 'woocommerce_pos_uuid', '' ); return \sprintf( 'var wcpos = wcpos || {}; wcpos.settings = { environment: %s, barcodes: %s, order_statuses: %s, countries: %s, storeCountry: %s, cloudPrintStoreOptions: %s, updateExtensionsCount: %s, unreadLogCounts: %s, currentUserId: %s, anon_id: %s, site_uuid: %s }; wcpos.translationVersion = %s;', json_encode( array( 'cloudflare' => ( new Cloudflare_Detector() )->detect() ) ), json_encode( $barcodes ), json_encode( $order_statuses ), json_encode( $countries ), json_encode( $store_country ), json_encode( array_values( (array) $store_options ) ), json_encode( $update_ext_count ), json_encode( $unread_logs ), json_encode( $current_user_id ), json_encode( $anon_id ), json_encode( $site_uuid ), json_encode( TRANSLATION_VERSION ) ); } /** * Get the count of extensions that have updates available. * * Uses the cached catalog transient and local plugin versions to avoid * remote fetches on every page load. Returns null if the catalog hasn't * been fetched yet. * * @return int|null */ private function get_update_available_count(): ?int { $cached = get_transient( ExtensionsService::TRANSIENT_KEY ); if ( false === $cached || ! \is_array( $cached ) ) { return null; } if ( ! \function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $installed_plugins = get_plugins(); $installed_by_slug = array(); $count = 0; foreach ( $installed_plugins as $plugin_file => $plugin_data ) { $parts = explode( '/', $plugin_file, 2 ); if ( isset( $parts[1] ) ) { $installed_by_slug[ $parts[0] ] = $plugin_data['Version'] ?? ''; } } foreach ( $cached as $entry ) { if ( ! \is_array( $entry ) ) { continue; } $slug = $entry['slug'] ?? ''; $remote_ver = $entry['latest_version'] ?? $entry['version'] ?? ''; if ( $slug && $remote_ver && isset( $installed_by_slug[ $slug ] ) ) { if ( version_compare( $installed_by_slug[ $slug ], $remote_ver, '<' ) ) { ++$count; } } } return $count; } }