PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / full-site-import / Runners / Attachments.php

Attachments.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/full-site-import/Runners/Attachments.php

378 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Modules\FullSiteImport\Runners;
4
5 use Templately\Modules\FullSiteImport\WPImport;
6 use Templately\Utils\Helper;
7
8 class Attachments extends WPContent {
9
10 private $data = [];
11
12 public function __construct($request_params) {
13 parent::__construct($request_params);
14 $this->attachments_init();
15 }
16
17 public function get_name(): string {
18 return 'attachments';
19 }
20
21 public function get_label(): string {
22 return __('Attachments', 'templately');
23 }
24
25 public function should_log(): bool {
26 return true;
27 }
28
29 public function get_action(): string {
30 return 'updateLog';
31 }
32
33 public function log_message(): string {
34 if ($this->total > 0) {
35 return __('Importing Attachments: (' . $this->get_processed() . '/' . $this->total . ')', 'templately');
36 }
37 return __('Importing Attachment.', 'templately');
38 }
39
40 public function should_run($data, $imported_data = []): bool {
41 return !empty($this->manifest['has_attachments']);
42 }
43
44 public function import($data, $imported_data): array {
45 $path = $this->dir_path;
46 $taxonomies = [];
47 $terms = [];
48 $results = [];
49 $this->data = $data;
50
51 $this->clear_old_el_cache();
52
53 $this->import_actions();
54
55 $import = $this->_import_type_data('attachments', $path, $imported_data, $taxonomies, $terms);
56 $results['attachments'] = $import['summary']['posts'];
57 $results['attachments_errors'] = $import['errors'];
58
59 $this->import_actions(true);
60
61 return $results;
62 }
63
64 /**
65 * Clear old Elementor cache and image hash metadata
66 *
67 * This method removes cached Elementor image hash data from previous imports to prevent
68 * the second image hash check (for original_attachment_url) from picking up attachments
69 * from previous imports. This ensures clean attachment mapping for each import session.
70 *
71 * Process:
72 * 1. Checks if cache has already been cleared for this session (via loop result)
73 * 2. Retrieves all meta_ids stored in '_templately_image_hash_meta_id' meta keys
74 * 3. Deletes the actual hash metadata entries referenced by those meta_ids
75 * 4. Removes the tracking meta_keys themselves
76 *
77 * @return void
78 */
79 public function clear_old_el_cache() {
80 global $wpdb;
81
82 $has_processed = $this->get_loop_result(false, 'attachments-cleared-old-el-cache', false);
83 if($has_processed){
84 return;
85 }
86
87 // Step 1: Retrieve all meta values where the meta_key is '_templately_image_hash_meta_id'
88 $meta_ids = $wpdb->get_col(
89 $wpdb->prepare(
90 "SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s",
91 '_templately_image_hash_meta_id'
92 )
93 );
94
95 // Step 2: Delete the corresponding meta entries using the retrieved meta_ids
96 if (!empty($meta_ids)) {
97 $placeholders = implode(',', array_fill(0, count($meta_ids), '%d'));
98 $wpdb->query(
99 $wpdb->prepare(
100 "DELETE FROM {$wpdb->postmeta} WHERE meta_id IN ($placeholders)",
101 ...$meta_ids
102 )
103 );
104
105 if (defined('TEMPLATELY_DEBUG_LOG') && TEMPLATELY_DEBUG_LOG) {
106 Helper::log('Deleted _elementor_source_image_hash entries with meta_ids: ' . implode(', ', $meta_ids), 'fsi_attachments');
107 }
108 }
109
110 // Step 3: Delete all '_templately_image_hash_meta_id' entries
111 $wpdb->delete(
112 $wpdb->postmeta,
113 array('meta_key' => '_templately_image_hash_meta_id'),
114 array('%s')
115 );
116
117 // Check for errors
118 if (defined('TEMPLATELY_DEBUG_LOG') && TEMPLATELY_DEBUG_LOG) {
119 if($wpdb->last_error){
120 Helper::log('MySQL Error during image hash cleanup: ' . $wpdb->last_error, 'fsi_attachments', 'error');
121 }
122 }
123
124 // Mark this operation as complete to prevent redundant execution
125 $this->set_loop_result(true, 'attachments-cleared-old-el-cache');
126 }
127
128 protected function import_actions($remove = false) {
129 parent::import_actions($remove);
130
131 if ($remove) {
132 remove_filter('templately_import_copy_attachment', [$this, 'copy_attachment_file'], 10);
133 remove_filter('templately_import_has_local_attachment', [$this, 'has_local_attachment_file'], 10);
134 remove_filter('wp_import_post_data_raw', [$this, 'ai_image_replacement_url'], 10);
135 remove_filter('wp_import_post_meta', [$this, 'ai_image_replacement_metadata'], 10);
136 } else {
137 add_filter('templately_import_copy_attachment', [$this, 'copy_attachment_file'], 10, 4);
138 add_filter('templately_import_has_local_attachment', [$this, 'has_local_attachment_file'], 10, 3);
139 add_filter('wp_import_post_data_raw', [$this, 'ai_image_replacement_url'], 10, 2);
140 add_filter('wp_import_post_meta', [$this, 'ai_image_replacement_metadata'], 10, 3);
141 }
142 }
143
144 /**
145 * Whether copy_attachment_file() would supply this attachment from the pack.
146 *
147 * Asked BEFORE the import, by whoever is deciding which attachments are worth fetching
148 * in parallel: a file that ships inside the pack is never downloaded, so queueing it
149 * would spend a request on something nobody will ask for. Must stay in step with the
150 * lookup in copy_attachment_file() — same exclusion, same path rule.
151 *
152 * @param bool $has Earlier answer.
153 * @param int|string $att_id The attachment's id in the pack.
154 * @param string $file_name The attachment's file name.
155 * @return bool
156 */
157 public function has_local_attachment_file($has, $att_id, $file_name) {
158 if ($has || empty($att_id) || $this->is_ai_image($att_id)) {
159 return (bool) $has;
160 }
161
162 $wp_filetype = wp_check_filetype((string) $file_name);
163
164 return file_exists($this->dir_path . 'attachments' . DIRECTORY_SEPARATOR . "{$att_id}.{$wp_filetype['ext']}");
165 }
166
167 public function copy_attachment_file($return, $att_id, $dest_file, $upload_dir) {
168 // @todo check mapping url to new id
169
170 if (!empty($att_id)) {
171 // skip ai image replacement from local file
172 if ($this->is_ai_image($att_id)) {
173 return $return;
174 }
175
176 $file_name = basename($dest_file);
177 $path = $this->dir_path . 'attachments' . DIRECTORY_SEPARATOR;
178 $wp_filetype = wp_check_filetype($file_name);
179 $ext = $wp_filetype['ext'];
180
181 $source_path = "{$path}{$att_id}.{$ext}";
182
183 if (!file_exists($source_path)) {
184 return $return;
185 }
186
187 $move_new_file = copy($source_path, $dest_file);
188
189 if (!$move_new_file) {
190 return $return;
191 }
192
193 $wp_filetype = wp_check_filetype_and_ext($source_path, $file_name);
194
195 // Set correct file permissions.
196 $stat = stat(dirname($dest_file));
197 $perms = $stat['mode'] & 0000666;
198 chmod($dest_file, $perms);
199
200 return [
201 'file' => $dest_file,
202 'url' => $upload_dir['url'] . "/$file_name",
203 'type' => $wp_filetype['type'],
204 'error' => false,
205 ];
206 }
207
208
209 return $return;
210 }
211
212 public function is_ai_image($post_id) {
213 if (!empty($post_id) && !empty($this->data["image_mappings"][$post_id])) {
214 return true;
215 }
216 return false;
217 }
218
219 /**
220 * @param $post
221 * @param WPImport $wp_importer
222 * @return array
223 */
224 public function ai_image_replacement_url($post, $wp_importer) {
225 $post_id = $post['post_id'] ?? null;
226 if ($this->is_ai_image($post_id) && !empty($post['post_type']) && $post['post_type'] === 'attachment') {
227 $image_data = $this->data["image_mappings"][$post_id];
228
229 if (!empty($image_data["newData"]["original"])) {
230 $source = $image_data["source"];
231 $new_id = $image_data["newData"]["id"] ?? null;
232
233 // A local WordPress media upload (the plugin's own picker): newData.id
234 // is an existing attachment id, so remap the imported post to it.
235 $is_wp_upload = $source === 'upload' && is_numeric($new_id) && get_post( (int) $new_id );
236
237 if($is_wp_upload){
238 $original_url = $image_data["originalUrl"];
239 $new_url = $image_data["newUrl"];
240
241 $wp_importer->update_post_meta( $new_id );
242
243 // Map pre-import ID to local ID.
244 $wp_importer->processed_posts[ (int) $post_id ] = (int) $new_id;
245
246 $wp_importer->set_url_map($original_url, $new_url);
247 $wp_importer->set_url_map($original_url, $new_url, true);
248
249 return $new_id;
250 }
251 else if($source === 'ai'){
252 // @todo: coming soon
253 }
254 else {
255 // External-URL replacement — a stock photo (Pexels/Unsplash) or a
256 // web-customizer upload stored on GCS (no local attachment id).
257 // Re-point the attachment to the external URL so WordPress
258 // sideloads it during import.
259 $external = $image_data["newData"]["original"] ?? $image_data["newUrl"] ?? '';
260 if (!empty($external)) {
261 $post['original_attachment_url'] = $post['attachment_url'];
262 $post['guid'] = $external;
263 $post['attachment_url'] = $external;
264 }
265 }
266 }
267 }
268 return $post;
269 }
270
271 public function ai_image_replacement_metadata($postmeta, $post_id, $post) {
272
273 if ($this->is_ai_image($post_id)) {
274 $image_data = $this->data["image_mappings"][$post_id]["newData"] ?? [];
275 // alt. provider, photographer
276 if (!empty($image_data["alt"])) {
277 $postmeta[] = [
278 'key' => '_wp_attachment_image_alt',
279 'value' => $image_data["alt"],
280 ];
281 }
282 if (!empty($image_data["provider"])) {
283 $postmeta[] = [
284 'key' => '_templately_ai_image_provider',
285 'value' => $image_data["provider"],
286 ];
287 }
288 if (!empty($image_data["photographer"])) {
289 $postmeta[] = [
290 'key' => '_templately_ai_image_photographer',
291 'value' => $image_data["photographer"],
292 ];
293 }
294 }
295
296 return $postmeta;
297 }
298
299 public function get_processed() {
300 $processed = $this->processed ?? [];
301 $succeed = $processed['succeed'] ?? [];
302 $failed = $processed['failed'] ?? [];
303
304 return count($succeed) + count($failed);
305 }
306
307 public function post_log($post, $result) {
308 if (isset($post['post_type'])) {
309 if ($post['post_type'] !== 'attachment') {
310 return;
311 }
312
313 // need to assign to processed so log_message() can use get_processed() to calculate progress
314 // also bellow code used it to calculate progress
315 $this->processed = $result;
316
317 $type = $post['post_type'];
318 $title = $post['post_title'];
319 }
320
321 if (empty($type) || empty($title)) {
322 return;
323 }
324
325 $progress = $this->total > 0 ? ceil((100 * ($this->get_processed())) / $this->total) : 100;
326
327 $this->log($progress);
328 }
329
330 public function attachments_init() {
331 add_filter('pre_http_request', [$this, 'pre_http_request'], 10, 3);
332 add_filter('http_response', [$this, 'http_response'], 10, 3);
333 // attachment insert hooks
334 add_filter('wp_insert_attachment_data', [$this, 'before_insert_attachment'], 10, 2);
335 add_action('add_attachment', [$this, 'after_insert_attachment'], 10, 1);
336 add_filter('wp_update_attachment_metadata', [$this, 'wp_update_attachment_metadata'], 99999, 2);
337 }
338
339 public function pre_http_request($preempt, $parsed_args, $url) {
340 $this->sse_log('attachments', 'Before downloading attachment: ' . esc_url($url), 1, 'eventLog');
341
342 return $preempt;
343 }
344
345 public function http_response($response, $parsed_args, $url) {
346 $this->sse_log('attachments', 'After downloading attachment: ' . $url, 1, 'eventLog');
347 return $response;
348 }
349
350 /**
351 * Log before inserting attachment.
352 */
353 public function before_insert_attachment($data, $postarr) {
354 $this->sse_log('attachments', 'Before inserting attachment: ' . ($data['post_title'] ?? ''), 1, 'eventLog');
355 return $data;
356 }
357
358 /**
359 * Log after attachment is inserted.
360 */
361 public function after_insert_attachment($post_ID) {
362 $post = get_post($post_ID);
363 $this->sse_log('attachments', 'After inserting attachment: ' . ($post->post_title ?? ''), 1, 'eventLog');
364 }
365
366 public function wp_update_attachment_metadata($metadata, $attachment_id) {
367 $this->sse_log('attachments', 'Updated attachment metadata: ' . $attachment_id, 1, 'eventLog');
368 // $this->sse_message([
369 // "action" => "eventLog",
370 // "type" => "attachments",
371 // "progress" => 1,
372 // "message" => "Updated attachment metadata => " . $attachment_id,
373 // "backtrace" => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 30),
374 // ]);
375 return $metadata;
376 }
377 }
378