PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.6.7
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.6.7
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 / SinglePostCache.php
reviews-feed / class / Common Last commit date
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