PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.6
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.6
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
metasync / includes / class-metasync-admin-ajax.php

class-metasync-admin-ajax.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.6, at includes/class-metasync-admin-ajax.php

1,330 lines 52.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5
6 class Metasync_Admin_Ajax
7 {
8 private static $instance = null;
9
10 private $db_redirection = null;
11
12 public static function instance()
13 {
14 if (null === self::$instance) {
15 self::$instance = new self();
16 }
17 return self::$instance;
18 }
19
20 private function __construct() {}
21
22 private function get_db_redirection()
23 {
24 if (null === $this->db_redirection) {
25 $this->db_redirection = new Metasync_Redirection_Database();
26 }
27 return $this->db_redirection;
28 }
29
30 public function ajax_import_external_data()
31 {
32 $execution_time = Metasync_Settings_Fields::instance()->get_execution_setting('max_execution_time');
33 if (function_exists('set_time_limit')) {
34 @set_time_limit($execution_time);
35 }
36
37 Metasync_Settings_Fields::instance()->apply_memory_limit();
38
39 check_ajax_referer('metasync_import_external_data', 'nonce');
40
41 if (!Metasync::current_user_has_plugin_access()) {
42 wp_send_json_error(['message' => 'Insufficient permissions.']);
43 }
44
45 $type = isset($_POST['type']) ? sanitize_text_field($_POST['type']) : '';
46 $plugin = isset($_POST['plugin']) ? sanitize_text_field($_POST['plugin']) : '';
47
48 if (empty($type) || empty($plugin)) {
49 wp_send_json_error(['message' => 'Missing required parameters.']);
50 }
51
52 require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-external-importer.php';
53 $importer = new Metasync_External_Importer($this->get_db_redirection());
54 $result = ['success' => false, 'message' => 'Unknown import type.'];
55
56 switch ($type) {
57 case 'redirections':
58 $result = $importer->import_redirections($plugin);
59 break;
60 case 'sitemap':
61 $result = $importer->import_sitemap($plugin);
62 break;
63 case 'robots':
64 $result = $importer->import_robots($plugin);
65 break;
66 case 'indexation':
67 $result = $importer->import_indexation($plugin);
68 break;
69 case 'schema':
70 $result = $importer->import_schema($plugin);
71 break;
72 }
73
74 if ($result['success']) {
75 wp_send_json_success($result);
76 } else {
77 wp_send_json_error($result);
78 }
79 }
80
81 public function ajax_import_seo_metadata()
82 {
83 $execution_time = Metasync_Settings_Fields::instance()->get_execution_setting('max_execution_time');
84 if (function_exists('set_time_limit')) {
85 @set_time_limit($execution_time);
86 }
87
88 Metasync_Settings_Fields::instance()->apply_memory_limit();
89
90 check_ajax_referer('metasync_import_seo_metadata', 'nonce');
91
92 if (!Metasync::current_user_has_plugin_access()) {
93 wp_send_json_error(['message' => 'Insufficient permissions.']);
94 }
95
96 $plugin = isset($_POST['plugin']) ? sanitize_text_field($_POST['plugin']) : '';
97 $import_titles = isset($_POST['import_titles']) ? (bool) intval($_POST['import_titles']) : true;
98 $import_descriptions = isset($_POST['import_descriptions']) ? (bool) intval($_POST['import_descriptions']) : true;
99 $overwrite_existing = isset($_POST['overwrite_existing']) ? (bool) intval($_POST['overwrite_existing']) : false;
100 $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
101
102 if (empty($plugin)) {
103 wp_send_json_error(['message' => 'Missing required plugin parameter.']);
104 }
105
106 require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-metasync-external-importer.php';
107 $importer = new Metasync_External_Importer($this->get_db_redirection());
108
109 $options = [
110 'import_titles' => $import_titles,
111 'import_descriptions' => $import_descriptions,
112 'overwrite_existing' => $overwrite_existing,
113 'batch_size' => 50,
114 'offset' => $offset
115 ];
116
117 $result = $importer->import_seo_metadata($plugin, $options);
118
119 if ($result['success']) {
120 wp_send_json_success($result);
121 } else {
122 wp_send_json_error($result);
123 }
124 }
125
126 public function lgSendCustomerParams()
127 {
128 $sync_request = new Metasync_Sync_Requests();
129
130 # use the existing apikey for backward compatibility
131 $general_options = Metasync::get_option('general') ?? [];
132 $token = $general_options['apikey'] ?? null;
133
134 # get the response
135 $response = $sync_request->SyncCustomerParams($token);
136
137 // Check if response is a throttling error object
138 if (is_object($response) && isset($response->throttled) && $response->throttled === true) {
139 wp_send_json($response);
140 wp_die();
141 }
142
143 // Check if response is null/false (other error cases)
144 if ($response === null || $response === false) {
145 wp_send_json(['error' => 'Sync failed - no response from sync method', 'detail' => 'The sync method returned null or false']);
146 wp_die();
147 }
148
149 $responseBody = wp_remote_retrieve_body($response);
150 $responseCode = wp_remote_retrieve_response_code($response);
151
152 if ($responseCode == 200) {
153 $dt = new DateTime();
154 $send_auth_token_timestamp = Metasync::get_option();
155 $send_auth_token_timestamp['general']['send_auth_token_timestamp'] = $dt->format('M d, Y h:i:s A');;
156 Metasync::set_option($send_auth_token_timestamp);
157
158 Metasync_Heartbeat_Manager::instance()->update_heartbeat_cache_after_sync(true, 'Sync Now - successful data sync');
159
160 $result = json_decode($responseBody);
161 if ( ! is_object( $result ) ) {
162 $result = new stdClass();
163 }
164 $timestamp = Metasync::get_option('general')['send_auth_token_timestamp'] ?? '';
165 $result->send_auth_token_timestamp = $timestamp;
166 $result->send_auth_token_diffrence = Metasync_Settings_Fields::instance()->time_elapsed_string($timestamp);
167 wp_send_json($result);
168 wp_die();
169 } else {
170 Metasync_Heartbeat_Manager::instance()->update_heartbeat_cache_after_sync(false, 'Sync Now - failed data sync');
171 }
172
173 $result = json_decode($responseBody);
174 wp_send_json($result);
175 wp_die();
176 }
177
178 public function ajax_update_db_structure()
179 {
180 if (!Metasync::current_user_has_plugin_access()) {
181 wp_die('Insufficient permissions');
182 }
183
184 if (empty($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'metasync_update_db_nonce')) {
185 wp_die('Security check failed');
186 }
187
188 try {
189 $this->get_db_redirection()->force_table_update();
190
191 wp_send_json_success('Database structure updated successfully');
192 } catch (Exception $e) {
193 wp_send_json_error('Database update failed: ' . $e->getMessage());
194 }
195 }
196
197 public function ajax_save_wizard_progress()
198 {
199 check_ajax_referer('metasync_wizard', 'nonce');
200
201 if (!Metasync::current_user_has_plugin_access()) {
202 wp_send_json_error(array('message' => 'Insufficient permissions'));
203 }
204
205 $step = isset($_POST['step']) ? intval($_POST['step']) : 0;
206 $data = isset($_POST['data']) ? $_POST['data'] : array();
207
208 $options = get_option('metasync_options', array());
209
210 if (isset($data['verification'])) {
211 if (!isset($options['general'])) {
212 $options['general'] = array();
213 }
214 $options['general']['google_verification'] = sanitize_text_field($data['verification']['google']);
215 $options['general']['bing_verification'] = sanitize_text_field($data['verification']['bing']);
216 }
217
218 if (isset($data['seo_settings'])) {
219 if (!isset($options['seo_controls'])) {
220 $options['seo_controls'] = array();
221 }
222
223 $options['seo_controls']['index_date_archives'] = $data['seo_settings']['date_archives'] ? 'false' : 'true';
224 $options['seo_controls']['index_author_archives'] = $data['seo_settings']['author_archives'] ? 'false' : 'true';
225 $options['seo_controls']['index_category_archives'] = $data['seo_settings']['category_archives'] ? 'false' : 'true';
226 $options['seo_controls']['index_tag_archives'] = $data['seo_settings']['tag_archives'] ? 'false' : 'true';
227 }
228
229 if (isset($data['schema'])) {
230 if (!isset($options['general'])) {
231 $options['general'] = array();
232 }
233 $options['general']['enable_schema_markup'] = $data['schema']['enabled'];
234 $options['general']['default_schema_type'] = sanitize_text_field($data['schema']['default_type']);
235 }
236
237 update_option('metasync_options', $options);
238
239 wp_send_json_success(array('message' => 'Progress saved'));
240 }
241
242 public function ajax_complete_wizard()
243 {
244 check_ajax_referer('metasync_wizard', 'nonce');
245
246 if (!Metasync::current_user_has_plugin_access()) {
247 wp_send_json_error(array('message' => 'Insufficient permissions'));
248 }
249
250 update_option('metasync_wizard_completed', array(
251 'completed' => true,
252 'completed_at' => current_time('mysql'),
253 'completed_by' => get_current_user_id(),
254 'version' => METASYNC_VERSION
255 ));
256
257 $user_id = get_current_user_id();
258 delete_transient("metasync_wizard_state_{$user_id}");
259
260 wp_send_json_success(array('message' => 'Wizard completed'));
261 }
262
263 public function ajax_validate_robots()
264 {
265 if (!Metasync::current_user_has_plugin_access()) {
266 wp_send_json_error('Insufficient permissions');
267 return;
268 }
269
270 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php';
271 $robots_txt = Metasync_Robots_Txt::get_instance();
272
273 $content = isset($_POST['content']) ? wp_unslash($_POST['content']) : '';
274 $validation = $robots_txt->validate_content($content);
275
276 wp_send_json_success($validation);
277 }
278
279 public function ajax_get_default_robots()
280 {
281 if (!Metasync::current_user_has_plugin_access()) {
282 wp_send_json_error('Insufficient permissions');
283 return;
284 }
285
286 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php';
287 $robots_txt = Metasync_Robots_Txt::get_instance();
288
289 wp_send_json_success(array(
290 'content' => $robots_txt->get_default_robots_content()
291 ));
292 }
293
294 public function ajax_preview_robots_backup()
295 {
296 if (!Metasync::current_user_has_plugin_access()) {
297 wp_send_json_error('Insufficient permissions');
298 return;
299 }
300
301 $backup_id = isset($_POST['backup_id']) ? intval($_POST['backup_id']) : 0;
302
303 if (!$backup_id) {
304 wp_send_json_error('Invalid backup ID');
305 return;
306 }
307
308 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt-database.php';
309 $database = Metasync_Robots_Txt_Database::get_instance();
310
311 $backup = $database->get_backup($backup_id);
312
313 if (!$backup) {
314 wp_send_json_error('Backup not found');
315 return;
316 }
317
318 wp_send_json_success(array(
319 'content' => $backup['content'],
320 'created_at' => $backup['created_at'],
321 'created_by_name' => isset($backup['created_by_name']) ? $backup['created_by_name'] : ''
322 ));
323 }
324
325 public function ajax_delete_robots_backup()
326 {
327 if (!Metasync::current_user_has_plugin_access()) {
328 wp_send_json_error(array('message' => esc_html__('Insufficient permissions', 'metasync')));
329 return;
330 }
331
332 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'metasync_delete_robots_backup')) {
333 wp_send_json_error(array('message' => esc_html__('Security check failed', 'metasync')));
334 return;
335 }
336
337 $backup_id = isset($_POST['backup_id']) ? intval($_POST['backup_id']) : 0;
338
339 if (!$backup_id) {
340 wp_send_json_error(array('message' => esc_html__('Invalid backup ID', 'metasync')));
341 return;
342 }
343
344 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php';
345 $robots_txt = Metasync_Robots_Txt::get_instance();
346
347 $result = $robots_txt->delete_backup($backup_id);
348
349 if ($result) {
350 wp_send_json_success(array('message' => esc_html__('Backup deleted successfully!', 'metasync')));
351 } else {
352 wp_send_json_error(array('message' => esc_html__('Failed to delete backup.', 'metasync')));
353 }
354 }
355
356 public function ajax_restore_robots_backup()
357 {
358 if (!Metasync::current_user_has_plugin_access()) {
359 wp_send_json_error(array('message' => esc_html__('Insufficient permissions', 'metasync')));
360 return;
361 }
362
363 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'metasync_restore_robots_backup')) {
364 wp_send_json_error(array('message' => esc_html__('Security check failed', 'metasync')));
365 return;
366 }
367
368 $backup_id = isset($_POST['backup_id']) ? intval($_POST['backup_id']) : 0;
369
370 if (!$backup_id) {
371 wp_send_json_error(array('message' => esc_html__('Invalid backup ID', 'metasync')));
372 return;
373 }
374
375 require_once plugin_dir_path(dirname(__FILE__)) . 'robots-txt/class-metasync-robots-txt.php';
376 $robots_txt = Metasync_Robots_Txt::get_instance();
377
378 $result = $robots_txt->restore_backup($backup_id);
379
380 if (is_wp_error($result)) {
381 wp_send_json_error(array('message' => $result->get_error_message()));
382 } else {
383 $current_content = $robots_txt->read_robots_file();
384
385 if (is_wp_error($current_content)) {
386 wp_send_json_error(array('message' => $current_content->get_error_message()));
387 } else {
388 wp_send_json_success(array(
389 'message' => esc_html__('robots.txt restored from backup successfully!', 'metasync'),
390 'content' => $current_content
391 ));
392 }
393 }
394 }
395
396 public function ajax_create_redirect_from_404()
397 {
398 if (empty($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'metasync_404_redirect')) {
399 wp_die('Security check failed');
400 }
401
402 if (!Metasync::current_user_has_plugin_access()) {
403 wp_die('Insufficient permissions');
404 }
405
406 $uri = sanitize_text_field($_POST['uri']);
407 $redirect_url = sanitize_url($_POST['redirect_url']);
408
409 if (empty($uri) || empty($redirect_url)) {
410 wp_send_json_error('Missing required parameters');
411 }
412
413 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor-database.php';
414 require_once plugin_dir_path(dirname(__FILE__)) . '404-monitor/class-metasync-404-monitor.php';
415 $db_404 = new Metasync_Error_Monitor_Database();
416 $monitor_404 = new Metasync_Error_Monitor($db_404);
417
418 $result = $monitor_404->create_redirection_from_404($uri, $redirect_url, 'Created from 404 suggestion');
419
420 if ($result) {
421 wp_send_json_success('Redirect created successfully');
422 } else {
423 wp_send_json_error('Failed to create redirect');
424 }
425 }
426
427 public function ajax_test_host_blocking_get()
428 {
429 if (!Metasync::current_user_has_plugin_access()) {
430 wp_send_json_error('Insufficient permissions');
431 return;
432 }
433
434 $endpoint = 'https://wp-check.searchatlas.com/ping';
435 $start_time = microtime(true);
436
437 $response = wp_remote_get($endpoint, array(
438 'timeout' => 30,
439 'user-agent' => 'MetaSync Plugin Host Test',
440 'headers' => array(
441 'Accept' => 'application/json',
442 'Content-Type' => 'application/json',
443 'Origin' => home_url(),
444 'Referer' => admin_url(),
445 'X-WordPress-Site' => home_url()
446 )
447 ));
448
449 $end_time = microtime(true);
450 $response_time = round(($end_time - $start_time) * 1000, 2);
451
452 if (is_wp_error($response)) {
453 wp_send_json_success(array(
454 'method' => 'GET',
455 'status' => 'error',
456 'response_time' => $response_time . 'ms',
457 'error' => $response->get_error_message(),
458 'blocked' => true,
459 'details' => 'Request failed - possible blocking detected'
460 ));
461 } else {
462 $status_code = wp_remote_retrieve_response_code($response);
463 $body = wp_remote_retrieve_body($response);
464 $headers = wp_remote_retrieve_headers($response);
465
466 $is_blocked = false;
467 $status_text = 'success';
468 $details = 'GET request completed successfully';
469 $parsed_response = null;
470
471 if ($status_code === 200) {
472 $parsed_response = json_decode($body, true);
473
474 if ($parsed_response && isset($parsed_response['results']['get'])) {
475 $get_result = $parsed_response['results']['get'];
476 $get_status_code = isset($get_result['statusCode']) ? $get_result['statusCode'] : null;
477
478 if ($get_status_code !== 200) {
479 $is_blocked = true;
480 $status_text = 'error';
481 $details = "GET request to target site returned status code {$get_status_code} - host blocking detected";
482 }
483 } else {
484 $is_blocked = true;
485 $status_text = 'error';
486 $details = 'Unable to parse response structure - possible blocking or endpoint issue';
487 }
488 } else {
489 $is_blocked = true;
490 $status_text = 'error';
491 $details = "External endpoint returned status code {$status_code} - possible blocking detected";
492 }
493
494 wp_send_json_success(array(
495 'method' => 'GET',
496 'status' => $status_text,
497 'response_time' => $response_time . 'ms',
498 'status_code' => $status_code,
499 'body' => $body,
500 'headers' => $headers->getAll(),
501 'blocked' => $is_blocked,
502 'details' => $details,
503 'parsed_response' => $parsed_response
504 ));
505 }
506 }
507
508 public function ajax_test_host_blocking_post()
509 {
510 if (!Metasync::current_user_has_plugin_access()) {
511 wp_send_json_error('Insufficient permissions');
512 return;
513 }
514
515 $endpoint = 'https://wp-check.searchatlas.com/ping';
516 $start_time = microtime(true);
517
518 $test_data = array(
519 'test' => 'host_blocking_test',
520 'timestamp' => current_time('mysql'),
521 'source' => 'metasync_plugin',
522 'method' => 'POST'
523 );
524
525 $response = wp_remote_post($endpoint, array(
526 'timeout' => 30,
527 'user-agent' => 'MetaSync Plugin Host Test',
528 'headers' => array(
529 'Accept' => 'application/json',
530 'Content-Type' => 'application/json',
531 'Origin' => home_url(),
532 'Referer' => admin_url(),
533 'X-WordPress-Site' => home_url()
534 ),
535 'body' => json_encode($test_data)
536 ));
537
538 $end_time = microtime(true);
539 $response_time = round(($end_time - $start_time) * 1000, 2);
540
541 if (is_wp_error($response)) {
542 wp_send_json_success(array(
543 'method' => 'POST',
544 'status' => 'error',
545 'response_time' => $response_time . 'ms',
546 'error' => $response->get_error_message(),
547 'blocked' => true,
548 'details' => 'Request failed - possible blocking detected',
549 'sent_data' => $test_data
550 ));
551 } else {
552 $status_code = wp_remote_retrieve_response_code($response);
553 $body = wp_remote_retrieve_body($response);
554 $headers = wp_remote_retrieve_headers($response);
555
556 $is_blocked = false;
557 $status_text = 'success';
558 $details = 'POST request completed successfully';
559 $parsed_response = null;
560
561 if ($status_code === 200) {
562 $parsed_response = json_decode($body, true);
563
564 if ($parsed_response && isset($parsed_response['results']['post'])) {
565 $post_result = $parsed_response['results']['post'];
566 $post_status_code = isset($post_result['statusCode']) ? $post_result['statusCode'] : null;
567
568 if ($post_status_code !== 200) {
569 $is_blocked = true;
570 $status_text = 'error';
571 $details = "POST request to target site returned status code {$post_status_code} - host blocking detected";
572 }
573 } else {
574 $is_blocked = true;
575 $status_text = 'error';
576 $details = 'Unable to parse response structure - possible blocking or endpoint issue';
577 }
578 } else {
579 $is_blocked = true;
580 $status_text = 'error';
581 $details = "External endpoint returned status code {$status_code} - possible blocking detected";
582 }
583
584 wp_send_json_success(array(
585 'method' => 'POST',
586 'status' => $status_text,
587 'response_time' => $response_time . 'ms',
588 'status_code' => $status_code,
589 'body' => $body,
590 'headers' => $headers->getAll(),
591 'blocked' => $is_blocked,
592 'details' => $details,
593 'sent_data' => $test_data,
594 'parsed_response' => $parsed_response
595 ));
596 }
597 }
598
599 public function execute_transient_cleanup()
600 {
601 global $wpdb;
602
603 $cleanup_stats = array(
604 'expired_transients' => 0,
605 'plugin_transients' => 0,
606 'rate_limit_transients' => 0,
607 'telemetry_transients' => 0,
608 'start_time' => microtime(true)
609 );
610
611 try {
612 delete_expired_transients(true);
613 $cleanup_stats['expired_transients'] = 'cleaned_by_wordpress';
614
615 $plugin_transients = $wpdb->get_results(
616 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_metasync_%'",
617 ARRAY_A
618 );
619
620 foreach ($plugin_transients as $transient) {
621 $transient_name = str_replace('_transient_', '', $transient['option_name']);
622 delete_transient($transient_name);
623 $cleanup_stats['plugin_transients']++;
624 }
625
626 $rate_limit_transients = $wpdb->get_results(
627 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_sa_connect_rate_limit_%'",
628 ARRAY_A
629 );
630
631 foreach ($rate_limit_transients as $transient) {
632 $transient_name = str_replace('_transient_', '', $transient['option_name']);
633 delete_transient($transient_name);
634 $cleanup_stats['rate_limit_transients']++;
635 }
636
637 $telemetry_transients = $wpdb->get_results(
638 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_metasync_telemetry_%'",
639 ARRAY_A
640 );
641
642 foreach ($telemetry_transients as $transient) {
643 $transient_name = str_replace('_transient_', '', $transient['option_name']);
644 delete_transient($transient_name);
645 $cleanup_stats['telemetry_transients']++;
646 }
647
648 $sa_connect_success_transients = $wpdb->get_results(
649 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_metasync_sa_connect_success_%'",
650 ARRAY_A
651 );
652
653 foreach ($sa_connect_success_transients as $transient) {
654 $transient_name = str_replace('_transient_', '', $transient['option_name']);
655 delete_transient($transient_name);
656 $cleanup_stats['sa_connect_success_transients'] = ($cleanup_stats['sa_connect_success_transients'] ?? 0) + 1;
657 }
658
659 $cleanup_stats['execution_time'] = round((microtime(true) - $cleanup_stats['start_time']) * 1000, 2);
660 $cleanup_stats['next_run'] = wp_next_scheduled('metasync_cleanup_transients') ?
661 date('Y-m-d H:i:s T', wp_next_scheduled('metasync_cleanup_transients')) : 'N/A';
662
663 error_log('MetaSync: Transient cleanup completed - ' . json_encode($cleanup_stats));
664
665 } catch (Exception $e) {
666 error_log('MetaSync: Transient cleanup failed - ' . $e->getMessage());
667 }
668 }
669
670 public function ajax_submit_issue_report()
671 {
672 try {
673 # Verify nonce
674 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'metasync_report_issue')) {
675 wp_send_json_error(array('message' => 'Security verification failed.'));
676 return;
677 }
678
679 # Check user capabilities
680 if (!Metasync::current_user_has_plugin_access()) {
681 wp_send_json_error(array('message' => 'Insufficient permissions.'));
682 return;
683 }
684
685 # Get and validate form data
686 $issue_message = isset($_POST['issue_message']) ? sanitize_textarea_field(wp_unslash($_POST['issue_message'])) : '';
687 $issue_severity = isset($_POST['issue_severity']) ? sanitize_text_field(wp_unslash($_POST['issue_severity'])) : 'warning';
688 $include_user_info = isset($_POST['include_user_info']) && sanitize_text_field(wp_unslash($_POST['include_user_info'])) === 'true';
689
690 # Validate severity level
691 $valid_severity_levels = array('info', 'warning', 'error', 'fatal');
692 if (!in_array($issue_severity, $valid_severity_levels, true)) {
693 $issue_severity = 'warning';
694 }
695
696 # Validate message length
697 if (empty($issue_message) || strlen($issue_message) < 10) {
698 wp_send_json_error(array('message' => 'Please provide a more detailed description (at least 10 characters).'));
699 return;
700 }
701 if (strlen($issue_message) > 1000) {
702 wp_send_json_error(array('message' => 'Message is too long. Please limit to 1000 characters.'));
703 return;
704 }
705
706 # Handle file upload if present
707 $attachment = null;
708 if (!empty($_FILES['issue_attachment']['tmp_name'])) {
709 # Validate file type
710 $allowed_types = array('image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp');
711 $file_type = $_FILES['issue_attachment']['type'];
712
713 if (!in_array($file_type, $allowed_types, true)) {
714 wp_send_json_error(array('message' => 'Invalid file type. Please upload a JPEG, PNG, GIF, or WebP image.'));
715 return;
716 }
717
718 # Validate file size (5MB max)
719 $max_size = 5 * 1024 * 1024; // 5MB
720 if ($_FILES['issue_attachment']['size'] > $max_size) {
721 wp_send_json_error(array('message' => 'File size exceeds 5MB. Please choose a smaller file.'));
722 return;
723 }
724
725 # Read file contents
726 $file_contents = file_get_contents($_FILES['issue_attachment']['tmp_name']);
727 if ($file_contents !== false) {
728 $attachment = array(
729 'filename' => sanitize_file_name($_FILES['issue_attachment']['name']),
730 'data' => $file_contents,
731 'content_type' => $file_type
732 );
733 }
734 }
735
736 # Get general options (same way as used throughout the plugin)
737 $general_options = Metasync::get_option('general');
738 if (!is_array($general_options)) {
739 $general_options = array();
740 }
741
742 $project_uuid = isset($general_options['otto_pixel_uuid']) ? sanitize_text_field($general_options['otto_pixel_uuid']) : '';
743
744 # Always use standardized title format for Sentry prioritization
745 $issue_title = !empty($project_uuid) ? 'Client Report ' . $project_uuid : 'Client Report (UUID Not Configured)';
746
747 # Collect system information with error handling
748 $active_plugins = get_option('active_plugins');
749 $plugin_count = is_array($active_plugins) ? count($active_plugins) : 0;
750
751 $active_theme = wp_get_theme();
752 $theme_name = is_object($active_theme) ? $active_theme->get('Name') : get_template();
753
754 $system_context = array(
755 'report_type' => 'manual_client_report',
756 'website_url' => esc_url_raw(home_url()),
757 'site_title' => sanitize_text_field(get_bloginfo('name')),
758 'admin_email' => sanitize_email(get_bloginfo('admin_email')),
759 'plugin_version' => defined('METASYNC_VERSION') ? METASYNC_VERSION : '1.0.0',
760 'plugin_name' => 'Search Engine Labs SEO (MetaSync)',
761 'wordpress_version' => get_bloginfo('version'),
762 'php_version' => PHP_VERSION,
763 'active_theme' => $theme_name,
764 'memory_limit' => ini_get('memory_limit'),
765 'multisite' => is_multisite(),
766 'project_uuid' => $project_uuid,
767 'active_plugins' => $plugin_count,
768 'report_timestamp' => current_time('mysql'),
769 'severity_level' => $issue_severity
770 );
771
772 # Add user information if requested
773 if ($include_user_info) {
774 $current_user = wp_get_current_user();
775 if ($current_user && $current_user->ID > 0) {
776 $system_context['reporter'] = array(
777 'username' => sanitize_user($current_user->user_login),
778 'email' => sanitize_email($current_user->user_email),
779 'display_name' => sanitize_text_field($current_user->display_name),
780 'roles' => is_array($current_user->roles) ? $current_user->roles : array()
781 );
782 }
783 }
784
785 # Send to Sentry using User Feedback API
786 $sent_to_sentry = false;
787
788 # Check if Sentry feedback function exists
789 if (!function_exists('metasync_sentry_capture_feedback')) {
790 # Log warning if function doesn't exist
791 if (defined('WP_DEBUG') && WP_DEBUG) {
792 error_log('MetaSync: Sentry feedback function not available for report submission.');
793 }
794 } else {
795 $feedback_data = array(
796 'message' => $issue_message,
797 'severity' => $issue_severity
798 );
799
800 # Add user information if requested
801 if ($include_user_info) {
802 $current_user = wp_get_current_user();
803 if ($current_user && $current_user->ID > 0) {
804 $feedback_data['name'] = sanitize_text_field($current_user->display_name);
805 $feedback_data['email'] = sanitize_email($current_user->user_email);
806 }
807 }
808
809 $sent_to_sentry = metasync_sentry_capture_feedback($feedback_data, $attachment);
810 }
811
812 if ($sent_to_sentry) {
813 wp_send_json_success(array(
814 'message' => 'Report submitted successfully! Our team will review it shortly.',
815 'project_uuid' => $project_uuid,
816 'report_title' => esc_html($issue_title)
817 ));
818 } else {
819 # Fallback: Log locally if Sentry fails or is unavailable
820 if (defined('WP_DEBUG') && WP_DEBUG) {
821 error_log(sprintf(
822 'MetaSync Client Report (Fallback): UUID: %s | Title: %s | Message: %s | Severity: %s',
823 $project_uuid,
824 $issue_title,
825 $issue_message,
826 $issue_severity
827 ));
828 }
829
830 wp_send_json_success(array(
831 'message' => 'Report logged locally. Note: Remote reporting may be unavailable.',
832 'project_uuid' => $project_uuid,
833 'report_title' => esc_html($issue_title),
834 'fallback' => true
835 ));
836 }
837
838 } catch (Exception $e) {
839 # Log the error securely (only in debug mode)
840 if (defined('WP_DEBUG') && WP_DEBUG) {
841 error_log(sprintf(
842 'MetaSync Report Submission Error: %s in %s on line %d',
843 $e->getMessage(),
844 $e->getFile(),
845 $e->getLine()
846 ));
847 }
848
849 # Send generic error message to client
850 wp_send_json_error(array('message' => 'Failed to submit report. Please try again later.'));
851 }
852 }
853
854 public function ajax_recover_password()
855 {
856 try {
857 if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'metasync_recover_password_nonce')) {
858 wp_send_json_error(array('message' => 'Security verification failed.'));
859 return;
860 }
861
862 $whitelabel_settings = Metasync::get_whitelabel_settings();
863 $password = $whitelabel_settings['settings_password'] ?? '';
864 $recovery_email = $whitelabel_settings['recovery_email'] ?? '';
865
866 if (empty($password)) {
867 wp_send_json_error(array('message' => 'No password is configured for recovery.'));
868 return;
869 }
870
871 if (empty($recovery_email) || !is_email($recovery_email)) {
872 wp_send_json_error(array('message' => 'No valid recovery email is configured. Please contact your administrator.'));
873 return;
874 }
875
876 $site_name = get_bloginfo('name');
877 $site_url = home_url();
878 $plugin_name = Metasync::get_effective_plugin_name('');
879 $settings_url = admin_url('admin.php?page=' . Metasync_Admin::$page_slug . '&tab=whitelabel');
880 $to = $recovery_email;
881
882 $subject = sprintf('[%s] Settings Password Recovery', $site_name);
883
884 $message = sprintf(
885 "Hello,\n\n" .
886 "A password recovery request was made for the %s settings on %s.\n\n" .
887 "Your Settings Password is:\n%s\n\n" .
888 "You can use this password to access the protected settings at:\n%s\n\n" .
889 "If you did not request this password recovery, please secure your WordPress admin account immediately.\n\n" .
890 "---\n" .
891 "This is an automated message from %s\n%s",
892 $plugin_name,
893 $site_name,
894 $password,
895 $settings_url,
896 $site_name,
897 $site_url
898 );
899
900 $from_name = !empty($whitelabel_settings['company_name'])
901 ? $whitelabel_settings['company_name']
902 : $site_name;
903
904 $headers = array(
905 'Content-Type: text/plain; charset=UTF-8',
906 sprintf('From: %s <%s>', $from_name, get_option('admin_email'))
907 );
908
909 $mail_error = '';
910 add_action('wp_mail_failed', function($error) use (&$mail_error) {
911 $mail_error = $error->get_error_message();
912 });
913
914 $sent = wp_mail($to, $subject, $message, $headers);
915
916 if ($sent) {
917 wp_send_json_success(array(
918 'message' => sprintf('Password recovery email sent to %s', esc_html($recovery_email))
919 ));
920 } else {
921 $error_message = 'Failed to send recovery email. ';
922 if (!empty($mail_error)) {
923 $error_message .= 'Error: ' . $mail_error;
924 } else {
925 $error_message .= 'Your server may not be configured to send emails. Please check your email configuration or contact your administrator.';
926 }
927
928 wp_send_json_error(array('message' => $error_message));
929 }
930
931 } catch (Exception $e) {
932 if (defined('WP_DEBUG') && WP_DEBUG) {
933 error_log(sprintf(
934 'MetaSync Password Recovery Error: %s in %s on line %d',
935 $e->getMessage(),
936 $e->getFile(),
937 $e->getLine()
938 ));
939 }
940
941 wp_send_json_error(array('message' => 'An error occurred while processing your request. Please try again later.'));
942 }
943 }
944
945 public function ajax_save_theme()
946 {
947 try {
948 # Verify nonce for security
949 if (!isset($_POST['_ajax_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_ajax_nonce'])), 'metasync_theme_nonce')) {
950 wp_send_json_error(array('message' => 'Security verification failed.'));
951 return;
952 }
953
954 # Check user capabilities
955 if (!Metasync::current_user_has_plugin_access()) {
956 wp_send_json_error(array('message' => 'Insufficient permissions.'));
957 return;
958 }
959
960 # Get and validate theme value
961 $theme = isset($_POST['theme']) ? sanitize_text_field(wp_unslash($_POST['theme'])) : '';
962
963 # Validate theme is either 'light' or 'dark'
964 if (!in_array($theme, array('light', 'dark'), true)) {
965 wp_send_json_error(array('message' => 'Invalid theme value.'));
966 return;
967 }
968
969 # Save theme preference to WordPress options
970 update_option('metasync_theme', $theme, true);
971
972 # Send success response
973 wp_send_json_success(array(
974 'message' => 'Theme preference saved successfully.',
975 'theme' => $theme
976 ));
977
978 } catch (Exception $e) {
979 # Log error if debug is enabled
980 if (defined('WP_DEBUG') && WP_DEBUG) {
981 error_log('MetaSync Theme Save Error: ' . $e->getMessage());
982 }
983
984 wp_send_json_error(array('message' => 'Failed to save theme preference.'));
985 }
986 }
987
988 public function ajax_track_one_click_activation()
989 {
990 # Check user capabilities
991 if (!Metasync::current_user_has_plugin_access()) {
992 wp_send_json_error(['message' => 'Insufficient permissions']);
993 return;
994 }
995
996 # Get parameters
997 $auth_method = isset($_POST['auth_method']) ? sanitize_text_field(wp_unslash($_POST['auth_method'])) : 'searchatlas_connect';
998 $is_reconnection = isset($_POST['is_reconnection']) ? filter_var($_POST['is_reconnection'], FILTER_VALIDATE_BOOLEAN) : false;
999
1000 # Track the event in GA4
1001 try {
1002 Metasync_GA4::get_instance()->track_one_click_activation($auth_method, $is_reconnection);
1003
1004 wp_send_json_success([
1005 'message' => '1-click activation tracked successfully',
1006 'auth_method' => $auth_method,
1007 'is_reconnection' => $is_reconnection
1008 ]);
1009 } catch (Exception $e) {
1010 # Still return success to avoid breaking the auth flow
1011 wp_send_json_success([
1012 'message' => 'Authentication successful'
1013 ]);
1014 }
1015 }
1016
1017 public function handle_export_whitelabel_settings()
1018 {
1019 # Check user capabilities
1020 if (!Metasync::current_user_has_plugin_access()) {
1021 wp_die('Insufficient permissions');
1022 }
1023
1024 # Verify nonce for security (check both GET and POST)
1025 $nonce = '';
1026 if (isset($_POST['_wpnonce'])) {
1027 $nonce = sanitize_text_field(wp_unslash($_POST['_wpnonce']));
1028 } elseif (isset($_GET['_wpnonce'])) {
1029 $nonce = sanitize_text_field(wp_unslash($_GET['_wpnonce']));
1030 }
1031
1032 if (empty($nonce) || !wp_verify_nonce($nonce, 'metasync_export_whitelabel')) {
1033 wp_die('Security verification failed.');
1034 }
1035
1036 try {
1037 # Get all whitelabel settings
1038 $whitelabel_settings = Metasync::get_whitelabel_settings();
1039
1040 # Get general settings that relate to whitelabel
1041 $general_settings = Metasync::get_option('general');
1042 $whitelabel_related_general = array();
1043
1044 # Include ALL whitelabel-related general settings
1045 $whitelabel_keys = array(
1046 'white_label_plugin_name',
1047 'white_label_plugin_description',
1048 'white_label_plugin_author',
1049 'white_label_plugin_author_uri',
1050 'white_label_plugin_uri',
1051 'white_label_plugin_menu_slug',
1052 'white_label_plugin_menu_icon',
1053 'whitelabel_otto_name'
1054 );
1055
1056 foreach ($whitelabel_keys as $key) {
1057 if (isset($general_settings[$key])) {
1058 $whitelabel_related_general[$key] = $general_settings[$key];
1059 }
1060 }
1061
1062 # Bundle the menu icon file so it survives import on a different site.
1063 # If the icon is a local URL (media library), replace it with a special
1064 # marker and include the actual file in the ZIP under the plugin folder.
1065 $bundled_icon_filename = null;
1066 $icon_url = $whitelabel_related_general['white_label_plugin_menu_icon'] ?? '';
1067 if (!empty($icon_url) && filter_var($icon_url, FILTER_VALIDATE_URL)) {
1068 $site_url = trailingslashit(site_url());
1069 if (strpos($icon_url, $site_url) === 0) {
1070 // Resolve URL to an absolute filesystem path
1071 $relative_path = str_replace($site_url, ABSPATH, $icon_url);
1072 $icon_abs_path = realpath($relative_path);
1073 if ($icon_abs_path && file_exists($icon_abs_path)) {
1074 $ext = strtolower(pathinfo($icon_abs_path, PATHINFO_EXTENSION));
1075 $bundled_icon_filename = 'whitelabel-icon.' . $ext;
1076 // Replace the URL with a marker so the importer knows to restore it
1077 $whitelabel_related_general['white_label_plugin_menu_icon'] = '__bundled_icon__' . $ext;
1078 }
1079 }
1080 }
1081
1082 # Prepare export data
1083 $export_data = array(
1084 'version' => '1.0',
1085 'exported_at' => current_time('mysql'),
1086 'whitelabel_settings' => $whitelabel_settings,
1087 'general_settings' => $whitelabel_related_general
1088 );
1089
1090 # Convert to JSON
1091 $json_data = wp_json_encode($export_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
1092
1093 if ($json_data === false) {
1094 wp_die('Failed to encode settings to JSON.');
1095 }
1096
1097 # Check if ZipArchive is available
1098 if (!class_exists('ZipArchive')) {
1099 wp_die('ZipArchive class is not available. Please enable PHP zip extension.');
1100 }
1101
1102 # Get plugin directory path (remove trailing slash for basename)
1103 $plugin_dir = rtrim(plugin_dir_path(dirname(__FILE__)), '/');
1104 $plugin_folder_name = basename($plugin_dir);
1105
1106 # Create temporary directory for zip file
1107 $upload_dir = wp_upload_dir();
1108 $temp_dir = $upload_dir['basedir'] . '/metasync-export-temp';
1109
1110 # Create temp directory if it doesn't exist
1111 if (!file_exists($temp_dir)) {
1112 wp_mkdir_p($temp_dir);
1113 }
1114
1115 # Generate unique filename
1116 $timestamp = date('Y-m-d_H-i-s');
1117 $zip_filename = 'metasync-whitelabel-plugin-' . $timestamp . '.zip';
1118 $json_filename = 'whitelabel-settings.json';
1119 $zip_path = $temp_dir . '/' . $zip_filename;
1120
1121 # Create zip file
1122 $zip = new ZipArchive();
1123 if ($zip->open($zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
1124 wp_die('Failed to create zip file.');
1125 }
1126
1127 # Add whitelabel settings JSON file to zip (inside plugin folder)
1128 $zip->addFromString($plugin_folder_name . '/' . $json_filename, $json_data);
1129
1130 # Bundle icon file if one was detected
1131 if ($bundled_icon_filename !== null && isset($icon_abs_path) && file_exists($icon_abs_path)) {
1132 $zip->addFile($icon_abs_path, $plugin_folder_name . '/' . $bundled_icon_filename);
1133 }
1134
1135
1136 # Files and directories to exclude from the zip
1137 $exclude_patterns = array(
1138 '.git',
1139 '.gitignore',
1140 '.gitattributes',
1141 'node_modules',
1142 '.DS_Store',
1143 'Thumbs.db',
1144 '.idea',
1145 '.vscode',
1146 'composer.lock',
1147 'package-lock.json',
1148 'yarn.lock',
1149 '.env',
1150 '.env.local',
1151 'docker-compose.yml',
1152 'Dockerfile',
1153 'Makefile',
1154 'renovate.json',
1155 'sonar-project.properties',
1156 'CODEOWNERS',
1157 'metasync-export-temp',
1158 'whitelabel-settings.json'
1159 );
1160
1161 # Recursively add plugin files to zip (with plugin folder as root in zip)
1162 self::add_directory_to_zip($zip, $plugin_dir . '/', $plugin_folder_name . '/', $exclude_patterns);
1163
1164 $zip->close();
1165
1166 # Check if file was created
1167 if (!file_exists($zip_path)) {
1168 wp_die('Zip file was not created successfully.');
1169 }
1170
1171 # Set headers for file download
1172 header('Content-Type: application/zip');
1173 header('Content-Disposition: attachment; filename="' . $zip_filename . '"');
1174 header('Content-Length: ' . filesize($zip_path));
1175 header('Pragma: no-cache');
1176 header('Expires: 0');
1177
1178 # Output file and clean up
1179 readfile($zip_path);
1180 unlink($zip_path);
1181
1182 # Clean up temp directory if empty
1183 if (is_dir($temp_dir) && count(scandir($temp_dir)) == 2) {
1184 rmdir($temp_dir);
1185 }
1186
1187 # Exit to prevent WordPress from adding anything to the response
1188 exit;
1189
1190 } catch (Exception $e) {
1191 # Log error if debug is enabled
1192 if (defined('WP_DEBUG') && WP_DEBUG) {
1193 error_log('MetaSync Whitelabel Export Error: ' . $e->getMessage());
1194 }
1195
1196 wp_die('Failed to export whitelabel settings: ' . $e->getMessage());
1197 }
1198 }
1199
1200 private static function add_directory_to_zip($zip, $dir, $zip_path = '', $exclude_patterns = array())
1201 {
1202 if (!is_dir($dir)) {
1203 return;
1204 }
1205
1206 $files = scandir($dir);
1207
1208 foreach ($files as $file) {
1209 if ($file === '.' || $file === '..') {
1210 continue;
1211 }
1212
1213 $file_path = $dir . $file;
1214 $zip_file_path = $zip_path . $file;
1215
1216 $should_exclude = false;
1217 foreach ($exclude_patterns as $pattern) {
1218 if (strpos($file, $pattern) !== false || strpos($file_path, $pattern) !== false) {
1219 $should_exclude = true;
1220 break;
1221 }
1222 }
1223
1224 if ($should_exclude) {
1225 continue;
1226 }
1227
1228 if (is_dir($file_path)) {
1229 $zip->addEmptyDir($zip_file_path);
1230 self::add_directory_to_zip($zip, $file_path . '/', $zip_file_path . '/', $exclude_patterns);
1231 } else {
1232 if (file_exists($file_path) && is_readable($file_path)) {
1233 $zip->addFile($file_path, $zip_file_path);
1234 }
1235 }
1236 }
1237 }
1238
1239 public function render_html_pages_dashboard_widget()
1240 {
1241 global $wpdb;
1242
1243 $query = "
1244 SELECT p.ID, p.post_title, p.post_modified, p.post_type
1245 FROM {$wpdb->posts} p
1246 INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
1247 WHERE p.post_status = 'publish'
1248 AND p.post_type IN ('post', 'page')
1249 AND (pm.meta_key = '_metasync_raw_html_enabled' OR pm.meta_key = '_metasync_custom_css')
1250 GROUP BY p.ID
1251 ORDER BY p.post_modified DESC
1252 LIMIT 10
1253 ";
1254
1255 $html_pages = $wpdb->get_results($query);
1256 $total_count = count($html_pages);
1257
1258 $label = $this->get_html_source_label();
1259
1260 if (empty($html_pages)) {
1261 echo '<div class="metasync-dashboard-widget-empty">';
1262 echo '<span class="dashicons dashicons-admin-page" style="font-size: 48px; opacity: 0.3; display: block; margin: 20px auto;"></span>';
1263 echo '<p style="text-align: center; color: #666;">';
1264 echo sprintf(__('No pages created with %s yet.', 'metasync'), '<strong>' . esc_html($label) . '</strong>');
1265 echo '</p>';
1266 echo '<p style="text-align: center;">';
1267 echo '<a href="' . admin_url('admin.php?page=' . Metasync_Admin::$page_slug) . '" class="button button-primary">';
1268 echo __('Get Started', 'metasync');
1269 echo '</a>';
1270 echo '</p>';
1271 echo '</div>';
1272 return;
1273 }
1274
1275 echo '<div class="metasync-dashboard-widget">';
1276
1277 echo '<div class="metasync-widget-stats">';
1278 echo '<div class="metasync-stat-box">';
1279 echo '<span class="metasync-stat-number">' . $total_count . '</span>';
1280 echo '<span class="metasync-stat-label">' . __('AI-Generated Pages', 'metasync') . '</span>';
1281 echo '</div>';
1282 echo '</div>';
1283
1284 echo '<div class="metasync-widget-list">';
1285 echo '<h4>' . __('Recent Pages', 'metasync') . '</h4>';
1286 echo '<ul>';
1287
1288 foreach ($html_pages as $page) {
1289 $edit_link = get_edit_post_link($page->ID);
1290 $view_link = get_permalink($page->ID);
1291 $time_ago = human_time_diff(strtotime($page->post_modified), current_time('timestamp'));
1292
1293 echo '<li class="metasync-widget-page-item">';
1294 echo '<span class="metasync-page-icon">⚡</span>';
1295 echo '<div class="metasync-page-details">';
1296 echo '<a href="' . esc_url($edit_link) . '" class="metasync-page-title">';
1297 echo esc_html($page->post_title ?: __('(no title)', 'metasync'));
1298 echo '</a>';
1299 echo '<span class="metasync-page-meta">';
1300 echo sprintf(__('Updated %s ago', 'metasync'), $time_ago);
1301 echo ' • ';
1302 echo '<a href="' . esc_url($view_link) . '" target="_blank">' . __('View', 'metasync') . '</a>';
1303 echo '</span>';
1304 echo '</div>';
1305 echo '</li>';
1306 }
1307
1308 echo '</ul>';
1309 echo '</div>';
1310
1311 echo '<div class="metasync-widget-footer">';
1312 echo '<a href="' . admin_url('edit.php?post_type=page') . '">';
1313 echo __('View All Pages', 'metasync') . ' →';
1314 echo '</a>';
1315 echo '</div>';
1316
1317 echo '</div>';
1318 }
1319
1320 private function get_html_source_label()
1321 {
1322 $whitelabel_company = Metasync::get_whitelabel_company_name();
1323 if (!empty($whitelabel_company)) {
1324 return $whitelabel_company . ' AI';
1325 }
1326
1327 return Metasync::get_effective_plugin_name() . ' AI';
1328 }
1329 }
1330