Changeset 3748854
- Timestamp:
- 10/15/2026 03:34:37 PM (11 days ago)
- Location:
- readable-agent-analytics
- Files:
-
- 2 added
- 2 deleted
- 10 edited
- 1 copied
-
tags/1.2.0 (copied) (copied from readable-agent-analytics/trunk )
-
tags/1.2.0/README.md (deleted)
-
tags/1.2.0/assets/admin.css (modified) (1 diff)
-
tags/1.2.0/includes/class-readable-agent-connection.php (added)
-
tags/1.2.0/includes/class-readable-agent-dashboard.php (modified) (3 diffs)
-
tags/1.2.0/includes/class-readable-agent-settings.php (modified) (6 diffs)
-
tags/1.2.0/readable-agent-analytics.php (modified) (4 diffs)
-
tags/1.2.0/readme.txt (modified) (7 diffs)
-
trunk/README.md (deleted)
-
trunk/assets/admin.css (modified) (1 diff)
-
trunk/includes/class-readable-agent-connection.php (added)
-
trunk/includes/class-readable-agent-dashboard.php (modified) (3 diffs)
-
trunk/includes/class-readable-agent-settings.php (modified) (6 diffs)
-
trunk/readable-agent-analytics.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
readable-agent-analytics/tags/1.2.0/assets/admin.css
r3739172 r3748854 14 14 .readable-agent-analytics-verification { margin-top: 18px; padding: 16px; border: 1px solid #d9d7e8; border-radius: 10px; background: #f8f7fc; } 15 15 .readable-agent-analytics-verification summary { cursor: pointer; color: #29256f; font-weight: 700; } 16 .readable-agent-analytics-connection-state { margin: 18px 0; padding: 16px; border: 1px solid #d9d7e8; border-radius: 10px; background: #f8f7fc; } 17 .readable-agent-analytics-connection-state strong { color: #29256f; font-size: 16px; } 18 .readable-agent-analytics-connected { border-color: #a8d5bd; background: #f1faf5; } 19 .readable-agent-analytics-inline-form { display: inline-block; margin-left: 8px; } 20 .readable-agent-analytics-danger { margin-top: 22px; padding-top: 18px; border-top: 1px solid #ecebf3; } 16 21 .readable-agent-analytics-card dl { margin: 0; } 17 22 .readable-agent-analytics-card dl div { display: flex; justify-content: space-between; gap: 18px; padding: 12px 0; border-bottom: 1px solid #ecebf3; } -
readable-agent-analytics/tags/1.2.0/includes/class-readable-agent-dashboard.php
r3739172 r3748854 124 124 <section class="readable-agent-analytics-card readable-agent-analytics-empty"> 125 125 <h2><?php esc_html_e('Your analytics dashboard will appear here', 'readable-agent-analytics'); ?></h2> 126 <p><?php esc_html_e(' Add the Site ID and WordPress analytics key below to start collecting matching visits.', 'readable-agent-analytics'); ?></p>126 <p><?php esc_html_e('Choose Connect to Readable below. After you confirm the site and data disclosure, analytics collection starts automatically.', 'readable-agent-analytics'); ?></p> 127 127 </section> 128 128 <?php … … 131 131 if ('authentication' === $result['state']) { 132 132 ?> 133 <div class="notice notice-error inline"><p><strong><?php esc_html_e('Reconnect Readable.', 'readable-agent-analytics'); ?></strong> <?php esc_html_e('The saved analytics key is invalid, expired, or does not include dashboard access. Paste a current WordPress analytics key below.', 'readable-agent-analytics'); ?></p></div>133 <div class="notice notice-error inline"><p><strong><?php esc_html_e('Reconnect Readable.', 'readable-agent-analytics'); ?></strong> <?php esc_html_e('The saved analytics key is invalid, expired, or does not include dashboard access. Disconnect and connect this site again to replace it securely.', 'readable-agent-analytics'); ?></p></div> 134 134 <?php 135 135 return; … … 139 139 <section class="readable-agent-analytics-card readable-agent-analytics-empty"> 140 140 <h2><?php esc_html_e('Analytics collection is paused', 'readable-agent-analytics'); ?></h2> 141 <p><?php esc_html_e('Your connection is saved, but no matching visits are collected or delivered until you explicitly enable analytics below.', 'readable-agent-analytics'); ?></p>141 <p><?php esc_html_e('Your connection is saved, but no matching visits are collected or delivered while collection is paused. Resume it in the connection settings below.', 'readable-agent-analytics'); ?></p> 142 142 </section> 143 143 <?php -
readable-agent-analytics/tags/1.2.0/includes/class-readable-agent-settings.php
r3739172 r3748854 33 33 __('Readable – AI Agent Analytics', 'readable-agent-analytics'), 34 34 wp_kses_post( 35 '<p>' . __('When a site administrator explicitly enables Readable AI Agent Analytics, the plugin records only recognized AI-agent requests and visits referred by supported AI assistants. It sends the HTTPS hostname and path, generalized agent or referral identity, response status, content type, response timing, and random event identifiers to Readable. It does not send IP addresses, cookies, authorization values, query strings, raw referral URLs, page content, request or response bodies, or browser fingerprints for referred people.', 'readable-agent-analytics') . '</p>' .35 '<p>' . __('When a site administrator connects Readable AI Agent Analytics and confirms the data disclosure, analytics collection starts. The plugin records only recognized AI-agent requests and visits referred by supported AI assistants. It sends the HTTPS hostname and path, generalized agent or referral identity, response status, content type, response timing, and random event identifiers to Readable. It does not send IP addresses, cookies, authorization values, query strings, raw referral URLs, page content, request or response bodies, or browser fingerprints for referred people. An administrator can pause collection or disconnect Readable at any time.', 'readable-agent-analytics') . '</p>' . 36 36 '<p>' . __('Queued events are retained locally for up to 30 days. Rejected events retained for troubleshooting are removed after 14 days. Readable’s service and privacy terms apply after delivery.', 'readable-agent-analytics') . ' <a href="https://www.tryreadable.ai/privacy">https://www.tryreadable.ai/privacy</a></p>' 37 37 ) … … 99 99 'site_claim_challenge' => $claim_challenge, 100 100 'site_claim_expires_at' => $claim_expires_at, 101 'pairing' => isset($current['pairing']) ? $current['pairing'] : array(), 102 'instance_id' => isset($current['instance_id']) ? $current['instance_id'] : '', 101 103 ); 102 104 } … … 144 146 } 145 147 148 public static function instance_id() { 149 $options = self::options(); 150 $current = isset($options['instance_id']) ? (string) $options['instance_id'] : ''; 151 if (preg_match('/^[A-Za-z0-9_-]{20,128}$/', $current)) { 152 return $current; 153 } 154 $current = rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '='); 155 $options['instance_id'] = $current; 156 if (!self::update_options($options)) { 157 throw new RuntimeException(esc_html__('This server could not securely store the plugin installation identifier.', 'readable-agent-analytics')); 158 } 159 return $current; 160 } 161 162 public static function pairing() { 163 $options = self::options(); 164 $pairing = isset($options['pairing']) && is_array($options['pairing']) ? $options['pairing'] : array(); 165 if (isset($pairing['verifier'])) { 166 $pairing['verifier'] = self::decrypt((string) $pairing['verifier']); 167 } 168 return $pairing; 169 } 170 171 public static function save_pairing($pairing, $ownership_token, $ownership_expires_at) { 172 $options = self::options(); 173 $pairing['verifier'] = self::encrypt((string) $pairing['verifier']); 174 if ('' === $pairing['verifier']) { 175 return false; 176 } 177 $options['pairing'] = $pairing; 178 $options['site_claim_challenge'] = $ownership_token; 179 $options['site_claim_expires_at'] = (int) $ownership_expires_at; 180 return self::update_options($options); 181 } 182 183 public static function clear_pairing() { 184 $options = self::options(); 185 $options['pairing'] = array(); 186 $options['site_claim_challenge'] = ''; 187 $options['site_claim_expires_at'] = 0; 188 self::update_options($options); 189 } 190 191 public static function save_connected_credentials($site_id, $api_key) { 192 $encrypted = self::encrypt($api_key); 193 if ('' === $encrypted) { 194 return false; 195 } 196 $options = self::options(); 197 $options['site_id'] = sanitize_text_field($site_id); 198 $options['api_key'] = $encrypted; 199 $options['enabled'] = true; 200 $options['pairing'] = array(); 201 $options['site_claim_challenge'] = ''; 202 $options['site_claim_expires_at'] = 0; 203 $saved = self::update_options($options); 204 if ($saved) { 205 Readable_Agent_Dashboard::clear_cache(); 206 } 207 return $saved; 208 } 209 210 public static function clear_connection() { 211 $options = self::options(); 212 $options['site_id'] = ''; 213 $options['api_key'] = ''; 214 $options['enabled'] = false; 215 $options['pairing'] = array(); 216 $options['site_claim_challenge'] = ''; 217 $options['site_claim_expires_at'] = 0; 218 self::update_options($options); 219 Readable_Agent_Dashboard::clear_cache(); 220 } 221 222 private static function update_options($options) { 223 $filter = array(__CLASS__, 'sanitize'); 224 $filter_name = 'sanitize_option_' . self::OPTION; 225 $removed = remove_filter($filter_name, $filter); 226 try { 227 return update_option(self::OPTION, $options, false); 228 } finally { 229 if ($removed) { 230 add_filter($filter_name, $filter); 231 } 232 } 233 } 234 146 235 private static function options() { 147 236 $value = get_option(self::OPTION, array()); … … 183 272 } 184 273 $status = get_option(self::STATUS_OPTION, array()); 274 $status_message = !empty($status['message']) ? (string) $status['message'] : ''; 275 if ('connected' === ($status['code'] ?? '') && self::is_configured()) { 276 $status_message = self::is_enabled() 277 ? __('Readable is connected and analytics collection is active. You can pause collection or disconnect at any time.', 'readable-agent-analytics') 278 : __('Readable is connected. Analytics collection is paused; resume it below or reconnect with the current consent flow.', 'readable-agent-analytics'); 279 } 185 280 $queue = Readable_Agent_Queue::counts(); 281 $pairing = self::pairing(); 186 282 ?> 187 283 <div class="wrap readable-agent-analytics-admin"> … … 193 289 194 290 <?php settings_errors(self::OPTION); ?> 195 <?php if ( !empty($status['message'])) : ?>291 <?php if ('' !== $status_message) : ?> 196 292 <div class="notice notice-info inline"><p> 197 293 <strong><?php echo esc_html(ucwords(str_replace('_', ' ', (string) $status['code']))); ?>:</strong> 198 <?php echo esc_html( (string) $status['message']); ?>294 <?php echo esc_html($status_message); ?> 199 295 </p></div> 200 296 <?php endif; ?> … … 205 301 <section class="readable-agent-analytics-card"> 206 302 <h2><?php esc_html_e('Connect this site', 'readable-agent-analytics'); ?></h2> 207 <p><?php esc_html_e('Paste the Site ID and one-time WordPress analytics key supplied by Readable.', 'readable-agent-analytics'); ?></p> 208 <form method="post" action="options.php"> 209 <?php settings_fields('readable_agent_analytics'); ?> 210 <label for="readable-site-id"><?php esc_html_e('Readable Site ID', 'readable-agent-analytics'); ?></label> 211 <input id="readable-site-id" class="regular-text" name="<?php echo esc_attr(self::OPTION); ?>[site_id]" value="<?php echo esc_attr(self::site_id()); ?>" autocomplete="off" /> 212 <label for="readable-api-key"><?php esc_html_e('WordPress analytics key', 'readable-agent-analytics'); ?></label> 213 <input id="readable-api-key" class="regular-text" type="password" name="<?php echo esc_attr(self::OPTION); ?>[api_key]" value="" autocomplete="new-password" placeholder="<?php echo self::api_key() ? esc_attr__('Saved — leave blank to keep it', 'readable-agent-analytics') : ''; ?>" /> 214 <label class="readable-agent-analytics-checkbox" for="readable-enabled"> 215 <input id="readable-enabled" type="checkbox" name="<?php echo esc_attr(self::OPTION); ?>[enabled]" value="1" <?php checked(self::is_enabled()); ?> /> 216 <span><?php esc_html_e('Enable analytics collection and send matching request facts to Readable', 'readable-agent-analytics'); ?></span> 217 </label> 218 <p class="description"><?php esc_html_e('Collection is off until you check this box. Review the privacy boundary below and update your site privacy notice before enabling it.', 'readable-agent-analytics'); ?></p> 219 <details class="readable-agent-analytics-verification"> 220 <summary><?php esc_html_e('Verify this site for a free Readable account', 'readable-agent-analytics'); ?></summary> 221 <p><?php esc_html_e('Create or sign in to your Readable account, start WordPress setup, then paste the short-lived ownership challenge here. Readable will read only that challenge from this site.', 'readable-agent-analytics'); ?></p> 222 <label for="readable-site-claim"><?php esc_html_e('Site verification challenge', 'readable-agent-analytics'); ?></label> 223 <input id="readable-site-claim" class="regular-text" name="<?php echo esc_attr(self::OPTION); ?>[site_claim_challenge]" value="" autocomplete="off" placeholder="rdbl_wp_claim_…" /> 224 <?php if ('' !== self::site_claim_challenge()) : ?> 225 <p class="description"><?php esc_html_e('A verification challenge is active for up to 20 minutes. Return to Readable and choose Verify site.', 'readable-agent-analytics'); ?></p> 303 <?php if (!self::is_configured()) : ?> 304 <p><?php esc_html_e('Sign in to Readable, confirm this site and the limited analytics data, then return here automatically with collection active. No Site ID or key copying is required.', 'readable-agent-analytics'); ?></p> 305 <?php if (!empty($pairing['connect_url']) && !empty($pairing['expires_at']) && (int) $pairing['expires_at'] > time()) : ?> 306 <div class="readable-agent-analytics-connection-state"> 307 <strong><?php esc_html_e('Connection in progress', 'readable-agent-analytics'); ?></strong> 308 <p><?php esc_html_e('If sign-in was interrupted, resume the same request. Repeated clicks will not create another request.', 'readable-agent-analytics'); ?></p> 309 <a class="button button-primary" href="<?php echo esc_url((string) $pairing['connect_url']); ?>"><?php esc_html_e('Resume Readable sign-in', 'readable-agent-analytics'); ?></a> 310 <form class="readable-agent-analytics-inline-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 311 <input type="hidden" name="action" value="readable_agent_connect_status" /> 312 <?php wp_nonce_field('readable_agent_connection'); ?> 313 <button class="button" type="submit"><?php esc_html_e('Check status', 'readable-agent-analytics'); ?></button> 314 </form> 315 <form class="readable-agent-analytics-inline-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 316 <input type="hidden" name="action" value="readable_agent_connect_start" /> 317 <input type="hidden" name="restart" value="1" /> 318 <?php wp_nonce_field('readable_agent_connection'); ?> 319 <button class="button" type="submit"><?php esc_html_e('Start a new request', 'readable-agent-analytics'); ?></button> 320 </form> 321 </div> 322 <?php else : ?> 323 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 324 <input type="hidden" name="action" value="readable_agent_connect_start" /> 325 <?php wp_nonce_field('readable_agent_connection'); ?> 326 <button class="button button-primary button-hero" type="submit"><?php esc_html_e('Connect to Readable', 'readable-agent-analytics'); ?></button> 327 </form> 328 <?php endif; ?> 329 <?php else : ?> 330 <div class="readable-agent-analytics-connection-state readable-agent-analytics-connected"> 331 <strong><?php esc_html_e('Connected securely', 'readable-agent-analytics'); ?></strong> 332 <?php /* translators: %s: Readable Site ID saved for this WordPress site. */ ?> 333 <p><?php echo esc_html(sprintf(__('Site ID %s is linked. The private key is encrypted in WordPress and is never shown in a URL.', 'readable-agent-analytics'), self::site_id())); ?></p> 334 </div> 335 <form method="post" action="options.php"> 336 <?php settings_fields('readable_agent_analytics'); ?> 337 <input type="hidden" name="<?php echo esc_attr(self::OPTION); ?>[site_id]" value="<?php echo esc_attr(self::site_id()); ?>" /> 338 <label class="readable-agent-analytics-checkbox" for="readable-enabled"> 339 <input id="readable-enabled" type="checkbox" name="<?php echo esc_attr(self::OPTION); ?>[enabled]" value="1" <?php checked(self::is_enabled()); ?> /> 340 <span><?php esc_html_e('Collect and send matching request facts to Readable', 'readable-agent-analytics'); ?></span> 341 </label> 342 <?php if (self::is_enabled()) : ?> 343 <p class="description"><?php esc_html_e('Collection started when you connected. Clear this box and save to pause collection without disconnecting Readable.', 'readable-agent-analytics'); ?></p> 344 <?php else : ?> 345 <p class="description"><?php esc_html_e('Collection is paused. Check this box and save to resume using the connection you already approved.', 'readable-agent-analytics'); ?></p> 226 346 <?php endif; ?> 227 </details> 228 <?php submit_button(__('Save connection', 'readable-agent-analytics')); ?> 229 </form> 347 <?php submit_button(__('Save collection setting', 'readable-agent-analytics'), 'secondary'); ?> 348 </form> 349 <form class="readable-agent-analytics-inline-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 350 <input type="hidden" name="action" value="readable_agent_connect_start" /> 351 <input type="hidden" name="restart" value="1" /> 352 <?php wp_nonce_field('readable_agent_connection'); ?> 353 <button class="button" type="submit"><?php esc_html_e('Reconnect securely', 'readable-agent-analytics'); ?></button> 354 <p class="description"><?php esc_html_e('Use this if dashboard access fails or you need to replace the saved key. The current connection stays in place unless the new verified connection succeeds.', 'readable-agent-analytics'); ?></p> 355 </form> 356 <form class="readable-agent-analytics-danger" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return window.confirm('<?php echo esc_js(__('Disconnect Readable and revoke this plugin key?', 'readable-agent-analytics')); ?>');"> 357 <input type="hidden" name="action" value="readable_agent_disconnect" /> 358 <?php wp_nonce_field('readable_agent_connection'); ?> 359 <button class="button" type="submit"><?php esc_html_e('Disconnect Readable', 'readable-agent-analytics'); ?></button> 360 </form> 361 <?php endif; ?> 230 362 </section> 231 363 -
readable-agent-analytics/tags/1.2.0/readable-agent-analytics.php
r3739172 r3748854 4 4 * Plugin URI: https://www.tryreadable.ai/integrations/wordpress 5 5 * Description: Privacy-safe AI agent and AI referral analytics for WordPress, powered by Readable. 6 * Version: 1. 1.06 * Version: 1.2.0 7 7 * Requires at least: 6.2 8 8 * Requires PHP: 7.4 … … 16 16 defined('ABSPATH') || exit; 17 17 18 define('READABLE_AGENT_ANALYTICS_VERSION', '1. 1.0');18 define('READABLE_AGENT_ANALYTICS_VERSION', '1.2.0'); 19 19 define('READABLE_AGENT_ANALYTICS_FILE', __FILE__); 20 20 define('READABLE_AGENT_ANALYTICS_DIR', plugin_dir_path(__FILE__)); 21 21 define('READABLE_AGENT_ANALYTICS_ENDPOINT', 'https://ingest.sonicadn.com/v1.0/traffic/data/wordpress'); 22 define('READABLE_AGENT_ANALYTICS_DASHBOARD_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/analytics'); 22 defined('READABLE_AGENT_ANALYTICS_DASHBOARD_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_DASHBOARD_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/analytics'); 23 defined('READABLE_AGENT_ANALYTICS_PAIRING_START_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_PAIRING_START_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/start'); 24 defined('READABLE_AGENT_ANALYTICS_PAIRING_EXCHANGE_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_PAIRING_EXCHANGE_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/exchange'); 25 defined('READABLE_AGENT_ANALYTICS_PAIRING_STATUS_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_PAIRING_STATUS_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/status'); 26 defined('READABLE_AGENT_ANALYTICS_DISCONNECT_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_DISCONNECT_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/disconnect'); 23 27 define('READABLE_AGENT_ANALYTICS_INTEGRATION_ID', 'readable_wordpress_v1'); 24 28 … … 28 32 require_once READABLE_AGENT_ANALYTICS_DIR . 'includes/class-readable-agent-dashboard.php'; 29 33 require_once READABLE_AGENT_ANALYTICS_DIR . 'includes/class-readable-agent-site-verification.php'; 34 require_once READABLE_AGENT_ANALYTICS_DIR . 'includes/class-readable-agent-connection.php'; 30 35 31 36 final class Readable_Agent_Analytics { … … 45 50 add_action('admin_enqueue_scripts', array('Readable_Agent_Settings', 'enqueue_assets')); 46 51 add_action('rest_api_init', array('Readable_Agent_Site_Verification', 'register_route')); 52 add_action('admin_post_readable_agent_connect_start', array('Readable_Agent_Connection', 'start')); 53 add_action('admin_post_readable_agent_connect_callback', array('Readable_Agent_Connection', 'callback')); 54 add_action('admin_post_nopriv_readable_agent_connect_callback', array('Readable_Agent_Connection', 'callback')); 55 add_action('admin_post_readable_agent_connect_status', array('Readable_Agent_Connection', 'status')); 56 add_action('admin_post_readable_agent_disconnect', array('Readable_Agent_Connection', 'disconnect')); 47 57 self::ensure_schedule(); 48 58 } -
readable-agent-analytics/tags/1.2.0/readme.txt
r3740840 r3748854 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 1.07 Stable tag: 1.2.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 34 34 = Set up your site = 35 35 36 Install the plugin , create or sign in to a free Readable account, and verify your WordPress site. Add the Site ID and WordPress analytics key shown by Readable, then explicitly choose to enable analytics collection. Collection stays off until a site administrator enables it. Your WordPress Address must use HTTPS, and its exact hostname must be enabled for analytics in Readable.36 Install the plugin and choose **Connect to Readable** in its settings. Sign in or create a free Readable account, then confirm the WordPress site, limited data access, and that connecting starts analytics collection. After a successful connection, analytics collection starts automatically and you return to WordPress. An administrator can pause collection or disconnect at any time. Existing installed sites that are already paused remain paused until an administrator resumes or reconnects them. Your WordPress Address must use HTTPS, and its exact hostname must be enabled for analytics in Readable. 37 37 38 38 Matching completed requests are placed in a durable local queue and sent to Readable in the background. Your visitor response does not wait for Readable. Dashboard reads are cached locally for five minutes and can be refreshed by an administrator. … … 52 52 = External service = 53 53 54 This plugin sends the limited analytics data described above to Readable's ingestion service at `https://ingest.sonicadn.com/v1.0/traffic/data/wordpress`. This service is required for the plugin's analytics functionality. Data is sent only after a valid Readable Site ID and WordPress analytics key have been configured and a site administrator explicitly enables analytics collection.54 This plugin sends the limited analytics data described above to Readable's ingestion service at `https://ingest.sonicadn.com/v1.0/traffic/data/wordpress`. This service is required for the plugin's analytics functionality. Data is sent only after a valid Readable Site ID and WordPress analytics key have been configured through the consented Connect flow and collection is active. An administrator can pause collection at any time. 55 55 56 56 The plugin also requests the site's aggregated analytics from `https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/analytics` when an administrator opens or refreshes the plugin settings page. The saved WordPress analytics key is sent in the HTTPS authorization header. The service derives the Readable site from that key and returns only the site's bounded seven-day WordPress analytics; it does not accept a site ID from the browser for this read. 57 58 During an administrator-initiated Connect flow, the plugin contacts Readable's setup service at `https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/start`, `/pairing/status`, and `/pairing/exchange`. It sends the WordPress site and callback URLs, random short-lived request and ownership tokens, a one-way verifier challenge, and a random plugin installation identifier so Readable can preserve the request, confirm site control, return safely to WordPress, and deliver the scoped credential without copying it. Status and exchange send the saved temporary request binding; the permanent key is returned only in the no-store HTTPS exchange response and is then encrypted in WordPress. These calls occur only after an administrator chooses Connect, Check status, Resume, or Start a new request. 59 60 When an administrator chooses Disconnect, the plugin contacts `https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/disconnect` with the saved key in the HTTPS authorization header so Readable can revoke that plugin connection before WordPress removes its local credentials. 57 61 58 62 Readable privacy policy: https://www.tryreadable.ai/privacy … … 66 70 1. Install and activate **Readable – AI Agent Analytics**. 67 71 2. In WordPress, open **Settings → Readable Agent Analytics**. 68 3. Visit https://www.tryreadable.ai/integrations/wordpress and create or sign in to a free Readable account.69 4. Start WordPress setup, paste the short-lived ownership challenge into the plugin's verification section, and return to Readable to verify the site.70 5. Copy the one-time Site ID and WordPress analytics key into the plugin.71 6. Review the privacy boundary , check **Enable analytics collection**, and save.72 3. Choose **Connect to Readable**. Sign in or create an account in the hosted Readable window. 73 4. Confirm the Readable account, exact WordPress hostname, limited analytics data access, and that connecting starts analytics collection. Readable verifies site control and returns to WordPress automatically. 74 5. WordPress exchanges a short-lived one-time code, stores the Site ID and scoped key encrypted, and starts collection; neither value needs to be copied. 75 6. Review the privacy boundary. You can pause collection without disconnecting, or disconnect and revoke the plugin key at any time. 72 76 73 77 Your WordPress Address must use HTTPS, and its exact hostname must be enabled for analytics in Readable. … … 75 79 == Screenshots == 76 80 77 1. Released 1.1.0 overview with fictional local sample data, showing seven-day metrics and daily activity.78 2. Released 1.1.0 top AI sources and pages, using fictional local sample data.79 3. Re leased 1.1.0 recent activity table, using fictional local sample data.80 4. Released 1.1.0 setup and delivery status with a placeholder Site ID, masked key, and fictional local sample data.81 1. AI audience overview with fictional sample data, showing seven-day metrics and daily activity. 82 2. Top AI sources and pages using fictional sample data. 83 3. Recent activity using fictional sample data. 84 4. Connect, pause, retry, and delivery status using placeholder identifiers and fictional sample data. 81 85 82 86 == Frequently Asked Questions == … … 120 124 = How do I get a Site ID and key? = 121 125 122 Visit https://www.tryreadable.ai/integrations/wordpress and choose **Set up free connection**. A free account can verify one WordPress site through a short-lived challenge visible only after a WordPress administrator saves it. Each site has one active key bound to its Readable WordPress integration. Replacing the keyrevokes the previous one.126 Open **Settings → Readable Agent Analytics** and choose **Connect to Readable**. A free account can verify one WordPress site through a short-lived, request-bound flow. The plugin receives its Site ID and scoped key through a server-to-server exchange and stores the key encrypted. Each site has one active key bound to its Readable WordPress integration; reconnecting revokes the previous one. 123 127 124 128 == Support == … … 127 131 128 132 == Changelog == 133 134 = 1.2.0 = 135 136 * Added hosted Connect with Readable sign-in/signup, site and data confirmation, automatic ownership verification, and encrypted credential return without copying keys. 137 * Analytics collection starts after the administrator confirms Connect and can be paused without disconnecting. 138 * Added a secure reconnect action that keeps the current connection until a verified replacement succeeds. 139 * Added safe retry and recovery for cancelled, expired, interrupted, and logged-out callback flows. 140 * Added explicit setup, status, exchange, dashboard, ingestion, and disconnect service disclosures. 129 141 130 142 = 1.1.0 = -
readable-agent-analytics/trunk/assets/admin.css
r3739172 r3748854 14 14 .readable-agent-analytics-verification { margin-top: 18px; padding: 16px; border: 1px solid #d9d7e8; border-radius: 10px; background: #f8f7fc; } 15 15 .readable-agent-analytics-verification summary { cursor: pointer; color: #29256f; font-weight: 700; } 16 .readable-agent-analytics-connection-state { margin: 18px 0; padding: 16px; border: 1px solid #d9d7e8; border-radius: 10px; background: #f8f7fc; } 17 .readable-agent-analytics-connection-state strong { color: #29256f; font-size: 16px; } 18 .readable-agent-analytics-connected { border-color: #a8d5bd; background: #f1faf5; } 19 .readable-agent-analytics-inline-form { display: inline-block; margin-left: 8px; } 20 .readable-agent-analytics-danger { margin-top: 22px; padding-top: 18px; border-top: 1px solid #ecebf3; } 16 21 .readable-agent-analytics-card dl { margin: 0; } 17 22 .readable-agent-analytics-card dl div { display: flex; justify-content: space-between; gap: 18px; padding: 12px 0; border-bottom: 1px solid #ecebf3; } -
readable-agent-analytics/trunk/includes/class-readable-agent-dashboard.php
r3739172 r3748854 124 124 <section class="readable-agent-analytics-card readable-agent-analytics-empty"> 125 125 <h2><?php esc_html_e('Your analytics dashboard will appear here', 'readable-agent-analytics'); ?></h2> 126 <p><?php esc_html_e(' Add the Site ID and WordPress analytics key below to start collecting matching visits.', 'readable-agent-analytics'); ?></p>126 <p><?php esc_html_e('Choose Connect to Readable below. After you confirm the site and data disclosure, analytics collection starts automatically.', 'readable-agent-analytics'); ?></p> 127 127 </section> 128 128 <?php … … 131 131 if ('authentication' === $result['state']) { 132 132 ?> 133 <div class="notice notice-error inline"><p><strong><?php esc_html_e('Reconnect Readable.', 'readable-agent-analytics'); ?></strong> <?php esc_html_e('The saved analytics key is invalid, expired, or does not include dashboard access. Paste a current WordPress analytics key below.', 'readable-agent-analytics'); ?></p></div>133 <div class="notice notice-error inline"><p><strong><?php esc_html_e('Reconnect Readable.', 'readable-agent-analytics'); ?></strong> <?php esc_html_e('The saved analytics key is invalid, expired, or does not include dashboard access. Disconnect and connect this site again to replace it securely.', 'readable-agent-analytics'); ?></p></div> 134 134 <?php 135 135 return; … … 139 139 <section class="readable-agent-analytics-card readable-agent-analytics-empty"> 140 140 <h2><?php esc_html_e('Analytics collection is paused', 'readable-agent-analytics'); ?></h2> 141 <p><?php esc_html_e('Your connection is saved, but no matching visits are collected or delivered until you explicitly enable analytics below.', 'readable-agent-analytics'); ?></p>141 <p><?php esc_html_e('Your connection is saved, but no matching visits are collected or delivered while collection is paused. Resume it in the connection settings below.', 'readable-agent-analytics'); ?></p> 142 142 </section> 143 143 <?php -
readable-agent-analytics/trunk/includes/class-readable-agent-settings.php
r3739172 r3748854 33 33 __('Readable – AI Agent Analytics', 'readable-agent-analytics'), 34 34 wp_kses_post( 35 '<p>' . __('When a site administrator explicitly enables Readable AI Agent Analytics, the plugin records only recognized AI-agent requests and visits referred by supported AI assistants. It sends the HTTPS hostname and path, generalized agent or referral identity, response status, content type, response timing, and random event identifiers to Readable. It does not send IP addresses, cookies, authorization values, query strings, raw referral URLs, page content, request or response bodies, or browser fingerprints for referred people.', 'readable-agent-analytics') . '</p>' .35 '<p>' . __('When a site administrator connects Readable AI Agent Analytics and confirms the data disclosure, analytics collection starts. The plugin records only recognized AI-agent requests and visits referred by supported AI assistants. It sends the HTTPS hostname and path, generalized agent or referral identity, response status, content type, response timing, and random event identifiers to Readable. It does not send IP addresses, cookies, authorization values, query strings, raw referral URLs, page content, request or response bodies, or browser fingerprints for referred people. An administrator can pause collection or disconnect Readable at any time.', 'readable-agent-analytics') . '</p>' . 36 36 '<p>' . __('Queued events are retained locally for up to 30 days. Rejected events retained for troubleshooting are removed after 14 days. Readable’s service and privacy terms apply after delivery.', 'readable-agent-analytics') . ' <a href="https://www.tryreadable.ai/privacy">https://www.tryreadable.ai/privacy</a></p>' 37 37 ) … … 99 99 'site_claim_challenge' => $claim_challenge, 100 100 'site_claim_expires_at' => $claim_expires_at, 101 'pairing' => isset($current['pairing']) ? $current['pairing'] : array(), 102 'instance_id' => isset($current['instance_id']) ? $current['instance_id'] : '', 101 103 ); 102 104 } … … 144 146 } 145 147 148 public static function instance_id() { 149 $options = self::options(); 150 $current = isset($options['instance_id']) ? (string) $options['instance_id'] : ''; 151 if (preg_match('/^[A-Za-z0-9_-]{20,128}$/', $current)) { 152 return $current; 153 } 154 $current = rtrim(strtr(base64_encode(random_bytes(24)), '+/', '-_'), '='); 155 $options['instance_id'] = $current; 156 if (!self::update_options($options)) { 157 throw new RuntimeException(esc_html__('This server could not securely store the plugin installation identifier.', 'readable-agent-analytics')); 158 } 159 return $current; 160 } 161 162 public static function pairing() { 163 $options = self::options(); 164 $pairing = isset($options['pairing']) && is_array($options['pairing']) ? $options['pairing'] : array(); 165 if (isset($pairing['verifier'])) { 166 $pairing['verifier'] = self::decrypt((string) $pairing['verifier']); 167 } 168 return $pairing; 169 } 170 171 public static function save_pairing($pairing, $ownership_token, $ownership_expires_at) { 172 $options = self::options(); 173 $pairing['verifier'] = self::encrypt((string) $pairing['verifier']); 174 if ('' === $pairing['verifier']) { 175 return false; 176 } 177 $options['pairing'] = $pairing; 178 $options['site_claim_challenge'] = $ownership_token; 179 $options['site_claim_expires_at'] = (int) $ownership_expires_at; 180 return self::update_options($options); 181 } 182 183 public static function clear_pairing() { 184 $options = self::options(); 185 $options['pairing'] = array(); 186 $options['site_claim_challenge'] = ''; 187 $options['site_claim_expires_at'] = 0; 188 self::update_options($options); 189 } 190 191 public static function save_connected_credentials($site_id, $api_key) { 192 $encrypted = self::encrypt($api_key); 193 if ('' === $encrypted) { 194 return false; 195 } 196 $options = self::options(); 197 $options['site_id'] = sanitize_text_field($site_id); 198 $options['api_key'] = $encrypted; 199 $options['enabled'] = true; 200 $options['pairing'] = array(); 201 $options['site_claim_challenge'] = ''; 202 $options['site_claim_expires_at'] = 0; 203 $saved = self::update_options($options); 204 if ($saved) { 205 Readable_Agent_Dashboard::clear_cache(); 206 } 207 return $saved; 208 } 209 210 public static function clear_connection() { 211 $options = self::options(); 212 $options['site_id'] = ''; 213 $options['api_key'] = ''; 214 $options['enabled'] = false; 215 $options['pairing'] = array(); 216 $options['site_claim_challenge'] = ''; 217 $options['site_claim_expires_at'] = 0; 218 self::update_options($options); 219 Readable_Agent_Dashboard::clear_cache(); 220 } 221 222 private static function update_options($options) { 223 $filter = array(__CLASS__, 'sanitize'); 224 $filter_name = 'sanitize_option_' . self::OPTION; 225 $removed = remove_filter($filter_name, $filter); 226 try { 227 return update_option(self::OPTION, $options, false); 228 } finally { 229 if ($removed) { 230 add_filter($filter_name, $filter); 231 } 232 } 233 } 234 146 235 private static function options() { 147 236 $value = get_option(self::OPTION, array()); … … 183 272 } 184 273 $status = get_option(self::STATUS_OPTION, array()); 274 $status_message = !empty($status['message']) ? (string) $status['message'] : ''; 275 if ('connected' === ($status['code'] ?? '') && self::is_configured()) { 276 $status_message = self::is_enabled() 277 ? __('Readable is connected and analytics collection is active. You can pause collection or disconnect at any time.', 'readable-agent-analytics') 278 : __('Readable is connected. Analytics collection is paused; resume it below or reconnect with the current consent flow.', 'readable-agent-analytics'); 279 } 185 280 $queue = Readable_Agent_Queue::counts(); 281 $pairing = self::pairing(); 186 282 ?> 187 283 <div class="wrap readable-agent-analytics-admin"> … … 193 289 194 290 <?php settings_errors(self::OPTION); ?> 195 <?php if ( !empty($status['message'])) : ?>291 <?php if ('' !== $status_message) : ?> 196 292 <div class="notice notice-info inline"><p> 197 293 <strong><?php echo esc_html(ucwords(str_replace('_', ' ', (string) $status['code']))); ?>:</strong> 198 <?php echo esc_html( (string) $status['message']); ?>294 <?php echo esc_html($status_message); ?> 199 295 </p></div> 200 296 <?php endif; ?> … … 205 301 <section class="readable-agent-analytics-card"> 206 302 <h2><?php esc_html_e('Connect this site', 'readable-agent-analytics'); ?></h2> 207 <p><?php esc_html_e('Paste the Site ID and one-time WordPress analytics key supplied by Readable.', 'readable-agent-analytics'); ?></p> 208 <form method="post" action="options.php"> 209 <?php settings_fields('readable_agent_analytics'); ?> 210 <label for="readable-site-id"><?php esc_html_e('Readable Site ID', 'readable-agent-analytics'); ?></label> 211 <input id="readable-site-id" class="regular-text" name="<?php echo esc_attr(self::OPTION); ?>[site_id]" value="<?php echo esc_attr(self::site_id()); ?>" autocomplete="off" /> 212 <label for="readable-api-key"><?php esc_html_e('WordPress analytics key', 'readable-agent-analytics'); ?></label> 213 <input id="readable-api-key" class="regular-text" type="password" name="<?php echo esc_attr(self::OPTION); ?>[api_key]" value="" autocomplete="new-password" placeholder="<?php echo self::api_key() ? esc_attr__('Saved — leave blank to keep it', 'readable-agent-analytics') : ''; ?>" /> 214 <label class="readable-agent-analytics-checkbox" for="readable-enabled"> 215 <input id="readable-enabled" type="checkbox" name="<?php echo esc_attr(self::OPTION); ?>[enabled]" value="1" <?php checked(self::is_enabled()); ?> /> 216 <span><?php esc_html_e('Enable analytics collection and send matching request facts to Readable', 'readable-agent-analytics'); ?></span> 217 </label> 218 <p class="description"><?php esc_html_e('Collection is off until you check this box. Review the privacy boundary below and update your site privacy notice before enabling it.', 'readable-agent-analytics'); ?></p> 219 <details class="readable-agent-analytics-verification"> 220 <summary><?php esc_html_e('Verify this site for a free Readable account', 'readable-agent-analytics'); ?></summary> 221 <p><?php esc_html_e('Create or sign in to your Readable account, start WordPress setup, then paste the short-lived ownership challenge here. Readable will read only that challenge from this site.', 'readable-agent-analytics'); ?></p> 222 <label for="readable-site-claim"><?php esc_html_e('Site verification challenge', 'readable-agent-analytics'); ?></label> 223 <input id="readable-site-claim" class="regular-text" name="<?php echo esc_attr(self::OPTION); ?>[site_claim_challenge]" value="" autocomplete="off" placeholder="rdbl_wp_claim_…" /> 224 <?php if ('' !== self::site_claim_challenge()) : ?> 225 <p class="description"><?php esc_html_e('A verification challenge is active for up to 20 minutes. Return to Readable and choose Verify site.', 'readable-agent-analytics'); ?></p> 303 <?php if (!self::is_configured()) : ?> 304 <p><?php esc_html_e('Sign in to Readable, confirm this site and the limited analytics data, then return here automatically with collection active. No Site ID or key copying is required.', 'readable-agent-analytics'); ?></p> 305 <?php if (!empty($pairing['connect_url']) && !empty($pairing['expires_at']) && (int) $pairing['expires_at'] > time()) : ?> 306 <div class="readable-agent-analytics-connection-state"> 307 <strong><?php esc_html_e('Connection in progress', 'readable-agent-analytics'); ?></strong> 308 <p><?php esc_html_e('If sign-in was interrupted, resume the same request. Repeated clicks will not create another request.', 'readable-agent-analytics'); ?></p> 309 <a class="button button-primary" href="<?php echo esc_url((string) $pairing['connect_url']); ?>"><?php esc_html_e('Resume Readable sign-in', 'readable-agent-analytics'); ?></a> 310 <form class="readable-agent-analytics-inline-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 311 <input type="hidden" name="action" value="readable_agent_connect_status" /> 312 <?php wp_nonce_field('readable_agent_connection'); ?> 313 <button class="button" type="submit"><?php esc_html_e('Check status', 'readable-agent-analytics'); ?></button> 314 </form> 315 <form class="readable-agent-analytics-inline-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 316 <input type="hidden" name="action" value="readable_agent_connect_start" /> 317 <input type="hidden" name="restart" value="1" /> 318 <?php wp_nonce_field('readable_agent_connection'); ?> 319 <button class="button" type="submit"><?php esc_html_e('Start a new request', 'readable-agent-analytics'); ?></button> 320 </form> 321 </div> 322 <?php else : ?> 323 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 324 <input type="hidden" name="action" value="readable_agent_connect_start" /> 325 <?php wp_nonce_field('readable_agent_connection'); ?> 326 <button class="button button-primary button-hero" type="submit"><?php esc_html_e('Connect to Readable', 'readable-agent-analytics'); ?></button> 327 </form> 328 <?php endif; ?> 329 <?php else : ?> 330 <div class="readable-agent-analytics-connection-state readable-agent-analytics-connected"> 331 <strong><?php esc_html_e('Connected securely', 'readable-agent-analytics'); ?></strong> 332 <?php /* translators: %s: Readable Site ID saved for this WordPress site. */ ?> 333 <p><?php echo esc_html(sprintf(__('Site ID %s is linked. The private key is encrypted in WordPress and is never shown in a URL.', 'readable-agent-analytics'), self::site_id())); ?></p> 334 </div> 335 <form method="post" action="options.php"> 336 <?php settings_fields('readable_agent_analytics'); ?> 337 <input type="hidden" name="<?php echo esc_attr(self::OPTION); ?>[site_id]" value="<?php echo esc_attr(self::site_id()); ?>" /> 338 <label class="readable-agent-analytics-checkbox" for="readable-enabled"> 339 <input id="readable-enabled" type="checkbox" name="<?php echo esc_attr(self::OPTION); ?>[enabled]" value="1" <?php checked(self::is_enabled()); ?> /> 340 <span><?php esc_html_e('Collect and send matching request facts to Readable', 'readable-agent-analytics'); ?></span> 341 </label> 342 <?php if (self::is_enabled()) : ?> 343 <p class="description"><?php esc_html_e('Collection started when you connected. Clear this box and save to pause collection without disconnecting Readable.', 'readable-agent-analytics'); ?></p> 344 <?php else : ?> 345 <p class="description"><?php esc_html_e('Collection is paused. Check this box and save to resume using the connection you already approved.', 'readable-agent-analytics'); ?></p> 226 346 <?php endif; ?> 227 </details> 228 <?php submit_button(__('Save connection', 'readable-agent-analytics')); ?> 229 </form> 347 <?php submit_button(__('Save collection setting', 'readable-agent-analytics'), 'secondary'); ?> 348 </form> 349 <form class="readable-agent-analytics-inline-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 350 <input type="hidden" name="action" value="readable_agent_connect_start" /> 351 <input type="hidden" name="restart" value="1" /> 352 <?php wp_nonce_field('readable_agent_connection'); ?> 353 <button class="button" type="submit"><?php esc_html_e('Reconnect securely', 'readable-agent-analytics'); ?></button> 354 <p class="description"><?php esc_html_e('Use this if dashboard access fails or you need to replace the saved key. The current connection stays in place unless the new verified connection succeeds.', 'readable-agent-analytics'); ?></p> 355 </form> 356 <form class="readable-agent-analytics-danger" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" onsubmit="return window.confirm('<?php echo esc_js(__('Disconnect Readable and revoke this plugin key?', 'readable-agent-analytics')); ?>');"> 357 <input type="hidden" name="action" value="readable_agent_disconnect" /> 358 <?php wp_nonce_field('readable_agent_connection'); ?> 359 <button class="button" type="submit"><?php esc_html_e('Disconnect Readable', 'readable-agent-analytics'); ?></button> 360 </form> 361 <?php endif; ?> 230 362 </section> 231 363 -
readable-agent-analytics/trunk/readable-agent-analytics.php
r3739172 r3748854 4 4 * Plugin URI: https://www.tryreadable.ai/integrations/wordpress 5 5 * Description: Privacy-safe AI agent and AI referral analytics for WordPress, powered by Readable. 6 * Version: 1. 1.06 * Version: 1.2.0 7 7 * Requires at least: 6.2 8 8 * Requires PHP: 7.4 … … 16 16 defined('ABSPATH') || exit; 17 17 18 define('READABLE_AGENT_ANALYTICS_VERSION', '1. 1.0');18 define('READABLE_AGENT_ANALYTICS_VERSION', '1.2.0'); 19 19 define('READABLE_AGENT_ANALYTICS_FILE', __FILE__); 20 20 define('READABLE_AGENT_ANALYTICS_DIR', plugin_dir_path(__FILE__)); 21 21 define('READABLE_AGENT_ANALYTICS_ENDPOINT', 'https://ingest.sonicadn.com/v1.0/traffic/data/wordpress'); 22 define('READABLE_AGENT_ANALYTICS_DASHBOARD_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/analytics'); 22 defined('READABLE_AGENT_ANALYTICS_DASHBOARD_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_DASHBOARD_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/analytics'); 23 defined('READABLE_AGENT_ANALYTICS_PAIRING_START_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_PAIRING_START_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/start'); 24 defined('READABLE_AGENT_ANALYTICS_PAIRING_EXCHANGE_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_PAIRING_EXCHANGE_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/exchange'); 25 defined('READABLE_AGENT_ANALYTICS_PAIRING_STATUS_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_PAIRING_STATUS_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/status'); 26 defined('READABLE_AGENT_ANALYTICS_DISCONNECT_ENDPOINT') || define('READABLE_AGENT_ANALYTICS_DISCONNECT_ENDPOINT', 'https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/disconnect'); 23 27 define('READABLE_AGENT_ANALYTICS_INTEGRATION_ID', 'readable_wordpress_v1'); 24 28 … … 28 32 require_once READABLE_AGENT_ANALYTICS_DIR . 'includes/class-readable-agent-dashboard.php'; 29 33 require_once READABLE_AGENT_ANALYTICS_DIR . 'includes/class-readable-agent-site-verification.php'; 34 require_once READABLE_AGENT_ANALYTICS_DIR . 'includes/class-readable-agent-connection.php'; 30 35 31 36 final class Readable_Agent_Analytics { … … 45 50 add_action('admin_enqueue_scripts', array('Readable_Agent_Settings', 'enqueue_assets')); 46 51 add_action('rest_api_init', array('Readable_Agent_Site_Verification', 'register_route')); 52 add_action('admin_post_readable_agent_connect_start', array('Readable_Agent_Connection', 'start')); 53 add_action('admin_post_readable_agent_connect_callback', array('Readable_Agent_Connection', 'callback')); 54 add_action('admin_post_nopriv_readable_agent_connect_callback', array('Readable_Agent_Connection', 'callback')); 55 add_action('admin_post_readable_agent_connect_status', array('Readable_Agent_Connection', 'status')); 56 add_action('admin_post_readable_agent_disconnect', array('Readable_Agent_Connection', 'disconnect')); 47 57 self::ensure_schedule(); 48 58 } -
readable-agent-analytics/trunk/readme.txt
r3740840 r3748854 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 1.07 Stable tag: 1.2.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 34 34 = Set up your site = 35 35 36 Install the plugin , create or sign in to a free Readable account, and verify your WordPress site. Add the Site ID and WordPress analytics key shown by Readable, then explicitly choose to enable analytics collection. Collection stays off until a site administrator enables it. Your WordPress Address must use HTTPS, and its exact hostname must be enabled for analytics in Readable.36 Install the plugin and choose **Connect to Readable** in its settings. Sign in or create a free Readable account, then confirm the WordPress site, limited data access, and that connecting starts analytics collection. After a successful connection, analytics collection starts automatically and you return to WordPress. An administrator can pause collection or disconnect at any time. Existing installed sites that are already paused remain paused until an administrator resumes or reconnects them. Your WordPress Address must use HTTPS, and its exact hostname must be enabled for analytics in Readable. 37 37 38 38 Matching completed requests are placed in a durable local queue and sent to Readable in the background. Your visitor response does not wait for Readable. Dashboard reads are cached locally for five minutes and can be refreshed by an administrator. … … 52 52 = External service = 53 53 54 This plugin sends the limited analytics data described above to Readable's ingestion service at `https://ingest.sonicadn.com/v1.0/traffic/data/wordpress`. This service is required for the plugin's analytics functionality. Data is sent only after a valid Readable Site ID and WordPress analytics key have been configured and a site administrator explicitly enables analytics collection.54 This plugin sends the limited analytics data described above to Readable's ingestion service at `https://ingest.sonicadn.com/v1.0/traffic/data/wordpress`. This service is required for the plugin's analytics functionality. Data is sent only after a valid Readable Site ID and WordPress analytics key have been configured through the consented Connect flow and collection is active. An administrator can pause collection at any time. 55 55 56 56 The plugin also requests the site's aggregated analytics from `https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/analytics` when an administrator opens or refreshes the plugin settings page. The saved WordPress analytics key is sent in the HTTPS authorization header. The service derives the Readable site from that key and returns only the site's bounded seven-day WordPress analytics; it does not accept a site ID from the browser for this read. 57 58 During an administrator-initiated Connect flow, the plugin contacts Readable's setup service at `https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/start`, `/pairing/status`, and `/pairing/exchange`. It sends the WordPress site and callback URLs, random short-lived request and ownership tokens, a one-way verifier challenge, and a random plugin installation identifier so Readable can preserve the request, confirm site control, return safely to WordPress, and deliver the scoped credential without copying it. Status and exchange send the saved temporary request binding; the permanent key is returned only in the no-store HTTPS exchange response and is then encrypted in WordPress. These calls occur only after an administrator chooses Connect, Check status, Resume, or Start a new request. 59 60 When an administrator chooses Disconnect, the plugin contacts `https://www.tryreadable.ai/api/external-agentic/integrations/wordpress/pairing/disconnect` with the saved key in the HTTPS authorization header so Readable can revoke that plugin connection before WordPress removes its local credentials. 57 61 58 62 Readable privacy policy: https://www.tryreadable.ai/privacy … … 66 70 1. Install and activate **Readable – AI Agent Analytics**. 67 71 2. In WordPress, open **Settings → Readable Agent Analytics**. 68 3. Visit https://www.tryreadable.ai/integrations/wordpress and create or sign in to a free Readable account.69 4. Start WordPress setup, paste the short-lived ownership challenge into the plugin's verification section, and return to Readable to verify the site.70 5. Copy the one-time Site ID and WordPress analytics key into the plugin.71 6. Review the privacy boundary , check **Enable analytics collection**, and save.72 3. Choose **Connect to Readable**. Sign in or create an account in the hosted Readable window. 73 4. Confirm the Readable account, exact WordPress hostname, limited analytics data access, and that connecting starts analytics collection. Readable verifies site control and returns to WordPress automatically. 74 5. WordPress exchanges a short-lived one-time code, stores the Site ID and scoped key encrypted, and starts collection; neither value needs to be copied. 75 6. Review the privacy boundary. You can pause collection without disconnecting, or disconnect and revoke the plugin key at any time. 72 76 73 77 Your WordPress Address must use HTTPS, and its exact hostname must be enabled for analytics in Readable. … … 75 79 == Screenshots == 76 80 77 1. Released 1.1.0 overview with fictional local sample data, showing seven-day metrics and daily activity.78 2. Released 1.1.0 top AI sources and pages, using fictional local sample data.79 3. Re leased 1.1.0 recent activity table, using fictional local sample data.80 4. Released 1.1.0 setup and delivery status with a placeholder Site ID, masked key, and fictional local sample data.81 1. AI audience overview with fictional sample data, showing seven-day metrics and daily activity. 82 2. Top AI sources and pages using fictional sample data. 83 3. Recent activity using fictional sample data. 84 4. Connect, pause, retry, and delivery status using placeholder identifiers and fictional sample data. 81 85 82 86 == Frequently Asked Questions == … … 120 124 = How do I get a Site ID and key? = 121 125 122 Visit https://www.tryreadable.ai/integrations/wordpress and choose **Set up free connection**. A free account can verify one WordPress site through a short-lived challenge visible only after a WordPress administrator saves it. Each site has one active key bound to its Readable WordPress integration. Replacing the keyrevokes the previous one.126 Open **Settings → Readable Agent Analytics** and choose **Connect to Readable**. A free account can verify one WordPress site through a short-lived, request-bound flow. The plugin receives its Site ID and scoped key through a server-to-server exchange and stores the key encrypted. Each site has one active key bound to its Readable WordPress integration; reconnecting revokes the previous one. 123 127 124 128 == Support == … … 127 131 128 132 == Changelog == 133 134 = 1.2.0 = 135 136 * Added hosted Connect with Readable sign-in/signup, site and data confirmation, automatic ownership verification, and encrypted credential return without copying keys. 137 * Analytics collection starts after the administrator confirms Connect and can be paused without disconnecting. 138 * Added a secure reconnect action that keeps the current connection until a verified replacement succeeds. 139 * Added safe retry and recovery for cancelled, expired, interrupted, and logged-out callback flows. 140 * Added explicit setup, status, exchange, dashboard, ingestion, and disconnect service disclosures. 129 141 130 142 = 1.1.0 =
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)