PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.8
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.8
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 2.6.4 2.6.3 All 139 releases
metasync / public / class-metasync-hidden-post.php

class-metasync-hidden-post.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.8, at public/class-metasync-hidden-post.php

449 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 class MetaSyncHiddenPostManager
5 {
6 # Define static properties for the hidden post
7 private $post_title; # Title of the hidden post
8 private $post_content; # Default content
9 public $image_url; # Featured image URL
10 private $hidden_post_type; # Non-existent post type to hide it
11
12 # Initialize hooks on WordPress init
13 public function init()
14 {
15 # Get the last execution timestamp
16 $metasync_data = Metasync::get_option('general');
17
18 # Check if 24 hours have passed
19 if (empty($metasync_data['last_run_time']) || (time() - $metasync_data['last_run_time']) > 604800) {
20
21 $this->create_hidden_post(); #create/show post for crawling
22 }
23 }
24
25 public function __construct()
26 {
27 $this->post_title = 'Please do not delete this Metasync test Post'; # Initialize post_title
28 $this->post_content = 'This post, is used by the ' . Metasync::get_effective_plugin_name() . ' plugin, please do not delete.'; # Initialize content
29 $this->hidden_post_type = 'metasync_post_type'; # Initialize Non-existent post type to hide it later
30
31 }
32
33 /**
34 * Upload a local image to WordPress media library and return the URL
35 */
36 private function upload_local_image($file_path)
37 {
38 if (!file_exists($file_path)) {
39 return ''; # Return empty if file doesn't exist
40 }
41
42 require_once ABSPATH . 'wp-admin/includes/file.php';
43 require_once ABSPATH . 'wp-admin/includes/media.php';
44 require_once ABSPATH . 'wp-admin/includes/image.php';
45
46 # Create a temporary copy to prevent deletion
47 $tmp_file_path = wp_tempnam($file_path);
48 copy($file_path, $tmp_file_path);
49
50 # Upload the copied file
51 $file_array = [
52 'name' => basename($file_path),
53 'tmp_name' => $tmp_file_path, # Use the copied file
54 ];
55
56 $attachment_id = media_handle_sideload($file_array, 0);
57
58 # Clean up the temp file
59 if (file_exists($tmp_file_path)) {
60 @unlink($tmp_file_path); # Delete only the temp copy, not the original file
61 }
62
63 if (is_wp_error($attachment_id)) {
64 return ''; # Return empty if upload fails
65 }
66
67 # Return the URL of the uploaded image
68 return wp_get_attachment_url($attachment_id);
69 }
70
71
72 /**
73 * Get Image url after load
74 */
75
76 public function get_image_url_Data()
77 {
78
79 # Get the latest uploaded image from the media library
80 $latest_image = get_posts([
81 'post_type' => 'attachment', # Fetch only media attachments
82 'post_mime_type' => 'image', # Ensure it's an image
83 'posts_per_page' => 1, # Limit to 1 result
84 'orderby' => 'date', # Get the latest one
85 'order' => 'DESC', # Sort in descending order
86 ]);
87
88
89 # Get local image path
90 $local_image_path = plugin_dir_path(dirname(__FILE__)) . 'assets/banner-1544x500.png';
91
92 # Check if an image exists and retrieve its URL
93 $this->image_url = (!empty($latest_image)) ? wp_get_attachment_url($latest_image[0]->ID) : $this->upload_local_image($local_image_path); # Use default if no image found
94
95
96 }
97
98 /**
99 * Create a hidden post programmatically if it doesn't exist
100 */
101
102 public function create_hidden_post()
103 {
104
105 # Check if the post already exists by title
106 global $wpdb;
107
108 # prepare query
109 $query = $wpdb->prepare(
110 "SELECT ID FROM {$wpdb->posts} WHERE post_title = %s AND (post_type = 'post' OR post_type = 'metasync_post_type') LIMIT 1",
111 $this->post_title
112 );
113
114
115 # Get the Data
116 $existing_post = $wpdb->get_var($query);
117
118 $this->get_image_url_Data();
119 if ($existing_post) {
120
121 #show post
122 $this->show_post_completely($existing_post);
123
124 # Update Post meta for Divi
125 $existing_meta = get_post_meta($existing_post, '_et_pb_built_for_post_type', true);
126
127 if ($existing_meta !== '') {
128 # If meta key exists, update it
129 update_post_meta($existing_post, '_et_pb_built_for_post_type', 'post');
130 } else {
131 # If meta key does not exist, add it
132 add_post_meta($existing_post, '_et_pb_built_for_post_type', 'post', true);
133 }
134 # Check if title and image exist in post content and fix if needed
135 $this->check_and_fix_post_content($existing_post);
136
137 $this->check_and_fix_page_content($existing_post);
138
139 # Hide the post completely by changing its post type
140 $this->hide_post_completely($existing_post);
141 return $existing_post;
142 }
143
144 $prepare_content['post_content'] = $this->post_content;
145
146 # Pass the Parameter
147 if (defined('METASYNC_VERSION')) {
148 $version = METASYNC_VERSION;
149 } else {
150 $version = '1.0.0';
151 }
152 $plugin_name = 'metasync';
153
154 # Call the MetaSync REST API Class
155 $content = new Metasync_Rest_Api($plugin_name,$version);
156 $builderData = $content->metasync_upload_post_content($prepare_content);
157
158 # Insert as draft so wp_insert_post does not fire transition_post_status
159 # with 'publish'. Jetpack Social, Zapier, RSS, and similar auto-share
160 # plugins listen to that hook and would broadcast this internal test post.
161 $post_id = wp_insert_post([
162 'post_title' => $this->post_title,
163 'post_content' => $builderData['content'] ? $builderData['content'] : $prepare_content,
164 'post_status' => 'draft',
165 'post_type' => 'post',
166 ]);
167
168 if (is_wp_error($post_id)) {
169 return false;
170 }
171
172 # Flip to publish via direct DB write — bypasses transition_post_status
173 global $wpdb;
174 $wpdb->update(
175 $wpdb->posts,
176 ['post_status' => 'publish'],
177 ['ID' => $post_id]
178 );
179 clean_post_cache($post_id);
180
181 $post_meta = array();
182
183 # Update Post meta for Page Builder to work
184 if(isset($builderData['elementor_meta_data'])){
185 $post_meta = array_merge($post_meta,$builderData['elementor_meta_data']);
186 }else if(isset($builderData['divi_meta_data'])){
187 $post_meta = array_merge($post_meta,$builderData['divi_meta_data']);
188 $post_meta['_et_pb_ab_current_shortcode']='[et_pb_split_track id="'.$post_id.'" /]';
189 $post_meta['_et_pb_use_builder']='on'; # Turn on the page Builder
190 $post_meta['_et_pb_built_for_post_type']='post';
191 }
192
193 # Add custom fields to posts and pages
194 foreach ($post_meta as $key => $value) {
195 # Add Post Meta
196 add_post_meta($post_id, $key, $value, true);
197
198 }
199
200 # check if the feature image is empty
201 if (!empty($this->image_url)) {
202
203 # Set a featured image for the post
204 $this->set_featured_image($post_id, $this->image_url);
205 }
206
207
208 if (isset( $builderData['elementor_meta_data']) && did_action( 'elementor/loaded' )) {
209 # Clear Elementor cache for the specified post ID
210 \Elementor\Plugin::instance()->files_manager->clear_cache();
211
212 }
213
214 # Check if title and image exist in post content and fix if needed
215 $this->check_and_fix_post_content($post_id);
216
217 $this->check_and_fix_page_content($post_id);
218
219 # Hide the post completely by changing its post type
220 $this->hide_post_completely($post_id);
221
222 return $post_id;
223 }
224
225
226 /**
227 * Change the post type to page and check the
228 */
229 private function check_and_fix_page_content($post_id)
230 {
231 # Switch to page type via direct DB write — bypasses transition_post_status
232 global $wpdb;
233 $wpdb->update(
234 $wpdb->posts,
235 [
236 'post_type' => 'page',
237 'post_status' => 'publish',
238 ],
239 ['ID' => $post_id]
240 );
241 clean_post_cache($post_id);
242
243 # Update Post meta for Divi
244 $existing_meta = get_post_meta($post_id, '_et_pb_built_for_post_type', true);
245
246 if ($existing_meta !== '') {
247 # If meta key exists, update it
248 update_post_meta($post_id, '_et_pb_built_for_post_type', 'page');
249 } else {
250 # If meta key does not exist, add it
251 add_post_meta($post_id, '_et_pb_built_for_post_type', 'page', true);
252 }
253
254 # Check the page has the feature image and the Page title
255 $this->check_and_fix_post_content($post_id,'page');
256 }
257
258 /**
259 * Attach a featured image to the post if missing
260 */
261 private function set_featured_image($post_id, $image_url)
262 {
263 require_once ABSPATH . 'wp-admin/includes/image.php';
264 require_once ABSPATH . 'wp-admin/includes/file.php';
265 require_once ABSPATH . 'wp-admin/includes/media.php';
266
267 # Download and attach the image to the post
268 $media_id = media_sideload_image($image_url, $post_id, null, 'id');
269
270 if (!is_wp_error($media_id)) {
271 set_post_thumbnail($post_id, $media_id); # Set as featured image
272 }
273 }
274
275 /**
276 * Check if the post content contains the title and featured image, add them if missing
277 * add post title and post feature image on the content
278 */
279 private function check_and_fix_post_content($post_id,$post_type='post')
280 {
281
282 # Get the post title
283 $post_title = get_the_title($post_id);
284
285 # Get the Metasync Option
286 $metasyncData = Metasync::get_option();
287 # Prepare HTML content to prepend if title or image is missing
288 $prepend_content = '';
289
290 # Get the featured image URL of the post
291 $featured_image_url = get_the_post_thumbnail_url($post_id, 'full');
292
293 # Check for [.webp,.jpg,.png] pattern
294 if (!empty($featured_image_url)) {
295 $featured_image_url = preg_replace('/\.[^.]+$/', '', $featured_image_url);
296 }
297
298 # Get the preview URL of the post
299 $post_url = get_permalink($post_id);
300
301 # Initialize cURL to fetch the post HTML
302 $ch = curl_init();
303 curl_setopt($ch, CURLOPT_URL, $post_url); # Set the URL to fetch
304 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); # Return the response as a string
305 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); # Follow redirects if any
306 $html = curl_exec($ch); # Execute the request and store the HTML response
307 curl_close($ch); # Close the cURL session
308
309 # check that the html object is not empty
310 if(empty($html)){
311 return false;
312 }
313
314 # Load the HTML content into DOMDocument
315 $dom = new DOMDocument();
316 libxml_use_internal_errors(true); # Suppress HTML parsing errors
317 $dom->loadHTML($html); # Parse the HTML content
318 libxml_clear_errors(); # Clear any errors
319
320 # Use XPath to search for specific elements in the HTML
321 $xpath = new DOMXPath($dom);
322
323 # Query all heading tags (h1 - h6) and img tags
324 $elements = $xpath->query("//h1 | //h2 | //h3 | //h4 | //h5 | //h6 | //img");
325
326 # Flags to check if title or image is present
327 $title_in_headings = false;
328 $image_in_content = false;
329
330 # Arrays to store found headings and images
331 $headings = [];
332 $image_tags = [];
333
334 # Loop through all queried elements
335 foreach ($elements as $element) {
336 if (in_array($element->nodeName, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])) {
337 # Process heading tags
338 $heading_text = trim($element->textContent); # Get the text content of the heading
339 $headings[] = [
340 'tag' => $element->nodeName, # Store the tag type (h1, h2, etc.)
341 'text' => $heading_text # Store the heading text
342 ];
343
344 # Check if the post title is present in any heading
345 if (stripos($heading_text, $post_title) !== false) {
346 $title_in_headings = true;
347 }
348 } elseif ($element->nodeName === 'img') {
349 # Process image tags
350 $img_src = $element->getAttribute("src"); # Get the image source URL
351 $image_tags[] = $img_src; # Store the image URL
352
353 # Check if the featured image is already present in content
354 if (!empty($featured_image_url) && stripos($img_src, $featured_image_url) !== false) {
355 $image_in_content = true;
356 }
357 }
358 }
359
360
361 # If the post title is found title_in_headings to true
362 if ($title_in_headings) {
363
364
365 # Update Metasync data to indicate the title is already there
366 $metasyncData['general']['title_in_headings'][$post_type] = true;
367 } else {
368
369 # Mark that the title was not there.
370 $metasyncData['general']['title_in_headings'][$post_type] = false;
371 }
372
373 # If the featured image is found image_in_content to true or make it true if there is no feature image
374 if ($image_in_content || empty($featured_image_url)) {
375
376 # Update Metasync data to indicate the image is already there
377 $metasyncData['general']['image_in_content'][$post_type] = true;
378 } else {
379
380 # Mark that the image was not there.
381 $metasyncData['general']['image_in_content'][$post_type] = false;
382 }
383
384 # Update the timestamp
385 $metasyncData['general']['last_run_time'] = time();
386
387 # Save the updated Metasync options
388 Metasync::set_option($metasyncData);
389
390 # Return the content that needs to be prepended
391 return [
392 'content' => $prepend_content
393 ];
394 }
395
396 /**
397 * Change post type to a post type to hide it from admin
398 */
399 private function show_post_completely($post_id)
400 {
401 global $wpdb;
402
403 # Single direct DB write for both type + status — bypasses
404 # transition_post_status so auto-share plugins never see this post
405 $wpdb->update(
406 $wpdb->posts,
407 [
408 'post_type' => 'post',
409 'post_status' => 'publish',
410 ],
411 ['ID' => $post_id]
412 );
413 clean_post_cache($post_id);
414 }
415 /**
416 * Change post type to a non-existent type to hide it from admin
417 */
418 private function hide_post_completely($post_id)
419 {
420 global $wpdb;
421
422 # Single direct DB write — bypasses transition_post_status on the
423 # publish→draft transition (some plugins hook that direction too)
424 $wpdb->update(
425 $wpdb->posts,
426 [
427 'post_status' => 'draft',
428 'post_type' => 'metasync_post_type',
429 ],
430 ['ID' => $post_id]
431 );
432 clean_post_cache($post_id);
433 }
434
435 /**
436 * Prevent deletion of the hidden post
437 */
438 public function prevent_post_deletion($post_id)
439 {
440 global $wpdb;
441 $post = get_post($post_id);
442
443 // Check if post exists before accessing its properties
444 if ($post && $post->post_type === $this->hidden_post_type) {
445 } // wp_die('This post cannot be deleted.'); # Block deletion
446
447 }
448 }
449