PluginProbe
Outrank / trunk
Outrank vtrunk
1.0.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9
outrank / outrank.php

outrank.php in Outrank trunk, at outrank.php

341 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
4 /*
5 * Plugin Name: Outrank
6 * Plugin URI: https://outrank.so
7 * Description: Get traffic and outrank competitors with automatic SEO-optimized content generation published to your WordPress site.
8 * Version: 1.0.10
9 * Author: Outrank
10 * License: GPLv2 or later
11 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
12 * Requires PHP: 8.0
13 * Requires at least: 6.4
14 * Tested up to: 6.9
15 */
16
17 define('OUTRANK_PLUGIN_PATH', plugin_dir_path(__FILE__));
18 define('OUTRANK_PLUGIN_URL', plugin_dir_url(__FILE__));
19 require_once plugin_dir_path(__FILE__) . 'includes/image-functions.php';
20
21 /**
22 * Create the outrank_manage table for a specific site.
23 * Used during activation and when new sites are created in multisite.
24 */
25 function outrank_create_table() {
26 global $wpdb;
27 $table_name = $wpdb->prefix . 'outrank_manage';
28 $charset_collate = $wpdb->get_charset_collate();
29
30 $sql = "
31 CREATE TABLE {$table_name} (
32 id INT(11) NOT NULL AUTO_INCREMENT,
33 image TEXT NOT NULL,
34 slug VARCHAR(191) NOT NULL,
35 title TEXT NOT NULL,
36 meta_description TEXT NOT NULL,
37 status VARCHAR(50) NOT NULL,
38 created_at DATETIME NOT NULL,
39 PRIMARY KEY (id),
40 UNIQUE KEY slug_unique (slug)
41 ) ENGINE=InnoDB $charset_collate;
42 ";
43
44 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
45 dbDelta($sql);
46 }
47
48 /**
49 * Check if the outrank_manage table exists for the current site.
50 *
51 * @return bool True if table exists, false otherwise.
52 */
53 function outrank_table_exists() {
54 global $wpdb;
55 $table_name = $wpdb->prefix . 'outrank_manage';
56
57 $cache_key = 'outrank_table_exists_' . get_current_blog_id();
58 $exists = wp_cache_get($cache_key, 'outrank');
59
60 if ($exists === false) {
61 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
62 $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_name)) === $table_name;
63 wp_cache_set($cache_key, $exists ? 1 : 0, 'outrank', 3600);
64 }
65
66 return (bool) $exists;
67 }
68
69 /**
70 * Ensure the table exists, create it if not.
71 * Call this before any table operations.
72 */
73 function outrank_ensure_table_exists() {
74 if (!outrank_table_exists()) {
75 outrank_create_table();
76 // Clear the cache after creating the table
77 wp_cache_delete('outrank_table_exists_' . get_current_blog_id(), 'outrank');
78 }
79 }
80
81 // Add admin menu pages
82 add_action('admin_menu', 'outrank_add_outrank_menu');
83 function outrank_add_outrank_menu() {
84 add_menu_page(
85 'Outrank Menu',
86 'Outrank',
87 'manage_options',
88 'outrank',
89 'outrank_page',
90 'data:image/svg+xml;base64,' . base64_encode(file_get_contents(OUTRANK_PLUGIN_PATH . 'images/icon.svg')),
91 60
92 );
93 add_submenu_page('outrank', 'Home', 'Home', 'manage_options', 'outrank', 'outrank_page');
94 add_submenu_page('outrank', 'Manage', 'Manage', 'manage_options', 'outrank_manage', 'outrank_manage_page');
95 }
96
97 // Redirect to manage page if no API key is set
98 add_action('admin_init', 'outrank_check_api_key_redirect');
99 function outrank_check_api_key_redirect() {
100 // Only redirect if we're on the Outrank home page
101 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- admin page routing, no form data processed
102 if (isset($_GET['page']) && $_GET['page'] === 'outrank') {
103 $apiKey = get_option('outrank_api_key');
104 if (empty($apiKey)) {
105 wp_safe_redirect(admin_url('admin.php?page=outrank_manage'));
106 exit;
107 }
108 }
109 }
110
111 // Handle activation redirect
112 add_action('admin_init', 'outrank_activation_redirect');
113 function outrank_activation_redirect() {
114 // Only redirect if transient exists
115 if (get_transient('outrank_activation_redirect')) {
116 delete_transient('outrank_activation_redirect');
117
118 // Don't redirect on multi-site activations or bulk plugin activations
119 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- standard WP activation check
120 if (is_network_admin() || isset($_GET['activate-multi'])) {
121 return;
122 }
123
124 // Redirect to manage page
125 wp_safe_redirect(admin_url('admin.php?page=outrank_manage'));
126 exit;
127 }
128 }
129
130 // Include admin pages
131 function outrank_page() {
132 include_once OUTRANK_PLUGIN_PATH . 'pages/home.php';
133 }
134
135 function outrank_manage_page() {
136 include_once OUTRANK_PLUGIN_PATH . 'pages/manage.php';
137 }
138
139 // Activation hook: Create custom table
140 register_activation_hook(__FILE__, 'outrank_activate');
141 function outrank_activate($network_wide = false) {
142 if (is_multisite() && $network_wide) {
143 // Network activation: create tables for all existing sites
144 $sites = get_sites(['fields' => 'ids']);
145 foreach ($sites as $site_id) {
146 switch_to_blog($site_id);
147 outrank_create_table();
148 restore_current_blog();
149 }
150 } else {
151 // Single site activation
152 outrank_create_table();
153 }
154
155 // Set transient for activation redirect
156 set_transient('outrank_activation_redirect', true, 30);
157 }
158
159 /**
160 * Create table when a new site is added in multisite.
161 * Hook into wp_initialize_site for WordPress 5.1+
162 *
163 * @param WP_Site $new_site New site object.
164 */
165 add_action('wp_initialize_site', 'outrank_on_new_site', 10, 1);
166 function outrank_on_new_site($new_site) {
167 if (!function_exists('is_plugin_active_for_network')) {
168 require_once ABSPATH . 'wp-admin/includes/plugin.php';
169 }
170
171 if (!is_plugin_active_for_network(plugin_basename(__FILE__))) {
172 return;
173 }
174
175 switch_to_blog($new_site->blog_id);
176 outrank_create_table();
177 restore_current_blog();
178 }
179
180 /**
181 * Backwards compatibility for WordPress < 5.1
182 * Create table when a new site is added using the older hook.
183 *
184 * @param int $blog_id The new blog ID.
185 */
186 add_action('wpmu_new_blog', 'outrank_on_new_blog_legacy', 10, 1);
187 function outrank_on_new_blog_legacy($blog_id) {
188 // Only run on WordPress < 5.1 (wp_initialize_site handles 5.1+)
189 if (version_compare(get_bloginfo('version'), '5.1', '>=')) {
190 return;
191 }
192
193 if (!function_exists('is_plugin_active_for_network')) {
194 require_once ABSPATH . 'wp-admin/includes/plugin.php';
195 }
196
197 if (!is_plugin_active_for_network(plugin_basename(__FILE__))) {
198 return;
199 }
200
201 switch_to_blog($blog_id);
202 outrank_create_table();
203 restore_current_blog();
204 }
205
206 /**
207 * Clean up when a site is deleted in multisite (WordPress 5.1+).
208 *
209 * @param WP_Site $old_site The site being deleted.
210 */
211 add_action('wp_uninitialize_site', 'outrank_on_delete_site', 10, 1);
212 function outrank_on_delete_site($old_site) {
213 $blog_id = is_object($old_site) ? $old_site->blog_id : $old_site;
214 outrank_cleanup_site($blog_id);
215 }
216
217 /**
218 * Backwards compatibility for WordPress < 5.1.
219 * Clean up when a site is deleted using the older hook.
220 *
221 * @param int $blog_id The site ID being deleted.
222 */
223 add_action('delete_blog', 'outrank_on_delete_blog_legacy', 10, 1);
224 function outrank_on_delete_blog_legacy($blog_id) {
225 // Only run on WordPress < 5.1 (wp_uninitialize_site handles 5.1+)
226 if (version_compare(get_bloginfo('version'), '5.1', '>=')) {
227 return;
228 }
229
230 outrank_cleanup_site($blog_id);
231 }
232
233 /**
234 * Clean up plugin data for a specific site.
235 *
236 * @param int $blog_id The site ID to clean up.
237 */
238 function outrank_cleanup_site($blog_id) {
239 global $wpdb;
240
241 switch_to_blog($blog_id);
242
243 $table_name = $wpdb->prefix . 'outrank_manage';
244 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.DirectDatabaseQuery.NoCaching
245 $wpdb->query($wpdb->prepare("DROP TABLE IF EXISTS %i", $table_name));
246
247 // Clean up options
248 delete_option('outrank_api_key');
249 delete_option('outrank_integration_id');
250 delete_option('outrank_post_as_draft');
251
252 restore_current_blog();
253 }
254
255 // Enqueue admin styles and scripts
256 add_action('admin_enqueue_scripts', 'outrank_add_plugin_assets');
257 function outrank_add_plugin_assets($hook_suffix = '') {
258 if (strpos($hook_suffix, 'outrank') === false) return; // Only enqueue on outrank pages
259
260 wp_enqueue_style('outrank-style', OUTRANK_PLUGIN_URL . 'css/manage.css', [], '1.0.8');
261 wp_enqueue_style('outrank-home-style', OUTRANK_PLUGIN_URL . 'css/home.css', [], '1.0.8');
262
263 wp_enqueue_script('outrank-script', OUTRANK_PLUGIN_URL . 'script/manage.js', ['jquery'], '1.0.8', true);
264 }
265
266 // Helper function to get all articles from DB
267 function outrank_get_articles() {
268 global $wpdb;
269
270 // Ensure table exists (handles multisite subsites)
271 outrank_ensure_table_exists();
272
273 $cache_key = 'outrank_all_articles_' . get_current_blog_id();
274 $articles = wp_cache_get($cache_key, 'outrank');
275
276 if ($articles === false) {
277 $table_name = $wpdb->prefix . 'outrank_manage';
278 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
279 $articles = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i ORDER BY created_at DESC", $table_name));
280 wp_cache_set($cache_key, $articles, 'outrank', 300); // Cache for 5 minutes
281 }
282
283 return $articles;
284 }
285
286 function outrank_clear_articles_cache() {
287 wp_cache_delete('outrank_all_articles_' . get_current_blog_id(), 'outrank');
288 }
289
290 require_once OUTRANK_PLUGIN_PATH . 'libs/api.php';
291
292 // Sync post status changes to outrank_manage table
293 add_action('transition_post_status', 'outrank_sync_post_status', 10, 3);
294 function outrank_sync_post_status($new_status, $old_status, $post) {
295 if ($post->post_type !== 'post') return;
296 if ($new_status === $old_status) return;
297
298 global $wpdb;
299 $table = $wpdb->prefix . 'outrank_manage';
300
301 if (!outrank_table_exists()) return;
302
303 $slug = $post->post_name;
304 // Strip __trashed suffix WordPress adds when trashing
305 $slug = preg_replace('/__trashed$/', '', $slug);
306
307 // Check if another post uses this slug (avoid conflicts)
308 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
309 $other_post = $wpdb->get_var($wpdb->prepare(
310 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_name = %s AND post_type = 'post' AND ID != %d",
311 $slug,
312 $post->ID
313 ));
314 if ($other_post > 0) return;
315
316 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
317 $wpdb->update($table, ['status' => $new_status], ['slug' => $slug]);
318
319 outrank_clear_articles_cache();
320 }
321
322 // Remove from outrank_manage when a post is permanently deleted
323 add_action('before_delete_post', 'outrank_sync_post_delete', 10, 1);
324 function outrank_sync_post_delete($post_id) {
325 $post = get_post($post_id);
326 if (!$post || $post->post_type !== 'post') return;
327
328 global $wpdb;
329 $table = $wpdb->prefix . 'outrank_manage';
330
331 if (!outrank_table_exists()) return;
332
333 $slug = $post->post_name;
334 $slug = preg_replace('/__trashed$/', '', $slug);
335
336 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
337 $wpdb->delete($table, ['slug' => $slug]);
338
339 outrank_clear_articles_cache();
340 }
341