PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.12
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.12
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 / includes / class-metasync-activator.php

class-metasync-activator.php in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.12, at includes/class-metasync-activator.php

423 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Fired during plugin activation
5 *
6 * @link https://searchatlas.com
7 * @since 1.0.0
8 *
9 * @package Metasync
10 * @subpackage Metasync/includes
11 */
12
13 /**
14 * Fired during plugin activation.
15 *
16 * This class defines all code necessary to run during the plugin's activation.
17 *
18 * @since 1.0.0
19 * @package Metasync
20 * @subpackage Metasync/includes
21 * @author Engineering Team <support@searchatlas.com>
22 */
23 class Metasync_Activator
24 {
25
26 /**
27 * Canonical list of MetaSync custom WP-Cron hooks.
28 * Shared with Metasync_Deactivator so deactivation cleans up every scheduled hook.
29 *
30 * @since 2.5.x
31 * @var string[]
32 */
33 public static $cron_hooks = [
34 'metasync_sync_log_daily_cleanup',
35 'metasync_announce_cron',
36 'metasync_rate_limit_cleanup',
37 'metasync_heartbeat_cron_check',
38 'metasync_burst_heartbeat',
39 'metasync_check_debug_limits',
40 'metasync_cleanup_transients',
41 'metasync_hidden_post_check',
42 'metasync_otto_recheck_404_exclusions',
43 'metasync_db_cleanup',
44 'metasync_media_batch_optimize_cron',
45 'metasync_speed_cache_cleanup',
46 'metasync_process_seo_job',
47 'metasync_process_otto_crawl_url_job',
48 'metasync_process_otto_batch_cache_job',
49 ];
50
51 /**
52 * Short Description. (use period)
53 *
54 * Long Description.
55 *
56 * @since 1.0.0
57 */
58 public static function activate()
59 {
60 // WordPress core sitemap functionality is required
61 // if (wp_sitemaps_get_server()->sitemaps_enabled() == false) {
62 // add_filter('wp_sitemaps_enabled', '__return_true');
63 // }
64
65 // Generate Plugin Auth Token on first activation
66 self::ensure_plugin_auth_token();
67
68 // Import whitelabel settings only if the JSON file is new or changed
69 // (prevents overwriting admin UI changes on every deactivate/activate cycle)
70 self::check_whitelabel_settings_update();
71
72 // Pre-SSO announce: tell backend plugin is installed (PR4 - heartbeat reliability)
73 update_option('metasync_announce_attempt_count', 0);
74 self::send_announce_ping();
75 update_option('metasync_announce_attempt_count', 1);
76
77 // Schedule cron for announce pings 2-5 (every 10 minutes)
78 if (!wp_next_scheduled('metasync_announce_cron')) {
79 wp_schedule_event(time() + 10 * MINUTE_IN_SECONDS, 'metasync_every_10_minutes', 'metasync_announce_cron');
80 }
81
82 // Set first activation flag for setup wizard
83 if (!get_option('metasync_first_activation_time')) {
84 update_option('metasync_first_activation_time', current_time('mysql'));
85 update_option('metasync_show_wizard', true);
86 }
87
88 // Auto-disable WP core sitemap if a MetaSync sitemap already exists (WP-396).
89 // Skip on fresh installs where no sitemap has been generated yet so the site
90 // doesn't end up with zero sitemaps.
91 if (get_option('metasync_sitemap_auto_update', false)
92 || !empty(get_option('metasync_sitemap_files', []))
93 || file_exists(ABSPATH . 'sitemap_index.xml')
94 ) {
95 update_option('metasync_disable_wp_sitemap', true);
96 }
97
98 flush_rewrite_rules();
99 }
100
101 /**
102 * Send pre-SSO announce ping to backend (zero-trust; backend rate-limits).
103 * POST /api/wp-plugin-announce/ with url + plugin_version; optional X-Plugin-Token for deduplication.
104 * Callable from activation and from init (rate-limited) when no API key yet.
105 *
106 * @since 2.5.x
107 */
108 public static function send_announce_ping()
109 {
110 $base = 'https://ca.searchatlas.com';
111 if (class_exists('Metasync_Endpoint_Manager')) {
112 $base = Metasync_Endpoint_Manager::get_endpoint('CA_API_DOMAIN');
113 } elseif (class_exists('Metasync')) {
114 $base = Metasync::CA_API_DOMAIN;
115 }
116 $url = rtrim($base, '/') . '/api/wp-plugin-announce/';
117
118 $body = wp_json_encode([
119 'url' => get_home_url(),
120 'plugin_version' => defined('METASYNC_VERSION') ? METASYNC_VERSION : 'unknown',
121 ]);
122
123 $options = get_option('metasync_options', []);
124 $plugin_auth_token = $options['general']['apikey'] ?? '';
125 $headers = [
126 'Content-Type' => 'application/json',
127 ];
128 if (!empty($plugin_auth_token)) {
129 $headers['X-Plugin-Token'] = $plugin_auth_token;
130 }
131
132 wp_remote_post($url, [
133 'body' => $body,
134 'headers' => $headers,
135 'timeout' => 10,
136 'blocking' => false,
137 ]);
138 }
139
140 /**
141 * Ensure Plugin Auth Token exists
142 * Generates a unique Plugin Auth Token during plugin activation
143 */
144 private static function ensure_plugin_auth_token()
145 {
146 $options = get_option('metasync_options', []);
147
148 if (empty($options['general']['apikey'])) {
149 // Generate unique Plugin Auth Token (alphanumeric only)
150 $plugin_auth_token = wp_generate_password(32, false, false);
151
152 // Initialize options structure if needed
153 if (!isset($options['general'])) {
154 $options['general'] = [];
155 }
156
157 // Store Plugin Auth Token
158 $options['general']['apikey'] = $plugin_auth_token;
159 update_option('metasync_options', $options);
160 }
161 }
162
163 /**
164 * Get the path to the whitelabel settings JSON file
165 *
166 * @return string|false Path to the file if it exists, false otherwise
167 * @since 2.5.0
168 */
169 public static function get_whitelabel_settings_file()
170 {
171 $plugin_dir = plugin_dir_path(dirname(__FILE__));
172
173 // Check for whitelabel-settings.json in plugin root
174 $json_file = $plugin_dir . 'whitelabel-settings.json';
175
176 // Also check in a common extracted zip location (if zip was extracted)
177 if (!file_exists($json_file)) {
178 $json_file = $plugin_dir . 'metasync/whitelabel-settings.json';
179 }
180
181 if (!file_exists($json_file)) {
182 return false;
183 }
184
185 return $json_file;
186 }
187
188 /**
189 * Check if whitelabel settings file has been updated and import if needed
190 * This method should be called on init to detect plugin uploads/updates
191 *
192 * @since 2.5.0
193 */
194 public static function check_whitelabel_settings_update()
195 {
196 $json_file = self::get_whitelabel_settings_file();
197
198 if ($json_file === false) {
199 return;
200 }
201
202 // Get current file modification time and content hash
203 $file_mtime = filemtime($json_file);
204 $file_hash = md5_file($json_file);
205
206 // Get stored file info
207 $stored_mtime = get_option('metasync_whitelabel_file_mtime', 0);
208 $stored_hash = get_option('metasync_whitelabel_file_hash', '');
209
210 // Check if file has changed (either modification time or content)
211 if ($file_mtime > $stored_mtime || $file_hash !== $stored_hash) {
212 // File has changed, import settings
213 self::import_whitelabel_settings();
214
215 // Store new file info
216 update_option('metasync_whitelabel_file_mtime', $file_mtime);
217 update_option('metasync_whitelabel_file_hash', $file_hash);
218 }
219 }
220
221 /**
222 * Import whitelabel settings from JSON file if available
223 * Checks for whitelabel-settings.json in the plugin directory or extracted zip
224 *
225 * This method is public to allow calling during both activation and plugin updates.
226 * @since 2.5.0
227 */
228 public static function import_whitelabel_settings()
229 {
230 $json_file = self::get_whitelabel_settings_file();
231
232 if ($json_file === false) {
233 return;
234 }
235
236 // Read JSON file
237 $json_content = file_get_contents($json_file);
238 if ($json_content === false) {
239 return;
240 }
241
242 // Decode JSON
243 $import_data = json_decode($json_content, true);
244 if ($import_data === null || json_last_error() !== JSON_ERROR_NONE) {
245 return;
246 }
247
248 // Validate import data structure
249 if (!isset($import_data['whitelabel_settings']) || !is_array($import_data['whitelabel_settings'])) {
250 return;
251 }
252
253 // Get current options
254 $options = get_option('metasync_options', array());
255
256 // Import whitelabel settings
257 if (isset($import_data['whitelabel_settings'])) {
258 $whitelabel_settings = $import_data['whitelabel_settings'];
259
260 // Initialize whitelabel array if needed
261 if (!isset($options['whitelabel'])) {
262 $options['whitelabel'] = array();
263 }
264
265 // Merge imported settings with existing (imported settings take precedence)
266 $options['whitelabel'] = array_merge($options['whitelabel'], $whitelabel_settings);
267
268 // Update timestamp
269 $options['whitelabel']['updated_at'] = time();
270 $options['whitelabel']['imported_at'] = current_time('mysql');
271 }
272
273 // Import general settings related to whitelabel
274 if (isset($import_data['general_settings']) && is_array($import_data['general_settings'])) {
275 if (!isset($options['general'])) {
276 $options['general'] = array();
277 }
278
279 // Merge general settings
280 foreach ($import_data['general_settings'] as $key => $value) {
281 $options['general'][$key] = $value;
282 }
283 }
284
285 // Restore bundled icon: if the icon value is a __bundled_icon__{ext} marker,
286 // copy the bundled file from the plugin directory to uploads and update the URL.
287 $icon_value = $options['general']['white_label_plugin_menu_icon'] ?? '';
288 if (!empty($icon_value) && strpos($icon_value, '__bundled_icon__') === 0) {
289 $ext = substr($icon_value, strlen('__bundled_icon__'));
290 $ext = preg_replace('/[^a-z0-9]/', '', strtolower($ext)); // sanitize
291 $bundled_file = plugin_dir_path(dirname(__FILE__)) . 'whitelabel-icon.' . $ext;
292
293 if (file_exists($bundled_file) && in_array($ext, ['png', 'svg'], true)) {
294 $upload_dir = wp_upload_dir();
295 $dest_dir = $upload_dir['basedir'] . '/metasync';
296 if (!file_exists($dest_dir)) {
297 wp_mkdir_p($dest_dir);
298 }
299 $dest_file = $dest_dir . '/whitelabel-icon.' . $ext;
300 if (copy($bundled_file, $dest_file)) {
301 $options['general']['white_label_plugin_menu_icon'] = $upload_dir['baseurl'] . '/metasync/whitelabel-icon.' . $ext;
302 } else {
303 // Could not copy — clear the broken marker so default icon shows
304 $options['general']['white_label_plugin_menu_icon'] = '';
305 }
306 } else {
307 // Bundled file missing or unsupported extension — clear the marker
308 $options['general']['white_label_plugin_menu_icon'] = '';
309 }
310 }
311
312 // Save updated options
313 update_option('metasync_options', $options);
314
315 // Update the plugin file headers so whitelabel shows even when deactivated
316 self::update_plugin_file_headers($import_data);
317
318 // Optionally delete the JSON file after successful import (uncomment if desired)
319 // unlink($json_file);
320 }
321
322 /**
323 * Sync plugin file headers from the current saved options in the database.
324 * Call this after saving whitelabel settings via the admin UI to ensure
325 * the plugin file headers reflect the latest whitelabel values.
326 *
327 * @since 2.5.0
328 */
329 public static function sync_plugin_file_headers()
330 {
331 $options = get_option('metasync_options', array());
332 $general = $options['general'] ?? array();
333
334 // Build the import_data format expected by update_plugin_file_headers
335 $import_data = array(
336 'general_settings' => $general,
337 );
338
339 self::update_plugin_file_headers($import_data);
340 }
341
342 /**
343 * Update the main plugin file headers with whitelabel values
344 * WordPress reads plugin metadata directly from the file header comments,
345 * so modifying these ensures whitelabel shows even when the plugin is deactivated.
346 *
347 * @param array $import_data The imported whitelabel data
348 * @since 2.5.0
349 */
350 private static function update_plugin_file_headers($import_data)
351 {
352 $plugin_file = plugin_dir_path(dirname(__FILE__)) . 'metasync.php';
353
354 if (!file_exists($plugin_file) || !is_writable($plugin_file)) {
355 return;
356 }
357
358 $content = file_get_contents($plugin_file);
359 if ($content === false) {
360 return;
361 }
362
363 $general = $import_data['general_settings'] ?? array();
364
365 // Map of whitelabel setting keys to plugin header field names
366 // with default values to restore when whitelabel is cleared
367 $header_map = array(
368 'white_label_plugin_name' => array(
369 'header' => 'Plugin Name',
370 'default' => 'Search Atlas: The Premier AI SEO Plugin for Instant Optimization',
371 ),
372 'white_label_plugin_description' => array(
373 'header' => 'Description',
374 'default' => 'Search Atlas SEO is an intuitive WordPress Plugin that transforms the most complicated, most labor-intensive SEO tasks into streamlined, straightforward processes. With a few clicks, the meta-bulk update feature automates the re-optimization of meta tags using AI to increase clicks. Stay up-to-date with the freshest Google Search data for your entire site or targeted URLs within the Meta Sync plug-in page.',
375 ),
376 'white_label_plugin_author' => array(
377 'header' => 'Author',
378 'default' => 'Search Atlas',
379 ),
380 'white_label_plugin_author_uri' => array(
381 'header' => 'Author URI',
382 'default' => 'https://searchatlas.com',
383 ),
384 'white_label_plugin_uri' => array(
385 'header' => 'Plugin URI',
386 'default' => 'https://searchatlas.com/',
387 ),
388 );
389
390 $modified = false;
391
392 foreach ($header_map as $setting_key => $field_config) {
393 $header_field = $field_config['header'];
394
395 // Use whitelabel value if set, otherwise restore default
396 $new_value = !empty($general[$setting_key])
397 ? $general[$setting_key]
398 : $field_config['default'];
399
400 // Match the header line: " * Field Name: any value"
401 // Handles varying whitespace between field name and value
402 $pattern = '/^(\s*\*\s*' . preg_quote($header_field, '/') . ':\s*)(.+)$/m';
403
404 if (preg_match($pattern, $content, $matches)) {
405 // Only replace if the value actually differs from what's in the file
406 if (trim($matches[2]) !== trim($new_value)) {
407 // Escape both backslashes and $ signs for preg_replace replacement string
408 $escaped_value = str_replace(array('\\', '$'), array('\\\\', '\\$'), $new_value);
409 $content = preg_replace($pattern, '${1}' . $escaped_value, $content, 1);
410 $modified = true;
411 }
412 }
413 }
414
415 if ($modified) {
416 file_put_contents($plugin_file, $content);
417
418 // Clear WordPress plugin cache so it reads the updated headers
419 wp_cache_delete('plugins', 'plugins');
420 }
421 }
422 }
423