PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.9
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.9
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.9, at includes/class-metasync-activator.php

413 lines 12.9 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 flush_rewrite_rules();
89 }
90
91 /**
92 * Send pre-SSO announce ping to backend (zero-trust; backend rate-limits).
93 * POST /api/wp-plugin-announce/ with url + plugin_version; optional X-Plugin-Token for deduplication.
94 * Callable from activation and from init (rate-limited) when no API key yet.
95 *
96 * @since 2.5.x
97 */
98 public static function send_announce_ping()
99 {
100 $base = 'https://ca.searchatlas.com';
101 if (class_exists('Metasync_Endpoint_Manager')) {
102 $base = Metasync_Endpoint_Manager::get_endpoint('CA_API_DOMAIN');
103 } elseif (class_exists('Metasync')) {
104 $base = Metasync::CA_API_DOMAIN;
105 }
106 $url = rtrim($base, '/') . '/api/wp-plugin-announce/';
107
108 $body = wp_json_encode([
109 'url' => get_home_url(),
110 'plugin_version' => defined('METASYNC_VERSION') ? METASYNC_VERSION : 'unknown',
111 ]);
112
113 $options = get_option('metasync_options', []);
114 $plugin_auth_token = $options['general']['apikey'] ?? '';
115 $headers = [
116 'Content-Type' => 'application/json',
117 ];
118 if (!empty($plugin_auth_token)) {
119 $headers['X-Plugin-Token'] = $plugin_auth_token;
120 }
121
122 wp_remote_post($url, [
123 'body' => $body,
124 'headers' => $headers,
125 'timeout' => 10,
126 'blocking' => false,
127 ]);
128 }
129
130 /**
131 * Ensure Plugin Auth Token exists
132 * Generates a unique Plugin Auth Token during plugin activation
133 */
134 private static function ensure_plugin_auth_token()
135 {
136 $options = get_option('metasync_options', []);
137
138 if (empty($options['general']['apikey'])) {
139 // Generate unique Plugin Auth Token (alphanumeric only)
140 $plugin_auth_token = wp_generate_password(32, false, false);
141
142 // Initialize options structure if needed
143 if (!isset($options['general'])) {
144 $options['general'] = [];
145 }
146
147 // Store Plugin Auth Token
148 $options['general']['apikey'] = $plugin_auth_token;
149 update_option('metasync_options', $options);
150 }
151 }
152
153 /**
154 * Get the path to the whitelabel settings JSON file
155 *
156 * @return string|false Path to the file if it exists, false otherwise
157 * @since 2.5.0
158 */
159 public static function get_whitelabel_settings_file()
160 {
161 $plugin_dir = plugin_dir_path(dirname(__FILE__));
162
163 // Check for whitelabel-settings.json in plugin root
164 $json_file = $plugin_dir . 'whitelabel-settings.json';
165
166 // Also check in a common extracted zip location (if zip was extracted)
167 if (!file_exists($json_file)) {
168 $json_file = $plugin_dir . 'metasync/whitelabel-settings.json';
169 }
170
171 if (!file_exists($json_file)) {
172 return false;
173 }
174
175 return $json_file;
176 }
177
178 /**
179 * Check if whitelabel settings file has been updated and import if needed
180 * This method should be called on init to detect plugin uploads/updates
181 *
182 * @since 2.5.0
183 */
184 public static function check_whitelabel_settings_update()
185 {
186 $json_file = self::get_whitelabel_settings_file();
187
188 if ($json_file === false) {
189 return;
190 }
191
192 // Get current file modification time and content hash
193 $file_mtime = filemtime($json_file);
194 $file_hash = md5_file($json_file);
195
196 // Get stored file info
197 $stored_mtime = get_option('metasync_whitelabel_file_mtime', 0);
198 $stored_hash = get_option('metasync_whitelabel_file_hash', '');
199
200 // Check if file has changed (either modification time or content)
201 if ($file_mtime > $stored_mtime || $file_hash !== $stored_hash) {
202 // File has changed, import settings
203 self::import_whitelabel_settings();
204
205 // Store new file info
206 update_option('metasync_whitelabel_file_mtime', $file_mtime);
207 update_option('metasync_whitelabel_file_hash', $file_hash);
208 }
209 }
210
211 /**
212 * Import whitelabel settings from JSON file if available
213 * Checks for whitelabel-settings.json in the plugin directory or extracted zip
214 *
215 * This method is public to allow calling during both activation and plugin updates.
216 * @since 2.5.0
217 */
218 public static function import_whitelabel_settings()
219 {
220 $json_file = self::get_whitelabel_settings_file();
221
222 if ($json_file === false) {
223 return;
224 }
225
226 // Read JSON file
227 $json_content = file_get_contents($json_file);
228 if ($json_content === false) {
229 return;
230 }
231
232 // Decode JSON
233 $import_data = json_decode($json_content, true);
234 if ($import_data === null || json_last_error() !== JSON_ERROR_NONE) {
235 return;
236 }
237
238 // Validate import data structure
239 if (!isset($import_data['whitelabel_settings']) || !is_array($import_data['whitelabel_settings'])) {
240 return;
241 }
242
243 // Get current options
244 $options = get_option('metasync_options', array());
245
246 // Import whitelabel settings
247 if (isset($import_data['whitelabel_settings'])) {
248 $whitelabel_settings = $import_data['whitelabel_settings'];
249
250 // Initialize whitelabel array if needed
251 if (!isset($options['whitelabel'])) {
252 $options['whitelabel'] = array();
253 }
254
255 // Merge imported settings with existing (imported settings take precedence)
256 $options['whitelabel'] = array_merge($options['whitelabel'], $whitelabel_settings);
257
258 // Update timestamp
259 $options['whitelabel']['updated_at'] = time();
260 $options['whitelabel']['imported_at'] = current_time('mysql');
261 }
262
263 // Import general settings related to whitelabel
264 if (isset($import_data['general_settings']) && is_array($import_data['general_settings'])) {
265 if (!isset($options['general'])) {
266 $options['general'] = array();
267 }
268
269 // Merge general settings
270 foreach ($import_data['general_settings'] as $key => $value) {
271 $options['general'][$key] = $value;
272 }
273 }
274
275 // Restore bundled icon: if the icon value is a __bundled_icon__{ext} marker,
276 // copy the bundled file from the plugin directory to uploads and update the URL.
277 $icon_value = $options['general']['white_label_plugin_menu_icon'] ?? '';
278 if (!empty($icon_value) && strpos($icon_value, '__bundled_icon__') === 0) {
279 $ext = substr($icon_value, strlen('__bundled_icon__'));
280 $ext = preg_replace('/[^a-z0-9]/', '', strtolower($ext)); // sanitize
281 $bundled_file = plugin_dir_path(dirname(__FILE__)) . 'whitelabel-icon.' . $ext;
282
283 if (file_exists($bundled_file) && in_array($ext, ['png', 'svg'], true)) {
284 $upload_dir = wp_upload_dir();
285 $dest_dir = $upload_dir['basedir'] . '/metasync';
286 if (!file_exists($dest_dir)) {
287 wp_mkdir_p($dest_dir);
288 }
289 $dest_file = $dest_dir . '/whitelabel-icon.' . $ext;
290 if (copy($bundled_file, $dest_file)) {
291 $options['general']['white_label_plugin_menu_icon'] = $upload_dir['baseurl'] . '/metasync/whitelabel-icon.' . $ext;
292 } else {
293 // Could not copy — clear the broken marker so default icon shows
294 $options['general']['white_label_plugin_menu_icon'] = '';
295 }
296 } else {
297 // Bundled file missing or unsupported extension — clear the marker
298 $options['general']['white_label_plugin_menu_icon'] = '';
299 }
300 }
301
302 // Save updated options
303 update_option('metasync_options', $options);
304
305 // Update the plugin file headers so whitelabel shows even when deactivated
306 self::update_plugin_file_headers($import_data);
307
308 // Optionally delete the JSON file after successful import (uncomment if desired)
309 // unlink($json_file);
310 }
311
312 /**
313 * Sync plugin file headers from the current saved options in the database.
314 * Call this after saving whitelabel settings via the admin UI to ensure
315 * the plugin file headers reflect the latest whitelabel values.
316 *
317 * @since 2.5.0
318 */
319 public static function sync_plugin_file_headers()
320 {
321 $options = get_option('metasync_options', array());
322 $general = $options['general'] ?? array();
323
324 // Build the import_data format expected by update_plugin_file_headers
325 $import_data = array(
326 'general_settings' => $general,
327 );
328
329 self::update_plugin_file_headers($import_data);
330 }
331
332 /**
333 * Update the main plugin file headers with whitelabel values
334 * WordPress reads plugin metadata directly from the file header comments,
335 * so modifying these ensures whitelabel shows even when the plugin is deactivated.
336 *
337 * @param array $import_data The imported whitelabel data
338 * @since 2.5.0
339 */
340 private static function update_plugin_file_headers($import_data)
341 {
342 $plugin_file = plugin_dir_path(dirname(__FILE__)) . 'metasync.php';
343
344 if (!file_exists($plugin_file) || !is_writable($plugin_file)) {
345 return;
346 }
347
348 $content = file_get_contents($plugin_file);
349 if ($content === false) {
350 return;
351 }
352
353 $general = $import_data['general_settings'] ?? array();
354
355 // Map of whitelabel setting keys to plugin header field names
356 // with default values to restore when whitelabel is cleared
357 $header_map = array(
358 'white_label_plugin_name' => array(
359 'header' => 'Plugin Name',
360 'default' => 'Search Atlas: The Premier AI SEO Plugin for Instant Optimization',
361 ),
362 'white_label_plugin_description' => array(
363 'header' => 'Description',
364 '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.',
365 ),
366 'white_label_plugin_author' => array(
367 'header' => 'Author',
368 'default' => 'Search Atlas',
369 ),
370 'white_label_plugin_author_uri' => array(
371 'header' => 'Author URI',
372 'default' => 'https://searchatlas.com',
373 ),
374 'white_label_plugin_uri' => array(
375 'header' => 'Plugin URI',
376 'default' => 'https://searchatlas.com/',
377 ),
378 );
379
380 $modified = false;
381
382 foreach ($header_map as $setting_key => $field_config) {
383 $header_field = $field_config['header'];
384
385 // Use whitelabel value if set, otherwise restore default
386 $new_value = !empty($general[$setting_key])
387 ? $general[$setting_key]
388 : $field_config['default'];
389
390 // Match the header line: " * Field Name: any value"
391 // Handles varying whitespace between field name and value
392 $pattern = '/^(\s*\*\s*' . preg_quote($header_field, '/') . ':\s*)(.+)$/m';
393
394 if (preg_match($pattern, $content, $matches)) {
395 // Only replace if the value actually differs from what's in the file
396 if (trim($matches[2]) !== trim($new_value)) {
397 // Escape both backslashes and $ signs for preg_replace replacement string
398 $escaped_value = str_replace(array('\\', '$'), array('\\\\', '\\$'), $new_value);
399 $content = preg_replace($pattern, '${1}' . $escaped_value, $content, 1);
400 $modified = true;
401 }
402 }
403 }
404
405 if ($modified) {
406 file_put_contents($plugin_file, $content);
407
408 // Clear WordPress plugin cache so it reads the updated headers
409 wp_cache_delete('plugins', 'plugins');
410 }
411 }
412 }
413