Upgrade
1 week ago
CLIService.php
1 week ago
FeedCacheUpdateService.php
1 week ago
MigrationReactivationNotice.php
1 week ago
SBR_Upgrader.php
1 week ago
SettingsManagerService.php
1 week ago
ShortcodeService.php
1 week ago
SiteUrlWatcher.php
1 week ago
FeedCacheUpdateService.php
153 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * Feed Cache Update Service |
| 5 | */ |
| 6 | |
| 7 | namespace SmashBalloon\Reviews\Common\Services; |
| 8 | |
| 9 | if (! defined('ABSPATH')) { |
| 10 | exit; |
| 11 | } |
| 12 | |
| 13 | use SmashBalloon\Reviews\Common\AuthorizationStatusCheck; |
| 14 | use SmashBalloon\Reviews\Common\FeedCacheUpdater; |
| 15 | use Smashballoon\Stubs\Services\ServiceProvider; |
| 16 | |
| 17 | |
| 18 | class FeedCacheUpdateService extends ServiceProvider { |
| 19 | public const CACHES_TABLE_NAME = 'sbr_feed_caches'; |
| 20 | |
| 21 | public const CRON_JOB_ADDITIONAL_BATCH = 'sbr_cron_additional_batch'; |
| 22 | |
| 23 | public const CRON_JOB_NAME = 'sbr_feed_update'; |
| 24 | |
| 25 | const RESULTS_PER_PAGE = 20; |
| 26 | |
| 27 | const RESULTS_PER_CRON_UPDATE = 10; |
| 28 | |
| 29 | /** |
| 30 | * @var AuthorizationStatusCheck |
| 31 | */ |
| 32 | private $auth_check; |
| 33 | |
| 34 | public function register() |
| 35 | { |
| 36 | add_shortcode('reviews-feed-cron-simulator', array( $this, 'init' )); |
| 37 | add_action(self::CRON_JOB_NAME, array( $this, 'init' )); |
| 38 | add_action(self::CRON_JOB_ADDITIONAL_BATCH, array( $this, 'init_additional_batch' )); |
| 39 | |
| 40 | add_action('sbr_before_shortcode_render', array( $this, 'maybe_check_cron_schedule' )); |
| 41 | } |
| 42 | |
| 43 | public function init() |
| 44 | { |
| 45 | $this->auth_check = new AuthorizationStatusCheck(); |
| 46 | if ($this->should_do_updates()) { |
| 47 | $this->auth_check->update_status( |
| 48 | [ 'last_cron_update' => time() ] |
| 49 | ); |
| 50 | $this->do_updates(); |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | public function init_additional_batch() |
| 55 | { |
| 56 | $this->do_updates(); |
| 57 | } |
| 58 | |
| 59 | public function should_do_updates() |
| 60 | { |
| 61 | $statuses = $this->auth_check->get_statuses(); |
| 62 | $time_with_minute_buffer = time() + 60; |
| 63 | $statuses['last_cron_update'] = 0; |
| 64 | if ($statuses['last_cron_update'] < $time_with_minute_buffer - $statuses['update_frequency']) { |
| 65 | return true; |
| 66 | } |
| 67 | |
| 68 | return false; |
| 69 | } |
| 70 | |
| 71 | public function do_updates() |
| 72 | { |
| 73 | $caches = $this->get_caches(); |
| 74 | |
| 75 | $updater = new FeedCacheUpdater($caches); |
| 76 | $updater->do_updates(); |
| 77 | |
| 78 | $num = count($caches); |
| 79 | if ($num === self::RESULTS_PER_CRON_UPDATE) { |
| 80 | // Schedule another batch in 5 minutes if needed |
| 81 | wp_schedule_single_event(time() + 300, self::CRON_JOB_ADDITIONAL_BATCH); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | public function get_caches() |
| 86 | { |
| 87 | return $this->feed_caches_query(array( 'cron_update' => true )); |
| 88 | } |
| 89 | |
| 90 | public function feed_caches_query($args) |
| 91 | { |
| 92 | global $wpdb; |
| 93 | $feed_cache_table_name = $wpdb->prefix . self::CACHES_TABLE_NAME; |
| 94 | |
| 95 | // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared -- Table name is safely constructed from $wpdb->prefix |
| 96 | if (! isset($args['cron_update'])) { |
| 97 | $sql = " |
| 98 | SELECT * FROM $feed_cache_table_name;"; |
| 99 | } else { |
| 100 | // Group by feed_id so each unique feed contributes ONE batch entry |
| 101 | // instead of one entry per cache_type. `update_or_insert()` flags |
| 102 | // both 'posts' and 'header' rows with `cron_update = 'yes'`, so a |
| 103 | // feed with both rows >12h-stale was previously yielding two batch |
| 104 | // entries, and FeedCacheUpdater::do_updates triggered a full |
| 105 | // Feed::get_set_cache() per entry — fetching reviews + header |
| 106 | // for the same feed twice in the same cron cycle. Customer |
| 107 | // Google Places API spend was 2x what it needed to be. |
| 108 | // |
| 109 | // MIN(id) / MIN(last_updated) make the query strict-mode safe |
| 110 | // when sql_mode includes ONLY_FULL_GROUP_BY; the LIMIT |
| 111 | // (`RESULTS_PER_CRON_UPDATE`) budget now yields that many |
| 112 | // unique feeds per cron tick instead of half as many feeds |
| 113 | // (when both rows for the same feed were stale). |
| 114 | // |
| 115 | // FeedCacheUpdater::do_updates only reads `feed_id` from each row, |
| 116 | // so collapsing duplicates is invisible to the caller — same feeds |
| 117 | // processed, half the HTTP fetches, no behavior change downstream. |
| 118 | $sql = $wpdb->prepare( |
| 119 | " |
| 120 | SELECT MIN(id) AS id, feed_id, MIN(last_updated) AS last_updated |
| 121 | FROM $feed_cache_table_name |
| 122 | WHERE cron_update = 'yes' |
| 123 | AND last_updated < %s |
| 124 | GROUP BY feed_id |
| 125 | ORDER BY last_updated ASC |
| 126 | LIMIT %d;", |
| 127 | gmdate('Y-m-d H:i:s', time() - 12 * HOUR_IN_SECONDS), |
| 128 | self::RESULTS_PER_CRON_UPDATE |
| 129 | ); |
| 130 | } |
| 131 | $results = $wpdb->get_results($sql, ARRAY_A); |
| 132 | // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared |
| 133 | return $results; |
| 134 | } |
| 135 | |
| 136 | public function maybe_check_cron_schedule() |
| 137 | { |
| 138 | if (! sbr_current_user_can('manage_reviews_feed_options')) { |
| 139 | return; |
| 140 | } |
| 141 | |
| 142 | self::schedule_cron_job(); |
| 143 | } |
| 144 | |
| 145 | public static function schedule_cron_job() |
| 146 | { |
| 147 | if (! wp_next_scheduled(self::CRON_JOB_NAME)) { |
| 148 | wp_schedule_event(time(), 'hourly', self::CRON_JOB_NAME); |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | } |
| 153 |