Core
3 weeks ago
Twitter
3 weeks ago
Config.php
3 weeks ago
EventRecorder.php
3 weeks ago
ReporterInterface.php
3 weeks ago
SmashUsageTracking.php
3 weeks ago
SmashUsageTracking.php
235 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Orchestrates Smash Usage Tracking: schedule, ensure site token, build payload, send. |
| 4 | * |
| 5 | * @package TwitterFeed\UsageTracking |
| 6 | * @since 2.6 |
| 7 | */ |
| 8 | |
| 9 | namespace TwitterFeed\UsageTracking; |
| 10 | |
| 11 | use TwitterFeed\UsageTracking\Core\RegisterSite; |
| 12 | use TwitterFeed\UsageTracking\Core\Sender; |
| 13 | use TwitterFeed\UsageTracking\Core\PayloadBuilder; |
| 14 | use TwitterFeed\UsageTracking\Core\Scheduler; |
| 15 | use TwitterFeed\UsageTracking\Twitter\TwitterFreeReporter; |
| 16 | |
| 17 | if ( ! defined( 'ABSPATH' ) ) { |
| 18 | exit; |
| 19 | } |
| 20 | |
| 21 | class SmashUsageTracking { |
| 22 | |
| 23 | /** @var ReporterInterface */ |
| 24 | private $reporter; |
| 25 | |
| 26 | /** @var RegisterSite */ |
| 27 | private $register_site; |
| 28 | |
| 29 | /** @var Sender */ |
| 30 | private $sender; |
| 31 | |
| 32 | /** @var PayloadBuilder */ |
| 33 | private $payload_builder; |
| 34 | |
| 35 | /** @var Scheduler */ |
| 36 | private $scheduler; |
| 37 | |
| 38 | public function __construct() { |
| 39 | $this->reporter = new TwitterFreeReporter(); |
| 40 | $this->register_site = new Core\RegisterSite(); |
| 41 | $this->sender = new Core\Sender(); |
| 42 | $this->payload_builder = new Core\PayloadBuilder( $this->reporter ); |
| 43 | $this->scheduler = new Core\Scheduler(); |
| 44 | |
| 45 | add_action( 'init', array( $this, 'maybe_schedule' ) ); |
| 46 | add_filter( 'cron_schedules', array( $this->scheduler, 'add_schedules' ) ); |
| 47 | add_action( Config::CRON_HOOK, array( $this, 'send_checkin' ) ); |
| 48 | |
| 49 | add_action( 'current_screen', array( $this, 'maybe_record_active_day' ) ); |
| 50 | add_action( 'wp_ajax_ctf_smash_usage_record_event', array( $this, 'ajax_record_event' ) ); |
| 51 | add_action( 'wp_ajax_ctf_smash_usage_record_session', array( $this, 'ajax_record_session' ) ); |
| 52 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_session_script' ), 20 ); |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * Schedule cron if enabled and not already scheduled. |
| 57 | */ |
| 58 | public function maybe_schedule() { |
| 59 | $this->scheduler->schedule(); |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Cron callback: ensure site token, build payload, send, update last_send. |
| 64 | */ |
| 65 | public function send_checkin() { |
| 66 | if ( ! Config::is_enabled() ) { |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | $host = wp_parse_url( home_url(), PHP_URL_HOST ); |
| 71 | if ( 'smashballoon.com' === $host || '.smashballoon.com' === substr( (string) $host, -17 ) ) { |
| 72 | return; |
| 73 | } |
| 74 | |
| 75 | $opt = get_option( Config::OPTION_TRACKING, array() ); |
| 76 | $last_send = is_array( $opt ) && isset( $opt['last_send'] ) ? (int) $opt['last_send'] : 0; |
| 77 | if ( $last_send > strtotime( '-1 week' ) ) { |
| 78 | return; |
| 79 | } |
| 80 | |
| 81 | $site_token = get_option( Config::OPTION_SITE_TOKEN, '' ); |
| 82 | if ( '' === $site_token || ! is_string( $site_token ) ) { |
| 83 | $site_token = $this->register_site->register( $this->reporter ); |
| 84 | if ( null === $site_token ) { |
| 85 | return; |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | $period_end = gmdate( 'Y-m-d', strtotime( 'yesterday' ) ); |
| 90 | $period_start = gmdate( 'Y-m-d', strtotime( $period_end . ' -6 days' ) ); |
| 91 | |
| 92 | $payload = $this->payload_builder->build( $site_token, $period_start, $period_end ); |
| 93 | $success = $this->sender->send( $payload ); |
| 94 | |
| 95 | if ( $success ) { |
| 96 | update_option( |
| 97 | Config::OPTION_TRACKING, |
| 98 | array( |
| 99 | 'enabled' => Config::is_enabled(), |
| 100 | 'last_send' => time(), |
| 101 | ), |
| 102 | false |
| 103 | ); |
| 104 | $sent_events = isset( $payload['dynamic_metrics']['events'] ) && is_array( $payload['dynamic_metrics']['events'] ) |
| 105 | ? $payload['dynamic_metrics']['events'] |
| 106 | : array(); |
| 107 | $this->reset_events_after_send( $sent_events ); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Remove only the event keys that were included in the sent payload. |
| 113 | * Events recorded by concurrent AJAX requests after the payload was built |
| 114 | * are left intact so they roll over into the next reporting period. |
| 115 | * |
| 116 | * @param array $sent_events Events map from the sent payload. |
| 117 | */ |
| 118 | private function reset_events_after_send( array $sent_events ) { |
| 119 | if ( ! empty( $sent_events ) ) { |
| 120 | $stored = get_option( EventRecorder::OPTION_NAME, array() ); |
| 121 | if ( is_array( $stored ) ) { |
| 122 | foreach ( array_keys( $sent_events ) as $key ) { |
| 123 | unset( $stored[ $key ] ); |
| 124 | } |
| 125 | update_option( EventRecorder::OPTION_NAME, $stored, false ); |
| 126 | } |
| 127 | } |
| 128 | update_option( Config::OPTION_SESSION_DURATIONS, array(), false ); |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * Unschedule cron (call when disabling tracking). |
| 133 | */ |
| 134 | public function unschedule() { |
| 135 | $this->scheduler->unschedule(); |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Record active day and optionally settings_page_viewed when on a CTF admin page. |
| 140 | */ |
| 141 | public function maybe_record_active_day( $screen ) { |
| 142 | if ( ! $screen || strpos( $screen->id, 'twitter-feed' ) === false ) { |
| 143 | return; |
| 144 | } |
| 145 | if ( ! Config::is_enabled() ) { |
| 146 | return; |
| 147 | } |
| 148 | EventRecorder::record_active_day(); |
| 149 | if ( isset( $screen->id ) && strpos( $screen->id, 'ctf-settings' ) !== false ) { |
| 150 | EventRecorder::record( 'settings_page_viewed' ); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | /** |
| 155 | * AJAX: record a single event from JS (e.g. setup_wizard_started). |
| 156 | */ |
| 157 | public function ajax_record_event() { |
| 158 | check_ajax_referer( 'ctf_smash_usage_record_event', 'nonce' ); |
| 159 | if ( ! current_user_can( 'manage_twitter_feed_options' ) && ! current_user_can( 'manage_options' ) ) { |
| 160 | wp_send_json_error(); |
| 161 | } |
| 162 | $event_name = isset( $_POST['event_name'] ) ? sanitize_text_field( wp_unslash( $_POST['event_name'] ) ) : ''; |
| 163 | $allowed = array( |
| 164 | 'setup_wizard_started', |
| 165 | 'setup_wizard_step_completed', |
| 166 | 'setup_wizard_completed', |
| 167 | 'setup_wizard_abandoned', |
| 168 | 'upgrade_prompt_shown', |
| 169 | ); |
| 170 | if ( '' !== $event_name && in_array( $event_name, $allowed, true ) ) { |
| 171 | EventRecorder::record( $event_name ); |
| 172 | } |
| 173 | wp_send_json_success(); |
| 174 | } |
| 175 | |
| 176 | /** |
| 177 | * AJAX: record session duration from JS (seconds). |
| 178 | */ |
| 179 | public function ajax_record_session() { |
| 180 | check_ajax_referer( 'ctf_smash_usage_record_session', 'nonce' ); |
| 181 | if ( ( ! current_user_can( 'manage_twitter_feed_options' ) && ! current_user_can( 'manage_options' )) || ! Config::is_enabled() ) { |
| 182 | wp_send_json_error(); |
| 183 | } |
| 184 | $seconds = isset( $_POST['duration_seconds'] ) ? (int) $_POST['duration_seconds'] : 0; |
| 185 | EventRecorder::record_session_duration( $seconds ); |
| 186 | wp_send_json_success(); |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Enqueue session-duration and record-event script on CTF admin pages. |
| 191 | */ |
| 192 | public function enqueue_session_script() { |
| 193 | if ( ! function_exists( 'get_current_screen' ) ) { |
| 194 | return; |
| 195 | } |
| 196 | $screen = get_current_screen(); |
| 197 | if ( ! $screen || strpos( $screen->id, 'twitter-feed' ) === false ) { |
| 198 | return; |
| 199 | } |
| 200 | $script_path = 'admin/assets/js/smash-usage-session.js'; |
| 201 | $path = dirname( __DIR__, 2 ) . '/' . $script_path; |
| 202 | if ( ! file_exists( $path ) ) { |
| 203 | return; |
| 204 | } |
| 205 | wp_enqueue_script( |
| 206 | 'ctf-smash-usage-session', |
| 207 | CTF_PLUGIN_URL . $script_path, |
| 208 | array( 'jquery' ), |
| 209 | defined( 'CTF_VERSION' ) ? CTF_VERSION : '1.0', |
| 210 | true |
| 211 | ); |
| 212 | wp_localize_script( |
| 213 | 'ctf-smash-usage-session', |
| 214 | 'ctfSmashUsageSession', |
| 215 | array( |
| 216 | 'ajax_url' => admin_url( 'admin-ajax.php' ), |
| 217 | 'nonce' => wp_create_nonce( 'ctf_smash_usage_record_session' ), |
| 218 | 'event_nonce' => wp_create_nonce( 'ctf_smash_usage_record_event' ), |
| 219 | ) |
| 220 | ); |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * Build the usage report payload without sending (for preview/debugging). |
| 225 | * |
| 226 | * @return array |
| 227 | */ |
| 228 | public function get_payload_preview() { |
| 229 | $period_end = gmdate( 'Y-m-d', strtotime( 'yesterday' ) ); |
| 230 | $period_start = gmdate( 'Y-m-d', strtotime( $period_end . ' -6 days' ) ); |
| 231 | |
| 232 | return $this->payload_builder->build( 'preview-no-api', $period_start, $period_end ); |
| 233 | } |
| 234 | } |
| 235 |