PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
← All changes | includes/class-metasync-common.php +71 -19 2.6.3 → trunk View file →
@@ -48,15 +48,16 @@
48 48 */
49 49 public function get_attachment_by_name($attachment_name)
50 50 {
51 51 global $wpdb;
52 - $post = $wpdb->get_row(
52 + $post = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- attachment lookup by guid/post_name — guid search has no WordPress API
53 53 $wpdb->prepare(
54 - "SELECT * FROM $wpdb->posts WHERE `post_name` = '%s' and `post_type` = 'attachment' LIMIT 1",
54 + "SELECT * FROM $wpdb->posts WHERE `post_name` = %s and `post_type` = 'attachment' LIMIT 1",
55 55 $attachment_name
56 56 )
57 57 );
58 - return get_post($post);
58 + // get_post(null) returns the global post — never a "not found" signal.
59 + return $post ? get_post($post) : null;
59 60 }
60 61
61 62 public function get_permalink_from_url($url)
62 63 {
@@ -71,16 +72,23 @@
71 72 */
72 73 public function get_file_name_by_url($url)
73 74 {
74 75 if (stripos($url, "https://cdn.midjourney.com/") !== false) {
75 - return pathinfo(str_replace("/", "_", parse_url($url, PHP_URL_PATH) ?? ''), PATHINFO_FILENAME);
76 + $path = wp_parse_url($url, PHP_URL_PATH);
77 + return pathinfo(false === $path ? '' : str_replace('/', '_', $path), PATHINFO_FILENAME);
76 78 } elseif (stripos($url, "https://drive.google.com/") !== false) {
77 79 $parse_url = wp_parse_url($url);
80 + // Modern permalinks are /file/d/<ID>/view — the ID lives in the path,
81 + // and there is no query component at all on those URLs.
82 + if (!empty($parse_url['path']) && preg_match('~/file/d/([^/]+)~', $parse_url['path'], $drive_matches)) {
83 + return $drive_matches[1];
84 + }
78 85 $args = [];
79 - wp_parse_str($parse_url['query'], $args);
80 - return $args['id'];
86 + wp_parse_str($parse_url['query'] ?? '', $args);
87 + return $args['id'] ?? null;
81 88 } else {
82 - return pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_FILENAME);
89 + $path = wp_parse_url($url, PHP_URL_PATH);
90 + return pathinfo(false === $path ? '' : $path, PATHINFO_FILENAME);
83 91 }
84 92 }
85 93
86 94 public function allowedDownloadSources($url)
@@ -100,18 +108,17 @@
100 108 public function get_media_id_from_url($url) {
101 109 global $wpdb;
102 110
103 111 // Search for any attachment matching the URL
104 - $query = $wpdb->prepare("
112 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- attachment lookup by guid/post_name — guid search has no WordPress API
113 + $attachment_id = $wpdb->get_var($wpdb->prepare("
105 114 SELECT ID
106 - FROM $wpdb->posts
115 + FROM {$wpdb->posts}
107 116 WHERE post_type = 'attachment'
108 117 AND guid = %s
109 118 LIMIT 1
110 - ", $url);
119 + ", $url));
111 120
112 - $attachment_id = $wpdb->get_var($query);
113 -
114 121 // Return the attachment ID if found, otherwise return false
115 122 return $attachment_id ? intval($attachment_id) : false;
116 123 }
117 124 /**
@@ -128,20 +135,31 @@
128 135
129 136 $tmp = download_url($url);
130 137 if (is_wp_error($tmp)){
131 138 $attachment_id = $this->get_media_id_from_url($url);
132 - error_log($attachment_id);
133 139 return $attachment_id;
134 140 }
135 141
136 142 $filename = $this->get_file_name_by_url($url);
137 143 // $filename = pathinfo($url, PATHINFO_FILENAME);
144 + // A null/empty name degenerates the post_name to "-<ext>", which collides
145 + // across unrelated images — fall back to the URL basename instead.
146 + if (null === $filename || '' === $filename) {
147 + $filename = pathinfo(wp_parse_url($url, PHP_URL_PATH) ?? '', PATHINFO_FILENAME);
148 + }
138 149 // eliminating query params from file name
139 150 $filename = explode("?", $filename)[0];
140 151 $extension = pathinfo($url, PATHINFO_EXTENSION);
141 152
142 153 if (!$extension || strlen($extension) > 4) {
143 - $mime = mime_content_type($tmp);
154 + // fileinfo extension is not always enabled on shared hosts — guard the call
155 + // and fall back to WordPress's extension-based detection.
156 + if (function_exists('mime_content_type')) {
157 + $mime = mime_content_type($tmp);
158 + } else {
159 + $filetype = wp_check_filetype(basename($url));
160 + $mime = $filetype['type'] ?? false;
161 + }
144 162 $mime = is_string($mime) ? sanitize_mime_type($mime) : false;
145 163
146 164 $mime_extensions = array(
147 165 'image/jpe' => 'jpe',
@@ -156,9 +174,9 @@
156 174 $extension = $mime_extensions[$mime];
157 175 } else {
158 176 // Safely delete temporary file if it exists
159 177 if (file_exists($tmp)) {
160 - unlink($tmp);
178 + wp_delete_file($tmp);
161 179 }
162 180 return false;
163 181 }
164 182 }
@@ -174,12 +192,20 @@
174 192 );
175 193
176 194 $get_attachment = $this->get_attachment_by_name($args['post_name']);
177 195
178 -
179 196 # remove if null logic check on 11 march 2025 for issue 264 and merge request 320
180 197 if (empty($get_attachment)) {
181 198 $attachment_id = media_handle_sideload($args, 0, $args['name']);
199 + // The sideload can fail (invalid remote file, upload disallowed) — check
200 + // before the result is used as a post ID anywhere below.
201 + if (is_wp_error($attachment_id)) {
202 + // Safely delete temporary file if it exists
203 + if (file_exists($tmp)) {
204 + wp_delete_file($tmp);
205 + }
206 + return false;
207 + }
182 208 update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt);
183 209 // check if the title is empty or not if it's has title update the title
184 210 if($title_text !== ''){
185 211 wp_update_post(
@@ -193,17 +219,43 @@
193 219 $attachment_id,
194 220 wp_get_original_image_path($attachment_id)
195 221 );
196 222 wp_update_attachment_metadata($attachment_id, $attach_data);
197 -
223 +
198 224 // Safely delete temporary file if it exists
199 225 if (file_exists($tmp)) {
200 - unlink($tmp);
226 + wp_delete_file($tmp);
201 227 }
202 228
203 - if (is_wp_error($attachment_id)) return false;
204 229 return $attachment_id;
205 230 } else {
231 + // An attachment record can outlive its file (e.g. the file was
232 + // deleted from disk outside WordPress). Since we already have a
233 + // fresh download, restore it into the existing attachment so its
234 + // URL stops 404ing — reusing the record keeps post references
235 + // intact and avoids piling up duplicate attachments.
236 + $existing_file = get_attached_file($get_attachment->ID);
237 + if (empty($existing_file) || !file_exists($existing_file)) {
238 + // wp_handle_sideload() takes $file by reference, so the
239 + // argument must be a variable — a literal array here is a
240 + // runtime fatal that kills the whole sync request.
241 + $file_array = array(
242 + 'name' => $args['name'],
243 + 'tmp_name' => $tmp,
244 + );
245 + $upload = wp_handle_sideload($file_array, array('test_form' => false));
246 + if (empty($upload['error']) && !empty($upload['file'])) {
247 + update_attached_file($get_attachment->ID, $upload['file']);
248 + $attach_data = wp_generate_attachment_metadata($get_attachment->ID, $upload['file']);
249 + wp_update_attachment_metadata($get_attachment->ID, $attach_data);
250 + }
251 + }
252 +
253 + // Delete the temporary file unless the sideload above moved it
254 + if (file_exists($tmp)) {
255 + wp_delete_file($tmp);
256 + }
257 +
206 258 // check if the title attribute is set on the image tag and then update the title
207 259 if($title_text !== ''){
208 260 wp_update_post(
209 261 array (