Admin
2 months ago
Builder
2 months ago
Customizer
2 months ago
Exceptions
2 months ago
Helpers
2 months ago
Integrations
2 months ago
Migrations
2 months ago
ReviewAlerts
2 months ago
Services
2 months ago
Settings
2 months ago
Support
2 months ago
Traits
2 months ago
Utils
2 months ago
AuthorizationStatusCheck.php
2 months ago
BusinessDataCache.php
2 months ago
Clear_Cache.php
2 months ago
Container.php
2 months ago
DisplayElements.php
2 months ago
Email_Notification.php
2 months ago
Error_Reporter.php
2 months ago
Feed.php
2 months ago
FeedCache.php
2 months ago
FeedCacheUpdater.php
2 months ago
FeedDisplay.php
2 months ago
Feed_Locator.php
2 months ago
Parser.php
2 months ago
PostAggregator.php
2 months ago
RemoteRequest.php
2 months ago
SBR_Education.php
2 months ago
SBR_Settings.php
2 months ago
ServiceContainer.php
2 months ago
SinglePostCache.php
2 months ago
TemplateRenderer.php
2 months ago
Tooltip_Wizard.php
2 months ago
Util.php
2 months ago
SinglePostCache.php
487 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * Class SinglePostCache |
| 5 | * |
| 6 | * @since 1.0 |
| 7 | */ |
| 8 | |
| 9 | namespace SmashBalloon\Reviews\Common; |
| 10 | |
| 11 | use SmashBalloon\Reviews\Common\Helpers\Data_Encryption; |
| 12 | |
| 13 | class SinglePostCache { |
| 14 | public const UPLOAD_FOLDER_NAME = 'sbr-feed-images'; |
| 15 | |
| 16 | public const POSTS_TABLE_NAME = SBR_POSTS_TABLE; |
| 17 | |
| 18 | /** |
| 19 | * @var array |
| 20 | */ |
| 21 | private $post_data; |
| 22 | |
| 23 | private $upload_dir; |
| 24 | |
| 25 | private $storage_data; |
| 26 | private $provider_id; |
| 27 | private $lang; |
| 28 | public $encryption; |
| 29 | |
| 30 | public function __construct($post_data, $media_finder = null, $provider_id = null) |
| 31 | { |
| 32 | // SMASH-1587: coerce a scalar 'provider' slug into ['name' => ...] before |
| 33 | // any $this->post_data['provider']['name'] read in this class (which would |
| 34 | // fatal on PHP 8). Shared normalizer in Util — single source of truth. |
| 35 | $this->post_data = Util::normalize_review_shape($post_data); |
| 36 | |
| 37 | $upload = wp_upload_dir(); |
| 38 | $upload_dir = $upload['basedir']; |
| 39 | $upload_dir = trailingslashit($upload_dir) . self::UPLOAD_FOLDER_NAME; |
| 40 | $this->upload_dir = $upload_dir; |
| 41 | |
| 42 | $this->lang = ''; |
| 43 | |
| 44 | $this->storage_data = array( |
| 45 | 'media_id' => '', |
| 46 | 'sizes' => '[]', |
| 47 | 'aspect_ratio' => 1, |
| 48 | 'images_done' => 0 |
| 49 | ); |
| 50 | $this->encryption = new Data_Encryption(); |
| 51 | } |
| 52 | |
| 53 | public function get_storage_data() |
| 54 | { |
| 55 | return $this->storage_data; |
| 56 | } |
| 57 | |
| 58 | public function get_post_data() |
| 59 | { |
| 60 | return $this->post_data; |
| 61 | } |
| 62 | |
| 63 | public function set_provider_id($provider_id) |
| 64 | { |
| 65 | $this->provider_id = $provider_id; |
| 66 | } |
| 67 | |
| 68 | public function set_lang($lang) |
| 69 | { |
| 70 | $this->lang = $lang; |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Check and process API media (reviews photos) |
| 75 | * |
| 76 | * Stub method for Common version. Pro version overrides with full implementation. |
| 77 | * |
| 78 | * @since 2.5.0 |
| 79 | * @return void |
| 80 | */ |
| 81 | public function check_api_media() |
| 82 | { |
| 83 | // No-op in Common version. Pro version handles media processing. |
| 84 | } |
| 85 | |
| 86 | public function get_provider_id() |
| 87 | { |
| 88 | return $this->provider_id; |
| 89 | } |
| 90 | |
| 91 | public function set_storage_data($key, $value) |
| 92 | { |
| 93 | return $this->storage_data[ $key ] = $value; |
| 94 | } |
| 95 | |
| 96 | |
| 97 | public function resize_images($image_sizes) |
| 98 | { |
| 99 | $new_file_name = $this->post_data['provider']['name'] . '-' . $this->post_data['review_id']; |
| 100 | |
| 101 | $image_source_set = ! empty($this->post_data['media']) ? $this->post_data['media'] : array(); |
| 102 | |
| 103 | $image_source_set = empty($image_source_set) && !empty($this->post_data['reviews_photos']) ? $this->post_data['reviews_photos'] : $image_source_set; |
| 104 | // the process is considered a success if one image is successfully resized |
| 105 | $one_successful_image_resize = false; |
| 106 | |
| 107 | foreach ($image_sizes as $image_size) { |
| 108 | $i = 0; |
| 109 | foreach ($image_source_set as $image_file_to_resize) { |
| 110 | // A scalar element (e.g. a flat URL string in reviews_photos) would |
| 111 | // fatal the ['type'] read on PHP 8; a partial array element (no |
| 112 | // 'type'/'url') would notice. Guard both (SMASH-1587 + PR #482 Copilot). |
| 113 | if ($i < 10 && is_array($image_file_to_resize) && isset($image_file_to_resize['type'], $image_file_to_resize['url']) && $image_file_to_resize['type'] === 'image') { |
| 114 | $this_image_file_name = $new_file_name . '-' . $i . '-' . $image_size . '.jpg'; |
| 115 | |
| 116 | $image_editor = wp_get_image_editor($image_file_to_resize['url']); |
| 117 | // not uncommon for the image editor to not work using it this way |
| 118 | if (! is_wp_error($image_editor)) { |
| 119 | $sizes = $image_editor->get_size(); |
| 120 | |
| 121 | $image_editor->resize($image_size, null); |
| 122 | |
| 123 | $full_file_name = trailingslashit($this->upload_dir) . $this_image_file_name; |
| 124 | |
| 125 | $saved_image = $image_editor->save($full_file_name); |
| 126 | |
| 127 | if ($saved_image) { |
| 128 | $one_successful_image_resize = true; |
| 129 | } |
| 130 | } else { |
| 131 | // was error |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | $i++; |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | if ($one_successful_image_resize) { |
| 140 | $aspect_ratio = round($sizes['width'] / $sizes['height'], 2); |
| 141 | $media_id = $new_file_name; |
| 142 | } else { |
| 143 | $aspect_ratio = 1; |
| 144 | if (empty($image_source_set)) { |
| 145 | $media_id = ''; |
| 146 | $image_sizes = array(); |
| 147 | } else { |
| 148 | $media_id = 'error'; |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | $this->storage_data['media_id'] = $media_id; |
| 153 | $this->storage_data['sizes'] = wp_json_encode($image_sizes); |
| 154 | $this->storage_data['aspect_ratio'] = $aspect_ratio; |
| 155 | $this->storage_data['images_done'] = 1; |
| 156 | } |
| 157 | |
| 158 | public function resize_avatar($image_size) |
| 159 | { |
| 160 | $new_file_name = $this->post_data['provider']['name'] . '-' . $this->post_data['review_id'] . '-avatar'; |
| 161 | $avatar = ! empty($this->post_data['reviewer']['avatar']) ? $this->post_data['reviewer']['avatar'] : false; |
| 162 | |
| 163 | if (empty($avatar)) { |
| 164 | return; |
| 165 | } |
| 166 | |
| 167 | $avatar_id = $new_file_name . '-' . $image_size; |
| 168 | $this_image_file_name = $avatar_id . '.png'; |
| 169 | |
| 170 | $image_editor = wp_get_image_editor($avatar); |
| 171 | // not uncommon for the image editor to not work using it this way |
| 172 | if (! is_wp_error($image_editor)) { |
| 173 | $sizes = $image_editor->get_size(); |
| 174 | |
| 175 | $image_editor->resize($image_size, null); |
| 176 | |
| 177 | $full_file_name = trailingslashit($this->upload_dir) . $this_image_file_name; |
| 178 | |
| 179 | $saved_image = $image_editor->save($full_file_name); |
| 180 | |
| 181 | if (! $saved_image) { |
| 182 | $avatar_id = 'error'; |
| 183 | } |
| 184 | } else { |
| 185 | $avatar_id = 'error'; |
| 186 | } |
| 187 | |
| 188 | |
| 189 | $this->storage_data['avatar_id'] = $avatar_id; |
| 190 | } |
| 191 | |
| 192 | public function db_record_exists() |
| 193 | { |
| 194 | $feed_id_match = $this->db_record(); |
| 195 | if (! empty($feed_id_match)) { |
| 196 | $this->storage_data['media_id'] = $feed_id_match['media_id']; |
| 197 | $this->storage_data['sizes'] = $feed_id_match['sizes']; |
| 198 | $this->storage_data['aspect_ratio'] = $feed_id_match['aspect_ratio']; |
| 199 | $this->storage_data['images_done'] = $feed_id_match['images_done']; |
| 200 | $this->storage_data['json_data'] = $feed_id_match['json_data']; |
| 201 | } |
| 202 | return null !== $feed_id_match; |
| 203 | } |
| 204 | |
| 205 | public function db_record() |
| 206 | { |
| 207 | global $wpdb; |
| 208 | $table_name = esc_sql($wpdb->prefix . self::POSTS_TABLE_NAME); |
| 209 | $provider_id = !empty($this->post_data['provider_id']) |
| 210 | ? $this->post_data['provider_id'] |
| 211 | : $this->provider_id; |
| 212 | |
| 213 | if (isset($this->post_data['review_id'])) { |
| 214 | // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is prefixed constant |
| 215 | $feed_id_match = $wpdb->get_results( |
| 216 | $wpdb->prepare( |
| 217 | "SELECT * FROM $table_name |
| 218 | WHERE post_id = %s AND lang = %s AND provider_id = %s LIMIT 1", |
| 219 | $this->post_data['review_id'], |
| 220 | $this->lang, |
| 221 | $provider_id |
| 222 | ), |
| 223 | ARRAY_A |
| 224 | ); |
| 225 | // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 226 | |
| 227 | if (! empty($feed_id_match[0])) { |
| 228 | return $feed_id_match[0]; |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | return null; |
| 233 | } |
| 234 | |
| 235 | public function store() |
| 236 | { |
| 237 | $rating = $this->post_data['rating']; |
| 238 | if (Util::is_facebook_collection_post($this->post_data)) { |
| 239 | $rating = $this->post_data['rating'] === 'positive' ? 5 : 1; |
| 240 | } |
| 241 | |
| 242 | $now = date('Y-m-d H:i:s'); |
| 243 | $post_id = $this->post_data['review_id']; |
| 244 | $provider_id = $this->get_provider_id(); |
| 245 | $json_data = $this->should_encrypt($this->post_data, wp_json_encode($this->post_data)); |
| 246 | $post_content = $this->should_encrypt($this->post_data, $this->post_data['text']); |
| 247 | $provider = $this->post_data['provider']['name']; |
| 248 | $business = $this->post_data['business']['id'] ?? ''; |
| 249 | $time_stamp = date('Y-m-d H:i:s', $this->post_data['time']); |
| 250 | |
| 251 | global $wpdb; |
| 252 | $table_name = esc_sql($wpdb->prefix . self::POSTS_TABLE_NAME); |
| 253 | |
| 254 | // Use INSERT ... ON DUPLICATE KEY UPDATE to prevent duplicates |
| 255 | // This handles race conditions where two requests try to insert the same review |
| 256 | // Note: We pass values directly in UPDATE clause instead of using VALUES() function |
| 257 | // for cross-version MySQL compatibility (VALUES() deprecated in MySQL 8.0.20+) |
| 258 | // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely prefixed constant |
| 259 | $result = $wpdb->query( |
| 260 | $wpdb->prepare( |
| 261 | "INSERT INTO $table_name |
| 262 | (created_on, post_id, time_stamp, json_data, post_content, rating, provider, provider_id, business, media_id, avatar_id, sizes, aspect_ratio, images_done, last_requested, lang) |
| 263 | VALUES (%s, %s, %s, %s, %s, %d, %s, %s, %s, %s, %s, %s, %s, %d, %s, %s) |
| 264 | ON DUPLICATE KEY UPDATE |
| 265 | json_data = %s, |
| 266 | post_content = %s, |
| 267 | rating = %d, |
| 268 | last_requested = %s", |
| 269 | // INSERT values |
| 270 | $now, |
| 271 | $post_id, |
| 272 | $time_stamp, |
| 273 | $json_data, |
| 274 | $post_content, |
| 275 | $rating, |
| 276 | $provider, |
| 277 | $provider_id, |
| 278 | $business, |
| 279 | $this->storage_data['media_id'], |
| 280 | $this->storage_data['avatar_id'] ?? '', |
| 281 | $this->storage_data['sizes'], |
| 282 | $this->storage_data['aspect_ratio'], |
| 283 | $this->storage_data['images_done'], |
| 284 | $now, |
| 285 | $this->lang, |
| 286 | // UPDATE values (repeated for ON DUPLICATE KEY UPDATE) |
| 287 | $json_data, |
| 288 | $post_content, |
| 289 | $rating, |
| 290 | $now |
| 291 | ) |
| 292 | ); |
| 293 | // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 294 | } |
| 295 | |
| 296 | public function update_single($strict_update = false) |
| 297 | { |
| 298 | $rating = $this->post_data['rating']; |
| 299 | if (Util::is_facebook_collection_post($this->post_data)) { |
| 300 | $rating = $this->post_data['rating'] === 'positive' ? 5 : 1; |
| 301 | } |
| 302 | |
| 303 | $to_store = array( |
| 304 | array('post_id', $this->post_data['review_id'], '%s'), |
| 305 | array('time_stamp', date('Y-m-d H:i:s', $this->post_data['time']), '%s'), |
| 306 | array('json_data', $this->should_encrypt($this->post_data, wp_json_encode($this->post_data)), '%s'), |
| 307 | array('post_content', $this->should_encrypt($this->post_data, $this->post_data['text']), '%s'), |
| 308 | array('rating', $rating, '%d'), |
| 309 | array('provider', $this->post_data['provider']['name'], '%s'), |
| 310 | array('provider_id', $this->get_provider_id(), '%s'), |
| 311 | array('business', $this->post_data['business']['id'] ?? '', '%s'), |
| 312 | array('media_id', $this->storage_data['media_id'], '%s'), |
| 313 | array('sizes', $this->storage_data['sizes'], '%s'), |
| 314 | array('aspect_ratio', $this->storage_data['aspect_ratio'], '%s'), |
| 315 | array('images_done', $this->storage_data['images_done'], '%d'), |
| 316 | array('last_requested', date('Y-m-d H:i:s'), '%s'), |
| 317 | ); |
| 318 | $data = array(); |
| 319 | $format = array(); |
| 320 | foreach ($to_store as $single_store) { |
| 321 | $data[$single_store[0]] = $single_store[1]; |
| 322 | $format[] = $single_store[2]; |
| 323 | } |
| 324 | |
| 325 | global $wpdb; |
| 326 | $table_name = esc_sql($wpdb->prefix . self::POSTS_TABLE_NAME); |
| 327 | $where = array(); |
| 328 | $where_format = array(); |
| 329 | |
| 330 | $where['post_id'] = $this->post_data['review_id']; |
| 331 | $where_format[] = '%s'; |
| 332 | |
| 333 | if ($strict_update) { |
| 334 | $where['provider_id'] = $this->get_provider_id(); |
| 335 | $where_format[] = '%s'; |
| 336 | } |
| 337 | |
| 338 | $error = $wpdb->update($table_name, $data, $where, $format, $where_format); |
| 339 | |
| 340 | if ($error !== false) { |
| 341 | $insert_id = $wpdb->insert_id; |
| 342 | } else { |
| 343 | // log error |
| 344 | } |
| 345 | } |
| 346 | |
| 347 | public function update($to_update) |
| 348 | { |
| 349 | $data = array(); |
| 350 | $format = array(); |
| 351 | foreach ($to_update as $single_update) { |
| 352 | $data[ $single_update[0] ] = $single_update[1]; |
| 353 | $format[] = $single_update[2]; |
| 354 | } |
| 355 | |
| 356 | global $wpdb; |
| 357 | $where = array(); |
| 358 | $where_format = array(); |
| 359 | |
| 360 | $where['post_id'] = $this->post_data['review_id']; |
| 361 | $where_format[] = '%s'; |
| 362 | $table_name = esc_sql($wpdb->prefix . self::POSTS_TABLE_NAME); |
| 363 | $error = $wpdb->update($table_name, $data, $where, $format, $where_format); |
| 364 | |
| 365 | if ($error !== false) { |
| 366 | $insert_id = $wpdb->insert_id; |
| 367 | } else { |
| 368 | // log error |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | public static function delete_resizing_table_and_images() |
| 373 | { |
| 374 | $upload = wp_upload_dir(); |
| 375 | |
| 376 | global $wpdb; |
| 377 | |
| 378 | $table_name = esc_sql($wpdb->prefix . self::POSTS_TABLE_NAME); |
| 379 | |
| 380 | self::delete_local_images(); |
| 381 | |
| 382 | // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is prefixed constant |
| 383 | $wpdb->query("DROP TABLE IF EXISTS $table_name"); |
| 384 | } |
| 385 | |
| 386 | public static function create_resizing_table_and_uploads_folder() |
| 387 | { |
| 388 | $upload = wp_upload_dir(); |
| 389 | $upload_dir = $upload['basedir']; |
| 390 | $upload_dir = trailingslashit($upload_dir) . self::UPLOAD_FOLDER_NAME; |
| 391 | if (! file_exists($upload_dir)) { |
| 392 | $created = wp_mkdir_p($upload_dir); |
| 393 | } |
| 394 | |
| 395 | global $wpdb; |
| 396 | $table_name = esc_sql($wpdb->prefix . self::POSTS_TABLE_NAME); |
| 397 | $max_index_length = 191; |
| 398 | $charset_collate = ''; |
| 399 | if (method_exists($wpdb, 'get_charset_collate')) { // get_charset_collate introduced in WP 3.5 |
| 400 | $charset_collate = $wpdb->get_charset_collate(); |
| 401 | } |
| 402 | |
| 403 | // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is prefixed constant |
| 404 | if ($wpdb->get_var("show tables like '$table_name'") !== $table_name) { |
| 405 | $sql = 'CREATE TABLE ' . $table_name . " ( |
| 406 | id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, |
| 407 | created_on DATETIME, |
| 408 | post_id VARCHAR(1000) DEFAULT '' NOT NULL, |
| 409 | time_stamp DATETIME, |
| 410 | json_data LONGTEXT DEFAULT '' NOT NULL, |
| 411 | post_content LONGTEXT DEFAULT '' NOT NULL, |
| 412 | rating INT(1) UNSIGNED NOT NULL, |
| 413 | provider VARCHAR(1000) DEFAULT '' NOT NULL, |
| 414 | provider_id VARCHAR(1000) DEFAULT '' NOT NULL, |
| 415 | business VARCHAR(1000) DEFAULT '' NOT NULL, |
| 416 | media_id VARCHAR(1000) DEFAULT '' NOT NULL, |
| 417 | sizes VARCHAR(1000) DEFAULT '' NOT NULL, |
| 418 | aspect_ratio DECIMAL (4,2) DEFAULT 0 NOT NULL, |
| 419 | avatar_id VARCHAR(1000) DEFAULT '' NOT NULL, |
| 420 | images_done TINYINT(1) DEFAULT 0 NOT NULL, |
| 421 | last_requested DATE, |
| 422 | lang VARCHAR(1000) DEFAULT '' NOT NULL, |
| 423 | INDEX provider (provider($max_index_length)), |
| 424 | INDEX business (business($max_index_length)), |
| 425 | INDEX provider_business (provider(10), business(15)), |
| 426 | INDEX provider_lang (provider(140),lang(51)) |
| 427 | ) $charset_collate;"; |
| 428 | // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is a CREATE TABLE statement with prefixed table name |
| 429 | $wpdb->query($sql); |
| 430 | } |
| 431 | $error = $wpdb->last_error; |
| 432 | $query = $wpdb->last_query; |
| 433 | } |
| 434 | |
| 435 | public static function delete_least_used_image() |
| 436 | { |
| 437 | } |
| 438 | |
| 439 | public function max_total_records_reached() |
| 440 | { |
| 441 | } |
| 442 | |
| 443 | public function media_supplied() |
| 444 | { |
| 445 | return $this->post_data['provider']['name'] !== 'yelp' && $this->post_data['provider']['name'] !== 'tripadvisor'; |
| 446 | } |
| 447 | |
| 448 | |
| 449 | public function should_encrypt($post, $element) |
| 450 | { |
| 451 | return $post['provider']['name'] === 'facebook' && $element !== null ? $this->encryption->maybe_encrypt($element) : $element; |
| 452 | } |
| 453 | |
| 454 | |
| 455 | /** |
| 456 | * Used to update or insert Single Post Data |
| 457 | * |
| 458 | * |
| 459 | * @return void |
| 460 | * |
| 461 | * @since 1.6 |
| 462 | */ |
| 463 | public function update_or_insert($strict_update = false) |
| 464 | { |
| 465 | if ($this->db_record_exists()) { |
| 466 | $this->update_single($strict_update); |
| 467 | } else { |
| 468 | $this->store(); |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | public static function delete_local_images() |
| 473 | { |
| 474 | $upload = wp_upload_dir(); |
| 475 | $upload_dir = $upload['basedir']; |
| 476 | $upload_dir = trailingslashit($upload_dir) . self::UPLOAD_FOLDER_NAME; |
| 477 | $image_files = glob(trailingslashit($upload_dir) . '*'); |
| 478 | if (!empty($image_files)) { |
| 479 | foreach ($image_files as $file) { |
| 480 | if (is_file($file)) { |
| 481 | unlink($file); |
| 482 | } |
| 483 | } |
| 484 | } |
| 485 | } |
| 486 | } |
| 487 |