PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / trunk
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More vtrunk
2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.7 2.6.8 2.6.5 2.6.4 2.6.3 2.6.2 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 trunk 1.0 1.0.1 1.0.2 1.0.3 1.1 1.1.1 1.1.2 1.2.0 2.0 2.1.0 2.1.1 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1
reviews-feed / class / Common / UsageTracking / Core / RegisterSite.php
reviews-feed / class / Common / UsageTracking / Core Last commit date
PayloadBuilder.php 6 days ago RegisterSite.php 6 days ago Scheduler.php 6 days ago Sender.php 6 days ago
RegisterSite.php
108 lines
1 <?php
2
3 /**
4 * Register-site API client. Fetches site_token from the API and stores it.
5 *
6 * @package SmashBalloon\Reviews\Common\UsageTracking\Core
7 * @since 1.0
8 */
9
10 namespace SmashBalloon\Reviews\Common\UsageTracking\Core;
11
12 use SmashBalloon\Reviews\Common\UsageTracking\Config;
13 use SmashBalloon\Reviews\Common\UsageTracking\ReporterInterface;
14
15 if (! defined('ABSPATH')) {
16 exit;
17 }
18
19 class RegisterSite {
20 /**
21 * Register the site with the API and store the returned site_token.
22 *
23 * @param ReporterInterface $reporter Plugin reporter (for slug/version).
24 * @return string|null Site token on success, null on failure.
25 */
26 public function register(ReporterInterface $reporter)
27 {
28 $existing = get_option(Config::OPTION_SITE_TOKEN, '');
29 if ('' !== $existing && is_string($existing)) {
30 return $existing;
31 }
32
33 if ('' === Config::get_api_url()) {
34 // Filter kill switch — no base URL, no request.
35 return null;
36 }
37
38 $url = Config::get_register_site_url();
39 $body = array(
40 'site_url' => home_url(),
41 'plugin_slug' => $reporter->get_plugin_slug(),
42 'plugin_version' => defined('SBRVER') ? SBRVER : '',
43 );
44
45 // Same timeout filter and clamp as Sender::send(), so register and report
46 // requests behave alike. Runs in the cron send path behind a 2-minute lock, so
47 // no page render waits on it.
48 $timeout = (int) apply_filters('sbr_smash_usage_tracking_request_timeout', Config::REQUEST_TIMEOUT);
49 $timeout = max(15, min(120, $timeout));
50
51 $response = wp_remote_post(
52 $url,
53 array(
54 'method' => 'POST',
55 'timeout' => $timeout,
56 // Mirrors Sender: no redirects, strict TLS, core URL validation.
57 'redirection' => 0,
58 'sslverify' => true,
59 'reject_unsafe_urls' => true,
60 'headers' => array(
61 'Content-Type' => 'application/json',
62 ),
63 'body' => wp_json_encode($body),
64 )
65 );
66
67 if (is_wp_error($response)) {
68 return null;
69 }
70
71 $code = wp_remote_retrieve_response_code($response);
72 if ($code < 200 || $code >= 300) {
73 return null;
74 }
75
76 $body_raw = wp_remote_retrieve_body($response);
77 $data = json_decode($body_raw, true);
78 if (! is_array($data)) {
79 return null;
80 }
81
82 $token = isset($data['site_token']) ? $data['site_token'] : (isset($data['token']) ? $data['token'] : null);
83 if (! is_string($token) || ! self::is_valid_token($token)) {
84 return null;
85 }
86
87 update_option(Config::OPTION_SITE_TOKEN, $token, false);
88 return $token;
89 }
90
91 /**
92 * Validate a site token before persisting it.
93 *
94 * sanitize_text_field() alone caps neither length nor charset: a hostile
95 * or buggy response returning a multi-megabyte "token" would be stored,
96 * make every payload exceed MAX_PAYLOAD_BYTES, and wedge the feature
97 * permanently (send() returns 0, which is not a token-rejection code, so
98 * the poisoned token would never be dropped).
99 *
100 * @param string $token Candidate token from the API response.
101 * @return bool
102 */
103 public static function is_valid_token(string $token): bool
104 {
105 return 1 === preg_match('/^[A-Za-z0-9._\-]{8,191}$/', $token);
106 }
107 }
108