PluginProbe
Photo Gallery by Re Gallery: Responsive Image Gallery with AI SEO / trunk
Photo Gallery by Re Gallery: Responsive Image Gallery with AI SEO vtrunk
1.20.14 1.20.13 1.20.12 1.20.11 1.20.10 1.20.9 1.20.8 1.20.7 1.20.6 1.20.5 1.20.4 1.20.3 1.20.2 1.20.1 1.20.0 1.19.2 1.19.1 1.19.0 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 All 135 releases
regallery / includes / migration / engine.php

engine.php in Photo Gallery by Re Gallery: Responsive Image Gallery with AI SEO trunk, at includes/migration/engine.php

561 lines 18.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 defined('ABSPATH') || die('Access Denied');
4
5 class REACG_Migration_Engine {
6 /** @var REACG_Migration_Provider_Interface[] */
7 private $providers = [];
8
9 public function __construct($providers = []) {
10 foreach ((array) $providers as $provider) {
11 if ($provider instanceof REACG_Migration_Provider_Interface) {
12 $this->providers[$provider->get_key()] = $provider;
13 }
14 }
15 }
16
17 public function get_sources() {
18 $sources = [];
19 foreach ($this->providers as $provider) {
20 $sources[] = [
21 'key' => $provider->get_key(),
22 'label' => $provider->get_label(),
23 'available' => (bool) $provider->is_available(),
24 ];
25 }
26
27 return $sources;
28 }
29
30 public function list_galleries($source, $args = []) {
31 $provider = $this->get_provider($source);
32 if (is_wp_error($provider)) {
33 return $provider;
34 }
35
36 if (!$provider->is_available()) {
37 return new WP_Error('reacg_migration_unavailable', __('Selected source is not available on this site.', 'regallery'));
38 }
39
40 $result = $provider->list_galleries($args);
41 if (is_wp_error($result)) {
42 return $result;
43 }
44
45 $result['items'] = $this->decorate_items_with_migration_status(
46 $provider->get_key(),
47 !empty($result['items']) ? $result['items'] : []
48 );
49
50 return $result;
51 }
52
53 public function list_all_galleries($args = []) {
54 $all_items = [];
55
56 foreach ($this->providers as $provider) {
57 if (!$provider->is_available()) {
58 continue;
59 }
60
61 $provider_result = $provider->list_galleries($args);
62 if (is_wp_error($provider_result)) {
63 continue;
64 }
65
66 $items = !empty($provider_result['items']) ? $provider_result['items'] : [];
67 $items = $this->decorate_items_with_migration_status($provider->get_key(), $items);
68
69 foreach ($items as $item) {
70 $item['source'] = $provider->get_key();
71 $item['source_label'] = $provider->get_label();
72 $all_items[] = $item;
73 }
74 }
75
76 usort($all_items, function ($a, $b) {
77 return strnatcasecmp(
78 !empty($a['title']) ? (string) $a['title'] : '',
79 !empty($b['title']) ? (string) $b['title'] : ''
80 );
81 });
82
83 return [
84 'items' => $all_items,
85 'total' => count($all_items),
86 'page' => 1,
87 'per_page' => count($all_items),
88 ];
89 }
90
91 public function import_galleries($source, $gallery_ids = [], $args = []) {
92 $provider = $this->get_provider($source);
93 if (is_wp_error($provider)) {
94 return $provider;
95 }
96
97 if (!$provider->is_available()) {
98 return new WP_Error('reacg_migration_unavailable', __('Selected source is not available on this site.', 'regallery'));
99 }
100
101 $results = [];
102 foreach ((array) $gallery_ids as $raw_gallery_id) {
103 $gallery_id = sanitize_text_field((string) $raw_gallery_id);
104 if ($gallery_id === '') {
105 continue;
106 }
107
108 $results[] = $this->import_single_gallery($provider, $gallery_id, $args);
109 }
110
111 return [
112 'source' => $source,
113 'results' => $results,
114 'imported' => count(array_filter($results, function ($row) {
115 return !empty($row['success']);
116 })),
117 'failed' => count(array_filter($results, function ($row) {
118 return empty($row['success']);
119 })),
120 ];
121 }
122
123 private function import_single_gallery($provider, $gallery_id, $args) {
124 $source_key = $provider->get_key();
125 $existing = $this->find_existing_import($source_key, $gallery_id);
126
127 $source_gallery = $provider->get_gallery($gallery_id);
128 if (is_wp_error($source_gallery)) {
129 return [
130 'success' => false,
131 'source_gallery_id' => $gallery_id,
132 'message' => $source_gallery->get_error_message(),
133 ];
134 }
135
136 $items = !empty($source_gallery['items']) && is_array($source_gallery['items']) ? $source_gallery['items'] : [];
137 $settings = !empty($source_gallery['settings']) && is_array($source_gallery['settings']) ? $source_gallery['settings'] : [];
138 $attachment_ids = [];
139
140 foreach ($items as $item) {
141 $attachment_id = $this->resolve_attachment_id($item);
142 if ($attachment_id) {
143 $attachment_ids[] = $attachment_id;
144 }
145 }
146
147 $attachment_ids = array_values(array_unique(array_filter(array_map('intval', $attachment_ids))));
148
149 $title = !empty($source_gallery['title']) ? sanitize_text_field($source_gallery['title']) : __('Imported Gallery', 'regallery');
150 $source_has_placements = (bool) $provider->source_exists_in_posts($gallery_id);
151
152 $saved_gallery_id = $existing
153 ? $this->update_regallery($existing, $title, $attachment_ids, [
154 'source' => $source_key,
155 'source_gallery_id' => $gallery_id,
156 'source_has_placements' => $source_has_placements,
157 ], $settings)
158 : $this->create_regallery($title, $attachment_ids, [
159 'source' => $source_key,
160 'source_gallery_id' => $gallery_id,
161 'source_has_placements' => $source_has_placements,
162 ], $settings);
163
164 if (is_wp_error($saved_gallery_id)) {
165 return [
166 'success' => false,
167 'source_gallery_id' => $gallery_id,
168 'message' => $saved_gallery_id->get_error_message(),
169 ];
170 }
171
172 return [
173 'success' => true,
174 'source_gallery_id' => $gallery_id,
175 'gallery_id' => intval($saved_gallery_id),
176 'images_count' => count($attachment_ids),
177 'message' => $existing
178 ? __('Gallery updated successfully.', 'regallery')
179 : __('Gallery imported successfully.', 'regallery'),
180 ];
181 }
182
183 private function resolve_attachment_id($item) {
184 if (!empty($item['attachment_id'])) {
185 $attachment_id = intval($item['attachment_id']);
186 if ($attachment_id > 0) {
187 $this->apply_attachment_item_meta($attachment_id, $item);
188 return $attachment_id;
189 }
190 }
191
192 if (!empty($item['local_file'])) {
193 $attachment_id = $this->local_file_to_attachment((string) $item['local_file'], $item);
194 if ($attachment_id > 0) {
195 $this->apply_attachment_item_meta($attachment_id, $item);
196 return intval($attachment_id);
197 }
198 }
199
200 if (!empty($item['url'])) {
201 $url = esc_url_raw($item['url']);
202 if (!empty($url)) {
203 $attachment_id = attachment_url_to_postid($url);
204 if ($attachment_id) {
205 $this->apply_attachment_item_meta($attachment_id, $item);
206 return intval($attachment_id);
207 }
208
209 return $this->sideload_url_to_attachment($url, $item);
210 }
211 }
212
213 return 0;
214 }
215
216 private function local_file_to_attachment($file_path, $item = []) {
217 $file_path = trim((string) $file_path);
218 if ($file_path === '' || !is_file($file_path) || !is_readable($file_path)) {
219 return 0;
220 }
221
222 $uploads = wp_get_upload_dir();
223 if (!empty($uploads['error'])) {
224 return 0;
225 }
226
227 $normalized_file = wp_normalize_path($file_path);
228 $normalized_base = wp_normalize_path(untrailingslashit($uploads['basedir']));
229
230 $relative = '';
231 if ($normalized_base !== '' && strpos($normalized_file, $normalized_base . '/') === 0) {
232 $relative = ltrim(substr($normalized_file, strlen($normalized_base)), '/');
233 } else {
234 $destination_dir = !empty($uploads['path']) ? $uploads['path'] : $uploads['basedir'];
235 if ($destination_dir === '') {
236 return 0;
237 }
238
239 wp_mkdir_p($destination_dir);
240 $filename = wp_basename($file_path);
241 $unique_name = wp_unique_filename($destination_dir, $filename);
242 $destination_path = trailingslashit($destination_dir) . $unique_name;
243
244 if (!@copy($file_path, $destination_path)) {
245 return 0;
246 }
247
248 $file_path = $destination_path;
249 $normalized_file = wp_normalize_path($file_path);
250 if ($normalized_base !== '' && strpos($normalized_file, $normalized_base . '/') === 0) {
251 $relative = ltrim(substr($normalized_file, strlen($normalized_base)), '/');
252 }
253 }
254
255 if ($relative !== '') {
256 $existing = get_posts([
257 'post_type' => 'attachment',
258 'post_status' => 'inherit',
259 'posts_per_page' => 1,
260 'fields' => 'ids',
261 'meta_key' => '_wp_attached_file',
262 'meta_value' => $relative,
263 'suppress_filters' => true,
264 ]);
265 if (!empty($existing[0])) {
266 return intval($existing[0]);
267 }
268 }
269
270 $filetype = wp_check_filetype(wp_basename($file_path), null);
271 $mime_type = !empty($filetype['type']) ? $filetype['type'] : 'image/jpeg';
272 $title = !empty($item['title']) ? sanitize_text_field($item['title']) : sanitize_text_field(pathinfo(wp_basename($file_path), PATHINFO_FILENAME));
273 if ($title === '') {
274 $title = __('Imported image', 'regallery');
275 }
276
277 $attachment_data = [
278 'post_mime_type' => $mime_type,
279 'post_title' => $title,
280 'post_content' => '',
281 'post_status' => 'inherit',
282 ];
283
284 if ($relative !== '' && !empty($uploads['baseurl'])) {
285 $attachment_data['guid'] = trailingslashit($uploads['baseurl']) . ltrim($relative, '/');
286 }
287
288 $attachment_id = wp_insert_attachment($attachment_data, $file_path, 0, true);
289 if (is_wp_error($attachment_id) || intval($attachment_id) <= 0) {
290 return 0;
291 }
292
293 require_once ABSPATH . 'wp-admin/includes/image.php';
294 $metadata = wp_generate_attachment_metadata(intval($attachment_id), $file_path);
295 if (!empty($metadata) && !is_wp_error($metadata)) {
296 wp_update_attachment_metadata(intval($attachment_id), $metadata);
297 }
298
299 return intval($attachment_id);
300 }
301
302 private function apply_attachment_item_meta($attachment_id, $item) {
303 $attachment_id = intval($attachment_id);
304 if ($attachment_id <= 0 || get_post_type($attachment_id) !== 'attachment') {
305 return;
306 }
307
308 if (!empty($item['alt'])) {
309 $existing_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
310 if ($existing_alt === '') {
311 update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($item['alt']));
312 }
313 }
314
315 if (!empty($item['action_url'])) {
316 update_post_meta($attachment_id, 'action_url', esc_url_raw($item['action_url']));
317 }
318
319 $update = ['ID' => $attachment_id];
320 $should_update = false;
321
322 if (!empty($item['caption'])) {
323 $attachment = get_post($attachment_id);
324 if ($attachment && $attachment->post_excerpt === '') {
325 $update['post_excerpt'] = sanitize_text_field($item['caption']);
326 $should_update = true;
327 }
328 }
329
330 if (!empty($item['description'])) {
331 $attachment = isset($attachment) ? $attachment : get_post($attachment_id);
332 if ($attachment && $attachment->post_content === '') {
333 $update['post_content'] = sanitize_textarea_field($item['description']);
334 $should_update = true;
335 }
336 }
337
338 if ($should_update) {
339 wp_update_post($update);
340 }
341 }
342
343 private function sideload_url_to_attachment($url, $item = []) {
344 require_once ABSPATH . 'wp-admin/includes/file.php';
345 require_once ABSPATH . 'wp-admin/includes/media.php';
346 require_once ABSPATH . 'wp-admin/includes/image.php';
347
348 $tmp_file = download_url($url);
349 if (is_wp_error($tmp_file)) {
350 return 0;
351 }
352
353 $file_name = wp_basename(parse_url($url, PHP_URL_PATH));
354 if (empty($file_name)) {
355 $file_name = 'reacg-migration-' . time() . '.jpg';
356 }
357
358 $file_array = [
359 'name' => $file_name,
360 'tmp_name' => $tmp_file,
361 ];
362
363 $attachment_id = media_handle_sideload($file_array, 0, !empty($item['description']) ? sanitize_text_field($item['description']) : '');
364
365 if (is_wp_error($attachment_id)) {
366 @unlink($tmp_file);
367 return 0;
368 }
369
370 if (!empty($item['alt'])) {
371 update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($item['alt']));
372 }
373 if (!empty($item['action_url'])) {
374 update_post_meta($attachment_id, 'action_url', esc_url_raw($item['action_url']));
375 }
376
377 return intval($attachment_id);
378 }
379
380 private function create_regallery($title, $attachment_ids, $source_meta = [], $settings_overrides = []) {
381 $post_id = wp_insert_post([
382 'post_title' => $title,
383 'post_status' => 'publish',
384 'post_type' => REACG_CUSTOM_POST_TYPE,
385 ]);
386
387 if (is_wp_error($post_id) || !$post_id) {
388 return new WP_Error('reacg_migration_create_failed', __('Failed to create Re Gallery post.', 'regallery'));
389 }
390
391 return $this->store_regallery_data($post_id, $title, $attachment_ids, $source_meta, $settings_overrides);
392 }
393
394 private function update_regallery($post_id, $title, $attachment_ids, $source_meta = [], $settings_overrides = []) {
395 $post_id = intval($post_id);
396 if ($post_id <= 0 || get_post_type($post_id) !== REACG_CUSTOM_POST_TYPE) {
397 return new WP_Error('reacg_migration_update_failed', __('Failed to update Re Gallery post.', 'regallery'));
398 }
399
400 $updated = wp_update_post([
401 'ID' => $post_id,
402 'post_title' => $title,
403 ], true);
404
405 if (is_wp_error($updated)) {
406 return new WP_Error('reacg_migration_update_failed', __('Failed to update Re Gallery post.', 'regallery'));
407 }
408
409 return $this->store_regallery_data($post_id, $title, $attachment_ids, $source_meta, $settings_overrides);
410 }
411
412 private function store_regallery_data($post_id, $title, $attachment_ids, $source_meta = [], $settings_overrides = []) {
413 $post_id = intval($post_id);
414
415 update_post_meta($post_id, 'images_ids', wp_json_encode($attachment_ids));
416 update_post_meta($post_id, 'images_count', count($attachment_ids));
417 update_post_meta($post_id, 'additional_data', '');
418 update_post_meta($post_id, 'gallery_timestamp', time());
419 update_post_meta($post_id, '_reacg_migration_source', sanitize_key($source_meta['source']));
420 update_post_meta($post_id, '_reacg_migration_source_gallery_id', sanitize_text_field($source_meta['source_gallery_id']));
421 update_post_meta($post_id, '_reacg_migration_source_has_placements', !empty($source_meta['source_has_placements']) ? '1' : '0');
422
423 if (!class_exists('REACG_Options')) {
424 require_once REACG_PLUGIN_DIR . '/includes/options.php';
425 }
426
427 $options_obj = new REACG_Options(false);
428 $options = $options_obj->get_options(0);
429 if (!empty($settings_overrides) && is_array($settings_overrides)) {
430 $options = array_replace_recursive($options, $this->sanitize_settings_overrides($settings_overrides));
431 }
432 $options['template_id'] = $post_id;
433 $options['title'] = $title;
434
435 $option_name = 'reacg_options' . $post_id;
436 $encoded_options = wp_json_encode($options);
437
438 $added = add_option($option_name, $encoded_options, '', false);
439 if (!$added) {
440 update_option($option_name, $encoded_options, false);
441 }
442 update_post_meta($post_id, 'options_timestamp', time());
443
444 return intval($post_id);
445 }
446
447 private function sanitize_settings_overrides($settings) {
448 $sanitized = [];
449
450 foreach ((array) $settings as $key => $value) {
451 $key = trim((string) $key);
452 if ($key === '') {
453 continue;
454 }
455
456 if (is_array($value)) {
457 $sanitized[$key] = $this->sanitize_settings_overrides($value);
458 continue;
459 }
460
461 if (is_bool($value)) {
462 $sanitized[$key] = $value;
463 continue;
464 }
465
466 if (is_numeric($value)) {
467 $sanitized[$key] = strpos((string) $value, '.') !== false ? floatval($value) : intval($value);
468 continue;
469 }
470
471 $sanitized[$key] = sanitize_text_field((string) $value);
472 }
473
474 return $sanitized;
475 }
476
477 private function find_existing_import($source, $source_gallery_id) {
478 $query = new WP_Query([
479 'post_type' => REACG_CUSTOM_POST_TYPE,
480 'post_status' => ['publish', 'draft', 'private'],
481 'posts_per_page' => 1,
482 'fields' => 'ids',
483 'meta_query' => [
484 'relation' => 'AND',
485 [
486 'key' => '_reacg_migration_source',
487 'value' => sanitize_key($source),
488 ],
489 [
490 'key' => '_reacg_migration_source_gallery_id',
491 'value' => sanitize_text_field((string) $source_gallery_id),
492 ],
493 ],
494 ]);
495
496 return !empty($query->posts[0]) ? intval($query->posts[0]) : 0;
497 }
498
499 private function decorate_items_with_migration_status($source, $items) {
500 $decorated = [];
501 foreach ((array) $items as $item) {
502 $source_gallery_id = isset($item['id']) ? (string) $item['id'] : '';
503 $migrated_gallery_id = $source_gallery_id !== ''
504 ? $this->find_existing_import($source, $source_gallery_id)
505 : 0;
506 $source_still_exists = !empty($migrated_gallery_id)
507 && $this->source_exists_in_posts($source, $source_gallery_id);
508 $source_has_placements = !empty($migrated_gallery_id)
509 ? $this->get_source_has_placements_flag($migrated_gallery_id)
510 : false;
511
512 if (!empty($migrated_gallery_id) && $source_still_exists && !$source_has_placements) {
513 $source_has_placements = true;
514 update_post_meta($migrated_gallery_id, '_reacg_migration_source_has_placements', '1');
515 }
516
517 $item['migrated'] = !empty($migrated_gallery_id);
518 $item['migrated_gallery_id'] = intval($migrated_gallery_id);
519 $item['replace_available'] = !empty($migrated_gallery_id) && $source_has_placements;
520 $item['replaced'] = !empty($migrated_gallery_id)
521 && $source_has_placements
522 && !$source_still_exists;
523 $decorated[] = $item;
524 }
525
526 return $decorated;
527 }
528
529 private function get_source_has_placements_flag($migrated_gallery_id) {
530 $value = get_post_meta(intval($migrated_gallery_id), '_reacg_migration_source_has_placements', true);
531
532 return $value === '1' || $value === 1 || $value === true;
533 }
534
535 /**
536 * Returns true if any post still contains a shortcode or block referencing
537 * this source gallery, i.e. the Replace action has not yet been run.
538 *
539 * Uses a two-step check: a broad LIKE query to find candidate posts, then
540 * an exact PHP regex on those posts to avoid false positives from IDs that
541 * are substrings of other IDs (e.g. ID "5" matching "[foogallery id="15"]").
542 */
543 private function source_exists_in_posts($source, $source_gallery_id) {
544 $provider = $this->get_provider($source);
545 if (is_wp_error($provider)) {
546 return false;
547 }
548
549 return (bool) $provider->source_exists_in_posts($source_gallery_id);
550 }
551
552 public function get_provider($source) {
553 $key = sanitize_key((string) $source);
554 if (empty($this->providers[$key])) {
555 return new WP_Error('reacg_migration_source_not_found', __('Migration source is not supported.', 'regallery'));
556 }
557
558 return $this->providers[$key];
559 }
560 }
561