PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.10
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.10
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 2.5.23 All 138 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.10, at public/class-metasync-hidden-post.php

453 lines 15.5 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 if (!class_exists('DOMDocument')) {
282 error_log('MetaSync: skipping check_and_fix_post_content — DOMDocument (php-dom extension) is not available on this server.');
283 return false;
284 }
285
286 # Get the post title
287 $post_title = get_the_title($post_id);
288
289 # Get the Metasync Option
290 $metasyncData = Metasync::get_option();
291 # Prepare HTML content to prepend if title or image is missing
292 $prepend_content = '';
293
294 # Get the featured image URL of the post
295 $featured_image_url = get_the_post_thumbnail_url($post_id, 'full');
296
297 # Check for [.webp,.jpg,.png] pattern
298 if (!empty($featured_image_url)) {
299 $featured_image_url = preg_replace('/\.[^.]+$/', '', $featured_image_url);
300 }
301
302 # Get the preview URL of the post
303 $post_url = get_permalink($post_id);
304
305 # Initialize cURL to fetch the post HTML
306 $ch = curl_init();
307 curl_setopt($ch, CURLOPT_URL, $post_url); # Set the URL to fetch
308 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); # Return the response as a string
309 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); # Follow redirects if any
310 $html = curl_exec($ch); # Execute the request and store the HTML response
311 curl_close($ch); # Close the cURL session
312
313 # check that the html object is not empty
314 if(empty($html)){
315 return false;
316 }
317
318 # Load the HTML content into DOMDocument
319 $dom = new DOMDocument();
320 libxml_use_internal_errors(true); # Suppress HTML parsing errors
321 $dom->loadHTML($html); # Parse the HTML content
322 libxml_clear_errors(); # Clear any errors
323
324 # Use XPath to search for specific elements in the HTML
325 $xpath = new DOMXPath($dom);
326
327 # Query all heading tags (h1 - h6) and img tags
328 $elements = $xpath->query("//h1 | //h2 | //h3 | //h4 | //h5 | //h6 | //img");
329
330 # Flags to check if title or image is present
331 $title_in_headings = false;
332 $image_in_content = false;
333
334 # Arrays to store found headings and images
335 $headings = [];
336 $image_tags = [];
337
338 # Loop through all queried elements
339 foreach ($elements as $element) {
340 if (in_array($element->nodeName, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])) {
341 # Process heading tags
342 $heading_text = trim($element->textContent); # Get the text content of the heading
343 $headings[] = [
344 'tag' => $element->nodeName, # Store the tag type (h1, h2, etc.)
345 'text' => $heading_text # Store the heading text
346 ];
347
348 # Check if the post title is present in any heading
349 if (stripos($heading_text, $post_title) !== false) {
350 $title_in_headings = true;
351 }
352 } elseif ($element->nodeName === 'img') {
353 # Process image tags
354 $img_src = $element->getAttribute("src"); # Get the image source URL
355 $image_tags[] = $img_src; # Store the image URL
356
357 # Check if the featured image is already present in content
358 if (!empty($featured_image_url) && stripos($img_src, $featured_image_url) !== false) {
359 $image_in_content = true;
360 }
361 }
362 }
363
364
365 # If the post title is found title_in_headings to true
366 if ($title_in_headings) {
367
368
369 # Update Metasync data to indicate the title is already there
370 $metasyncData['general']['title_in_headings'][$post_type] = true;
371 } else {
372
373 # Mark that the title was not there.
374 $metasyncData['general']['title_in_headings'][$post_type] = false;
375 }
376
377 # If the featured image is found image_in_content to true or make it true if there is no feature image
378 if ($image_in_content || empty($featured_image_url)) {
379
380 # Update Metasync data to indicate the image is already there
381 $metasyncData['general']['image_in_content'][$post_type] = true;
382 } else {
383
384 # Mark that the image was not there.
385 $metasyncData['general']['image_in_content'][$post_type] = false;
386 }
387
388 # Update the timestamp
389 $metasyncData['general']['last_run_time'] = time();
390
391 # Save the updated Metasync options
392 Metasync::set_option($metasyncData);
393
394 # Return the content that needs to be prepended
395 return [
396 'content' => $prepend_content
397 ];
398 }
399
400 /**
401 * Change post type to a post type to hide it from admin
402 */
403 private function show_post_completely($post_id)
404 {
405 global $wpdb;
406
407 # Single direct DB write for both type + status — bypasses
408 # transition_post_status so auto-share plugins never see this post
409 $wpdb->update(
410 $wpdb->posts,
411 [
412 'post_type' => 'post',
413 'post_status' => 'publish',
414 ],
415 ['ID' => $post_id]
416 );
417 clean_post_cache($post_id);
418 }
419 /**
420 * Change post type to a non-existent type to hide it from admin
421 */
422 private function hide_post_completely($post_id)
423 {
424 global $wpdb;
425
426 # Single direct DB write — bypasses transition_post_status on the
427 # publish→draft transition (some plugins hook that direction too)
428 $wpdb->update(
429 $wpdb->posts,
430 [
431 'post_status' => 'draft',
432 'post_type' => 'metasync_post_type',
433 ],
434 ['ID' => $post_id]
435 );
436 clean_post_cache($post_id);
437 }
438
439 /**
440 * Prevent deletion of the hidden post
441 */
442 public function prevent_post_deletion($post_id)
443 {
444 global $wpdb;
445 $post = get_post($post_id);
446
447 // Check if post exists before accessing its properties
448 if ($post && $post->post_type === $this->hidden_post_type) {
449 } // wp_die('This post cannot be deleted.'); # Block deletion
450
451 }
452 }
453