client = &$client; $this->ticketTemplate = __DIR__ . '/../views/insights-support-ticket-email.php'; } /** * Don't show the notice * * @return SE_License_SDK_Insights */ public function hide_optin_notice() { $this->should_show_optin = false; return $this; } /** * Set first installation time of the package. * * @param int $time GMT/UTC-0 Unix Timestamp * * @return SE_License_SDK_Insights */ public function set_first_install_time( int $time ) { if ( $time ) { $this->first_install_time = absint( $time ); } return $this; } /** * Set OptIn notice delay * @param int $delay delay time in seconds. * * @return SE_License_SDK_Insights */ public function set_optin_notice_delay( int $delay ) { if ( $delay ) { $this->optin_notice_delay = absint( $delay ); } return $this; } /** * Set custom notice text * * @param string $text Admin Notice Test. * * @return SE_License_SDK_Insights */ public function notice( string $text ): SE_License_SDK_Insights { $this->notice = $text; return $this; } /** * Initialize insights * * @return void */ public function init() { if ( $this->did_init ) { return; } // Initialize. $init_method = 'init_' . $this->client->getType(); if ( method_exists( $this, $init_method ) ) { // init_{plugin/theme} $this->$init_method(); } $this->did_init = true; } /** * Initialize theme hooks * * @return void */ private function init_theme() { $this->init_common(); add_action( 'switch_theme', [ $this, 'deactivation_cleanup' ] ); add_action( 'switch_theme', [ $this, 'theme_deactivated' ], 12, 3 ); } /** * Initialize plugin hooks * * @return void */ private function init_plugin() { // Plugin deactivate popup. if ( ! $this->client->is_local_request() ) { add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_deactivation_assets' ] ); add_action( 'admin_footer', [ $this, 'deactivate_scripts' ], PHP_INT_MAX ); //add_action( 'plugin_action_links_' . $this->client->getBasename(), [ $this, 'plugin_action_links' ] ); } $this->init_common(); register_activation_hook( $this->client->getPackageFile(), [ $this, 'activate_plugin' ] ); register_deactivation_hook( $this->client->getPackageFile(), [ $this, 'deactivation_cleanup' ] ); } /** * Initialize common hooks * * @return void */ protected function init_common() { add_action( 'admin_init', [ $this, 'handle_optIn_optOut' ] ); add_action( 'removable_query_args', [ $this, 'add_removable_query_args' ] ); // Uninstall reason. add_action( 'wp_ajax_' . $this->client->getHookName( 'submit-uninstall-reason' ), [ $this, 'uninstall_reason_submission' ] ); // Ticket submission. add_action( 'wp_ajax_' . $this->client->getHookName( 'submit-support-ticket' ), [ $this, 'support_ticket_submission' ] ); // cron events. add_filter( 'cron_schedules', [ $this, 'add_weekly_schedule' ] ); add_action( $this->client->getSlug() . '_tracker_send_event', [ $this, 'send_tracking_data' ] ); } public function set_support_url( string $supportURL ): SE_License_SDK_Insights { $this->supportURL = $supportURL; return $this; } public function set_support_response( ?string $supportResponse ): SE_License_SDK_Insights { $this->supportResponse = $supportResponse; return $this; } public function set_support_error_response( ?string $supportErrorResponse ): SE_License_SDK_Insights { $this->supportErrorResponse = $supportErrorResponse; return $this; } public function set_ticket_recipient( string $ticketRecipient ): SE_License_SDK_Insights { $this->ticketRecipient = $ticketRecipient; return $this; } public function set_ticket_template( string $ticketTemplate ): SE_License_SDK_Insights { if ( $ticketTemplate && is_file( $ticketTemplate ) ) { $this->ticketTemplate = realpath( $ticketTemplate ); } return $this; } /** * Send tracking data to absp_service_api server * * @param boolean $override override current settings. * * @return void */ public function send_tracking_data( bool $override = false ) { // Skip on AJAX Requests. if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { return; } if ( ! $this->is_tracking_allowed() && ! $override ) { return; } // Send a maximum of once per week. $last_send = $this->get_last_send(); /** * Tracking interval * * @param string $interval A valid date/time string */ $trackingInterval = apply_filters( $this->client->getSlug() . '_tracking_interval', '-1 week' ); try { $intervalCheck = strtotime( $trackingInterval ); } catch ( Exception $e ) { // fallback to default 1 week if filter returned unusable data. $intervalCheck = strtotime( '-1 week' ); } if ( $last_send && $last_send > $intervalCheck && ! $override ) { return; } $this->client->request( [ 'body' => $this->get_tracking_data(), 'route' => 'log-usage' ] ); $this->client->set_option( 'tracking_last_send', time() ); } /** * Get the tracking data points * * @return array */ protected function get_tracking_data(): array { [ 'name' => $name, 'version' => $version ] = $this->get_runtime_info(); [ 'db_name' => $db_name, 'db_version' => $db_version ] = $this->get_db_info(); [ 'admin_email' => $admin_emails, 'admin_name' => $admin_name ] = $this->client->get_admin_info(); $all_plugins = $this->get_plugins_data(); $theme = wp_get_theme(); $theme_data = [ 'slug' => $theme->get_stylesheet(), 'name' => $theme->get( 'Name' ), 'version' => $theme->get( 'Version' ), 'author' => $theme->get( 'Author' ), 'author_url' => $theme->get( 'AuthorURI' ), 'theme_url' => $theme->get( 'ThemeURI' ), 'is_child' => false, ]; if ( $theme->parent() ) { $theme_data['is_child'] = true; $theme_data['parent_slug'] = $theme->parent()->get_stylesheet(); $theme_data['parent_name'] = $theme->parent()->get( 'Name' ); $theme_data['parent_version'] = $theme->parent()->get( 'Version' ); $theme_data['parent_author'] = $theme->parent()->get( 'Author' ); $theme_data['parent_author_url'] = $theme->parent()->get( 'AuthorURI' ); $theme_data['parent_theme_url'] = $theme->parent()->get( 'ThemeURI' ); } $data = [ 'core_name' => 'WordPress', 'core_version' => get_bloginfo( 'version' ), 'locale' => get_locale(), 'server_name' => $this->get_server_software_name(), 'server_version' => $this->get_server_version(), 'db_name' => $db_name, 'db_version' => $db_version, 'runtime' => $name, 'runtime_version' => $version, 'os_name' => php_uname( 's' ), 'os_arch' => php_uname( 'm' ), 'os_version' => $this->get_os_version(), 'ip_address' => $this->client->get_server_ip_address(), 'usage_log' => [ 'admin_name' => $admin_name, 'admin_email' => $admin_emails, 'os_info' => php_uname(), 'url' => esc_url( home_url() ), 'site' => $this->__get_site_name(), 'active_plugins' => $all_plugins['active_plugins'], 'inactive_plugins' => $all_plugins['inactive_plugins'], 'theme' => $theme_data, 'wp_memory_limit' => defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : 'N/A', 'debug_mode' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ), 'multisite' => is_multisite(), 'php_execution_time' => @ini_get( 'max_execution_time' ), // phpcs:ignore 'php_max_upload_size' => size_format( wp_max_upload_size() ), 'php_default_timezone' => date_default_timezone_get(), 'ext_php_soap' => class_exists( 'SoapClient' ), 'ext_php_fsockopen' => function_exists( 'fsockopen' ), 'ext_php_curl' => function_exists( 'curl_init' ), 'ext_php_ftp' => function_exists( 'ftp_connect' ), 'ext_php_sftp' => function_exists( 'ssh2_connect' ), ], ]; $usage_log = $this->get_usage_log(); if ( ! empty( $usage_log ) ) { $data['usage_log'] = $data['usage_log'] + $usage_log; } return apply_filters( $this->client->getHookName( 'tracker_data' ), $data ); } protected function set_usage_log_callback( $usage_log_callback): SE_License_SDK_Insights { $this->usage_log_callback = $usage_log_callback; return $this; } /** * If a child class wants to send extra data * * @return array */ protected function get_usage_log(): array { if ( is_callable( $this->usage_log_callback ) ) { $usage_log = call_user_func( $this->usage_log_callback ); if ( $usage_log && is_array( $usage_log ) ) { return $usage_log; } } return []; } /** * Explain the user which data we collect * * @return array */ protected function get_data_collection_list(): array { $data = array_merge( [ 'server_env' => __( 'Server environment details (MySQL version, MySQL version, Server software & version, etc.).', 'storeengine-sdk' ), 'wp_env' => __( 'WordPress installation details (version, debug mode, max upload size).', 'storeengine-sdk' ), 'wp_settings' => __( 'WordPress settings (site language, active and inactive plugins & themes).', 'storeengine-sdk' ), 'site_meta' => __( 'Site Name & URL.', 'storeengine-sdk' ), 'admin_meta' => __( 'Admin Name & Email.', 'storeengine-sdk' ), ], $this->data_collection_list ); return array_unique( array_filter( $data ) ); } public function set_data_being_collected( ?array $items ): SE_License_SDK_Insights { $this->data_collection_list = $items ? $items : []; return $this; } /** * Check if the user has opted into tracking * * @return bool */ public function is_tracking_allowed(): bool { // If hide_notice is set (optIn notice is hidden by default), then tracking is also disable. // But uninstallation tracking is active. return 'yes' === $this->client->get_option( 'allow_tracking', 'no' ); } /** * Get the last time a tracking was sent * * @return false|int */ public function get_last_send() { return $this->client->get_option( 'tracking_last_send', false ); } /** * Check if the notice has been dismissed or enabled * * @return boolean */ public function is_notice_dismissed(): bool { return 'hide' === $this->client->get_option( 'tracking_notice', 'show' ); } /** * Schedule the event weekly * * @return void */ private function maybe_schedule_event() { $hook_name = $this->client->getSlug() . '_tracker_send_event'; if ( ! wp_next_scheduled( $hook_name ) ) { wp_schedule_event( time(), 'weekly', $hook_name ); } } /** * Clear any scheduled hook * * @return void */ private function __clear_schedule_event() { wp_clear_scheduled_hook( $this->client->getSlug() . '_tracker_send_event' ); } /** * Tracking Opt In URL * @return string */ public function get_opt_in_url(): string { return add_query_arg( [ 'optAct' => $this->client->getHookName( 'tracker_optIn' ), '_wpnonce' => wp_create_nonce( $this->client->getHookName( 'insight_action' ) ), ] ); } /** * Tracking Opt Out URL * @return string */ public function get_opt_out_url(): string { return add_query_arg( [ 'optAct' => $this->client->getHookName( 'tracker_optOut' ), '_wpnonce' => wp_create_nonce( $this->client->getHookName( 'insight_action' ) ), ] ); } public function maybe_show_optin_notice(): bool { // Don't show if not configured properly. if ( ! $this->should_show_optin || ! $this->first_install_time || ! $this->optin_notice_delay ) { return false; } // Don't show if not admin. if ( ! $this->current_user_can() ) { return false; } // Don't show tracking if a local server or already dismissed or already allowed. if ( $this->client->is_local_request() || $this->is_notice_dismissed() || $this->is_tracking_allowed() ) { return false; } // Don't show until configured delay after first installation time. return current_time( 'timestamp', true ) >= $this->first_install_time + $this->optin_notice_delay; } /** * Display the admin notice to users that have not opted-in or out * * @return void */ public function admin_notice() { $what_tracked = $this->get_data_collection_list(); $terms = ''; $privacy_policy = ''; $terms_policy_text = ''; if ( $this->privacy_policy_url ) { $privacy_policy = '' . esc_html__( 'Privacy Policy', 'storeengine-sdk' ) . ''; } if ( $this->terms_url ) { $terms = '' . esc_html__( 'Terms of Services', 'storeengine-sdk' ) . ''; } if ( $terms || $privacy_policy ) { if ( $terms && $privacy_policy ) { $terms_policy_text = sprintf( /* translators: 1: Privacy Policy Link, 2: Terms Links */ __( 'Please read our %1$s and %2$s', 'storeengine-sdk' ), $privacy_policy, $terms ); } else { /* translators: 1: Privacy Policy or Terms Link */ $terms_policy_text = sprintf( __( 'Please read our %1$s', 'storeengine-sdk' ), ! $privacy_policy ? $terms : $privacy_policy ); } } if ( empty( $this->notice ) ) { /* translators: 1: plugin name. */ $this->notice = __( '
We’d love to stay in touch and share useful updates, tips, and special offers to help you get the most from %1$s. Your privacy is our priority. No spam — ever.
', 'storeengine-sdk' ); } $this->notice = sprintf( $this->notice, '' . esc_html( $this->client->getPackageName() ) . '' ); include __DIR__ . '/../views/insights-opt-in-notice.php'; } protected function current_user_can() { return current_user_can( 'manage_options' ) || current_user_can( 'install_plugins' ) || current_user_can( 'install_themes' ); } /** * handle the optIn/optOut * * @return void */ public function handle_optIn_optOut() { if ( $this->maybe_show_optin_notice() && $this->current_user_can() ) { // Tracking notice. add_action( 'admin_notices', [ $this, 'admin_notice' ] ); } if ( isset( $_REQUEST['_wpnonce'], $_REQUEST['optAct'] ) && $this->current_user_can() ) { $items = [ $this->client->getHookName( 'tracker_optIn' ), $this->client->getHookName( 'tracker_optOut' ), ]; $opt_act = sanitize_text_field( wp_unslash( $_REQUEST['optAct'] ) ); if ( in_array( $opt_act, $items ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), $this->client->getHookName( 'insight_action' ) ) ) { if ( $this->client->getHookName( 'tracker_optOut' ) === $_REQUEST['optAct'] ) { $this->optOut(); } else { $this->optIn(); } wp_safe_redirect( remove_query_arg( [ 'optAct', '_wpnonce' ] ) ); exit; } } } /** * Add query vars to removable query args array * * @param array $removable_query_args array of removable args. * * @return array */ public function add_removable_query_args( array $removable_query_args ): array { return array_merge( $removable_query_args, [ 'optAct', '_wpnonce' ] ); } /** * Tracking optIn * * @return void * @see Insights::send_tracking_data() */ public function optIn() { $this->client->set_option( 'allow_tracking', 'yes' ); $this->client->set_option( 'tracking_notice', 'hide' ); $this->__clear_schedule_event(); $this->maybe_schedule_event(); $this->client->request( [ 'body' => [ 'opt_in' => true ], 'route' => 'opt-in' ] ); $this->send_tracking_data( true ); } /** * optOut from tracking * * @return void */ public function optOut( $hide_notice = true ) { // Only a site that had opted in has anything to withdraw, so only then // tell the server that sharing stopped. Never send usage data here: // send_tracking_data( true ) skips the consent check, and calling it sent // the full payload from an administrator who had just clicked "No thanks". $was_allowed = $this->is_tracking_allowed(); $this->client->set_option( 'allow_tracking', 'no' ); $this->client->set_option( 'tracking_notice', $hide_notice ? 'hide' : 'show' ); $this->__clear_schedule_event(); if ( $was_allowed ) { $this->client->request( [ 'body' => [ 'opt_in' => false ], 'route' => 'opt-in' ] ); } } /** * Get the number of post counts * * @param string $post_type PostType name to get count for. * * @return integer */ public function get_post_count( $post_type ) { global $wpdb; // phpcs:disable return (int) $wpdb->get_var( $wpdb->prepare( "SELECT count(ID) FROM $wpdb->posts WHERE post_type = %s and post_status = 'publish'", $post_type ) ); // phpcs:enable } private function get_server_software_name(): ?string { if ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ) { return null; // No web server in CLI } if ( empty( $_SERVER['SERVER_SOFTWARE'] ) ) { return null; } // Example: "Apache/2.4.57 (Unix)" → "Apache" // Example: "nginx/1.25.2" → "nginx" return preg_split( '/[\/\s]+/', $_SERVER['SERVER_SOFTWARE'] )[0] ?? null; } private function get_server_version(): ?string { if ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ) { return null; // No server software in CLI } if ( empty( $_SERVER['SERVER_SOFTWARE'] ) ) { return null; } if ( preg_match( '/\d+\.\d+(?:\.\d+)?/', $_SERVER['SERVER_SOFTWARE'], $matches ) ) { return $matches[0]; } return null; } private function get_runtime_info(): array { if ( defined( 'HHVM_VERSION' ) ) { return [ 'name' => 'HHVM', 'version' => HHVM_VERSION ]; } if ( PHP_SAPI === 'phpdbg' ) { return [ 'name' => 'PHPDBG', 'version' => PHP_VERSION ]; } return [ 'name' => 'PHP', 'version' => PHP_VERSION ]; } private function get_db_info(): array { global $wpdb; $db_name = null; $db_version = null; $dbh_class = get_class( $wpdb->dbh ); // Try WordPress helper first (MySQL/MariaDB only) if ( method_exists( $wpdb, 'db_server_info' ) ) { $server_info = $wpdb->db_server_info(); } elseif ( isset( $wpdb->dbh ) ) { if ( $wpdb->dbh instanceof mysqli ) { // mysqli $server_info = $wpdb->dbh->server_info; } elseif ( $wpdb->dbh instanceof \PDO ) { // PDO $server_info = $wpdb->dbh->getAttribute( \PDO::ATTR_SERVER_VERSION ); //$driver_name = $wpdb->dbh->getAttribute( \PDO::ATTR_DRIVER_NAME ); } } if ( ! empty( $server_info ) ) { if ( stripos( $server_info, 'MariaDB' ) !== false ) { $db_name = 'MariaDB'; $db_version = preg_replace( '/.*MariaDB\s*/i', '', $server_info ); } elseif ( stripos( $server_info, 'PostgreSQL' ) !== false ) { $db_name = 'PostgreSQL'; $db_version = preg_replace( '/[^0-9\.].*$/', '', $server_info ); } elseif ( stripos( $server_info, 'SQLite' ) !== false ) { $db_name = 'SQLite'; $db_version = preg_replace( '/[^0-9\.].*$/', '', $server_info ); } else { // Default to MySQL $db_name = 'MySQL'; $db_version = preg_replace( '/[^0-9\.].*$/', '', $server_info ); } } return [ 'db_name' => $db_name, 'db_version' => $db_version ]; } private function get_os_version(): ?string { if ( php_uname( 's' ) !== 'Darwin' ) { return php_uname( 'r' ); } $plistPath = '/System/Library/CoreServices/SystemVersion.plist'; if ( ! is_readable( $plistPath ) ) { return null; } $plistContent = file_get_contents( $plistPath ); if ( $plistContent === false ) { return null; } // Matchget_se_url() ) . '" target="_blank" style="color:#008DFF">' . esc_html__( 'StoreEngine', 'storeengine-sdk' ) . '', esc_html( $this->client->getVersion() ) ); ?>