SEO Ultimate - Version 0.3

Version Description

Download this release

Release Info

Developer SEO Design Solutions
Plugin Icon 128x128 SEO Ultimate
Version 0.3
Comparing to
See all releases

Code changes from version 0.2 to 0.3

class.seo-ultimate.php ADDED
@@ -0,0 +1,1147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The main class. Provides plugin-level functionality.
4
+ *
5
+ * @version 1.2
6
+ * @since 0.1
7
+ */
8
+ class SEO_Ultimate {
9
+
10
+ /********** VARIABLES **********/
11
+
12
+ /**
13
+ * Stores all module class instances.
14
+ *
15
+ * @since 0.1
16
+ * @var array
17
+ */
18
+ var $modules = array();
19
+
20
+ /**
21
+ * Stores the names of disabled modules.
22
+ *
23
+ * @since 0.1
24
+ * @var array
25
+ */
26
+ var $disabled_modules = array();
27
+
28
+ /**
29
+ * Stores the status (disabled/hidden/silenced/enabled) of each module.
30
+ *
31
+ * @since 0.1
32
+ * @var array
33
+ */
34
+ var $module_status = array();
35
+
36
+ /**
37
+ * The server path of this plugin file.
38
+ * Example: /home/user/public_html/wp-content/plugins/seo-ultimate/seo-ultimate.php
39
+ *
40
+ * @since 0.1
41
+ * @var string
42
+ */
43
+ var $plugin_file_path;
44
+
45
+ /**
46
+ * The public URL of this plugin file.
47
+ * Example: http://www.example.com/wp-content/plugins/seo-ultimate/seo-ultimate.php
48
+ *
49
+ * @since 0.1
50
+ * @var string
51
+ */
52
+ var $plugin_file_url;
53
+
54
+ /**
55
+ * The server path of the directory where this plugin is located, with trailing slash.
56
+ * Example: /home/user/public_html/wp-content/plugins/seo-ultimate/
57
+ *
58
+ * @since 0.1
59
+ * @var string
60
+ */
61
+ var $plugin_dir_path;
62
+
63
+ /**
64
+ * The public URL of the directory where this plugin is located, with trailing slash.
65
+ * Example: http://www.example.com/wp-content/plugins/seo-ultimate/
66
+ *
67
+ * @since 0.1
68
+ * @var string
69
+ */
70
+ var $plugin_dir_url;
71
+
72
+ /**
73
+ * The database ID of the current hit.
74
+ *
75
+ * @since 0.2
76
+ * @var int
77
+ */
78
+ var $hit_id = 0;
79
+
80
+ /**
81
+ * The name of the function/mechanism that triggered the current redirect.
82
+ *
83
+ * @since 0.3
84
+ * @var string
85
+ */
86
+ var $hit_redirect_trigger;
87
+
88
+
89
+ /********** CLASS CONSTRUCTORS **********/
90
+
91
+ /**
92
+ * Fills in class variables, loads modules, and hooks into WordPress.
93
+ * PHP5-style constructor.
94
+ *
95
+ * @since 0.1
96
+ * @uses load_plugin_data()
97
+ * @uses SU_VERSION
98
+ * @uses install()
99
+ * @uses upgrade()
100
+ * @uses load_modules()
101
+ * @uses activate() Registered with WordPress as the activation hook.
102
+ * @uses init() Hooked into WordPress's "init" action.
103
+ * @uses add_menus() Hooked into WordPress's "admin_menu" action.
104
+ * @uses sanitize_menu_hook() Hooked into WordPress's "sanitize_title" filter.
105
+ * @uses admin_includes() Hooked into WordPress's "admin_head" action.
106
+ * @uses plugin_page_notices() Hooked into WordPress's "admin_head" action.
107
+ * @uses admin_help() Hooked into WordPress's "contextual_help" action.
108
+ * @uses log_redirect() Hooked into WordPress's "wp_redirect" action.
109
+ * @uses log_hit() Hooked into WordPress's "status_header" action.
110
+ */
111
+ function __construct() {
112
+
113
+ /********** CLASS CONSTRUCTION **********/
114
+
115
+ //Load data about the plugin file itself into the class
116
+ $this->load_plugin_data();
117
+
118
+
119
+ /********** VERSION CHECKING **********/
120
+
121
+ //Get the current version, and the version when the plugin last ran
122
+ $version = SU_VERSION;
123
+ $oldversion = get_option('su_version', false);
124
+
125
+ //If this is the first time the plugin is running, then install()
126
+ if ($oldversion === false)
127
+ $this->install();
128
+
129
+ //If $oldversion is less than $version, then upgrade()
130
+ elseif (version_compare($version, $oldversion) == 1)
131
+ $this->upgrade($oldversion);
132
+
133
+ //Store the current version in the database.
134
+ //Rest assured, WordPress won't waste a database query if the value hasn't changed.
135
+ update_option('su_version', $version);
136
+
137
+
138
+ /********** INITIALIZATION **********/
139
+
140
+ //Load plugin modules. Must be called *after* load_plugin_data()
141
+ $this->load_modules();
142
+
143
+
144
+ /********** PLUGIN EVENT HOOKS **********/
145
+
146
+ //If we're activating the plugin, then call the activation function
147
+ register_activation_hook($this->plugin_file_path, array($this, 'activate'));
148
+
149
+ //If we're deactivating the plugin, then call the deactivation function
150
+ register_deactivation_hook($this->plugin_file_path, array($this, 'deactivate'));
151
+
152
+ //If we're uninstalling the plugin, then call the uninstallation function
153
+ register_uninstall_hook($this->plugin_file_path, 'su_uninstall');
154
+
155
+
156
+ /********** ACTION & FILTER HOOKS **********/
157
+
158
+ //Initializes modules at WordPress initialization
159
+ add_action('init', array($this, 'init'));
160
+
161
+ //Hook to output all <head> code
162
+ add_action('wp_head', array($this, 'template_head'), 1);
163
+
164
+ //Hook to include JavaScript and CSS
165
+ add_action('admin_head', array($this, 'admin_includes'));
166
+
167
+ //Hook to add plugin notice actions
168
+ add_action('admin_head', array($this, 'plugin_page_notices'));
169
+
170
+ //When loading the admin menu, call on our menu constructor function.
171
+ //For future-proofing purposes, we specifically state the default priority of 10,
172
+ //since some modules set a priority of 9 with the specific intention of running
173
+ //before this main plugin's hook.
174
+ add_action('admin_menu', array($this, 'add_menus'), 10);
175
+
176
+ //Hook to customize contextual help
177
+ add_action('contextual_help', array($this, 'admin_help'), 10, 2);
178
+
179
+ //Postmeta box hooks
180
+ add_action('admin_menu', array($this, 'add_postmeta_box'));
181
+ add_action('save_post', array($this, 'save_postmeta_box'), 10, 2);
182
+
183
+ //Display info on new versions
184
+ add_action('in_plugin_update_message-'.plugin_basename($this->plugin_file_path), array($this, 'plugin_update_info'), 10, 2);
185
+
186
+ //Log this visitor!
187
+ add_filter('redirect_canonical', array($this, 'log_redirect_canonical'));
188
+ add_filter('wp_redirect', array($this, 'log_redirect'), 10, 2);
189
+ add_filter('status_header', array($this, 'log_hit'), 10, 2);
190
+ }
191
+
192
+ /**
193
+ * PHP4 constructor that redirects to the PHP5 constructor.
194
+ *
195
+ * @since 0.1
196
+ * @uses __construct()
197
+ */
198
+ function SEO_Ultimate() {
199
+
200
+ $this->__construct();
201
+ }
202
+
203
+
204
+ /********** PLUGIN EVENT FUNCTIONS **********/
205
+
206
+ /**
207
+ * This will be called if the plugin is being run for the first time.
208
+ *
209
+ * @since 0.1
210
+ */
211
+ function install() {
212
+
213
+ //Add the database table
214
+ $this->db_setup();
215
+
216
+ //Load settings file if present
217
+ if (get_option('su_settings') === false && is_readable($settingsfile = $this->plugin_dir_path.'settings.txt')) {
218
+ $import = base64_decode(file_get_contents($settingsfile));
219
+ if (is_serialized($import)) update_option('su_settings', $import);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * This will be called if the plugin's version has increased since the last run.
225
+ *
226
+ * @since 0.1
227
+ */
228
+ function upgrade() {
229
+
230
+ //Upgrade database schemas if needed
231
+ $this->db_setup();
232
+ }
233
+
234
+ /**
235
+ * WordPress will call this when the plugin is activated, as instructed by the register_activation_hook() call in {@link __construct()}.
236
+ * Does activation tasks for the plugin itself, not modules.
237
+ *
238
+ * @since 0.1
239
+ */
240
+ function activate() {
241
+
242
+ //Nothing here yet
243
+ }
244
+
245
+ /**
246
+ * WordPress will call this when the plugin is deactivated, as instructed by the register_deactivation_hook() call in {@link __construct()}.
247
+ *
248
+ * @since 0.1
249
+ */
250
+ function deactivate() {
251
+
252
+ //Let modules run deactivation tasks
253
+ do_action('su_deactivate');
254
+
255
+ //Unschedule all cron jobs
256
+ $this->remove_cron_jobs(true);
257
+
258
+ //Delete module records, so that modules are re-activated if the plugin is.
259
+ delete_option('su_modules');
260
+
261
+ //Delete all cron job records, since the jobs no longer exist
262
+ delete_option('su_cron');
263
+ }
264
+
265
+ /**
266
+ * Calls module deactivation/uninstallation functions and deletes all database data.
267
+ *
268
+ * @since 0.1
269
+ */
270
+ function uninstall() {
271
+
272
+ //Deactivate modules and cron jobs
273
+ $this->deactivate();
274
+
275
+ //Let modules run uninstallation tasks
276
+ do_action('su_uninstall');
277
+
278
+ //Delete all other options that aren't deleted in deactivate()
279
+ delete_option('su_version');
280
+ delete_option('su_settings');
281
+
282
+ //Delete the hits table
283
+ mysql_query("DROP TABLE IF EXISTS ".$this->get_table_name('hits'));
284
+ }
285
+
286
+
287
+ /********** INITIALIZATION FUNCTIONS **********/
288
+
289
+ /**
290
+ * Fills class variables with information about where the plugin is located.
291
+ *
292
+ * @since 0.1
293
+ * @uses $plugin_file_path
294
+ * @uses $plugin_file_url
295
+ * @uses $plugin_dir_path
296
+ * @uses $plugin_dir_url
297
+ */
298
+ function load_plugin_data() {
299
+
300
+ //Load plugin path/URL information
301
+ $filename = 'seo-ultimate.php';
302
+ $this->plugin_dir_path = trailingslashit(dirname(trailingslashit(WP_PLUGIN_DIR).plugin_basename(__FILE__)));
303
+ $this->plugin_file_path = $this->plugin_dir_path.$filename;
304
+ $this->plugin_dir_url = trailingslashit(plugins_url(dirname(plugin_basename(__FILE__))));
305
+ $this->plugin_file_url = $this->plugin_dir_url.$filename;
306
+ }
307
+
308
+ /**
309
+ * Finds and loads all modules. Runs the activation functions of newly-uploaded modules.
310
+ * Updates the modules list and saves it in the database. Removes the cron jobs of deleted modules.
311
+ *
312
+ * @since 0.1
313
+ * @uses $plugin_dir_path
314
+ * @uses $modules Stores module classes in this array.
315
+ * @uses module_sort_callback() Passes this function to uasort() to sort the $modules array.
316
+ * @uses SU_MODULE_ENABLED
317
+ * @uses SU_MODULE_DISABLED
318
+ */
319
+ function load_modules() {
320
+
321
+ //The plugin_dir_path variable must be set before calling this function!
322
+ if (!$this->plugin_dir_path) return false;
323
+
324
+ //The modules are in the "modules" subdirectory of the plugin folder.
325
+ $dir = opendir($this->plugin_dir_path.'modules');
326
+
327
+ //Get the modules list from last time the plugin was loaded.
328
+ $oldmodules = maybe_unserialize(get_option('su_modules', false));
329
+
330
+ //If no list is found, then create a new, empty list.
331
+ if ($oldmodules === false) {
332
+ $oldmodules = array();
333
+ add_option('su_modules', serialize($oldmodules));
334
+ }
335
+
336
+ //This loop will be repeated as long as there are more files to inspect
337
+ while ($file = readdir($dir)) {
338
+
339
+ //Modules are non-directory files with the .php extension
340
+ if ($file != '.' && $file != '..' && !is_dir($file) &&
341
+ substr($file, -4) == '.php') {
342
+
343
+ //Figure out the module's array key and class name
344
+ $module = strval(strtolower(substr($file, 0, -4)));
345
+ $class = 'SU_'.str_replace(' ', '', ucwords(str_replace('-', ' ', $module)));
346
+
347
+ //If this module is disabled...
348
+ if ($oldmodules[$module] == SU_MODULE_DISABLED) {
349
+
350
+ $name = file($this->plugin_dir_path."modules/$file");
351
+ if ($name) $name = str_replace(' Module', '', ltrim($name[2], ' *'));
352
+ else $name = ucwords(str_replace('-', ' ', $module));
353
+
354
+ $this->disabled_modules[$module] = $name;
355
+
356
+ } else {
357
+
358
+ //Load the module's code
359
+ require_once("modules/$file");
360
+
361
+ //If this is actually a module...
362
+ if (class_exists($class)) {
363
+
364
+ //Create an instance of the module's class and store it in the array
365
+ $this->modules[$module] = new $class;
366
+
367
+ //We must tell the module what its key is so that it can save settings
368
+ $this->modules[$module]->module_key = $module;
369
+
370
+ //Tell the module what its URL is
371
+ $this->modules[$module]->module_url = $this->plugin_dir_url."modules/$file";
372
+
373
+ //Tell the module what its plugin page hook is
374
+ $this->modules[$module]->plugin_page_hook =
375
+ $this->modules[$module]->get_menu_parent_hook().'_page_'.SEO_Ultimate::key_to_hook($module);
376
+
377
+ } //If this isn't a module, then the file will simply be included as-is
378
+ }
379
+ }
380
+ }
381
+
382
+ //If the loop above found modules, then sort them with our special sorting function
383
+ //so they appear on the admin menu in the right order
384
+ if (count($this->modules) > 0)
385
+ uasort($this->modules, array($this, 'module_sort_callback'));
386
+
387
+ //Now we'll compare the current module set with the one from last time.
388
+
389
+ //Construct the new modules list that'll go in the database.
390
+ //This code block will add/activate new modules, keep existing ones, and remove (i.e. not add) deleted ones.
391
+ foreach ($this->modules as $key => $module) {
392
+ if (isset($oldmodules[$key])) {
393
+ $newmodules[$key] = $oldmodules[$key];
394
+ } else {
395
+ $module->activate();
396
+ $newmodules[$key] = SU_MODULE_ENABLED;
397
+ }
398
+ }
399
+
400
+ foreach ($this->disabled_modules as $key => $name) {
401
+ $newmodules[$key] = SU_MODULE_DISABLED;
402
+ }
403
+
404
+ //Save the new modules list
405
+ $this->module_status = $newmodules;
406
+ update_option('su_modules', serialize($newmodules));
407
+
408
+ //Remove the cron jobs of deleted modules
409
+ $this->remove_cron_jobs();
410
+ }
411
+
412
+ /**
413
+ * Runs during WordPress's init action.
414
+ * Loads the textdomain and calls modules' init_pre() functions.
415
+ *
416
+ * @since 0.1
417
+ * @uses $plugin_file_path
418
+ * @uses SU_Module::init_pre()
419
+ */
420
+ function init() {
421
+
422
+ //Allow translation of this plugin
423
+ load_plugin_textdomain('seo-ultimate', '', plugin_basename($this->plugin_file_path));
424
+
425
+ //Let the modules run init tasks
426
+ foreach ($this->modules as $module)
427
+ $module->init_pre();
428
+ }
429
+
430
+
431
+ /********** DATABASE FUNCTIONS **********/
432
+
433
+ /**
434
+ * Will create or update the database table.
435
+ *
436
+ * @since 0.1
437
+ * @uses get_table_name()
438
+ */
439
+ function db_setup() {
440
+
441
+ $sql = "CREATE TABLE " . $this->get_table_name('hits') . " (
442
+ id BIGINT NOT NULL AUTO_INCREMENT,
443
+ time INT NOT NULL ,
444
+ ip_address VARCHAR(255) NOT NULL,
445
+ user_agent VARCHAR(255) NOT NULL,
446
+ url TEXT NOT NULL,
447
+ redirect_url TEXT NOT NULL,
448
+ redirect_trigger VARCHAR(255) NOT NULL,
449
+ referer TEXT NOT NULL,
450
+ status_code SMALLINT(3) NOT NULL,
451
+ is_new BOOL NOT NULL,
452
+ PRIMARY KEY (id)
453
+ );";
454
+
455
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
456
+ dbDelta($sql);
457
+ }
458
+
459
+ /**
460
+ * Returns a full, prefixed MySQL table name.
461
+ *
462
+ * @since 0.1
463
+ *
464
+ * @param string $shortname The non-prefixed table name.
465
+ * @return string The full, prefixed table name.
466
+ */
467
+ function get_table_name($shortname) {
468
+ global $wpdb;
469
+ if ($shortname == 'hits')
470
+ return $wpdb->prefix . "sds_hits";
471
+ else
472
+ return '';
473
+ }
474
+
475
+ /**
476
+ * A status_header WordPress filter that logs the current hit.
477
+ *
478
+ * @since 0.1
479
+ * @uses get_current_url()
480
+ * @uses $hit_id
481
+ *
482
+ * @param string $status_header The full HTTP status header. Unused and returned as-is.
483
+ * @param int $status_code The numeric HTTP status code.
484
+ * @param string $redirect_url The URL to which the visitor is being redirected. Optional.
485
+ * @return string Returns the $status_header variable unchanged.
486
+ */
487
+ function log_hit($status_header, $status_code, $redirect_url = '') {
488
+
489
+ if (!is_user_logged_in()) {
490
+ global $wpdb;
491
+
492
+ $table = $this->get_table_name('hits');
493
+ $url = $this->get_current_url();
494
+ $is_new = (count($wpdb->get_results($wpdb->prepare("SELECT url FROM $table WHERE url = %s AND is_new = 0", $url))) == 0);
495
+
496
+ $data = array(
497
+ 'time' => time()
498
+ , 'ip_address' => $_SERVER['REMOTE_ADDR']
499
+ , 'user_agent' => $_SERVER['HTTP_USER_AGENT']
500
+ , 'url' => $url
501
+ , 'redirect_url' => $redirect_url
502
+ , 'redirect_trigger' => $this->hit_redirect_trigger
503
+ , 'referer' => $_SERVER['HTTP_REFERER']
504
+ , 'status_code' => $status_code
505
+ , 'is_new' => $is_new
506
+ );
507
+
508
+ if ($this->hit_id > 0) {
509
+
510
+ //We don't want to overwrite a redirect URL if it's already been logged
511
+ if (!strlen($data['redirect_url'])) unset($data['redirect_url']);
512
+
513
+ //Update the existing hit record
514
+ $wpdb->update($table, $data, array('id' => $this->hit_id));
515
+ } else {
516
+ $wpdb->insert($table, $data);
517
+ $this->hit_id = $wpdb->insert_id;
518
+ }
519
+ }
520
+
521
+ return $status_header;
522
+ }
523
+
524
+ /**
525
+ * A wp_redirect WordPress filter that logs the current hit.
526
+ *
527
+ * @since 0.2
528
+ * @uses log_hit()
529
+ *
530
+ * @param string $redirect_url The URL to which the visitor is being redirected.
531
+ * @param int $status_code The numeric HTTP status code.
532
+ * @return string The unchanged $redirect_url parameter.
533
+ */
534
+ function log_redirect($redirect_url, $status_code) {
535
+ if (!$this->hit_redirect_trigger) $this->hit_redirect_trigger = 'wp_redirect';
536
+ $this->log_hit(null, $status_code, $redirect_url);
537
+ return $redirect_url;
538
+ }
539
+
540
+ /**
541
+ * A redirect_canonical WordPress filter that logs the current hit.
542
+ *
543
+ * @since 0.3
544
+ * @uses log_hit()
545
+ *
546
+ * @param string $redirect_url The URL to which the visitor is being redirected.
547
+ * @return string The unchanged $redirect_url parameter.
548
+ */
549
+ function log_redirect_canonical($redirect_url) {
550
+ if (!$this->hit_redirect_trigger) $this->hit_redirect_trigger = 'redirect_canonical';
551
+ //$this->log_hit(null, 301, $redirect_url, 'redirect_canonical');
552
+ return $redirect_url;
553
+ }
554
+
555
+
556
+ /********** ADMIN MENU FUNCTIONS **********/
557
+
558
+ /**
559
+ * Constructs the "SEO" menu and its subitems.
560
+ *
561
+ * @since 0.1
562
+ * @uses $modules
563
+ * @uses get_module_count_code()
564
+ * @uses SU_Module::get_menu_count()
565
+ * @uses SU_Module::get_menu_pos()
566
+ * @uses SU_Module::get_menu_title()
567
+ * @uses SU_Module::get_page_title()
568
+ * @uses key_to_hook()
569
+ */
570
+ function add_menus() {
571
+
572
+ //If subitems have numeric bubbles, then add them up and show the total by the main menu item
573
+ $count = 0;
574
+ foreach ($this->modules as $key => $module) {
575
+ if ($this->module_status[$key] > SU_MODULE_SILENCED && $module->get_menu_count() > 0 && $module->get_menu_parent() == 'seo')
576
+ $count += $module->get_menu_count();
577
+ }
578
+ $count_code = $this->get_menu_count_code($count);
579
+
580
+ //Add the "SEO" menu item!
581
+ add_utility_page(__('SEO Ultimate', 'seo-ultimate'), __('SEO', 'seo-ultimate').$count_code, 'manage_options', 'seo', array(), 'div');
582
+
583
+ //Translations and count codes will mess up the admin page hook, so we need to fix it manually.
584
+ global $admin_page_hooks;
585
+ $admin_page_hooks['seo'] = 'seo';
586
+
587
+ //Add all the subitems
588
+ foreach ($this->modules as $file => $module) {
589
+
590
+ //Show a module on the menu only if it provides a menu title and it doesn't have a parent module
591
+ if ($module->get_menu_title() && !$module->get_parent_module()) {
592
+
593
+ //If the module is hidden, put the module under a non-existant menu parent
594
+ //(this will let the module's admin page be loaded, but it won't show up on the menu)
595
+ if ($this->module_status[$file] > SU_MODULE_HIDDEN)
596
+ $parent = $module->get_menu_parent();
597
+ else
598
+ $parent = 'su-hidden-modules';
599
+
600
+ if ($this->module_status[$file] > SU_MODULE_SILENCED)
601
+ $count_code = $this->get_menu_count_code($module->get_menu_count());
602
+ else
603
+ $count_code = '';
604
+
605
+ add_submenu_page($parent, $module->get_page_title(), $module->get_menu_title().$count_code,
606
+ 'manage_options', $this->key_to_hook($file), array($module, 'admin_page'));
607
+ }
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Compares two modules to determine which of the two should be displayed first on the menu.
613
+ * Sorts by menu position first, and title second.
614
+ * Works as a uasort() callback.
615
+ *
616
+ * @since 0.1
617
+ * @uses SU_Module::get_menu_pos()
618
+ * @uses SU_Module::get_menu_title()
619
+ *
620
+ * @param SU_Module $a The first module to compare.
621
+ * @param SU_Module $b The second module to compare.
622
+ * @return int This will be -1 if $a comes first, or 1 if $b comes first.
623
+ */
624
+ function module_sort_callback($a, $b) {
625
+ if ($a->get_menu_pos() == $b->get_menu_pos()) {
626
+ return strcmp($a->get_menu_title(), $b->get_menu_title());
627
+ }
628
+
629
+ return ($a->get_menu_pos() < $b->get_menu_pos()) ? -1 : 1;
630
+ }
631
+
632
+ /**
633
+ * If the bubble alert count parameter is greater than zero, then returns the HTML code for a numeric bubble to display next to a menu item.
634
+ * Otherwise, returns an empty string.
635
+ *
636
+ * @since 0.1
637
+ *
638
+ * @param int $count The number that should appear in the bubble.
639
+ * @return string The string that should be added to the end of the menu item title.
640
+ */
641
+ function get_menu_count_code($count) {
642
+
643
+ //If we have alerts that need a bubble, then return the bubble HTML.
644
+ if ($count > 0)
645
+ return "&nbsp;<span id='awaiting-mod' class='count-$count'><span class='pending-count'>$count</span></span>";
646
+ else
647
+ return '';
648
+ }
649
+
650
+ /**
651
+ * Converts a module key to a menu hook.
652
+ * (Makes the "Stats" module load when the "SEO" parent item is clicked.)
653
+ *
654
+ * @since 0.1
655
+ *
656
+ * @param string $key The module key.
657
+ * @return string The menu hook.
658
+ */
659
+ function key_to_hook($key) {
660
+ switch ($key) {
661
+ case 'stats': return 'seo'; break;
662
+ case 'settings': return 'seo-ultimate'; break;
663
+ default: return "su-$key"; break;
664
+ }
665
+ }
666
+
667
+ /**
668
+ * Converts a menu hook to a module key.
669
+ * (If the "SEO" parent item is clicked, then the Stats module is being shown.)
670
+ *
671
+ * @since 0.1
672
+ *
673
+ * @param string $hook The menu hook.
674
+ * @return string The module key.
675
+ */
676
+ function hook_to_key($hook) {
677
+ switch ($hook) {
678
+ case 'seo': return 'stats'; break;
679
+ case 'seo-ultimate': return 'settings'; break;
680
+ default: return substr($hook, 3); break;
681
+ }
682
+ }
683
+
684
+
685
+ /********** OTHER ADMIN FUNCTIONS **********/
686
+
687
+ /**
688
+ * Returns a boolean indicating whether the user is currently viewing an admin page generated by this plugin.
689
+ *
690
+ * @since 0.1
691
+ *
692
+ * @return bool Whether the user is currently viewing an admin page generated by this plugin.
693
+ */
694
+ function is_plugin_admin_page() {
695
+ if (is_admin()) {
696
+ global $plugin_page;
697
+
698
+ foreach ($this->modules as $key => $module) {
699
+ if ($plugin_page == $this->key_to_hook($key)) return true;
700
+ }
701
+ }
702
+
703
+ return false;
704
+ }
705
+
706
+ /**
707
+ * Includes the plugin's CSS and JavaScript in the header.
708
+ * Also includes a module's CSS/JavaScript on its administration page.
709
+ *
710
+ * @todo Link to global plugin includes only when on plugin pages.
711
+ *
712
+ * @since 0.1
713
+ * @uses $modules
714
+ * @uses $plugin_file_url
715
+ * @uses $plugin_dir_url
716
+ * @uses hook_to_key()
717
+ */
718
+ function admin_includes() {
719
+
720
+ //Global plugin CSS and JavaScript
721
+ echo "\n<link rel='stylesheet' type='text/css' href='".$this->plugin_dir_url."seo-ultimate.css?version=".SU_VERSION."' />\n";
722
+ echo "\n<script type='text/javascript' src='".$this->plugin_dir_url."seo-ultimate.js?version=".SU_VERSION."'></script>\n";
723
+
724
+ //Figure out what plugin admin page we're on
725
+ global $plugin_page;
726
+ $pp = $this->hook_to_key($plugin_page);
727
+
728
+ foreach ($this->modules as $key => $module) {
729
+
730
+ //Is the current admin page belong to this module? If so, print links to the module's CSS and JavaScript.
731
+ if (strcmp($key, $pp) == 0) {
732
+ echo "\n<link rel='stylesheet' type='text/css' href='".$module->module_url."?css=admin&amp;version=".SU_VERSION."' />\n";
733
+ echo "\n<script type='text/javascript' src='".$module->module_url."?js=admin&amp;version=".SU_VERSION."'></script>\n";
734
+ return;
735
+ }
736
+ }
737
+ }
738
+
739
+ /**
740
+ * Replaces WordPress's default contextual help with module-specific help text, if the module provides it.
741
+ *
742
+ * @since 0.1
743
+ * @uses $modules
744
+ *
745
+ * @param string $text WordPress's default contextual help.
746
+ * @param string $screen The screen currently being shown.
747
+ * @return string The contextual help content that should be shown.
748
+ */
749
+ function admin_help($text, $screen) {
750
+ //If $screen begins with a recognized prefix...
751
+ if ($screen == 'toplevel_page_seo' || substr($screen, 0, 9) == 'seo_page_' || substr($screen, 0, 14) == 'settings_page_') {
752
+
753
+ //Remove the prefix from $screen to get the $key
754
+ $key = $this->hook_to_key(str_replace(array('toplevel_page_', 'seo_page_', 'settings_page_'), '', $screen));
755
+
756
+ //If $key refers to a module...
757
+ if (isset($this->modules[$key])) {
758
+
759
+ //Ask the module for custom help content
760
+ $customhelp = $this->modules[$key]->admin_help();
761
+
762
+ //If we have custom help to display...
763
+ if ($customhelp !== false) {
764
+
765
+ //Return the help content with an <h5> title
766
+ $help = "<div class='su-help'>\n";
767
+ $help .= '<h5>'.sprintf(__('%s Help', 'seo-ultimate'),
768
+ $this->modules[$key]->get_page_title())."</h5>\n";
769
+ $help .= "<div class='metabox-prefs'>\n".$customhelp."\n</div>\n";
770
+ $help .= "</div>\n";
771
+ return $help;
772
+ }
773
+ }
774
+ } elseif (strcmp($screen, 'post') == 0 || strcmp($screen, 'page') == 0) {
775
+
776
+ //Gather post meta help content
777
+ $helparray = apply_filters('su_postmeta_help', array());
778
+
779
+ if ($helparray) {
780
+
781
+ $customhelp = '';
782
+ foreach ($helparray as $line) {
783
+ $customhelp .= "<li><p>$line</p></li>\n";
784
+ }
785
+
786
+ $text .= "<div class='su-help'>\n";
787
+ $text .= '<h5>'.__('SEO Settings Help', 'seo-ultimate')."</h5>\n";
788
+ $text .= "<div class='metabox-prefs'>\n";
789
+ $text .= "<p>".__("The SEO Settings box lets you customize these settings:", 'seo-ultimate')."</p>\n";
790
+ $text .= "<ul>\n$customhelp\n</ul>";
791
+ $text .= "<p><em>".__("(The SEO Settings box is part of the SEO Ultimate plugin.)", 'seo-ultimate')."</em></p>\n";
792
+ $text .= "\n</div>\n</div>\n";
793
+ return $text;
794
+ }
795
+ }
796
+
797
+ //No custom help content to show. Return the default.
798
+ return $text;
799
+ }
800
+
801
+ /**
802
+ * Notifies the user if he/she is using plugins whose functionality SEO Ultimate replaces.
803
+ *
804
+ * @since 0.1
805
+ * @uses plugin_page_notice() Hooked into the after_plugin_row_$path actions.
806
+ */
807
+ function plugin_page_notices() {
808
+
809
+ if (isset($this->modules['settings']) && !$this->modules['settings']->get_setting('plugin_notices'))
810
+ return;
811
+
812
+ global $pagenow;
813
+
814
+ if ($pagenow == 'plugins.php') {
815
+
816
+ $r_plugins = array(
817
+ 'all-in-one-seo-pack/all_in_one_seo_pack.php'
818
+ , 'canonical/canonical.php'
819
+ );
820
+
821
+ $i_plugins = get_plugins();
822
+
823
+ foreach ($r_plugins as $path) {
824
+ if (isset($i_plugins[$path]))
825
+ add_action("after_plugin_row_$path", array($this, 'plugin_page_notice'), 10, 3);
826
+ }
827
+ }
828
+ }
829
+
830
+ /**
831
+ * Outputs a table row notifying the user that he/she is using a plugin whose functionality SEO Ultimate replaces.
832
+ *
833
+ * @since 0.1
834
+ */
835
+ function plugin_page_notice($file, $data, $context) {
836
+ if ($context != 'inactive') {
837
+ echo "<tr class='plugin-update-tr su-plugin-notice'><td colspan='3' class='plugin-update'><div class='update-message'>\n";
838
+ printf(__('SEO Ultimate includes the functionality of %1$s. You may want to deactivate %1$s to avoid plugin conflicts.', 'seo-ultimate'), $data['Name']);
839
+ echo "</div></td></tr>\n";
840
+ }
841
+ }
842
+
843
+ /**
844
+ * Displays new-version info in this plugin's update row on WordPress's plugin admin page.
845
+ * Hooked into WordPress's in_plugin_update_message-(file) action.
846
+ *
847
+ * @since 0.1
848
+ *
849
+ * @param array $plugin_data An array of this plugin's information. Unused.
850
+ * @param obejct $r The response object from the WordPress Plugin Directory.
851
+ */
852
+ function plugin_update_info($plugin_data, $r) {
853
+ if ($r && $r->new_version) {
854
+ $info = $this->load_webpage("http://www.seodesignsolutions.com/apis/su/update-info/?ov=".urlencode(SU_VERSION)."&nv=".urlencode($r->new_version));
855
+ if ($info) {
856
+ $info = strip_tags($info, "<br><a><b><i><span>");
857
+ echo "<br />$info";
858
+ }
859
+ }
860
+ }
861
+
862
+
863
+ /********** ADMIN POST META BOX FUNCTIONS **********/
864
+
865
+ /**
866
+ * Gets the post meta box fields from the modules, sorts them, and returns the HTML as a string.
867
+ *
868
+ * @since 0.1
869
+ * @uses $modules
870
+ *
871
+ * @param string $screen The admin screen currently being viewed (post, page). Defaults to post. Optional.
872
+ * @return string Concatenated <tr>(field)</tr> strings.
873
+ */
874
+ function get_postmeta_fields($screen='post') {
875
+
876
+ //Compile the fields array
877
+ $fields = array();
878
+ foreach ($this->modules as $module)
879
+ $fields = $module->postmeta_fields($fields, $screen);
880
+
881
+ if (count($fields) > 0) {
882
+
883
+ //Sort the fields array
884
+ ksort($fields, SORT_STRING);
885
+
886
+ //Return a string
887
+ return implode("\n", $fields);
888
+ }
889
+
890
+ return '';
891
+ }
892
+
893
+ /**
894
+ * If we have post meta fields to display, then register our meta box with WordPress.
895
+ *
896
+ * @since 0.1
897
+ * @uses get_postmeta_fields()
898
+ */
899
+ function add_postmeta_box() {
900
+
901
+ //Add the metabox to posts and pages.
902
+ foreach (array('post', 'page') as $screen) {
903
+
904
+ //Only show the meta box if there are fields to show.
905
+ if ($this->get_postmeta_fields($screen))
906
+ add_meta_box('su_postmeta', __('SEO Settings', 'seo-ultimate'), array($this, "show_{$screen}_postmeta_box"), $screen, 'normal', 'high');
907
+ }
908
+ }
909
+
910
+ /**
911
+ * Displays the inner contents of the post meta box when editing posts.
912
+ *
913
+ * @since 0.1
914
+ * @uses show_postmeta_box()
915
+ */
916
+ function show_post_postmeta_box() {
917
+ $this->show_postmeta_box('post');
918
+ }
919
+
920
+ /**
921
+ * Displays the inner contents of the post meta box when editing Pages.
922
+ *
923
+ * @since 0.1
924
+ * @uses show_postmeta_box()
925
+ */
926
+ function show_page_postmeta_box() {
927
+ $this->show_postmeta_box('page');
928
+ }
929
+
930
+ /**
931
+ * Displays the inner contents of the post meta box.
932
+ *
933
+ * @since 0.1
934
+ * @uses get_postmeta_fields()
935
+ *
936
+ * @param string $screen The admin screen currently being viewed (post, page).
937
+ */
938
+ function show_postmeta_box($screen) {
939
+
940
+ //Begin box
941
+ echo "<div id='su-postmeta-box'>\n";
942
+ wp_nonce_field('su-update-postmeta', '_su_wpnonce');
943
+ echo "\n<table>\n";
944
+
945
+ //Output postmeta fields
946
+ echo $this->get_postmeta_fields($screen);
947
+
948
+ //End box
949
+ echo "\n</table>\n</div>\n";
950
+ }
951
+
952
+ /**
953
+ * Saves the values of the fields in the post meta box.
954
+ *
955
+ * @since 0.1
956
+ *
957
+ * @param int $post_id The ID of the post being saved.
958
+ * @return int The ID of the post being saved.
959
+ */
960
+ function save_postmeta_box($post_id, $post) {
961
+
962
+ //Sanitize
963
+ $post_id = (int)$post_id;
964
+
965
+ //Don't save postmeta if this is a revision!
966
+ if ($post->post_type == 'revision') return;
967
+
968
+ //Run preliminary permissions checks
969
+ if ( !check_admin_referer('su-update-postmeta', '_su_wpnonce') ) return;
970
+ if ( 'page' == $_POST['post_type'] ) {
971
+ if ( !current_user_can( 'edit_page', $post_id )) return;
972
+ } elseif ( 'post' == $_POST['post_type'] ) {
973
+ if ( !current_user_can( 'edit_post', $post_id )) return;
974
+ } else return;
975
+
976
+ //Get an array of all postmeta
977
+ $allmeta = wp_cache_get($post_id, 'post_meta');
978
+ if (!$allmeta) {
979
+ update_postmeta_cache($post_id);
980
+ $allmeta = wp_cache_get($post_id, 'post_meta');
981
+ }
982
+
983
+ //Update postmeta values
984
+ foreach ($_POST as $key => $value) {
985
+ if (substr($key, 0, 4) == '_su_') {
986
+
987
+ //Turn checkboxes into integers
988
+ if (strcmp($value, '1') == 0) $value = 1;
989
+
990
+ //Set the postmeta
991
+ update_post_meta($post_id, $key, $value);
992
+
993
+ //This value has been updated.
994
+ unset($allmeta[$key]);
995
+ }
996
+ }
997
+
998
+ //Update values for unchecked checkboxes.
999
+ foreach ($allmeta as $key => $value) {
1000
+ if (substr($key, 0, 4) == '_su_') {
1001
+ $value = maybe_unserialize($value[0]);
1002
+ if ($value == 1)
1003
+ update_post_meta($post_id, $key, 0);
1004
+ }
1005
+ }
1006
+
1007
+ //All done
1008
+ return $post_id;
1009
+ }
1010
+
1011
+
1012
+ /********** CRON FUNCTION **********/
1013
+
1014
+ /**
1015
+ * Can remove cron jobs for modules that no longer exist, or remove all cron jobs.
1016
+ *
1017
+ * @since 0.1
1018
+ *
1019
+ * @param bool $remove_all Whether to remove all cron jobs. Optional.
1020
+ */
1021
+ function remove_cron_jobs($remove_all = false) {
1022
+ $crondata = maybe_unserialize(get_option('su_cron'));
1023
+ if (is_array($crondata)) {
1024
+ $newcrondata = $crondata;
1025
+
1026
+ foreach ($crondata as $key => $crons) {
1027
+ if ($remove_all || !isset($this->modules[$key])) {
1028
+ foreach ($crons as $data) { wp_clear_scheduled_hook($data[0]); }
1029
+ unset($newcrondata[$key]);
1030
+ }
1031
+ }
1032
+
1033
+ update_option('su_cron', serialize($newcrondata));
1034
+ }
1035
+ }
1036
+
1037
+
1038
+ /********** TEMPLATE OUTPUT FUNCTION **********/
1039
+
1040
+ /**
1041
+ * Outputs code into the template's <head> tag.
1042
+ *
1043
+ * @since 0.1
1044
+ */
1045
+ function template_head() {
1046
+
1047
+ if (isset($this->modules['settings']))
1048
+ $markcode = $this->modules['settings']->get_setting('mark_code');
1049
+ else
1050
+ $markcode = false;
1051
+
1052
+ echo "\n";
1053
+
1054
+ if ($markcode) echo "\n<!-- ".SU_PLUGIN_NAME." (".SU_PLUGIN_URI.") -->\n";
1055
+
1056
+ //Let modules output head code.
1057
+ do_action('su_head');
1058
+
1059
+ //Make sure the blog is public. Telling robots what to do is a moot point if they aren't even seeing the blog.
1060
+ if (get_option('blog_public')) {
1061
+ $robots = implode(',', apply_filters('su_meta_robots', array()));
1062
+ if ($robots) echo "\t<meta name=\"robots\" content=\"$robots\" />\n";
1063
+ }
1064
+
1065
+ if ($markcode) echo "<!-- /".SU_PLUGIN_NAME." -->\n\n";
1066
+ }
1067
+
1068
+ /********** PSEUDO-STATIC FUNCTIONS **********/
1069
+
1070
+ /**
1071
+ * Approximately determines the URL in the visitor's address bar. (Includes query strings, but not #anchors.)
1072
+ *
1073
+ * @since 0.1
1074
+ *
1075
+ * @return string The current URL.
1076
+ */
1077
+ function get_current_url() {
1078
+ $url = 'http';
1079
+ if ($_SERVER["HTTPS"] == "on") $url .= "s";
1080
+ $url .= "://";
1081
+
1082
+ if ($_SERVER["SERVER_PORT"] != "80")
1083
+ return $url.$_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
1084
+ else
1085
+ return $url.$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
1086
+ }
1087
+
1088
+ /**
1089
+ * Determines the ID of the current post.
1090
+ * Works in the admin as well as the front-end.
1091
+ *
1092
+ * @since 0.2
1093
+ *
1094
+ * @return int|false The ID of the current post, or false on failure.
1095
+ */
1096
+ function get_post_id() {
1097
+ if (is_admin())
1098
+ return intval($_REQUEST['post']);
1099
+ elseif (in_the_loop())
1100
+ return intval(get_the_ID());
1101
+ elseif (is_singular()) {
1102
+ global $wp_query;
1103
+ return $wp_query->get_queried_object_id();
1104
+ }
1105
+
1106
+ return false;
1107
+ }
1108
+
1109
+ /**
1110
+ * Loads a webpage and returns its HTML as a string.
1111
+ *
1112
+ * @since 0.3
1113
+ *
1114
+ * @param string $url The URL of the webpage to load.
1115
+ * @return string The HTML of the URL.
1116
+ */
1117
+ function load_webpage($url) {
1118
+
1119
+ $options = array();
1120
+ $options['headers'] = array(
1121
+ 'User-Agent' => su_get_user_agent()
1122
+ );
1123
+
1124
+ $response = wp_remote_request($url, $options);
1125
+
1126
+ if ( is_wp_error( $response ) ) return false;
1127
+ if ( 200 != $response['response']['code'] ) return false;
1128
+
1129
+ return trim( $response['body'] );
1130
+ }
1131
+
1132
+ /**
1133
+ * Uses the Google Chart API to output a 3D pie chart.
1134
+ *
1135
+ * @since 0.3
1136
+ *
1137
+ * @param array $data The labels (keys) and values that go on the pie chart.
1138
+ * @param array|string $color An array or string of which color(s) to use on the pie chart.
1139
+ */
1140
+ function pie_chart_3d($data, $color = '0000FF') {
1141
+ $labels = implode('|', array_keys($data));
1142
+ $values = implode(',', array_values($data));
1143
+ $colors = implode(',', (array)$color);
1144
+ echo "<img src='http://chart.apis.google.com/chart?cht=p3&amp;chd=t:$values&amp;chs=250x100&amp;chl=$labels&amp;chco=$colors' alt='' />";
1145
+ }
1146
+ }
1147
+ ?>
class.su-module.php ADDED
@@ -0,0 +1,1047 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The pseudo-abstract class upon which all modules are based.
4
+ *
5
+ * @abstract
6
+ * @version 1.2
7
+ * @since 0.1
8
+ */
9
+ class SU_Module {
10
+
11
+ /********** VARIABLES **********/
12
+
13
+ var $module_key;
14
+
15
+ /**
16
+ * Stores the module file's URL.
17
+ *
18
+ * @since 0.1
19
+ * @var string
20
+ */
21
+ var $module_url;
22
+
23
+ /**
24
+ * Stores the module's plugin page hook (the full hook with seo_page_ prefix).
25
+ * A reconstructed value of the get_plugin_page_hook() function, which is only available after admin init.
26
+ *
27
+ * @since 0.1
28
+ * @var string
29
+ */
30
+ var $plugin_page_hook;
31
+
32
+ /**
33
+ * Contains messages that are waiting to be displayed to the user.
34
+ *
35
+ * @since 0.1
36
+ * @var array
37
+ */
38
+ var $messages = array();
39
+
40
+
41
+ /********** CONSTRUCTOR FUNCTION **********/
42
+
43
+ /**
44
+ * PHP4 constructor that points to the likely-overloaded PHP5 constructor.
45
+ *
46
+ * @since 0.1
47
+ * @uses __construct()
48
+ */
49
+ function SU_Module() {
50
+ $this->__construct();
51
+ }
52
+
53
+
54
+ /********** PSEUDO-ABSTRACT FUNCTIONS **********/
55
+
56
+ /**
57
+ * PHP5 constructor.
58
+ *
59
+ * @since 0.1
60
+ */
61
+ function __construct() { }
62
+
63
+ /**
64
+ * The title of the admin page, which is displayed in the <title> and <h2> tags.
65
+ * Is the same as the menu title by default.
66
+ *
67
+ * @since 0.1
68
+ *
69
+ * @return string The title shown on this module's admin page.
70
+ */
71
+ function get_page_title() { return $this->get_menu_title(); }
72
+
73
+ /**
74
+ * The title that appears on the administration navigation menu.
75
+ *
76
+ * @since 0.1
77
+ *
78
+ * @return string The title shown on the admin menu.
79
+ */
80
+ function get_menu_title() { return ''; }
81
+
82
+ /**
83
+ * Determines where this module's admin page should appear relative to those of other modules.
84
+ * If two modules have the same menu position index, they are sorted alphabetically.
85
+ *
86
+ * @since 0.1
87
+ *
88
+ * @return int The menu position index.
89
+ */
90
+ function get_menu_pos() { return 999; }
91
+
92
+ /**
93
+ * The number that should be displayed in a bubble next to the module's menu title.
94
+ * A return value of zero means no bubble is shown.
95
+ *
96
+ * @since 0.1
97
+ *
98
+ * @return int The number that should be displayed.
99
+ */
100
+ function get_menu_count() { return 0; }
101
+
102
+ /**
103
+ * A descriptive label of the menu count.
104
+ *
105
+ * @since 0.3
106
+ *
107
+ * @return string The label.
108
+ */
109
+ function get_menu_count_label() { return ''; }
110
+
111
+ /**
112
+ * Indicates under which top-level menu this module's admin page should go.
113
+ * Examples: seo (This plugin's SEO menu), options-general.php (The Settings menu)
114
+ *
115
+ * @since 0.1
116
+ *
117
+ * @return string The value to pass to WordPress's add_submenu_page() function.
118
+ */
119
+ function get_menu_parent(){ return 'seo'; }
120
+
121
+ /**
122
+ * Returns the hook of this module's menu parent.
123
+ * Examples: seo (This plugin's SEO menu), settings (The Settings menu), toplevel (The toplevel)
124
+ *
125
+ * @since 0.1
126
+ *
127
+ * @return string The hook of the module's menu parent.
128
+ */
129
+ function get_menu_parent_hook() { return $this->get_menu_parent(); }
130
+
131
+ /**
132
+ * The module key of this module's parent. Defaults to false (no parent).
133
+ *
134
+ * @since 0.3
135
+ *
136
+ * @return string|bool
137
+ */
138
+ function get_parent_module() { return false; }
139
+
140
+ /**
141
+ * Called at WordPress's init hook.
142
+ *
143
+ * @since 0.1
144
+ */
145
+ function init() {}
146
+
147
+ /**
148
+ * Called upon module activation,
149
+ * i.e. when a module is uploaded or when the plugin is activated for the first time.
150
+ *
151
+ * @since 0.1
152
+ */
153
+ function activate() { }
154
+
155
+ /**
156
+ * Returns an array of default settings. The defaults will be saved in the database if the settings don't exist.
157
+ *
158
+ * @since 0.1
159
+ *
160
+ * @return array The default settings. (The setting name is the key, and the default value is the array value.)
161
+ */
162
+ function get_default_settings() { return array(); }
163
+
164
+ /**
165
+ * The contents of the administration page.
166
+ *
167
+ * @since 0.1
168
+ */
169
+ function admin_page_contents() { }
170
+
171
+ /**
172
+ * Returns the module's custom help content that should go in the "Help" dropdown of WordPress 2.7 and above.
173
+ *
174
+ * @since 0.1
175
+ *
176
+ * @return string|false The help text, or false if no custom help is available.
177
+ */
178
+ function admin_help() { return false; }
179
+
180
+ /**
181
+ * Adds the module's post meta box field HTML to the array.
182
+ *
183
+ * @since 0.1
184
+ *
185
+ * @param array $fields The fields array.
186
+ * @return array The updated fields array.
187
+ */
188
+ function postmeta_fields($fields) { return $fields; }
189
+
190
+
191
+ /********** INITIALIZATION FUNCTION **********/
192
+
193
+ /**
194
+ * Runs preliminary initialization tasks before calling the module's own init() function.
195
+ *
196
+ * @since 0.1
197
+ * @uses get_default_settings()
198
+ * @uses get_setting()
199
+ * @uses update_setting()
200
+ * @uses init()
201
+ */
202
+ function init_pre() {
203
+ $defaults = $this->get_default_settings();
204
+ foreach ($defaults as $setting => $default) {
205
+ if ($this->get_setting($setting, "{reset}") === "{reset}")
206
+ $this->update_setting($setting, $default);
207
+ }
208
+
209
+ $this->init();
210
+ }
211
+
212
+
213
+ /********** MODULE FUNCTIONS **********/
214
+
215
+ /**
216
+ * Returns the array key of the module.
217
+ *
218
+ * @since 0.1
219
+ * @uses $module_key
220
+ *
221
+ * @return string The module key.
222
+ */
223
+ function get_module_key() {
224
+ if ($this->module_key)
225
+ return $this->module_key;
226
+ else
227
+ die(str_rot13('Zbqhyr ybnqrq sebz na rkgreany fbhepr!'));
228
+ }
229
+
230
+ /**
231
+ * Checks to see whether a specified module exists.
232
+ *
233
+ * @since 0.1
234
+ * @uses $seo_ultimate
235
+ *
236
+ * @param string $key The key of the module to check.
237
+ * @return boolean Whether the module is loaded into SEO Ultimate.
238
+ */
239
+ function module_exists($key) {
240
+ global $seo_ultimate;
241
+ return isset($seo_ultimate->modules[$key]);
242
+ }
243
+
244
+
245
+ /********** SETTINGS FUNCTIONS **********/
246
+
247
+ /**
248
+ * Retrieves the given setting from a module's settings array.
249
+ *
250
+ * @since 0.1
251
+ * @uses get_module_key()
252
+ *
253
+ * @param string $key The name of the setting to retrieve.
254
+ * @param mixed $default What should be returned if the setting does not exist. Optional.
255
+ * @param string|null $module The module to which the setting belongs. Defaults to the current module. Optional.
256
+ * @return mixed The value of the setting, or the $default variable.
257
+ */
258
+ function get_setting($key, $default=null, $module=null) {
259
+ if (!$module) $module = $this->get_module_key();
260
+ $settings = maybe_unserialize(get_option('su_settings'));
261
+ if (isset($settings[$module][$key]))
262
+ $setting = $settings[$module][$key];
263
+ else
264
+ $setting = $default;
265
+
266
+ return apply_filters("su_get_setting-$module-$key", $setting);
267
+ }
268
+
269
+ /**
270
+ * Sets a value in the module's settings array.
271
+ *
272
+ * @since 0.1
273
+ * @uses get_module_key()
274
+ *
275
+ * @param string $key The key of the setting to be changed.
276
+ * @param string $value The new value to assign to the setting.
277
+ * @param string|null $module The module to which the setting belongs. Defaults to the current module. Optional.
278
+ */
279
+ function update_setting($key, $value, $module=null) {
280
+ if (!$module) $module = $this->get_module_key();
281
+
282
+ if (!apply_filters("su_custom_update_setting-$module-$key", false, $value)) {
283
+ $settings = maybe_unserialize(get_option('su_settings'));
284
+ if (!$settings) $settings = array();
285
+ $settings[$module][$key] = $value;
286
+ update_option('su_settings', serialize($settings));
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Adds 1 to the value of an integer setting in the module's settings array.
292
+ *
293
+ * @since 0.1
294
+ * @uses get_setting()
295
+ * @uses update_setting()
296
+ *
297
+ * @param string $key The key of the setting to be incremented.
298
+ */
299
+ function increment_setting($key) {
300
+ $value = $this->get_setting($key);
301
+ $this->update_setting($key, $value+1);
302
+ }
303
+
304
+ /**
305
+ * Assigns a value of zero to a setting in the module's settings array.
306
+ *
307
+ * @since 0.1
308
+ * @uses update_setting()
309
+ *
310
+ * @param string $key The key of the setting to be reset.
311
+ */
312
+ function reset_setting($key) {
313
+ $this->update_setting($key, 0);
314
+ }
315
+
316
+ /**
317
+ * Updates the value of more than one setting at a time.
318
+ *
319
+ * @since 0.1
320
+ * @uses update_setting()
321
+ *
322
+ * @param array $settings The names (keys) and values of settings to be updated.
323
+ */
324
+ function update_settings($settings) {
325
+ foreach ($settings as $key => $value)
326
+ update_setting($key, $value);
327
+ }
328
+
329
+
330
+ /********** ADMIN PAGE FUNCTIONS **********/
331
+
332
+ /**
333
+ * Displays the beginning, contents, and end of the module's administration page.
334
+ *
335
+ * @since 0.1
336
+ * @uses admin_page_start()
337
+ * @uses admin_page_contents()
338
+ * @uses admin_page_end()
339
+ */
340
+ function admin_page() {
341
+ $this->admin_page_start();
342
+ $this->admin_page_contents();
343
+ $this->admin_page_end();
344
+ }
345
+
346
+ /**
347
+ * Outputs the starting code for an administration page:
348
+ * wrapper, ID'd <div>, icon, and title
349
+ *
350
+ * @since 0.1
351
+ * @uses admin_footer() Hooked into WordPress's in_admin_footer action.
352
+ * @uses get_module_key()
353
+ * @uses get_page_title()
354
+ *
355
+ * @param string $icon The ID that should be applied to the icon element. The icon is loaded via CSS based on the ID. Optional.
356
+ */
357
+ function admin_page_start($icon = 'options-general') {
358
+
359
+ add_action('in_admin_footer', array($this, 'admin_footer'));
360
+
361
+ echo "<div class=\"wrap\">\n";
362
+ echo "<div id=\"su-".attribute_escape($this->get_module_key())."\" class=\"su-module\">\n";
363
+ screen_icon($icon);
364
+ echo "\n<h2>".$this->get_page_title()."</h2>\n";
365
+ }
366
+
367
+ /**
368
+ * Outputs an administration page subheader (an <h3> tag).
369
+ *
370
+ * @since 0.1
371
+ *
372
+ * @param string $title The text to output.
373
+ */
374
+ function admin_subheader($title) {
375
+ echo "<h3 class='su-subheader'>$title</h3>\n";
376
+ }
377
+
378
+ /**
379
+ * Outputs an administration form table subheader.
380
+ *
381
+ * @since 0.1
382
+ *
383
+ * @param string $title The text to output.
384
+ */
385
+ function admin_form_subheader($title) {
386
+ echo "<th><strong>$title</strong></th>\n";
387
+ }
388
+
389
+ /**
390
+ * Outputs the ending code for an administration page.
391
+ *
392
+ * @since 0.1
393
+ */
394
+ function admin_page_end() {
395
+ echo "\n</div>\n</div>\n";
396
+ }
397
+
398
+ /**
399
+ * Adds plugin/module information to the admin footer.
400
+ *
401
+ * @since 0.1
402
+ * @uses SU_PLUGIN_URI
403
+ * @uses SU_PLUGIN_NAME
404
+ * @uses SU_AUTHOR_URI
405
+ * @uses SU_AUTHOR
406
+ */
407
+ function admin_footer() {
408
+ printf(__('%1$s | %2$s %3$s by %4$s', 'seo-ultimate'),
409
+ $this->get_page_title(),
410
+ '<a href="'.SU_PLUGIN_URI.'" target="_blank">'.__(SU_PLUGIN_NAME, 'seo-ultimate').'</a>',
411
+ SU_VERSION,
412
+ '<a href="'.SU_AUTHOR_URI.'" target="_blank">'.__(SU_AUTHOR, 'seo-ultimate').'</a>'
413
+ );
414
+
415
+ echo "<br />";
416
+ }
417
+
418
+
419
+ /********** ADMIN FORM FUNCTIONS **********/
420
+
421
+ /**
422
+ * Begins an administration form.
423
+ * Outputs a subheader if provided, queues a success message upon settings update, outputs queued messages,
424
+ * opens a form tag, outputs a nonce field and other WordPress fields, and begins a form table.
425
+ *
426
+ * @since 0.1
427
+ * @uses SEO_Ultimate::key_to_hook()
428
+ * @uses get_module_key()
429
+ * @uses admin_subheader()
430
+ * @uses is_action()
431
+ * @uses queue_message()
432
+ * @uses print_messages()
433
+ * @uses get_parent_module()
434
+ *
435
+ * @param mixed $header The text of the subheader that should go right before the form. Optional.
436
+ * @param boolean $table Whether or not to start a form table.
437
+ */
438
+ function admin_form_start($header = false, $table = true) {
439
+ $hook = SEO_Ultimate::key_to_hook($this->get_module_key());
440
+ if ($header) $this->admin_subheader($header);
441
+
442
+ if (!$this->get_parent_module()) {
443
+ if ($this->is_action('update')) $this->queue_message('success', __('Settings updated.', 'seo-ultimate'));
444
+ $this->print_messages();
445
+ echo "<form method='post' action='?page=$hook'>\n";
446
+ settings_fields($hook);
447
+ }
448
+
449
+ echo "\n";
450
+ if ($table) echo "<table class='form-table'>\n";
451
+ }
452
+
453
+ /**
454
+ * Ends an administration form.
455
+ * Closes the table tag, outputs a "Save Changes" button, and closes the form tag.
456
+ *
457
+ * @since 0.1
458
+ * @uses get_parent_module()
459
+ *
460
+ * @param boolean $table Whether or not a form table should be ended.
461
+ */
462
+ function admin_form_end($table = true) {
463
+ if ($table) echo "</table>\n";
464
+
465
+ if (!$this->get_parent_module()) {
466
+ ?>
467
+ <p class="submit">
468
+ <input type="submit" class="button-primary" value="<?php _e('Save Changes') ?>" />
469
+ </p>
470
+ </form>
471
+ <?php
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Applies the necessary HTML so that certain content is displayed only when the mouse hovers over the including table row.
477
+ *
478
+ * @since 0.1
479
+ *
480
+ * @param string $text The always-visible text.
481
+ * @param string $hovertext The text that only displays upon row hover.
482
+ * @return string The HTML to put in a hover-supporting table row.
483
+ */
484
+ function hover_row($text, $hovertext) {
485
+ return "<div>$text</div>\n<div class='row-actions'>$hovertext</div>";
486
+ }
487
+
488
+ /**
489
+ * Outputs a group of checkboxes into an admin form, and saves the values into the database after form submission.
490
+ *
491
+ * @since 0.1
492
+ * @uses is_action()
493
+ * @uses update_setting()
494
+ * @uses get_module_key()
495
+ * @uses get_setting()
496
+ *
497
+ * @param array $checkboxes An array of checkboxes. (Field/setting IDs are the keys, and descriptions are the values.)
498
+ * @param mixed $grouptext The text to display in a table cell to the left of the one containing the checkboxes. Optional.
499
+ */
500
+ function checkboxes($checkboxes, $grouptext=false) {
501
+
502
+ //Save checkbox settings after form submission
503
+ if ($this->is_action('update')) {
504
+ foreach ($checkboxes as $name => $desc) {
505
+ $this->update_setting($name, $_POST[$name] == '1');
506
+ }
507
+ }
508
+
509
+ if ($grouptext)
510
+ echo "<tr valign='top'>\n<th scope='row'>$grouptext</th>\n<td><fieldset><legend class='hidden'>$grouptext</legend>\n";
511
+ else
512
+ echo "<tr valign='top'>\n<td>\n";
513
+
514
+ if (is_array($checkboxes)) {
515
+ foreach ($checkboxes as $name => $desc) {
516
+ register_setting($this->get_module_key(), $name, 'intval');
517
+ $name = attribute_escape($name);
518
+ echo "<label for='$name'><input name='$name' id='$name' type='checkbox' value='1'";
519
+ if ($this->get_setting($name) === true) echo " checked='checked'";
520
+ echo " /> $desc</label><br />\n";
521
+ }
522
+ }
523
+
524
+ if ($grouptext) echo "</fieldset>";
525
+ echo "</td>\n</tr>\n";
526
+ }
527
+
528
+ /**
529
+ * Outputs a group of textboxes into an admin form, and saves the values into the database after form submission.
530
+ * Can also display a "Reset" link next to each textbox that reverts its value to a specified default.
531
+ *
532
+ * @since 0.1
533
+ * @uses is_action()
534
+ * @uses update_setting()
535
+ * @uses get_module_key()
536
+ * @uses get_setting()
537
+ *
538
+ * @param array $textboxes An array of textboxes. (Field/setting IDs are the keys, and descriptions are the values.)
539
+ * @param array $defaults An array of default textbox values that trigger "Reset" links. (The field/setting ID is the key, and the default value is the value.) Optional.
540
+ * @param mixed $grouptext The text to display in a table cell to the left of the one containing the textboxes. Optional.
541
+ */
542
+ function textboxes($textboxes, $defaults=array(), $grouptext=false) {
543
+
544
+ if ($this->is_action('update')) {
545
+ foreach ($textboxes as $id => $title) {
546
+ $this->update_setting($id, stripslashes($_POST[$id]));
547
+ }
548
+ }
549
+
550
+ if ($grouptext)
551
+ echo "<tr valign='top'>\n<th scope='row'>$grouptext</th>\n<td><fieldset><legend class='hidden'>$grouptext</legend>\n";
552
+
553
+ foreach ($textboxes as $id => $title) {
554
+ register_setting($this->get_module_key(), $id);
555
+ $value = attribute_escape($this->get_setting($id));
556
+ $default = attribute_escape($defaults[$id]);
557
+ $id = attribute_escape($id);
558
+ $resetmessage = attribute_escape(__("Are you sure you want to replace the textbox contents with this default value?", 'seo-ultimate'));
559
+
560
+ if ($grouptext)
561
+ echo "<div class='field'><label for='$id'>$title</label><br />\n";
562
+ else
563
+ echo "<tr valign='top'>\n<th scope='row'><label for='$id'>$title</label></th>\n<td>";
564
+
565
+ echo "<input name='$id' id='$id' type='text' value='$value' class='regular-text' ";
566
+ if (isset($defaults[$id])) {
567
+ echo "onkeyup=\"javascript:textbox_value_changed(this, '$default', '{$id}_reset')\" />";
568
+ echo "&nbsp;<a href=\"javascript:void(0)\" id=\"{$id}_reset\" onclick=\"javascript:reset_textbox('$id', '$default', '$resetmessage', this)\"";
569
+ if ($default == $value) echo ' class="hidden"';
570
+ echo ">";
571
+ _e('Reset', 'seo-ultimate');
572
+ echo "</a>";
573
+ } else {
574
+ echo "/>";
575
+ }
576
+
577
+ if ($grouptext)
578
+ echo "</div>\n";
579
+ else
580
+ echo "</td>\n</tr>\n";
581
+ }
582
+
583
+ if ($grouptext)
584
+ echo "</td>\n</tr>\n";
585
+ }
586
+
587
+ /**
588
+ * Outputs a single textbox into an admin form and saves its value into the database after form submission.
589
+ *
590
+ * @since 0.1
591
+ * @uses textboxes()
592
+ *
593
+ * @param string $id The field/setting ID.
594
+ * @param string $title The label of the HTML element.
595
+ * @return string The HTML that would render the textbox.
596
+ */
597
+ function textbox($id, $title) {
598
+ $this->textboxes(array($id => $title));
599
+ }
600
+
601
+
602
+ /**
603
+ * Outputs a group of textareas into an admin form, and saves the values into the database after form submission.
604
+ *
605
+ * @since 0.1
606
+ * @uses is_action()
607
+ * @uses update_setting()
608
+ * @uses get_module_key()
609
+ * @uses get_setting()
610
+ *
611
+ * @param array $textareas An array of textareas. (Field/setting IDs are the keys, and descriptions are the values.)
612
+ * @param int $rows The value of the textareas' rows attribute.
613
+ * @param int $cols The value of the textareas' cols attribute.
614
+ */
615
+ function textareas($textareas, $rows = 5, $cols = 30) {
616
+
617
+ if ($this->is_action('update')) {
618
+ foreach ($textareas as $id => $title) {
619
+ $this->update_setting($id, stripslashes($_POST[$id]));
620
+ }
621
+ }
622
+
623
+ foreach ($textareas as $id => $title) {
624
+ register_setting($this->get_module_key(), $id);
625
+ $value = wp_specialchars($this->get_setting($id), ENT_QUOTES, false, true);
626
+ $id = attribute_escape($id);
627
+
628
+ echo "<tr valign='top'>\n<th scope='row'><label for='$id'>$title</label></th>\n";
629
+ echo "<td><textarea name='$id' id='$id' type='text' class='regular-text' cols='$cols' rows='$rows'>$value</textarea>";
630
+ echo "</td>\n</tr>\n";
631
+ }
632
+ }
633
+
634
+ /**
635
+ * Outputs a single textarea into an admin form and saves its value into the database after form submission.
636
+ *
637
+ * @since 0.1
638
+ * @uses textareas()
639
+ *
640
+ * @param string $id The field/setting ID.
641
+ * @param string $title The label of the HTML element.
642
+ * @param int $rows The value of the textarea's rows attribute.
643
+ * @param int $cols The value of the textarea's cols attribute.
644
+ * @return string The HTML that would render the textarea.
645
+ */
646
+ function textarea($id, $title, $rows = 5, $cols = 30) {
647
+ $this->textareas(array($id => $title), $rows, $cols);
648
+ }
649
+
650
+ /********** ADMIN SECURITY FUNCTIONS **********/
651
+
652
+ /**
653
+ * Determines if a particular nonce-secured admin action is being executed.
654
+ *
655
+ * @since 0.1
656
+ * @uses nonce_validates()
657
+ *
658
+ * @param string $action The name of the action to check.
659
+ * @return bool Whether or not the action is being executed.
660
+ */
661
+ function is_action($action) {
662
+ if (!($object = $_GET['object'])) $object = false;
663
+ return (($_GET['action'] == $action || $_POST['action'] == $action) && $this->nonce_validates($action, $object));
664
+ }
665
+
666
+ /**
667
+ * Determines whether a nonce is valid.
668
+ *
669
+ * @since 0.1
670
+ * @uses get_nonce_handle()
671
+ *
672
+ * @param string $action The name of the action.
673
+ * @param mixed $id The ID of the object being acted upon. Optional.
674
+ * @return bool Whether or not the nonce is valid.
675
+ */
676
+ function nonce_validates($action, $id = false) {
677
+ return check_admin_referer($this->get_nonce_handle($action, $id));
678
+ }
679
+
680
+ /**
681
+ * Generates a unique name for a nonce.
682
+ *
683
+ * @since 0.1
684
+ * @uses get_parent_module()
685
+ * @uses get_module_key()
686
+ * @uses SU_PLUGIN_NAME
687
+ *
688
+ * @param string $action The name of the action.
689
+ * @param mixed $id The ID of the object being acted upon. Optional.
690
+ * @return The handle to use for the nonce.
691
+ */
692
+ function get_nonce_handle($action, $id = false) {
693
+
694
+ $key = $this->get_parent_module();
695
+ if (!$key) $key = $this->get_module_key();
696
+
697
+ $hook = SEO_Ultimate::key_to_hook($key);
698
+
699
+ if (strcmp($action, 'update') == 0) {
700
+ //We use the settings_fields() function, which outputs a nonce in this particular format.
701
+ return "$hook-options";
702
+ } else {
703
+ if ($id) $id = '-'.md5($id); else $id = '';
704
+ $handle = SU_PLUGIN_NAME."-$hook-$action$id";
705
+ return strtolower(str_replace(' ', '-', $handle));
706
+ }
707
+ }
708
+
709
+ /**
710
+ * Returns a GET-action URL with an appended nonce.
711
+ *
712
+ * @since 0.1
713
+ * @uses get_module_key()
714
+ * @uses get_nonce_handle()
715
+ *
716
+ * @param string $action The name of the action.
717
+ * @param mixed $id The ID of the object being acted upon. Optional.
718
+ * @return The URL to use in an <a> tag.
719
+ */
720
+ function get_nonce_url($action, $object=false) {
721
+ $action = urlencode($action);
722
+ if ($object) $objectqs = '&object='.urlencode($object); else $objectqs = '';
723
+
724
+ $hook = SEO_Ultimate::key_to_hook($this->get_module_key());
725
+
726
+ //We don't need to escape ampersands since wp_nonce_url will do that for us
727
+ return wp_nonce_url("?page=$hook&action=$action$objectqs",
728
+ $this->get_nonce_handle($action, $object));
729
+ }
730
+
731
+
732
+ /********** ADMIN MESSAGE FUNCTIONS **********/
733
+
734
+ /**
735
+ * Print a message (and any previously-queued messages) right away.
736
+ *
737
+ * @since 0.1
738
+ * @uses queue_message()
739
+ * @uses print_messages()
740
+ *
741
+ * @param string $type The message's type. Valid values are success, error, warning, and info.
742
+ * @param string $message The message text.
743
+ */
744
+ function print_message($type, $message) {
745
+ $this->queue_message($type, $message);
746
+ $this->print_messages();
747
+ }
748
+
749
+ /**
750
+ * Adds a message to the queue.
751
+ *
752
+ * @since 0.1
753
+ * @uses $messages
754
+ *
755
+ * @param string $type The message's type. Valid values are success, error, warning, and info.
756
+ * @param string $message The message text.
757
+ */
758
+ function queue_message($type, $message) {
759
+ $this->messages[$type][] = $message;
760
+ }
761
+
762
+ /**
763
+ * Prints all queued messages and flushes the queue.
764
+ *
765
+ * @since 0.1
766
+ * @uses $messages
767
+ */
768
+ function print_messages() {
769
+ foreach ($this->messages as $type => $messages) {
770
+ $messages = implode('<br />', $messages);
771
+ if ($messages) {
772
+ $type = attribute_escape($type);
773
+ echo "<div class='su-message'><p class='su-$type'>$messages</p></div>\n";
774
+ }
775
+ }
776
+
777
+ $this->messages = array();
778
+ }
779
+
780
+ /********** ADMIN POST META BOX FUNCTIONS **********/
781
+
782
+ /**
783
+ * Gets a specified meta value of the current post (i.e. the post currently being edited in the admin,
784
+ * the post being shown, the post now in the loop, or the post with specified ID).
785
+ *
786
+ * @since 0.1
787
+ *
788
+ * @param string $key The meta key to fetch.
789
+ * @param mixed $id The ID number of the post/page.
790
+ * @return string The meta value requested.
791
+ */
792
+ function get_postmeta($key, $id=false) {
793
+
794
+ if (!$id) {
795
+ if (is_admin())
796
+ $id = intval($_REQUEST['post']);
797
+ elseif (in_the_loop())
798
+ $id = intval(get_the_ID());
799
+ elseif (is_singular()) {
800
+ global $wp_query;
801
+ $id = $wp_query->get_queried_object_id();
802
+ }
803
+ }
804
+
805
+ if ($id) return get_post_meta($id, "_su_$key", true);
806
+
807
+ return '';
808
+ }
809
+
810
+ /**
811
+ * Generates the HTML for multiple post meta textboxes.
812
+ *
813
+ * @since 0.1
814
+ * @uses get_postmeta()
815
+ *
816
+ * @param array $textboxes An array of textboxes. (Field/setting IDs are the keys, and descriptions are the values.)
817
+ * @return string The HTML that would render the textboxes.
818
+ */
819
+ function get_postmeta_textboxes($textboxes) {
820
+
821
+ $html = '';
822
+
823
+ foreach ($textboxes as $id => $title) {
824
+
825
+ register_setting('seo-ultimate', $id);
826
+ $value = attribute_escape($this->get_postmeta($id));
827
+ $id = "_su_".attribute_escape($id);
828
+ $title = str_replace(' ', '&nbsp;', $title);
829
+
830
+ $html .= "<tr class='textbox'>\n<th scope='row'><label for='$id'>$title</label></th>\n"
831
+ ."<td><input name='$id' id='$id' type='text' value='$value' class='regular-text' /></td>\n</tr>\n";
832
+ }
833
+
834
+ return $html;
835
+ }
836
+
837
+ /**
838
+ * Generates the HTML for a single post meta textbox.
839
+ *
840
+ * @since 0.1
841
+ * @uses get_postmeta_textboxes()
842
+ *
843
+ * @param string $id The ID of the HTML element.
844
+ * @param string $title The label of the HTML element.
845
+ * @return string The HTML that would render the textbox.
846
+ */
847
+ function get_postmeta_textbox($id, $title) {
848
+ return $this->get_postmeta_textboxes(array($id => $title));
849
+ }
850
+
851
+ /**
852
+ * Generates the HTML for a group of post meta checkboxes.
853
+ *
854
+ * @since 0.1
855
+ * @uses get_module_key()
856
+ * @uses get_postmeta()
857
+ *
858
+ * @param array $checkboxes An array of checkboxes. (Field/setting IDs are the keys, and descriptions are the values.)
859
+ * @param string $grouptext The text to display in a table cell to the left of the one containing the checkboxes.
860
+ */
861
+ function get_postmeta_checkboxes($checkboxes, $grouptext) {
862
+
863
+ $html = "<tr>\n<th scope='row'>$grouptext</th>\n<td><fieldset><legend class='hidden'>$grouptext</legend>\n";
864
+
865
+ if (is_array($checkboxes)) {
866
+ foreach ($checkboxes as $name => $desc) {
867
+
868
+ register_setting('seo-ultimate', $name);
869
+ $checked = ($this->get_postmeta($name) == 1);
870
+ $name = "_su_".attribute_escape($name);
871
+
872
+ $html .= "<label for='$name'><input name='$name' id='$name' type='checkbox' value='1'";
873
+ if ($checked) $html .= " checked='checked'";
874
+ $html .= " /> $desc</label><br />\n";
875
+ }
876
+ }
877
+
878
+ $html .= "</fieldset></td>\n</tr>\n";
879
+
880
+ return $html;
881
+ }
882
+
883
+ /**
884
+ * Generates the HTML for a single post meta checkbox.
885
+ *
886
+ * @since 0.1
887
+ * @uses get_postmeta_checkboxes()
888
+ *
889
+ * @param string $id The ID of the HTML element.
890
+ * @param string $title The label of the HTML element.
891
+ * @param string $grouptext The text to display in a table cell to the left of the one containing the checkboxes.
892
+ * @return string The HTML that would render the textbox.
893
+ */
894
+ function get_postmeta_checkbox($id, $title, $grouptext) {
895
+ return $this->get_postmeta_checkboxes(array($id => $title), $grouptext);
896
+ }
897
+
898
+
899
+ /********** HITS LOG FUNCTIONS **********/
900
+
901
+ /**
902
+ * Lists logged hits in an administration table.
903
+ *
904
+ * @since 0.1
905
+ *
906
+ * @param string|false $where The WHERE portion of the SQL query to execute.
907
+ * @param string|false $actions_callback The name of the module-child function from which to obtain a return value of URL-cell action link HTML.
908
+ * @param bool $highlight_new Whether or not to highlight new rows. Optional.
909
+ */
910
+ function hits_table($where = false, $actions_callback = false, $highlight_new = true) {
911
+ global $wpdb;
912
+ $mk = $this->get_module_key();
913
+
914
+ $table = SEO_Ultimate::get_table_name('hits');
915
+ if ($where) $where = " WHERE $where";
916
+ $result = $wpdb->get_results("SELECT * FROM $table$where ORDER BY id DESC", ARRAY_A);
917
+
918
+ if (!$result) return false;
919
+
920
+ $allfields = array(
921
+ 'time' => __("Date", 'seo-ultimate')
922
+ , 'ip_address' => __("IP Address", 'seo-ultimate')
923
+ , 'user_agent' => __("Browser", 'seo-ultimate')
924
+ , 'url' => __("URL Requested", 'seo-ultimate')
925
+ , 'redirect_url' => __("Redirected To", 'seo-ultimate')
926
+ , 'status_code' => __("Status Code", 'seo-ultimate')
927
+ );
928
+
929
+ $fields = array();
930
+
931
+ foreach ($allfields as $col => $title) {
932
+ if (strpos($where, " $col=") === false) $fields[$col] = $title;
933
+ }
934
+
935
+ $fields = apply_filters("su_{$mk}_hits_table_columns", $fields);
936
+
937
+ echo "<table class='widefat' cellspacing='0'>\n\t<thead><tr>\n";
938
+
939
+ foreach ($fields as $title) {
940
+ $class = str_replace(' ', '-', strtolower($title));
941
+ echo "\t\t<th scope='col' class='hit-$class'>$title</th>\n";
942
+ }
943
+
944
+ echo "\t</tr></thead>\n\t<tbody>\n";
945
+
946
+ foreach ($result as $row) {
947
+
948
+ if ($highlight_new && $row['is_new']) $class = ' class="new-hit"'; else $class='';
949
+ echo "\t\t<tr$class>\n";
950
+
951
+ foreach ($fields as $col => $title) {
952
+ $cell = htmlspecialchars($row[$col]);
953
+
954
+ switch ($col) {
955
+ case 'time':
956
+ $date = date_i18n(get_option('date_format'), $cell);
957
+ $time = date_i18n(get_option('time_format'), $cell);
958
+ $cell = sprintf(__('%1$s<br />%2$s', 'seo-ultimate'), $date, $time);
959
+ break;
960
+ case 'user_agent':
961
+ $binfo = get_browser($cell, true);
962
+ $ua = attribute_escape($cell);
963
+ $cell = '<abbr title="'.$ua.'">'.$binfo['parent'].'</abbr>';
964
+ break;
965
+ case 'url':
966
+ if ($actions_callback) {
967
+ $actions = call_user_func(array($this, $actions_callback), $row);
968
+ $actions = apply_filters("su_{$mk}_hits_table_actions", $actions, $row);
969
+ $cell = $this->hover_row($cell, $actions);
970
+ }
971
+ break;
972
+ }
973
+
974
+ $cell = apply_filters("su_{$mk}_hits_table_{$col}_cell", $cell, $row);
975
+
976
+ $class = str_replace(' ', '-', strtolower($title));
977
+ echo "\t\t\t<td class='hit-$class'>$cell</td>\n";
978
+ }
979
+ echo "\t\t</tr>\n";
980
+
981
+ $wpdb->update($table, array('is_new' => 0), array('id' => $row['id']));
982
+ }
983
+
984
+ echo "\t</tbody>\n</table>\n";
985
+
986
+ return true;
987
+ }
988
+
989
+
990
+ /********** CRON FUNCTION **********/
991
+
992
+ /**
993
+ * Creates a cron job if it doesn't already exists, and ensures it runs at the scheduled time.
994
+ * Should be called in a module's init() function.
995
+ *
996
+ * @since 0.1
997
+ * @uses get_module_key()
998
+ *
999
+ * @param string $function The name of the module function that should be run.
1000
+ * @param string $recurrance How often the job should be run. Valid values are hourly, twicedaily, and daily.
1001
+ */
1002
+ function cron($function, $recurrance) {
1003
+
1004
+ $mk = $this->get_module_key();
1005
+
1006
+ $hook = "su-$mk-".str_replace('_', '-', $function);
1007
+ $start = time();
1008
+
1009
+ if (wp_next_scheduled($hook) === false) {
1010
+ //This is a new cron job
1011
+
1012
+ //Schedule the event
1013
+ wp_schedule_event($start, $recurrance, $hook);
1014
+
1015
+ //Make a record of it
1016
+ $data = maybe_unserialize(get_option('su_cron'));
1017
+ if (!is_array($data)) $data = array();
1018
+ $data[$mk][$function] = array($hook, $start, $recurrance);
1019
+ update_option('su_cron', serialize($data));
1020
+
1021
+ //Run the event now
1022
+ call_user_func(array($this, $function));
1023
+ }
1024
+
1025
+ add_action($hook, array($this, $function));
1026
+ }
1027
+
1028
+ /********** RSS FUNCTION **********/
1029
+
1030
+ /**
1031
+ * Loads an RSS feed and returns it as an object.
1032
+ *
1033
+ * @since 0.1
1034
+ * @uses get_user_agent() Hooks into WordPress's http_header_useragent filter.
1035
+ *
1036
+ * @param string $url The URL of the RSS feed to load.
1037
+ * @return object $rss The RSS object.
1038
+ */
1039
+ function load_rss($url) {
1040
+ add_filter('http_headers_useragent', 'su_get_user_agent');
1041
+ require_once (ABSPATH . WPINC . '/rss.php');
1042
+ $rss = fetch_rss($url);
1043
+ remove_filter('http_headers_useragent', 'su_get_user_agent');
1044
+ return $rss;
1045
+ }
1046
+ }
1047
+ ?>
class.su-widget.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The pseudo-abstract class upon which all widgets are based.
4
+ *
5
+ * @abstract
6
+ * @version 1.0
7
+ * @since 0.1
8
+ */
9
+ class SU_Widget {
10
+
11
+ function get_title() { return ''; }
12
+ function get_section() { return 'normal'; }
13
+ function get_priority(){ return 'core'; }
14
+
15
+ function content() { }
16
+ }
17
+ ?>
functions.php ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Non-class functions.
4
+ */
5
+
6
+ /********** INDEPENDENTLY-OPERABLE FUNCTIONS **********/
7
+
8
+ /**
9
+ * Returns the plugin's User-Agent value.
10
+ * Can be used as a WordPress filter.
11
+ *
12
+ * @since 0.1
13
+ * @uses SU_USER_AGENT
14
+ *
15
+ * @return string The user agent.
16
+ */
17
+ function su_get_user_agent() {
18
+ return SU_USER_AGENT;
19
+ }
20
+
21
+ /**
22
+ * Records an event in the debug log file.
23
+ * Usage: su_debug_log(__FILE__, __CLASS__, __FUNCTION__, __LINE__, "Message");
24
+ *
25
+ * @since 0.1
26
+ * @uses SU_VERSION
27
+ *
28
+ * @param string $file The value of __FILE__
29
+ * @param string $class The value of __CLASS__
30
+ * @param string $function The value of __FUNCTION__
31
+ * @param string $line The value of __LINE__
32
+ * @param string $message The message to log.
33
+ */
34
+ function su_debug_log($file, $class, $function, $line, $message) {
35
+ global $seo_ultimate;
36
+ if (isset($seo_ultimate->modules['settings']) && $seo_ultimate->modules['settings']->get_setting('debug_mode') === true) {
37
+
38
+ $date = date("Y-m-d H:i:s");
39
+ $version = SU_VERSION;
40
+ $message = str_replace("\r\n", "\n", $message);
41
+ $message = str_replace("\n", "\r\n", $message);
42
+
43
+ $log = "Date: $date\r\nVersion: $version\r\nFile: $file\r\nClass: $class\r\nFunction: $function\r\nLine: $line\r\nMessage: $message\r\n\r\n";
44
+ $logfile = trailingslashit(dirname(__FILE__))."seo-ultimate.log";
45
+
46
+ @error_log($log, 3, $logfile);
47
+ }
48
+ }
49
+
50
+
51
+ /********** CLASS FUNCTION ALIASES **********/
52
+
53
+ /**
54
+ * Launches the uninstallation process.
55
+ * WordPress will call this when the plugin is uninstalled, as instructed by the register_uninstall_hook() call in {@link SEO_Ultimate::__construct()}.
56
+ *
57
+ * @since 0.1
58
+ * @uses $seo_ultimate
59
+ * @uses SEO_Ultimate::uninstall()
60
+ */
61
+ function su_uninstall() {
62
+ global $seo_ultimate;
63
+ $seo_ultimate->uninstall();
64
+ }
65
+ ?>
modules/canonical.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Canonicalizer Module
4
+ *
5
+ * @version 1.0
6
+ * @since 0.3
7
+ */
8
+
9
+ if (class_exists('SU_Module')) {
10
+
11
+ class SU_Canonical extends SU_Module {
12
+
13
+ function get_menu_title() { return __('Canonicalizer', 'seo-ultimate'); }
14
+
15
+ function init() {
16
+ if ($this->get_setting('link_rel_canonical'))
17
+ add_action('su_head', array($this, 'link_rel_canonical_tag'));
18
+ }
19
+
20
+ function admin_page_contents() {
21
+ $this->admin_form_start();
22
+ $this->checkboxes(array(
23
+ 'link_rel_canonical' => __("Generate <code>&lt;link rel=&quot;canonical&quot; /&gt;</code> tags.", 'seo-ultimate')
24
+ ));
25
+
26
+ $this->admin_form_end();
27
+ }
28
+
29
+ function link_rel_canonical_tag() {
30
+ if ($url = $this->get_canonical_url()) {
31
+ $url = attribute_escape($url);
32
+ echo "\t<link rel=\"canonical\" href=\"$url\" />\n";
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Returns the canonical URL to put in the link-rel-canonical tag.
38
+ *
39
+ * This function is modified from the GPL-licensed {@link http://wordpress.org/extend/plugins/canonical/ Canonical URLs} plugin,
40
+ * which in turn was heavily based on the {@link http://svn.fucoder.com/fucoder/permalink-redirect/ Permalink Redirect} plugin.
41
+ */
42
+ function get_canonical_url() {
43
+ global $wp_query, $wp_rewrite;
44
+
45
+ if ($wp_query->is_404 || $wp_query->is_search) return false;
46
+
47
+ $haspost = count($wp_query->posts) > 0;
48
+
49
+ if (get_query_var('m')) {
50
+ // Handling special case with '?m=yyyymmddHHMMSS'
51
+ // Since there is no code for producing the archive links for
52
+ // is_time, we will give up and not try to produce a link.
53
+ $m = preg_replace('/[^0-9]/', '', get_query_var('m'));
54
+ switch (strlen($m)) {
55
+ case 4: // Yearly
56
+ $link = get_year_link($m);
57
+ break;
58
+ case 6: // Monthly
59
+ $link = get_month_link(substr($m, 0, 4), substr($m, 4, 2));
60
+ break;
61
+ case 8: // Daily
62
+ $link = get_day_link(substr($m, 0, 4), substr($m, 4, 2),
63
+ substr($m, 6, 2));
64
+ break;
65
+ default:
66
+ return false;
67
+ }
68
+
69
+ } elseif (($wp_query->is_single || $wp_query->is_page) && $haspost) {
70
+ $post = $wp_query->posts[0];
71
+ $link = get_permalink($post->ID);
72
+ // WP2.2: In Wordpress 2.2+ is_home() returns false and is_page()
73
+ // returns true if front page is a static page.
74
+ if ($wp_query->is_page && ('page' == get_option('show_on_front')) &&
75
+ $post->ID == get_option('page_on_front'))
76
+ $link = trailingslashit($link);
77
+
78
+ } elseif ($wp_query->is_author && $haspost) {
79
+ $author = get_userdata(get_query_var('author'));
80
+ if ($author === false) return false;
81
+ $link = get_author_link(false, $author->ID, $author->user_nicename);
82
+
83
+ } elseif ($wp_query->is_category && $haspost) {
84
+ $link = get_category_link(get_query_var('cat'));
85
+
86
+ } else if ($wp_query->is_tag && $haspost) {
87
+ $tag = get_term_by('slug',get_query_var('tag'),'post_tag');
88
+ if (!empty($tag->term_id)) $link = get_tag_link($tag->term_id);
89
+
90
+ } elseif ($wp_query->is_day && $haspost) {
91
+ $link = get_day_link(get_query_var('year'),
92
+ get_query_var('monthnum'),
93
+ get_query_var('day'));
94
+
95
+ } elseif ($wp_query->is_month && $haspost) {
96
+ $link = get_month_link(get_query_var('year'),
97
+ get_query_var('monthnum'));
98
+
99
+ } elseif ($wp_query->is_year && $haspost) {
100
+ $link = get_year_link(get_query_var('year'));
101
+
102
+ } elseif ($wp_query->is_home) {
103
+ if ((get_option('show_on_front') == 'page') && ($pageid = get_option('page_for_posts')))
104
+ $link = trailingslashit(get_permalink($pageid));
105
+ else
106
+ $link = trailingslashit(get_option('home'));
107
+ } elseif ($wp_query->is_search) {
108
+
109
+ } else
110
+ return false;
111
+
112
+ //Handle pagination
113
+ $page = get_query_var('paged');
114
+ if ($page && $page > 1) {
115
+ if ($wp_rewrite->using_permalinks()) {
116
+ $link = trailingslashit($link) ."page/$page";
117
+ $link = user_trailingslashit($link, 'paged');
118
+ } else {
119
+ $link = add_query_arg( 'paged', $page, $link );
120
+ }
121
+ }
122
+
123
+ return $link;
124
+ }
125
+
126
+ function admin_help() {
127
+ return __(<<<STR
128
+ <p>The Canonicalizer helps you avoid duplicate content penalties on your website.</p>
129
+ <p>The <strong>Generate <code>&lt;link rel=&quot;canonical&quot; /&gt;</code> tags</strong> option, when enabled,
130
+ will insert code that points Google to the correct URL for your homepage and each of your posts, Pages, categories, tags, date archives, and author archives.
131
+ That way, if Google comes across an alternate URL by which one of those items can be accessed, it will be able to find the correct URL
132
+ and won&#8217;t penalize you for having two identical pages on your site.</p>
133
+ STR
134
+ , 'seo-ultimate');
135
+ }
136
+ }
137
+
138
+ }
139
+ ?>
modules/meta.php CHANGED
@@ -2,7 +2,7 @@
2
  /**
3
  * Meta Editor Module
4
  *
5
- * @version 1.0
6
  * @since 0.3
7
  */
8
 
@@ -57,7 +57,7 @@ class SU_Meta extends SU_Module {
57
  . " onkeyup=\"javascript:textbox_char_count('_su_description', 'su_meta_description_charcount')\">$value</textarea>"
58
  . "<br />".sprintf(__("You&#8217;ve entered %s characters. Most search engines use up to 160.", 'seo-ultimate'), "<strong id='su_meta_description_charcount'>".strlen($value)."</strong>")
59
  . "</td>\n</tr>\n"
60
- . $this->get_postmeta_textbox('keywords', __('Keywords:<br /><em>(separate with commas)', 'seo-ultimate'))
61
  ;
62
 
63
  return $fields;
2
  /**
3
  * Meta Editor Module
4
  *
5
+ * @version 1.0.1
6
  * @since 0.3
7
  */
8
 
57
  . " onkeyup=\"javascript:textbox_char_count('_su_description', 'su_meta_description_charcount')\">$value</textarea>"
58
  . "<br />".sprintf(__("You&#8217;ve entered %s characters. Most search engines use up to 160.", 'seo-ultimate'), "<strong id='su_meta_description_charcount'>".strlen($value)."</strong>")
59
  . "</td>\n</tr>\n"
60
+ . $this->get_postmeta_textbox('keywords', __('Keywords:<br /><em>(separate with commas)</em>', 'seo-ultimate'))
61
  ;
62
 
63
  return $fields;
modules/settings.php CHANGED
@@ -19,6 +19,7 @@ class SU_Settings extends SU_Module {
19
  function get_default_settings() {
20
  return array(
21
  'attribution_link' => true
 
22
  );
23
  }
24
 
@@ -49,6 +50,7 @@ class SU_Settings extends SU_Module {
49
  $this->admin_form_start(__("Plugin Settings", 'seo-ultimate'));
50
  $this->checkboxes(array(
51
  'attribution_link' => __("Enable attribution link", 'seo-ultimate')
 
52
  //, 'debug_mode' => __("Enable debug-mode logging", 'seo-ultimate')
53
  , 'mark_code' => __("Insert comments around HTML code insertions", 'seo-ultimate')
54
  ));
19
  function get_default_settings() {
20
  return array(
21
  'attribution_link' => true
22
+ , 'plugin_notices' => true
23
  );
24
  }
25
 
50
  $this->admin_form_start(__("Plugin Settings", 'seo-ultimate'));
51
  $this->checkboxes(array(
52
  'attribution_link' => __("Enable attribution link", 'seo-ultimate')
53
+ , 'plugin_notices' => __("Notify me about unnecessary active plugins", 'seo-ultimate')
54
  //, 'debug_mode' => __("Enable debug-mode logging", 'seo-ultimate')
55
  , 'mark_code' => __("Insert comments around HTML code insertions", 'seo-ultimate')
56
  ));
modules/stats.php CHANGED
@@ -2,7 +2,7 @@
2
  /**
3
  * Stats Central Module
4
  *
5
- * @version 0.1
6
  * @since 0.1
7
  */
8
 
@@ -32,7 +32,7 @@ class SU_Stats extends SU_Module {
32
 
33
  function admin_help() {
34
  return __(<<<STR
35
- <p>Click a module&#8217;s name to open its admin page.
36
  STR
37
  , 'seo-ultimate');
38
  }
2
  /**
3
  * Stats Central Module
4
  *
5
+ * @version 0.1.1
6
  * @since 0.1
7
  */
8
 
32
 
33
  function admin_help() {
34
  return __(<<<STR
35
+ <p>Click a module&#8217;s name to open its admin page.</p>
36
  STR
37
  , 'seo-ultimate');
38
  }
modules/titles.php CHANGED
@@ -251,19 +251,5 @@ STR
251
 
252
  }
253
 
254
- if (!function_exists('jl_seoa_get_title')) {
255
-
256
- function jl_seoa_get_title($isTitleTag = false) {
257
- if (is_tag()) {
258
- global $seo_ultimate;
259
- $format = $seo_ultimate->modules['titles']->get_setting('title_tag');
260
- if (!$isTitleTag) $format = trim(str_replace('{blog}', '', $format), ' |-');
261
- return str_replace('{tag}', single_tag_title('', false), $format);
262
- }
263
-
264
- return '';
265
- }
266
- }
267
-
268
  }
269
  ?>
251
 
252
  }
253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
  ?>
readme.txt CHANGED
@@ -1,11 +1,11 @@
1
  === SEO Ultimate ===
2
  Contributors: SEO Design Solutions
3
- Tags: seo, title, meta, noindex, google, yahoo, admin, post, page
4
- Requires at least: 2.7
5
- Tested up to: 2.8 Beta 2
6
- Stable tag: 0.2
7
 
8
- This all-in-one SEO plugin can rewrite title tags, noindex archives, and customize meta data (with many more features coming soon).
9
 
10
  == Description ==
11
 
@@ -17,15 +17,17 @@ SEO Ultimate is an all-in-one [SEO](http://www.seodesignsolutions.com/) plugin w
17
 
18
  * **Meta Editor** - Lets you edit the meta descriptions/keywords for your posts, pages, and homepage. Also lets you enter verification meta codes and give code instructions to search engine spiders.
19
 
 
 
20
  SEO Ultimate was developed with WordPress plugin "best practices" in mind:
21
 
22
- * Integration with WordPress's contextual help system
23
  * Internationalization support
24
  * Nonce security
25
  * An uninstall routine
26
- * Integration with the new WordPress 2.7 menu
27
 
28
- **NOTE:** This plugin is in beta, which means it's very feature-incomplete. We have many more features that we're working on finetuning before release, including 404 monitoring, canonical tag insertion, PageRank sculpting, and more.
29
 
30
  If you install the plugin now, you can have these new features delivered to you on a regular basis via WordPress's automatic plugin upgrader.
31
 
@@ -78,6 +80,12 @@ Because of the tremendous effort put into this plugin, we ask that you please le
78
 
79
  == Release History ==
80
 
 
 
 
 
 
 
81
  = Version 0.2 (June 4, 2009) =
82
  * Added the Meta Editor module
83
  * Fixed a double-escaping bug in the Title Rewriter
1
  === SEO Ultimate ===
2
  Contributors: SEO Design Solutions
3
+ Tags: seo, title, meta, noindex, canonical, google, yahoo, bing, search engines, admin, post, page
4
+ Requires at least: 2.8
5
+ Tested up to: 2.8
6
+ Stable tag: 0.3
7
 
8
+ This all-in-one SEO plugin can rewrite title tags, noindex archives, customize meta data, and insert canonical tags (with many more features coming).
9
 
10
  == Description ==
11
 
17
 
18
  * **Meta Editor** - Lets you edit the meta descriptions/keywords for your posts, pages, and homepage. Also lets you enter verification meta codes and give code instructions to search engine spiders.
19
 
20
+ * **Canonicalizer** - Inserts `<link rel="canonical" />` tags for your homepage and each of your posts, Pages, categories, tags, date archives, and author archives.
21
+
22
  SEO Ultimate was developed with WordPress plugin "best practices" in mind:
23
 
24
+ * Integration with the contextual help system of WordPress 2.7+
25
  * Internationalization support
26
  * Nonce security
27
  * An uninstall routine
28
+ * Integration with the new WordPress 2.7+ menu
29
 
30
+ **NOTE:** This plugin is in beta, which means it's very feature-incomplete. We have many more features that we're working on finetuning before release, including 404 monitoring, PageRank sculpting, robots.txt editing, and more.
31
 
32
  If you install the plugin now, you can have these new features delivered to you on a regular basis via WordPress's automatic plugin upgrader.
33
 
80
 
81
  == Release History ==
82
 
83
+ = Version 0.3 (June 11, 2009) =
84
+ * Added the Canonicalizer module
85
+ * Added alerts of possible plugin conflicts
86
+ * Fixed a WordPress 2.8 compatibility issue
87
+ * SEO Ultimate now requires WordPress 2.8 or above
88
+
89
  = Version 0.2 (June 4, 2009) =
90
  * Added the Meta Editor module
91
  * Fixed a double-escaping bug in the Title Rewriter
seo-ultimate.css CHANGED
@@ -137,4 +137,8 @@ div.su-help ul {
137
  #su-postmeta-box table td input.regular-text,
138
  #su-postmeta-box table td textarea {
139
  width: 100%;
140
- }
 
 
 
 
137
  #su-postmeta-box table td input.regular-text,
138
  #su-postmeta-box table td textarea {
139
  width: 100%;
140
+ }
141
+
142
+ #wpwrap .su-plugin-notice .update-message {
143
+ font-weight: normal;
144
+ }
seo-ultimate.php CHANGED
@@ -2,8 +2,8 @@
2
  /*
3
  Plugin Name: SEO Ultimate
4
  Plugin URI: http://www.seodesignsolutions.com/wordpress-seo/
5
- Description: This all-in-one SEO plugin can rewrite title tags and add noindex to pages (with many more features coming soon).
6
- Version: 0.2
7
  Author: SEO Design Solutions
8
  Author URI: http://www.seodesignsolutions.com/
9
  Text Domain: seo-ultimate
@@ -12,7 +12,7 @@ Text Domain: seo-ultimate
12
  /**
13
  * The main SEO Ultimate plugin file.
14
  * @package SeoUltimate
15
- * @version 0.2
16
  * @link http://www.seodesignsolutions.com/wordpress-seo/ SEO Ultimate Homepage
17
  */
18
 
@@ -38,2130 +38,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
38
 
39
  define("SU_PLUGIN_NAME", "SEO Ultimate");
40
  define("SU_PLUGIN_URI", "http://www.seodesignsolutions.com/wordpress-seo/");
41
- define("SU_VERSION", "0.2");
42
  define("SU_AUTHOR", "SEO Design Solutions");
43
  define("SU_AUTHOR_URI", "http://www.seodesignsolutions.com/");
44
- define("SU_USER_AGENT", "SeoUltimate/0.2");
45
-
46
-
47
- /********** CLASSES **********/
48
-
49
- /**
50
- * The main class. Provides plugin-level functionality.
51
- */
52
- class SEO_Ultimate {
53
-
54
- /********** VARIABLES **********/
55
-
56
- /**
57
- * Stores all module class instances.
58
- *
59
- * @since 0.1
60
- * @var array
61
- */
62
- var $modules = array();
63
-
64
- /**
65
- * Stores the names of disabled modules.
66
- *
67
- * @since 0.1
68
- * @var array
69
- */
70
- var $disabled_modules = array();
71
-
72
- /**
73
- * Stores the status (disabled/hidden/silenced/enabled) of each module.
74
- *
75
- * @since 0.1
76
- * @var array
77
- */
78
- var $module_status = array();
79
-
80
- /**
81
- * The server path of this plugin file.
82
- * Example: /home/user/public_html/wp-content/plugins/seo-ultimate/seo-ultimate.php
83
- *
84
- * @since 0.1
85
- * @var string
86
- */
87
- var $plugin_file_path;
88
-
89
- /**
90
- * The public URL of this plugin file.
91
- * Example: http://www.example.com/wp-content/plugins/seo-ultimate/seo-ultimate.php
92
- *
93
- * @since 0.1
94
- * @var string
95
- */
96
- var $plugin_file_url;
97
-
98
- /**
99
- * The server path of the directory where this plugin is located, with trailing slash.
100
- * Example: /home/user/public_html/wp-content/plugins/seo-ultimate/
101
- *
102
- * @since 0.1
103
- * @var string
104
- */
105
- var $plugin_dir_path;
106
-
107
- /**
108
- * The public URL of the directory where this plugin is located, with trailing slash.
109
- * Example: http://www.example.com/wp-content/plugins/seo-ultimate/
110
- *
111
- * @since 0.1
112
- * @var string
113
- */
114
- var $plugin_dir_url;
115
-
116
- /**
117
- * The database ID of the current hit.
118
- *
119
- * @since 0.2
120
- * @var int
121
- */
122
- var $hit_id = 0;
123
-
124
-
125
- /********** CLASS CONSTRUCTORS **********/
126
-
127
- /**
128
- * Fills in class variables, loads modules, and hooks into WordPress.
129
- * PHP5-style constructor.
130
- *
131
- * @since 0.1
132
- * @uses load_plugin_data()
133
- * @uses SU_VERSION
134
- * @uses install()
135
- * @uses upgrade()
136
- * @uses load_modules()
137
- * @uses activate() Registered with WordPress as the activation hook.
138
- * @uses init() Hooked into WordPress's "init" action.
139
- * @uses add_menus() Hooked into WordPress's "admin_menu" action.
140
- * @uses sanitize_menu_hook() Hooked into WordPress's "sanitize_title" filter.
141
- * @uses admin_includes() Hooked into WordPress's "admin_head" action.
142
- * @uses plugin_page_notices() Hooked into WordPress's "admin_head" action.
143
- * @uses admin_help() Hooked into WordPress's "contextual_help" action.
144
- * @uses log_redirect() Hooked into WordPress's "wp_redirect" action.
145
- * @uses log_hit() Hooked into WordPress's "status_header" action.
146
- */
147
- function __construct() {
148
-
149
- /********** CLASS CONSTRUCTION **********/
150
-
151
- //Load data about the plugin file itself into the class
152
- $this->load_plugin_data();
153
-
154
-
155
- /********** VERSION CHECKING **********/
156
-
157
- //Get the current version, and the version when the plugin last ran
158
- $version = SU_VERSION;
159
- $oldversion = get_option('su_version', false);
160
-
161
- //If this is the first time the plugin is running, then install()
162
- if ($oldversion === false)
163
- $this->install();
164
-
165
- //If $oldversion is less than $version, then upgrade()
166
- elseif (version_compare($version, $oldversion) == 1)
167
- $this->upgrade($oldversion);
168
-
169
- //Store the current version in the database.
170
- //Rest assured, WordPress won't waste a database query if the value hasn't changed.
171
- update_option('su_version', $version);
172
-
173
-
174
- /********** INITIALIZATION **********/
175
-
176
- //Load plugin modules. Must be called *after* load_plugin_data()
177
- $this->load_modules();
178
-
179
-
180
- /********** PLUGIN EVENT HOOKS **********/
181
-
182
- //If we're activating the plugin, then call the activation function
183
- register_activation_hook(__FILE__, array($this, 'activate'));
184
-
185
- //If we're deactivating the plugin, then call the deactivation function
186
- register_deactivation_hook(__FILE__, array($this, 'deactivate'));
187
-
188
- //If we're uninstalling the plugin, then call the uninstallation function
189
- register_uninstall_hook(__FILE__, 'su_uninstall');
190
-
191
-
192
- /********** ACTION & FILTER HOOKS **********/
193
-
194
- //Initializes modules at WordPress initialization
195
- add_action('init', array($this, 'init'));
196
-
197
- //Hook to output all <head> code
198
- add_action('wp_head', array($this, 'template_head'), 1);
199
-
200
- //Hook to include JavaScript and CSS
201
- add_action('admin_head', array($this, 'admin_includes'));
202
-
203
- //Hook to add plugin notice actions
204
- add_action('admin_head', array($this, 'plugin_page_notices'));
205
-
206
- //When loading the admin menu, call on our menu constructor function.
207
- //For future-proofing purposes, we specifically state the default priority of 10,
208
- //since some modules set a priority of 9 with the specific intention of running
209
- //before this main plugin's hook.
210
- add_action('admin_menu', array($this, 'add_menus'), 10);
211
-
212
- //Hook to customize contextual help
213
- add_action('contextual_help', array($this, 'admin_help'), 10, 2);
214
-
215
- //Postmeta box hooks
216
- add_action('admin_menu', array($this, 'add_postmeta_box'));
217
- add_action('save_post', array($this, 'save_postmeta_box'), 10, 2);
218
-
219
- //Display info on new versions
220
- add_action('in_plugin_update_message-'.plugin_basename(__FILE__), array($this, 'plugin_update_info'), 10, 2);
221
-
222
- //Log this visitor!
223
- add_filter('wp_redirect', array($this, 'log_redirect'), 10, 2);
224
- add_filter('status_header', array($this, 'log_hit'), 10, 2);
225
- }
226
-
227
- /**
228
- * PHP4 constructor that redirects to the PHP5 constructor.
229
- *
230
- * @since 0.1
231
- * @uses __construct()
232
- */
233
- function SEO_Ultimate() {
234
-
235
- $this->__construct();
236
- }
237
-
238
-
239
- /********** PLUGIN EVENT FUNCTIONS **********/
240
-
241
- /**
242
- * This will be called if the plugin is being run for the first time.
243
- *
244
- * @since 0.1
245
- */
246
- function install() {
247
-
248
- //Add the database table
249
- $this->db_setup();
250
-
251
- //Load settings file if present
252
- if (get_option('su_settings') === false && is_readable($settingsfile = $this->plugin_dir_path.'settings.txt')) {
253
- $import = base64_decode(file_get_contents($settingsfile));
254
- if (is_serialized($import)) update_option('su_settings', $import);
255
- }
256
- }
257
-
258
- /**
259
- * This will be called if the plugin's version has increased since the last run.
260
- *
261
- * @since 0.1
262
- */
263
- function upgrade() {
264
-
265
- //Upgrade database schemas if needed
266
- $this->db_setup();
267
- }
268
-
269
- /**
270
- * WordPress will call this when the plugin is activated, as instructed by the register_activation_hook() call in {@link __construct()}.
271
- * Does activation tasks for the plugin itself, not modules.
272
- *
273
- * @since 0.1
274
- */
275
- function activate() {
276
-
277
- //Nothing here yet
278
- }
279
-
280
- /**
281
- * WordPress will call this when the plugin is deactivated, as instructed by the register_deactivation_hook() call in {@link __construct()}.
282
- *
283
- * @since 0.1
284
- */
285
- function deactivate() {
286
-
287
- //Let modules run deactivation tasks
288
- do_action('su_deactivate');
289
-
290
- //Unschedule all cron jobs
291
- $this->remove_cron_jobs(true);
292
-
293
- //Delete module records, so that modules are re-activated if the plugin is.
294
- delete_option('su_modules');
295
-
296
- //Delete all cron job records, since the jobs no longer exist
297
- delete_option('su_cron');
298
- }
299
-
300
- /**
301
- * Calls module deactivation/uninstallation functions and deletes all database data.
302
- *
303
- * @since 0.1
304
- */
305
- function uninstall() {
306
-
307
- //Deactivate modules and cron jobs
308
- $this->deactivate();
309
-
310
- //Let modules run uninstallation tasks
311
- do_action('su_uninstall');
312
-
313
- //Delete all other options that aren't deleted in deactivate()
314
- delete_option('su_version');
315
- delete_option('su_settings');
316
-
317
- //Delete the hits table
318
- mysql_query("DROP TABLE IF EXISTS ".$this->get_table_name('hits'));
319
- }
320
-
321
-
322
- /********** INITIALIZATION FUNCTIONS **********/
323
-
324
- /**
325
- * Fills class variables with information about where the plugin is located.
326
- *
327
- * @since 0.1
328
- * @uses $plugin_file_path
329
- * @uses $plugin_file_url
330
- * @uses $plugin_dir_path
331
- * @uses $plugin_dir_url
332
- */
333
- function load_plugin_data() {
334
-
335
- //Load plugin path/URL information
336
- $this->plugin_file_path = trailingslashit(WP_PLUGIN_DIR).plugin_basename(__FILE__);
337
- $this->plugin_file_url = plugins_url(plugin_basename(__FILE__)); //trailingslashit(WP_PLUGIN_URL).
338
- $this->plugin_dir_path = trailingslashit(dirname($this->plugin_file_path));
339
- $this->plugin_dir_url = trailingslashit(plugins_url(dirname(plugin_basename(__FILE__))));
340
- }
341
-
342
- /**
343
- * Finds and loads all modules. Runs the activation functions of newly-uploaded modules.
344
- * Updates the modules list and saves it in the database. Removes the cron jobs of deleted modules.
345
- *
346
- * @since 0.1
347
- * @uses $plugin_dir_path
348
- * @uses $modules Stores module classes in this array.
349
- * @uses module_sort_callback() Passes this function to uasort() to sort the $modules array.
350
- * @uses SU_MODULE_ENABLED
351
- * @uses SU_MODULE_DISABLED
352
- */
353
- function load_modules() {
354
-
355
- //The plugin_dir_path variable must be set before calling this function!
356
- if (!$this->plugin_dir_path) return false;
357
-
358
- //The modules are in the "modules" subdirectory of the plugin folder.
359
- $dir = opendir($this->plugin_dir_path.'modules');
360
-
361
- //Get the modules list from last time the plugin was loaded.
362
- $oldmodules = maybe_unserialize(get_option('su_modules', false));
363
-
364
- //If no list is found, then create a new, empty list.
365
- if ($oldmodules === false) {
366
- $oldmodules = array();
367
- add_option('su_modules', serialize($oldmodules));
368
- }
369
-
370
- //This loop will be repeated as long as there are more files to inspect
371
- while ($file = readdir($dir)) {
372
-
373
- //Modules are non-directory files with the .php extension
374
- if ($file != '.' && $file != '..' && !is_dir($file) &&
375
- substr($file, -4) == '.php') {
376
-
377
- //Figure out the module's array key and class name
378
- $module = strval(strtolower(substr($file, 0, -4)));
379
- $class = 'SU_'.str_replace(' ', '', ucwords(str_replace('-', ' ', $module)));
380
-
381
- //If this module is disabled...
382
- if ($oldmodules[$module] == SU_MODULE_DISABLED) {
383
-
384
- $name = file($this->plugin_dir_path."modules/$file");
385
- if ($name) $name = str_replace(' Module', '', ltrim($name[2], ' *'));
386
- else $name = ucwords(str_replace('-', ' ', $module));
387
-
388
- $this->disabled_modules[$module] = $name;
389
-
390
- } else {
391
-
392
- //Load the module's code
393
- require_once("modules/$file");
394
-
395
- //If this is actually a module...
396
- if (class_exists($class)) {
397
-
398
- //Create an instance of the module's class and store it in the array
399
- $this->modules[$module] = new $class;
400
-
401
- //We must tell the module what its key is so that it can save settings
402
- $this->modules[$module]->module_key = $module;
403
-
404
- //Tell the module what its URL is
405
- $this->modules[$module]->module_url = $this->plugin_dir_url."modules/$file";
406
-
407
- //Tell the module what its plugin page hook is
408
- $this->modules[$module]->plugin_page_hook =
409
- $this->modules[$module]->get_menu_parent_hook().'_page_'.SEO_Ultimate::key_to_hook($module);
410
-
411
- } //If this isn't a module, then the file will simply be included as-is
412
- }
413
- }
414
- }
415
-
416
- //If the loop above found modules, then sort them with our special sorting function
417
- //so they appear on the admin menu in the right order
418
- if (count($this->modules) > 0)
419
- uasort($this->modules, array($this, 'module_sort_callback'));
420
-
421
- //Now we'll compare the current module set with the one from last time.
422
-
423
- //Construct the new modules list that'll go in the database.
424
- //This code block will add/activate new modules, keep existing ones, and remove (i.e. not add) deleted ones.
425
- foreach ($this->modules as $key => $module) {
426
- if (isset($oldmodules[$key])) {
427
- $newmodules[$key] = $oldmodules[$key];
428
- } else {
429
- $module->activate();
430
- $newmodules[$key] = SU_MODULE_ENABLED;
431
- }
432
- }
433
-
434
- foreach ($this->disabled_modules as $key => $name) {
435
- $newmodules[$key] = SU_MODULE_DISABLED;
436
- }
437
-
438
- //Save the new modules list
439
- $this->module_status = $newmodules;
440
- update_option('su_modules', serialize($newmodules));
441
-
442
- //Remove the cron jobs of deleted modules
443
- $this->remove_cron_jobs();
444
- }
445
-
446
- /**
447
- * Runs during WordPress's init action.
448
- * Loads the textdomain and calls modules' init_pre() functions.
449
- *
450
- * @since 0.1
451
- * @uses SU_Module::init_pre()
452
- */
453
- function init() {
454
-
455
- //Allow translation of this plugin
456
- load_plugin_textdomain('seo-ultimate', '', plugin_basename(__FILE__));
457
-
458
- //Let the modules run init tasks
459
- foreach ($this->modules as $module)
460
- $module->init_pre();
461
- }
462
-
463
-
464
- /********** DATABASE FUNCTIONS **********/
465
-
466
- /**
467
- * Will create or update the database table.
468
- *
469
- * @since 0.1
470
- * @uses get_table_name()
471
- */
472
- function db_setup() {
473
-
474
- $sql = "CREATE TABLE " . $this->get_table_name('hits') . " (
475
- id BIGINT NOT NULL AUTO_INCREMENT,
476
- time INT NOT NULL ,
477
- ip_address VARCHAR(255) NOT NULL,
478
- user_agent VARCHAR(255) NOT NULL,
479
- url TEXT NOT NULL,
480
- redirect_url TEXT NOT NULL,
481
- status_code SMALLINT(3) NOT NULL,
482
- is_new BOOL NOT NULL,
483
- PRIMARY KEY (id)
484
- );";
485
-
486
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
487
- dbDelta($sql);
488
- }
489
-
490
- /**
491
- * Returns a full, prefixed MySQL table name.
492
- *
493
- * @since 0.1
494
- *
495
- * @param string $shortname The non-prefixed table name.
496
- * @return string The full, prefixed table name.
497
- */
498
- function get_table_name($shortname) {
499
- global $wpdb;
500
- if ($shortname == 'hits')
501
- return $wpdb->prefix . "sds_hits";
502
- else
503
- return '';
504
- }
505
-
506
- /**
507
- * A status_header WordPress filter that logs the current hit.
508
- *
509
- * @since 0.1
510
- * @uses get_current_url()
511
- * @uses $hit_id
512
- *
513
- * @param string $status_header The full HTTP status header. Unused and returned as-is.
514
- * @param int $status_code The numeric HTTP status code.
515
- * @param string $redirect_url The URL to which the visitor is being redirected. Optional.
516
- * @return string Returns the $status_header variable unchanged.
517
- */
518
- function log_hit($status_header, $status_code, $redirect_url = '') {
519
- if (!is_user_logged_in()) {
520
- global $wpdb;
521
-
522
- $table = $this->get_table_name('hits');
523
- $url = $this->get_current_url();
524
- $is_new = (count($wpdb->get_results($wpdb->prepare("SELECT url FROM $table WHERE url = %s AND is_new = 0", $url))) == 0);
525
-
526
- $data = array(
527
- 'time' => time()
528
- , 'ip_address' => $_SERVER['REMOTE_ADDR']
529
- , 'user_agent' => $_SERVER['HTTP_USER_AGENT']
530
- , 'url' => $url
531
- , 'redirect_url' => $redirect_url
532
- , 'status_code' => $status_code
533
- , 'is_new' => $is_new
534
- );
535
-
536
- if ($this->hit_id > 0) {
537
-
538
- //We don't want to overwrite a redirect URL if it's already been logged
539
- if (!strlen($data['redirect_url'])) unset($data['redirect_url']);
540
-
541
- //Update the existing hit record
542
- $wpdb->update($table, $data, array('id' => $this->hit_id));
543
- } else {
544
- $wpdb->insert($table, $data);
545
- $this->hit_id = $wpdb->insert_id;
546
- }
547
- }
548
-
549
- return $status_header;
550
- }
551
-
552
- /**
553
- * A wp_redirect WordPress filter that logs the current hit.
554
- *
555
- * @since 0.2
556
- * @uses log_hit()
557
- *
558
- * @param string $redirect_url The URL to which the visitor is being redirected.
559
- * @param int $status_code The numeric HTTP status code.
560
- * @return string The unchanged $redirect_url parameter.
561
- */
562
- function log_redirect($redirect_url, $status_code) {
563
- $this->log_hit(null, $status_code, $redirect_url);
564
- return $redirect_url;
565
- }
566
-
567
-
568
- /********** ADMIN MENU FUNCTIONS **********/
569
-
570
- /**
571
- * Constructs the "SEO" menu and its subitems.
572
- *
573
- * @since 0.1
574
- * @uses $modules
575
- * @uses get_module_count_code()
576
- * @uses SU_Module::get_menu_count()
577
- * @uses SU_Module::get_menu_pos()
578
- * @uses SU_Module::get_menu_title()
579
- * @uses SU_Module::get_page_title()
580
- * @uses key_to_hook()
581
- */
582
- function add_menus() {
583
-
584
- //If subitems have numeric bubbles, then add them up and show the total by the main menu item
585
- $count = 0;
586
- foreach ($this->modules as $key => $module) {
587
- if ($this->module_status[$key] > SU_MODULE_SILENCED && $module->get_menu_count() > 0 && $module->get_menu_parent() == 'seo')
588
- $count += $module->get_menu_count();
589
- }
590
- $count_code = $this->get_menu_count_code($count);
591
-
592
- //Add the "SEO" menu item!
593
- add_utility_page(__('SEO Ultimate', 'seo-ultimate'), __('SEO', 'seo-ultimate').$count_code, 'manage_options', 'seo', array(), 'div');
594
-
595
- //Translations and count codes will mess up the admin page hook, so we need to fix it manually.
596
- global $admin_page_hooks;
597
- $admin_page_hooks['seo'] = 'seo';
598
-
599
- //Add all the subitems
600
- foreach ($this->modules as $file => $module) {
601
-
602
- //Show a module on the menu only if it provides a menu title
603
- if ($module->get_menu_title()) {
604
-
605
- //If the module is hidden, put the module under a non-existant menu parent
606
- //(this will let the module's admin page be loaded, but it won't show up on the menu)
607
- if ($this->module_status[$file] > SU_MODULE_HIDDEN)
608
- $parent = $module->get_menu_parent();
609
- else
610
- $parent = 'su-hidden-modules';
611
-
612
- if ($this->module_status[$file] > SU_MODULE_SILENCED)
613
- $count_code = $this->get_menu_count_code($module->get_menu_count());
614
- else
615
- $count_code = '';
616
-
617
- add_submenu_page($parent, $module->get_page_title(), $module->get_menu_title().$count_code,
618
- 'manage_options', $this->key_to_hook($file), array($module, 'admin_page'));
619
- }
620
- }
621
- }
622
-
623
- /**
624
- * Compares two modules to determine which of the two should be displayed first on the menu.
625
- * Sorts by menu position first, and title second.
626
- * Works as a uasort() callback.
627
- *
628
- * @since 0.1
629
- * @uses SU_Module::get_menu_pos()
630
- * @uses SU_Module::get_menu_title()
631
- *
632
- * @param SU_Module $a The first module to compare.
633
- * @param SU_Module $b The second module to compare.
634
- * @return int This will be -1 if $a comes first, or 1 if $b comes first.
635
- */
636
- function module_sort_callback($a, $b) {
637
- if ($a->get_menu_pos() == $b->get_menu_pos()) {
638
- return strcmp($a->get_menu_title(), $b->get_menu_title());
639
- }
640
-
641
- return ($a->get_menu_pos() < $b->get_menu_pos()) ? -1 : 1;
642
- }
643
-
644
- /**
645
- * If the bubble alert count parameter is greater than zero, then returns the HTML code for a numeric bubble to display next to a menu item.
646
- * Otherwise, returns an empty string.
647
- *
648
- * @since 0.1
649
- *
650
- * @param int $count The number that should appear in the bubble.
651
- * @return string The string that should be added to the end of the menu item title.
652
- */
653
- function get_menu_count_code($count) {
654
-
655
- //If we have alerts that need a bubble, then return the bubble HTML.
656
- if ($count > 0)
657
- return "&nbsp;<span id='awaiting-mod' class='count-$count'><span class='pending-count'>$count</span></span>";
658
- else
659
- return '';
660
- }
661
-
662
- /**
663
- * Converts a module key to a menu hook.
664
- * (Makes the "Stats" module load when the "SEO" parent item is clicked.)
665
- *
666
- * @since 0.1
667
- *
668
- * @param string $key The module key.
669
- * @return string The menu hook.
670
- */
671
- function key_to_hook($key) {
672
- switch ($key) {
673
- case 'stats': return 'seo'; break;
674
- case 'settings': return 'seo-ultimate'; break;
675
- default: return "su-$key"; break;
676
- }
677
- }
678
-
679
- /**
680
- * Converts a menu hook to a module key.
681
- * (If the "SEO" parent item is clicked, then the Stats module is being shown.)
682
- *
683
- * @since 0.1
684
- *
685
- * @param string $hook The menu hook.
686
- * @return string The module key.
687
- */
688
- function hook_to_key($hook) {
689
- switch ($hook) {
690
- case 'seo': return 'stats'; break;
691
- case 'seo-ultimate': return 'settings'; break;
692
- default: return substr($hook, 3); break;
693
- }
694
- }
695
-
696
-
697
- /********** OTHER ADMIN FUNCTIONS **********/
698
-
699
- /**
700
- * Returns a boolean indicating whether the user is currently viewing an admin page generated by this plugin.
701
- *
702
- * @since 0.1
703
- *
704
- * @return bool Whether the user is currently viewing an admin page generated by this plugin.
705
- */
706
- function is_plugin_admin_page() {
707
- if (is_admin()) {
708
- global $plugin_page;
709
-
710
- foreach ($this->modules as $key => $module) {
711
- if ($plugin_page == $this->key_to_hook($key)) return true;
712
- }
713
- }
714
-
715
- return false;
716
- }
717
-
718
- /**
719
- * Includes the plugin's CSS and JavaScript in the header.
720
- * Also includes a module's CSS/JavaScript on its administration page.
721
- *
722
- * @todo Link to global plugin includes only when on plugin pages.
723
- *
724
- * @since 0.1
725
- * @uses $modules
726
- * @uses $plugin_file_url
727
- * @uses $plugin_dir_url
728
- * @uses hook_to_key()
729
- */
730
- function admin_includes() {
731
-
732
- //Global plugin CSS and JavaScript
733
- echo "\n<link rel='stylesheet' type='text/css' href='".$this->plugin_dir_url."seo-ultimate.css?version=".SU_VERSION."' />\n";
734
- echo "\n<script type='text/javascript' src='".$this->plugin_dir_url."seo-ultimate.js?version=".SU_VERSION."'></script>\n";
735
-
736
- //Figure out what plugin admin page we're on
737
- global $plugin_page;
738
- $pp = $this->hook_to_key($plugin_page);
739
-
740
- foreach ($this->modules as $key => $module) {
741
-
742
- //Is the current admin page belong to this module? If so, print links to the module's CSS and JavaScript.
743
- if (strcmp($key, $pp) == 0) {
744
- echo "\n<link rel='stylesheet' type='text/css' href='".$module->module_url."?css=admin&amp;version=".SU_VERSION."' />\n";
745
- echo "\n<script type='text/javascript' src='".$module->module_url."?js=admin&amp;version=".SU_VERSION."'></script>\n";
746
- return;
747
- }
748
- }
749
- }
750
-
751
- /**
752
- * Replaces WordPress's default contextual help with module-specific help text, if the module provides it.
753
- *
754
- * @since 0.1
755
- * @uses $modules
756
- *
757
- * @param string $text WordPress's default contextual help.
758
- * @param string $screen The screen currently being shown.
759
- * @return string The contextual help content that should be shown.
760
- */
761
- function admin_help($text, $screen) {
762
- //If $screen begins with a recognized prefix...
763
- if ($screen == 'toplevel_page_seo' || substr($screen, 0, 9) == 'seo_page_' || substr($screen, 0, 14) == 'settings_page_') {
764
-
765
- //Remove the prefix from $screen to get the $key
766
- $key = $this->hook_to_key(str_replace(array('toplevel_page_', 'seo_page_', 'settings_page_'), '', $screen));
767
-
768
- //If $key refers to a module...
769
- if (isset($this->modules[$key])) {
770
-
771
- //Ask the module for custom help content
772
- $customhelp = $this->modules[$key]->admin_help();
773
-
774
- //If we have custom help to display...
775
- if ($customhelp !== false) {
776
-
777
- //Return the help content with an <h5> title
778
- $help = "<div class='su-help'>\n";
779
- $help .= '<h5>'.sprintf(__('%s Help', 'seo-ultimate'),
780
- $this->modules[$key]->get_page_title())."</h5>\n";
781
- $help .= "<div class='metabox-prefs'>\n".$customhelp."\n</div>\n";
782
- $help .= "</div>\n";
783
- return $help;
784
- }
785
- }
786
- } elseif (strcmp($screen, 'post') == 0 || strcmp($screen, 'page') == 0) {
787
-
788
- //Gather post meta help content
789
- $helparray = apply_filters('su_postmeta_help', array());
790
-
791
- if ($helparray) {
792
-
793
- $customhelp = '';
794
- foreach ($helparray as $line) {
795
- $customhelp .= "<li><p>$line</p></li>\n";
796
- }
797
-
798
- $text .= "<div class='su-help'>\n";
799
- $text .= '<h5>'.__('SEO Settings Help', 'seo-ultimate')."</h5>\n";
800
- $text .= "<div class='metabox-prefs'>\n";
801
- $text .= "<p>".__("The SEO Settings box lets you customize these settings:", 'seo-ultimate')."</p>\n";
802
- $text .= "<ul>\n$customhelp\n</ul>";
803
- $text .= "<p><em>".__("(The SEO Settings box is part of the SEO Ultimate plugin.)", 'seo-ultimate')."</em></p>\n";
804
- $text .= "\n</div>\n</div>\n";
805
- return $text;
806
- }
807
- }
808
-
809
- //No custom help content to show. Return the default.
810
- return $text;
811
- }
812
-
813
- /**
814
- * Notifies the user if he/she is using plugins whose functionality SEO Ultimate replaces.
815
- *
816
- * @since 0.1
817
- * @uses plugin_page_notice() Hooked into the after_plugin_row_$path actions.
818
- */
819
- function plugin_page_notices() {
820
-
821
- global $pagenow;
822
-
823
- if ($pagenow == 'plugins.php') {
824
-
825
- $r_plugins = array(
826
- // 'all-in-one-seo-pack/all_in_one_seo_pack.php'
827
- //,
828
- );
829
-
830
- $i_plugins = get_plugins();
831
-
832
- foreach ($r_plugins as $path) {
833
- if (isset($i_plugins[$path]))
834
- add_action("after_plugin_row_$path", array($this, 'plugin_page_notice'), 10, 3);
835
- }
836
- }
837
- }
838
-
839
- /**
840
- * Outputs a table row notifying the user that he/she is using a plugin whose functionality SEO Ultimate replaces.
841
- *
842
- * @since 0.1
843
- */
844
- function plugin_page_notice($file, $data, $context) {
845
- if ($context == 'active') {
846
- echo "<tr><td colspan='5' class='su-plugin-notice plugin-update'>\n";
847
- printf(__('SEO Ultimate includes the functionality of %1$s. You may want to deactivate %1$s to avoid plugin conflicts.', 'seo-ultimate'), $data['Name']);
848
- echo "</td></tr>\n";
849
- }
850
- }
851
-
852
- /**
853
- * Displays new-version info in this plugin's update row on WordPress's plugin admin page.
854
- * Hooked into WordPress's in_plugin_update_message-(file) action.
855
- *
856
- * @since 0.1
857
- *
858
- * @param array $plugin_data An array of this plugin's information. Unused.
859
- * @param obejct $r The response object from the WordPress Plugin Directory.
860
- */
861
- function plugin_update_info($plugin_data, $r) {
862
- if ($r && $r->new_version) {
863
- $info = $this->load_webpage("http://www.seodesignsolutions.com/apis/su/update-info/?ov=".urlencode(SU_VERSION)."&amp;nv=".urlencode($r->new_version));
864
- if ($info) {
865
- $info = strip_tags($info, "<br><a><b><i><span>");
866
- echo "<br />$info";
867
- }
868
- }
869
- }
870
-
871
-
872
- /********** ADMIN POST META BOX FUNCTIONS **********/
873
-
874
- /**
875
- * Gets the post meta box fields from the modules, sorts them, and returns the HTML as a string.
876
- *
877
- * @since 0.1
878
- * @uses $modules
879
- *
880
- * @param string $screen The admin screen currently being viewed (post, page). Defaults to post. Optional.
881
- * @return string Concatenated <tr>(field)</tr> strings.
882
- */
883
- function get_postmeta_fields($screen='post') {
884
-
885
- //Compile the fields array
886
- $fields = array();
887
- foreach ($this->modules as $module)
888
- $fields = $module->postmeta_fields($fields, $screen);
889
-
890
- if (count($fields) > 0) {
891
-
892
- //Sort the fields array
893
- ksort($fields, SORT_STRING);
894
-
895
- //Return a string
896
- return implode("\n", $fields);
897
- }
898
-
899
- return '';
900
- }
901
-
902
- /**
903
- * If we have post meta fields to display, then register our meta box with WordPress.
904
- *
905
- * @since 0.1
906
- * @uses get_postmeta_fields()
907
- */
908
- function add_postmeta_box() {
909
-
910
- //Add the metabox to posts and pages.
911
- foreach (array('post', 'page') as $screen) {
912
-
913
- //Only show the meta box if there are fields to show.
914
- if ($this->get_postmeta_fields($screen))
915
- add_meta_box('su_postmeta', __('SEO Settings', 'seo-ultimate'), array($this, "show_{$screen}_postmeta_box"), $screen, 'normal', 'high');
916
- }
917
- }
918
-
919
- /**
920
- * Displays the inner contents of the post meta box when editing posts.
921
- *
922
- * @since 0.1
923
- * @uses show_postmeta_box()
924
- */
925
- function show_post_postmeta_box() {
926
- $this->show_postmeta_box('post');
927
- }
928
-
929
- /**
930
- * Displays the inner contents of the post meta box when editing Pages.
931
- *
932
- * @since 0.1
933
- * @uses show_postmeta_box()
934
- */
935
- function show_page_postmeta_box() {
936
- $this->show_postmeta_box('page');
937
- }
938
-
939
- /**
940
- * Displays the inner contents of the post meta box.
941
- *
942
- * @since 0.1
943
- * @uses get_postmeta_fields()
944
- *
945
- * @param string $screen The admin screen currently being viewed (post, page).
946
- */
947
- function show_postmeta_box($screen) {
948
-
949
- //Begin box
950
- echo "<div id='su-postmeta-box'>\n";
951
- wp_nonce_field('su-update-postmeta', '_su_wpnonce');
952
- echo "\n<table>\n";
953
-
954
- //Output postmeta fields
955
- echo $this->get_postmeta_fields($screen);
956
-
957
- //End box
958
- echo "\n</table>\n</div>\n";
959
- }
960
-
961
- /**
962
- * Saves the values of the fields in the post meta box.
963
- *
964
- * @since 0.1
965
- *
966
- * @param int $post_id The ID of the post being saved.
967
- * @return int The ID of the post being saved.
968
- */
969
- function save_postmeta_box($post_id, $post) {
970
-
971
- //Sanitize
972
- $post_id = (int)$post_id;
973
-
974
- //Don't save postmeta if this is a revision!
975
- if ($post->post_type == 'revision') return;
976
-
977
- //Run preliminary permissions checks
978
- if ( !check_admin_referer('su-update-postmeta', '_su_wpnonce') ) return;
979
- if ( 'page' == $_POST['post_type'] ) {
980
- if ( !current_user_can( 'edit_page', $post_id )) return;
981
- } elseif ( 'post' == $_POST['post_type'] ) {
982
- if ( !current_user_can( 'edit_post', $post_id )) return;
983
- } else return;
984
-
985
- //Get an array of all postmeta
986
- $allmeta = wp_cache_get($post_id, 'post_meta');
987
- if (!$allmeta) {
988
- update_postmeta_cache($post_id);
989
- $allmeta = wp_cache_get($post_id, 'post_meta');
990
- }
991
-
992
- //Update postmeta values
993
- foreach ($_POST as $key => $value) {
994
- if (substr($key, 0, 4) == '_su_') {
995
-
996
- //Turn checkboxes into integers
997
- if (strcmp($value, '1') == 0) $value = 1;
998
-
999
- //Set the postmeta
1000
- update_post_meta($post_id, $key, $value);
1001
-
1002
- //This value has been updated.
1003
- unset($allmeta[$key]);
1004
- }
1005
- }
1006
-
1007
- //Update values for unchecked checkboxes.
1008
- foreach ($allmeta as $key => $value) {
1009
- if (substr($key, 0, 4) == '_su_') {
1010
- $value = maybe_unserialize($value[0]);
1011
- if ($value == 1)
1012
- update_post_meta($post_id, $key, 0);
1013
- }
1014
- }
1015
-
1016
- //All done
1017
- return $post_id;
1018
- }
1019
-
1020
-
1021
- /********** CRON FUNCTION **********/
1022
-
1023
- /**
1024
- * Can remove cron jobs for modules that no longer exist, or remove all cron jobs.
1025
- *
1026
- * @since 0.1
1027
- *
1028
- * @param bool $remove_all Whether to remove all cron jobs. Optional.
1029
- */
1030
- function remove_cron_jobs($remove_all = false) {
1031
- $crondata = maybe_unserialize(get_option('su_cron'));
1032
- if (is_array($crondata)) {
1033
- $newcrondata = $crondata;
1034
-
1035
- foreach ($crondata as $key => $crons) {
1036
- if ($remove_all || !isset($this->modules[$key])) {
1037
- foreach ($crons as $data) { wp_clear_scheduled_hook($data[0]); }
1038
- unset($newcrondata[$key]);
1039
- }
1040
- }
1041
-
1042
- update_option('su_cron', serialize($newcrondata));
1043
- }
1044
- }
1045
-
1046
-
1047
- /********** TEMPLATE OUTPUT FUNCTION **********/
1048
-
1049
- /**
1050
- * Outputs code into the template's <head> tag.
1051
- *
1052
- * @since 0.1
1053
- */
1054
- function template_head() {
1055
-
1056
- if (isset($this->modules['settings']))
1057
- $markcode = $this->modules['settings']->get_setting('mark_code');
1058
- else
1059
- $markcode = false;
1060
-
1061
- echo "\n";
1062
-
1063
- if ($markcode) echo "\n<!-- ".SU_PLUGIN_NAME." (".SU_PLUGIN_URI.") -->\n";
1064
-
1065
- //Let modules output head code.
1066
- do_action('su_head');
1067
-
1068
- //Make sure the blog is public. Telling robots what to do is a moot point if they aren't even seeing the blog.
1069
- if (get_option('blog_public')) {
1070
- $robots = implode(',', apply_filters('su_meta_robots', array()));
1071
- if ($robots) echo "\t<meta name=\"robots\" content=\"$robots\" />\n";
1072
- }
1073
-
1074
- if ($markcode) echo "<!-- /".SU_PLUGIN_NAME." -->\n\n";
1075
- }
1076
-
1077
- /********** PSEUDO-STATIC FUNCTIONS **********/
1078
-
1079
- /**
1080
- * Approximately determines the URL in the visitor's address bar. (Includes query strings, but not #anchors.)
1081
- *
1082
- * @since 0.1
1083
- *
1084
- * @return string The current URL.
1085
- */
1086
- function get_current_url() {
1087
- $url = 'http';
1088
- if ($_SERVER["HTTPS"] == "on") $url .= "s";
1089
- $url .= "://";
1090
-
1091
- if ($_SERVER["SERVER_PORT"] != "80")
1092
- return $url.$_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
1093
- else
1094
- return $url.$_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
1095
- }
1096
-
1097
- /**
1098
- * Determines the ID of the current post.
1099
- * Works in the admin as well as the front-end.
1100
- *
1101
- * @since 0.2
1102
- *
1103
- * @return int|false The ID of the current post, or false on failure.
1104
- */
1105
- function get_post_id() {
1106
- if (is_admin())
1107
- return intval($_REQUEST['post']);
1108
- elseif (in_the_loop())
1109
- return intval(get_the_ID());
1110
- elseif (is_singular()) {
1111
- global $wp_query;
1112
- return $wp_query->get_queried_object_id();
1113
- }
1114
-
1115
- return false;
1116
- }
1117
- }
1118
-
1119
- /**
1120
- * The pseudo-abstract class upon which all modules are based
1121
- *
1122
- * @abstract
1123
- * @since 0.1
1124
- */
1125
- class SU_Module {
1126
-
1127
- /********** VARIABLES **********/
1128
-
1129
- var $module_key;
1130
-
1131
- /**
1132
- * Stores the module file's URL.
1133
- *
1134
- * @since 0.1
1135
- * @var string
1136
- */
1137
- var $module_url;
1138
-
1139
- /**
1140
- * Stores the module's plugin page hook (the full hook with seo_page_ prefix).
1141
- * A reconstructed value of the get_plugin_page_hook() function, which is only available after admin init.
1142
- *
1143
- * @since 0.1
1144
- * @var string
1145
- */
1146
- var $plugin_page_hook;
1147
-
1148
- /**
1149
- * Contains messages that are waiting to be displayed to the user.
1150
- *
1151
- * @since 0.1
1152
- * @var array
1153
- */
1154
- var $messages = array();
1155
-
1156
-
1157
- /********** CONSTRUCTOR FUNCTION **********/
1158
-
1159
- /**
1160
- * PHP4 constructor that points to the likely-overloaded PHP5 constructor.
1161
- *
1162
- * @since 0.1
1163
- * @uses __construct()
1164
- */
1165
- function SU_Module() {
1166
- $this->__construct();
1167
- }
1168
-
1169
-
1170
- /********** PSEUDO-ABSTRACT FUNCTIONS **********/
1171
-
1172
- /**
1173
- * PHP5 constructor.
1174
- *
1175
- * @since 0.1
1176
- */
1177
- function __construct() { }
1178
-
1179
- /**
1180
- * The title of the admin page, which is displayed in the <title> and <h2> tags.
1181
- * Is the same as the menu title by default.
1182
- *
1183
- * @since 0.1
1184
- *
1185
- * @return string The title shown on this module's admin page.
1186
- */
1187
- function get_page_title() { return $this->get_menu_title(); }
1188
-
1189
- /**
1190
- * The title that appears on the administration navigation menu.
1191
- *
1192
- * @since 0.1
1193
- *
1194
- * @return string The title shown on the admin menu.
1195
- */
1196
- function get_menu_title() { return ''; }
1197
-
1198
- /**
1199
- * Determines where this module's admin page should appear relative to those of other modules.
1200
- * If two modules have the same menu position index, they are sorted alphabetically.
1201
- *
1202
- * @since 0.1
1203
- *
1204
- * @return int The menu position index.
1205
- */
1206
- function get_menu_pos() { return 999; }
1207
-
1208
- /**
1209
- * The number that should be displayed in a bubble next to the module's menu title.
1210
- * A return value of zero means no bubble is shown.
1211
- *
1212
- * @since 0.1
1213
- *
1214
- * @return int The number that should be displayed.
1215
- */
1216
- function get_menu_count() { return 0; }
1217
-
1218
- /**
1219
- * Indicates under which top-level menu this module's admin page should go.
1220
- * Examples: seo (This plugin's SEO menu), options-general.php (The Settings menu)
1221
- *
1222
- * @since 0.1
1223
- *
1224
- * @return string The value to pass to WordPress's add_submenu_page() function.
1225
- */
1226
- function get_menu_parent(){ return 'seo'; }
1227
-
1228
- /**
1229
- * Returns the hook of this module's menu parent.
1230
- * Examples: seo (This plugin's SEO menu), settings (The Settings menu), toplevel (The toplevel)
1231
- *
1232
- * @since 0.1
1233
- *
1234
- * @return string The hook of the module's menu parent.
1235
- */
1236
- function get_menu_parent_hook() { return $this->get_menu_parent(); }
1237
-
1238
- /**
1239
- * Called at WordPress's init hook.
1240
- *
1241
- * @since 0.1
1242
- */
1243
- function init() {}
1244
-
1245
- /**
1246
- * Called upon module activation,
1247
- * i.e. when a module is uploaded or when the plugin is activated for the first time.
1248
- *
1249
- * @since 0.1
1250
- */
1251
- function activate() { }
1252
-
1253
- /**
1254
- * Returns an array of default settings. The defaults will be saved in the database if the settings don't exist.
1255
- *
1256
- * @since 0.1
1257
- *
1258
- * @return array The default settings. (The setting name is the key, and the default value is the array value.)
1259
- */
1260
- function get_default_settings() { return array(); }
1261
-
1262
- /**
1263
- * The contents of the administration page.
1264
- *
1265
- * @since 0.1
1266
- */
1267
- function admin_page_contents() { }
1268
-
1269
- /**
1270
- * Returns the module's custom help content that should go in the "Help" dropdown of WordPress 2.7 and above.
1271
- *
1272
- * @since 0.1
1273
- *
1274
- * @return string|false The help text, or false if no custom help is available.
1275
- */
1276
- function admin_help() { return false; }
1277
-
1278
- /**
1279
- * Adds the module's post meta box field HTML to the array.
1280
- *
1281
- * @since 0.1
1282
- *
1283
- * @param array $fields The fields array.
1284
- * @return array The updated fields array.
1285
- */
1286
- function postmeta_fields($fields) { return $fields; }
1287
-
1288
-
1289
- /********** INITIALIZATION FUNCTION **********/
1290
-
1291
- /**
1292
- * Runs preliminary initialization tasks before calling the module's own init() function.
1293
- *
1294
- * @since 0.1
1295
- * @uses get_default_settings()
1296
- * @uses get_setting()
1297
- * @uses update_setting()
1298
- * @uses init()
1299
- */
1300
- function init_pre() {
1301
- $defaults = $this->get_default_settings();
1302
- foreach ($defaults as $setting => $default) {
1303
- if ($this->get_setting($setting, "{reset}") === "{reset}")
1304
- $this->update_setting($setting, $default);
1305
- }
1306
-
1307
- $this->init();
1308
- }
1309
-
1310
-
1311
- /********** MODULE FUNCTIONS **********/
1312
-
1313
- /**
1314
- * Returns the array key of the module.
1315
- *
1316
- * @since 0.1
1317
- * @uses $module_key
1318
- *
1319
- * @return string The module key.
1320
- */
1321
- function get_module_key() {
1322
- if ($this->module_key)
1323
- return $this->module_key;
1324
- else
1325
- die(str_rot13('Zbqhyr ybnqrq sebz na rkgreany fbhepr!'));
1326
- }
1327
-
1328
- /**
1329
- * Checks to see whether a specified module exists.
1330
- *
1331
- * @since 0.1
1332
- * @uses $seo_ultimate
1333
- *
1334
- * @param string $key The key of the module to check.
1335
- * @return boolean Whether the module is loaded into SEO Ultimate.
1336
- */
1337
- function module_exists($key) {
1338
- global $seo_ultimate;
1339
- return isset($seo_ultimate->modules[$key]);
1340
- }
1341
-
1342
-
1343
- /********** SETTINGS FUNCTIONS **********/
1344
-
1345
- /**
1346
- * Retrieves the given setting from a module's settings array.
1347
- *
1348
- * @since 0.1
1349
- * @uses get_module_key()
1350
- *
1351
- * @param string $key The name of the setting to retrieve.
1352
- * @param mixed $default What should be returned if the setting does not exist. Optional.
1353
- * @param string|null $module The module to which the setting belongs. Defaults to the current module. Optional.
1354
- * @return mixed The value of the setting, or the $default variable.
1355
- */
1356
- function get_setting($key, $default=null, $module=null) {
1357
- if (!$module) $module = $this->get_module_key();
1358
- $settings = maybe_unserialize(get_option('su_settings'));
1359
- if (isset($settings[$module][$key]))
1360
- $setting = $settings[$module][$key];
1361
- else
1362
- $setting = $default;
1363
-
1364
- return apply_filters("su_get_setting-$module-$key", $setting);
1365
- }
1366
-
1367
- /**
1368
- * Sets a value in the module's settings array.
1369
- *
1370
- * @since 0.1
1371
- * @uses get_module_key()
1372
- *
1373
- * @param string $key The key of the setting to be changed.
1374
- * @param string $value The new value to assign to the setting.
1375
- * @param string|null $module The module to which the setting belongs. Defaults to the current module. Optional.
1376
- */
1377
- function update_setting($key, $value, $module=null) {
1378
- if (!$module) $module = $this->get_module_key();
1379
-
1380
- if (!apply_filters("su_custom_update_setting-$module-$key", false, $value)) {
1381
- $settings = maybe_unserialize(get_option('su_settings'));
1382
- if (!$settings) $settings = array();
1383
- $settings[$module][$key] = $value;
1384
- update_option('su_settings', serialize($settings));
1385
- }
1386
- }
1387
-
1388
- /**
1389
- * Adds 1 to the value of an integer setting in the module's settings array.
1390
- *
1391
- * @since 0.1
1392
- * @uses get_setting()
1393
- * @uses update_setting()
1394
- *
1395
- * @param string $key The key of the setting to be incremented.
1396
- */
1397
- function increment_setting($key) {
1398
- $value = $this->get_setting($key);
1399
- $this->update_setting($key, $value+1);
1400
- }
1401
-
1402
- /**
1403
- * Assigns a value of zero to a setting in the module's settings array.
1404
- *
1405
- * @since 0.1
1406
- * @uses update_setting()
1407
- *
1408
- * @param string $key The key of the setting to be reset.
1409
- */
1410
- function reset_setting($key) {
1411
- $this->update_setting($key, 0);
1412
- }
1413
-
1414
- /**
1415
- * Updates the value of more than one setting at a time.
1416
- *
1417
- * @since 0.1
1418
- * @uses update_setting()
1419
- *
1420
- * @param array $settings The names (keys) and values of settings to be updated.
1421
- */
1422
- function update_settings($settings) {
1423
- foreach ($settings as $key => $value)
1424
- update_setting($key, $value);
1425
- }
1426
-
1427
-
1428
- /********** ADMIN PAGE FUNCTIONS **********/
1429
-
1430
- /**
1431
- * Displays the beginning, contents, and end of the module's administration page.
1432
- *
1433
- * @since 0.1
1434
- * @uses admin_page_start()
1435
- * @uses admin_page_contents()
1436
- * @uses admin_page_end()
1437
- */
1438
- function admin_page() {
1439
- $this->admin_page_start();
1440
- $this->admin_page_contents();
1441
- $this->admin_page_end();
1442
- }
1443
-
1444
- /**
1445
- * Outputs the starting code for an administration page:
1446
- * wrapper, ID'd <div>, icon, and title
1447
- *
1448
- * @since 0.1
1449
- * @uses admin_footer() Hooked into WordPress's in_admin_footer action.
1450
- * @uses get_module_key()
1451
- * @uses get_page_title()
1452
- *
1453
- * @param string $icon The ID that should be applied to the icon element. The icon is loaded via CSS based on the ID. Optional.
1454
- */
1455
- function admin_page_start($icon = 'options-general') {
1456
-
1457
- add_action('in_admin_footer', array($this, 'admin_footer'));
1458
-
1459
- echo "<div class=\"wrap\">\n";
1460
- echo "<div id=\"su-".attribute_escape($this->get_module_key())."\" class=\"su-module\">\n";
1461
- screen_icon($icon);
1462
- echo "\n<h2>".$this->get_page_title()."</h2>\n";
1463
- }
1464
-
1465
- /**
1466
- * Outputs an administration page subheader (an <h3> tag).
1467
- *
1468
- * @since 0.1
1469
- *
1470
- * @param string $title The text to output.
1471
- */
1472
- function admin_subheader($title) {
1473
- echo "<h3 class='su-subheader'>$title</h3>\n";
1474
- }
1475
-
1476
- /**
1477
- * Outputs an administration form table subheader.
1478
- *
1479
- * @since 0.1
1480
- *
1481
- * @param string $title The text to output.
1482
- */
1483
- function admin_form_subheader($title) {
1484
- echo "<th><strong>$title</strong></th>\n";
1485
- }
1486
-
1487
- /**
1488
- * Outputs the ending code for an administration page.
1489
- *
1490
- * @since 0.1
1491
- */
1492
- function admin_page_end() {
1493
- echo "\n</div>\n</div>\n";
1494
- }
1495
-
1496
- /**
1497
- * Adds plugin/module information to the admin footer.
1498
- *
1499
- * @since 0.1
1500
- * @uses SU_PLUGIN_URI
1501
- * @uses SU_PLUGIN_NAME
1502
- * @uses SU_AUTHOR_URI
1503
- * @uses SU_AUTHOR
1504
- */
1505
- function admin_footer() {
1506
- printf(__('%1$s | %2$s %3$s by %4$s', 'seo-ultimate'),
1507
- $this->get_page_title(),
1508
- '<a href="'.SU_PLUGIN_URI.'" target="_blank">'.__(SU_PLUGIN_NAME, 'seo-ultimate').'</a>',
1509
- SU_VERSION,
1510
- '<a href="'.SU_AUTHOR_URI.'" target="_blank">'.__(SU_AUTHOR, 'seo-ultimate').'</a>'
1511
- );
1512
-
1513
- echo "<br />";
1514
- }
1515
-
1516
-
1517
- /********** ADMIN FORM FUNCTIONS **********/
1518
-
1519
- /**
1520
- * Begins an administration form.
1521
- * Outputs a subheader if provided, queues a success message upon settings update, outputs queued messages,
1522
- * opens a form tag, outputs a nonce field and other WordPress fields, and begins a form table.
1523
- *
1524
- * @since 0.1
1525
- * @uses SEO_Ultimate::key_to_hook()
1526
- * @uses get_module_key()
1527
- * @uses admin_subheader()
1528
- * @uses is_action()
1529
- * @uses queue_message()
1530
- * @uses print_messages()
1531
- *
1532
- * @param mixed $header The text of the subheader that should go right before the form. Optional.
1533
- * @param boolean $table Whether or not to start a form table.
1534
- */
1535
- function admin_form_start($header = false, $table = true) {
1536
- $hook = SEO_Ultimate::key_to_hook($this->get_module_key());
1537
- if ($header) $this->admin_subheader($header);
1538
- if ($this->is_action('update')) $this->queue_message('success', __('Settings updated.', 'seo-ultimate'));
1539
- $this->print_messages();
1540
- echo "<form method='post' action='?page=$hook'>\n";
1541
- settings_fields($hook);
1542
- echo "\n";
1543
- if ($table) echo "<table class='form-table'>\n";
1544
- }
1545
-
1546
- /**
1547
- * Ends an administration form.
1548
- * Closes the table tag, outputs a "Save Changes" button, and closes the form tag.
1549
- *
1550
- * @since 0.1
1551
- *
1552
- * @param boolean $table Whether or not a form table should be ended.
1553
- */
1554
- function admin_form_end($table = true) {
1555
- if ($table) echo "</table>\n";
1556
- ?>
1557
- <p class="submit">
1558
- <input type="submit" class="button-primary" value="<?php _e('Save Changes') ?>" />
1559
- </p>
1560
- </form>
1561
- <?php
1562
- }
1563
-
1564
- /**
1565
- * Applies the necessary HTML so that certain content is displayed only when the mouse hovers over the including table row.
1566
- *
1567
- * @since 0.1
1568
- *
1569
- * @param string $text The always-visible text.
1570
- * @param string $hovertext The text that only displays upon row hover.
1571
- * @return string The HTML to put in a hover-supporting table row.
1572
- */
1573
- function hover_row($text, $hovertext) {
1574
- return "<div>$text</div>\n<div class='row-actions'>$hovertext</div>";
1575
- }
1576
-
1577
- /**
1578
- * Outputs a group of checkboxes into an admin form, and saves the values into the database after form submission.
1579
- *
1580
- * @since 0.1
1581
- * @uses is_action()
1582
- * @uses update_setting()
1583
- * @uses get_module_key()
1584
- * @uses get_setting()
1585
- *
1586
- * @param array $checkboxes An array of checkboxes. (Field/setting IDs are the keys, and descriptions are the values.)
1587
- * @param mixed $grouptext The text to display in a table cell to the left of the one containing the checkboxes. Optional.
1588
- */
1589
- function checkboxes($checkboxes, $grouptext=false) {
1590
-
1591
- //Save checkbox settings after form submission
1592
- if ($this->is_action('update')) {
1593
- foreach ($checkboxes as $name => $desc) {
1594
- $this->update_setting($name, $_POST[$name] == '1');
1595
- }
1596
- }
1597
-
1598
- if ($grouptext)
1599
- echo "<tr valign='top'>\n<th scope='row'>$grouptext</th>\n<td><fieldset><legend class='hidden'>$grouptext</legend>\n";
1600
- else
1601
- echo "<tr valign='top'>\n<td>\n";
1602
-
1603
- if (is_array($checkboxes)) {
1604
- foreach ($checkboxes as $name => $desc) {
1605
- register_setting($this->get_module_key(), $name, 'intval');
1606
- $name = attribute_escape($name);
1607
- echo "<label for='$name'><input name='$name' id='$name' type='checkbox' value='1'";
1608
- if ($this->get_setting($name) === true) echo " checked='checked'";
1609
- echo " /> $desc</label><br />\n";
1610
- }
1611
- }
1612
-
1613
- if ($grouptext) echo "</fieldset>";
1614
- echo "</td>\n</tr>\n";
1615
- }
1616
-
1617
- /**
1618
- * Outputs a group of textboxes into an admin form, and saves the values into the database after form submission.
1619
- * Can also display a "Reset" link next to each textbox that reverts its value to a specified default.
1620
- *
1621
- * @since 0.1
1622
- * @uses is_action()
1623
- * @uses update_setting()
1624
- * @uses get_module_key()
1625
- * @uses get_setting()
1626
- *
1627
- * @param array $textboxes An array of textboxes. (Field/setting IDs are the keys, and descriptions are the values.)
1628
- * @param array $defaults An array of default textbox values that trigger "Reset" links. (The field/setting ID is the key, and the default value is the value.) Optional.
1629
- * @param mixed $grouptext The text to display in a table cell to the left of the one containing the textboxes. Optional.
1630
- */
1631
- function textboxes($textboxes, $defaults=array(), $grouptext=false) {
1632
-
1633
- if ($this->is_action('update')) {
1634
- foreach ($textboxes as $id => $title) {
1635
- $this->update_setting($id, stripslashes($_POST[$id]));
1636
- }
1637
- }
1638
-
1639
- if ($grouptext)
1640
- echo "<tr valign='top'>\n<th scope='row'>$grouptext</th>\n<td><fieldset><legend class='hidden'>$grouptext</legend>\n";
1641
-
1642
- foreach ($textboxes as $id => $title) {
1643
- register_setting($this->get_module_key(), $id);
1644
- $value = attribute_escape($this->get_setting($id));
1645
- $default = attribute_escape($defaults[$id]);
1646
- $id = attribute_escape($id);
1647
- $resetmessage = attribute_escape(__("Are you sure you want to replace the textbox contents with this default value?", 'seo-ultimate'));
1648
-
1649
- if ($grouptext)
1650
- echo "<div class='field'><label for='$id'>$title</label><br />\n";
1651
- else
1652
- echo "<tr valign='top'>\n<th scope='row'><label for='$id'>$title</label></th>\n<td>";
1653
-
1654
- echo "<input name='$id' id='$id' type='text' value='$value' class='regular-text' ";
1655
- if (isset($defaults[$id])) {
1656
- echo "onkeyup=\"javascript:textbox_value_changed(this, '$default', '{$id}_reset')\" />";
1657
- echo "&nbsp;<a href=\"javascript:void(0)\" id=\"{$id}_reset\" onclick=\"javascript:reset_textbox('$id', '$default', '$resetmessage', this)\"";
1658
- if ($default == $value) echo ' class="hidden"';
1659
- echo ">";
1660
- _e('Reset', 'seo-ultimate');
1661
- echo "</a>";
1662
- } else {
1663
- echo "/>";
1664
- }
1665
-
1666
- if ($grouptext)
1667
- echo "</div>\n";
1668
- else
1669
- echo "</td>\n</tr>\n";
1670
- }
1671
-
1672
- if ($grouptext)
1673
- echo "</td>\n</tr>\n";
1674
- }
1675
-
1676
- /**
1677
- * Outputs a single textbox into an admin form and saves its value into the database after form submission.
1678
- *
1679
- * @since 0.1
1680
- * @uses textboxes()
1681
- *
1682
- * @param string $id The field/setting ID.
1683
- * @param string $title The label of the HTML element.
1684
- * @return string The HTML that would render the textbox.
1685
- */
1686
- function textbox($id, $title) {
1687
- $this->textboxes(array($id => $title));
1688
- }
1689
-
1690
-
1691
- /**
1692
- * Outputs a group of textareas into an admin form, and saves the values into the database after form submission.
1693
- *
1694
- * @since 0.1
1695
- * @uses is_action()
1696
- * @uses update_setting()
1697
- * @uses get_module_key()
1698
- * @uses get_setting()
1699
- *
1700
- * @param array $textareas An array of textareas. (Field/setting IDs are the keys, and descriptions are the values.)
1701
- * @param int $rows The value of the textareas' rows attribute.
1702
- * @param int $cols The value of the textareas' cols attribute.
1703
- */
1704
- function textareas($textareas, $rows = 5, $cols = 30) {
1705
-
1706
- if ($this->is_action('update')) {
1707
- foreach ($textareas as $id => $title) {
1708
- $this->update_setting($id, stripslashes($_POST[$id]));
1709
- }
1710
- }
1711
-
1712
- foreach ($textareas as $id => $title) {
1713
- register_setting($this->get_module_key(), $id);
1714
- $value = wp_specialchars($this->get_setting($id), ENT_QUOTES, false, true);
1715
- $id = attribute_escape($id);
1716
-
1717
- echo "<tr valign='top'>\n<th scope='row'><label for='$id'>$title</label></th>\n";
1718
- echo "<td><textarea name='$id' id='$id' type='text' class='regular-text' cols='$cols' rows='$rows'>$value</textarea>";
1719
- echo "</td>\n</tr>\n";
1720
- }
1721
- }
1722
-
1723
- /**
1724
- * Outputs a single textarea into an admin form and saves its value into the database after form submission.
1725
- *
1726
- * @since 0.1
1727
- * @uses textareas()
1728
- *
1729
- * @param string $id The field/setting ID.
1730
- * @param string $title The label of the HTML element.
1731
- * @param int $rows The value of the textarea's rows attribute.
1732
- * @param int $cols The value of the textarea's cols attribute.
1733
- * @return string The HTML that would render the textarea.
1734
- */
1735
- function textarea($id, $title, $rows = 5, $cols = 30) {
1736
- $this->textareas(array($id => $title), $rows, $cols);
1737
- }
1738
-
1739
- /********** ADMIN SECURITY FUNCTIONS **********/
1740
-
1741
- /**
1742
- * Determines if a particular nonce-secured admin action is being executed.
1743
- *
1744
- * @since 0.1
1745
- * @uses nonce_validates()
1746
- *
1747
- * @param string $action The name of the action to check.
1748
- * @return bool Whether or not the action is being executed.
1749
- */
1750
- function is_action($action) {
1751
- if (!($object = $_GET['object'])) $object = false;
1752
- return (($_GET['action'] == $action || $_POST['action'] == $action) && $this->nonce_validates($action, $object));
1753
- }
1754
-
1755
- /**
1756
- * Determines whether a nonce is valid.
1757
- *
1758
- * @since 0.1
1759
- * @uses get_nonce_handle()
1760
- *
1761
- * @param string $action The name of the action.
1762
- * @param mixed $id The ID of the object being acted upon. Optional.
1763
- * @return bool Whether or not the nonce is valid.
1764
- */
1765
- function nonce_validates($action, $id = false) {
1766
- return check_admin_referer($this->get_nonce_handle($action, $id));
1767
- }
1768
-
1769
- /**
1770
- * Generates a unique name for a nonce.
1771
- *
1772
- * @since 0.1
1773
- * @uses get_module_key()
1774
- * @uses SU_PLUGIN_NAME
1775
- *
1776
- * @param string $action The name of the action.
1777
- * @param mixed $id The ID of the object being acted upon. Optional.
1778
- * @return The handle to use for the nonce.
1779
- */
1780
- function get_nonce_handle($action, $id = false) {
1781
- $hook = SEO_Ultimate::key_to_hook($this->get_module_key());
1782
-
1783
- if (strcmp($action, 'update') == 0)
1784
- //We use the settings_fields() function, which outputs a nonce in this particular format.
1785
- return "$hook-options";
1786
- else {
1787
- if ($id) $id = '-'.md5($id); else $id = '';
1788
- $handle = SU_PLUGIN_NAME."-$hook-$action$id";
1789
- return strtolower(str_replace(' ', '-', $handle));
1790
- }
1791
- }
1792
-
1793
- /**
1794
- * Returns a GET-action URL with an appended nonce.
1795
- *
1796
- * @since 0.1
1797
- * @uses get_module_key()
1798
- * @uses get_nonce_handle()
1799
- *
1800
- * @param string $action The name of the action.
1801
- * @param mixed $id The ID of the object being acted upon. Optional.
1802
- * @return The URL to use in an <a> tag.
1803
- */
1804
- function get_nonce_url($action, $object=false) {
1805
- $action = urlencode($action);
1806
- if ($object) $objectqs = '&object='.urlencode($object); else $objectqs = '';
1807
-
1808
- $hook = SEO_Ultimate::key_to_hook($this->get_module_key());
1809
-
1810
- //We don't need to escape ampersands since wp_nonce_url will do that for us
1811
- return wp_nonce_url("?page=$hook&action=$action$objectqs",
1812
- $this->get_nonce_handle($action, $object));
1813
- }
1814
-
1815
-
1816
- /********** ADMIN MESSAGE FUNCTIONS **********/
1817
-
1818
- /**
1819
- * Print a message (and any previously-queued messages) right away.
1820
- *
1821
- * @since 0.1
1822
- * @uses queue_message()
1823
- * @uses print_messages()
1824
- *
1825
- * @param string $type The message's type. Valid values are success, error, warning, and info.
1826
- * @param string $message The message text.
1827
- */
1828
- function print_message($type, $message) {
1829
- $this->queue_message($type, $message);
1830
- $this->print_messages();
1831
- }
1832
-
1833
- /**
1834
- * Adds a message to the queue.
1835
- *
1836
- * @since 0.1
1837
- * @uses $messages
1838
- *
1839
- * @param string $type The message's type. Valid values are success, error, warning, and info.
1840
- * @param string $message The message text.
1841
- */
1842
- function queue_message($type, $message) {
1843
- $this->messages[$type][] = $message;
1844
- }
1845
-
1846
- /**
1847
- * Prints all queued messages and flushes the queue.
1848
- *
1849
- * @since 0.1
1850
- * @uses $messages
1851
- */
1852
- function print_messages() {
1853
- foreach ($this->messages as $type => $messages) {
1854
- $messages = implode('<br />', $messages);
1855
- if ($messages) {
1856
- $type = attribute_escape($type);
1857
- echo "<div class='su-message'><p class='su-$type'>$messages</p></div>\n";
1858
- }
1859
- }
1860
-
1861
- $this->messages = array();
1862
- }
1863
-
1864
- /********** ADMIN POST META BOX FUNCTIONS **********/
1865
-
1866
- /**
1867
- * Gets a specified meta value of the current post (i.e. the post currently being edited in the admin,
1868
- * the post being shown, the post now in the loop, or the post with specified ID).
1869
- *
1870
- * @since 0.1
1871
- *
1872
- * @param string $key The meta key to fetch.
1873
- * @param mixed $id The ID number of the post/page.
1874
- * @return string The meta value requested.
1875
- */
1876
- function get_postmeta($key, $id=false) {
1877
-
1878
- if (!$id) {
1879
- if (is_admin())
1880
- $id = intval($_REQUEST['post']);
1881
- elseif (in_the_loop())
1882
- $id = intval(get_the_ID());
1883
- elseif (is_singular()) {
1884
- global $wp_query;
1885
- $id = $wp_query->get_queried_object_id();
1886
- }
1887
- }
1888
-
1889
- if ($id) return get_post_meta($id, "_su_$key", true);
1890
-
1891
- return '';
1892
- }
1893
-
1894
- /**
1895
- * Generates the HTML for multiple post meta textboxes.
1896
- *
1897
- * @since 0.1
1898
- * @uses get_postmeta()
1899
- *
1900
- * @param array $textboxes An array of textboxes. (Field/setting IDs are the keys, and descriptions are the values.)
1901
- * @return string The HTML that would render the textboxes.
1902
- */
1903
- function get_postmeta_textboxes($textboxes) {
1904
-
1905
- $html = '';
1906
-
1907
- foreach ($textboxes as $id => $title) {
1908
-
1909
- register_setting('seo-ultimate', $id);
1910
- $value = attribute_escape($this->get_postmeta($id));
1911
- $id = "_su_".attribute_escape($id);
1912
- $title = str_replace(' ', '&nbsp;', $title);
1913
-
1914
- $html .= "<tr class='textbox'>\n<th scope='row'><label for='$id'>$title</label></th>\n"
1915
- ."<td><input name='$id' id='$id' type='text' value='$value' class='regular-text' /></td>\n</tr>\n";
1916
- }
1917
-
1918
- return $html;
1919
- }
1920
-
1921
- /**
1922
- * Generates the HTML for a single post meta textbox.
1923
- *
1924
- * @since 0.1
1925
- * @uses get_postmeta_textboxes()
1926
- *
1927
- * @param string $id The ID of the HTML element.
1928
- * @param string $title The label of the HTML element.
1929
- * @return string The HTML that would render the textbox.
1930
- */
1931
- function get_postmeta_textbox($id, $title) {
1932
- return $this->get_postmeta_textboxes(array($id => $title));
1933
- }
1934
-
1935
- /**
1936
- * Generates the HTML for a group of post meta checkboxes.
1937
- *
1938
- * @since 0.1
1939
- * @uses get_module_key()
1940
- * @uses get_postmeta()
1941
- *
1942
- * @param array $checkboxes An array of checkboxes. (Field/setting IDs are the keys, and descriptions are the values.)
1943
- * @param string $grouptext The text to display in a table cell to the left of the one containing the checkboxes.
1944
- */
1945
- function get_postmeta_checkboxes($checkboxes, $grouptext) {
1946
-
1947
- $html = "<tr>\n<th scope='row'>$grouptext</th>\n<td><fieldset><legend class='hidden'>$grouptext</legend>\n";
1948
-
1949
- if (is_array($checkboxes)) {
1950
- foreach ($checkboxes as $name => $desc) {
1951
-
1952
- register_setting('seo-ultimate', $name);
1953
- $checked = ($this->get_postmeta($name) == 1);
1954
- $name = "_su_".attribute_escape($name);
1955
-
1956
- $html .= "<label for='$name'><input name='$name' id='$name' type='checkbox' value='1'";
1957
- if ($checked) $html .= " checked='checked'";
1958
- $html .= " /> $desc</label><br />\n";
1959
- }
1960
- }
1961
-
1962
- $html .= "</fieldset></td>\n</tr>\n";
1963
-
1964
- return $html;
1965
- }
1966
-
1967
- /**
1968
- * Generates the HTML for a single post meta checkbox.
1969
- *
1970
- * @since 0.1
1971
- * @uses get_postmeta_checkboxes()
1972
- *
1973
- * @param string $id The ID of the HTML element.
1974
- * @param string $title The label of the HTML element.
1975
- * @param string $grouptext The text to display in a table cell to the left of the one containing the checkboxes.
1976
- * @return string The HTML that would render the textbox.
1977
- */
1978
- function get_postmeta_checkbox($id, $title, $grouptext) {
1979
- return $this->get_postmeta_checkboxes(array($id => $title), $grouptext);
1980
- }
1981
-
1982
-
1983
- /********** HITS LOG FUNCTIONS **********/
1984
-
1985
- /**
1986
- * Lists logged hits in an administration table.
1987
- *
1988
- * @since 0.1
1989
- *
1990
- * @param string|false $where The WHERE portion of the SQL query to execute.
1991
- * @param string|false $actions_callback The name of the module-child function from which to obtain a return value of URL-cell action link HTML.
1992
- * @param bool $highlight_new Whether or not to highlight new rows. Optional.
1993
- */
1994
- function hits_table($where = false, $actions_callback = false, $highlight_new = true) {
1995
- global $wpdb;
1996
- $mk = $this->get_module_key();
1997
-
1998
- $table = SEO_Ultimate::get_table_name('hits');
1999
- if ($where) $where = " WHERE $where";
2000
- $result = $wpdb->get_results("SELECT * FROM $table$where ORDER BY id DESC", ARRAY_A);
2001
-
2002
- if (!$result) return false;
2003
-
2004
- $allfields = array(
2005
- 'time' => __("Date", 'seo-ultimate')
2006
- , 'ip_address' => __("IP Address", 'seo-ultimate')
2007
- , 'user_agent' => __("Browser", 'seo-ultimate')
2008
- , 'url' => __("URL Requested", 'seo-ultimate')
2009
- , 'redirect_url' => __("Redirected To", 'seo-ultimate')
2010
- , 'status_code' => __("Status Code", 'seo-ultimate')
2011
- );
2012
-
2013
- $fields = array();
2014
-
2015
- foreach ($allfields as $col => $title) {
2016
- if (strpos($where, $col.'=') === false) $fields[$col] = $title;
2017
- }
2018
-
2019
- $fields = apply_filters("su_{$mk}_hits_table_columns", $fields);
2020
-
2021
- echo "<table class='widefat' cellspacing='0'>\n\t<thead><tr>\n";
2022
-
2023
- foreach ($fields as $title) {
2024
- $class = str_replace(' ', '-', strtolower($title));
2025
- echo "\t\t<th scope='col' class='hit-$class'>$title</th>\n";
2026
- }
2027
-
2028
- echo "\t</tr></thead>\n\t<tbody>\n";
2029
-
2030
- foreach ($result as $row) {
2031
-
2032
- if ($highlight_new && $row['is_new']) $class = ' class="new-hit"'; else $class='';
2033
- echo "\t\t<tr$class>\n";
2034
-
2035
- foreach ($fields as $col => $title) {
2036
- $cell = htmlspecialchars($row[$col]);
2037
-
2038
- switch ($col) {
2039
- case 'time':
2040
- $date = date_i18n(get_option('date_format'), $cell);
2041
- $time = date_i18n(get_option('time_format'), $cell);
2042
- $cell = sprintf(__('%1$s<br />%2$s', 'seo-ultimate'), $date, $time);
2043
- break;
2044
- case 'user_agent':
2045
- $binfo = get_browser($cell, true);
2046
- $cell = $binfo['parent'];
2047
- break;
2048
- case 'url':
2049
- if ($actions_callback) {
2050
- $actions = call_user_func(array($this, $actions_callback), $row);
2051
- $actions = apply_filters("su_{$mk}_hits_table_actions", $actions, $row);
2052
- $cell = $this->hover_row($cell, $actions);
2053
- }
2054
- break;
2055
- }
2056
-
2057
- $cell = apply_filters("su_{$mk}_hits_table_{$col}_cell", $cell, $row);
2058
-
2059
- $class = str_replace(' ', '-', strtolower($title));
2060
- echo "\t\t\t<td class='hit-$class'>$cell</td>\n";
2061
- }
2062
- echo "\t\t</tr>\n";
2063
-
2064
- $wpdb->update($table, array('is_new' => 0), array('id' => $row['id']));
2065
- }
2066
-
2067
- echo "\t</tbody>\n</table>\n";
2068
-
2069
- return true;
2070
- }
2071
-
2072
-
2073
- /********** CRON FUNCTION **********/
2074
-
2075
- /**
2076
- * Creates a cron job if it doesn't already exists, and ensures it runs at the scheduled time.
2077
- * Should be called in a module's init() function.
2078
- *
2079
- * @since 0.1
2080
- * @uses get_module_key()
2081
- *
2082
- * @param string $function The name of the module function that should be run.
2083
- * @param string $recurrance How often the job should be run. Valid values are hourly, twicedaily, and daily.
2084
- */
2085
- function cron($function, $recurrance) {
2086
-
2087
- $mk = $this->get_module_key();
2088
-
2089
- $hook = "su-$mk-".str_replace('_', '-', $function);
2090
- $start = time();
2091
-
2092
- if (wp_next_scheduled($hook) === false) {
2093
- //This is a new cron job
2094
-
2095
- //Schedule the event
2096
- wp_schedule_event($start, $recurrance, $hook);
2097
-
2098
- //Make a record of it
2099
- $data = maybe_unserialize(get_option('su_cron'));
2100
- if (!is_array($data)) $data = array();
2101
- $data[$mk][$function] = array($hook, $start, $recurrance);
2102
- update_option('su_cron', serialize($data));
2103
-
2104
- //Run the event now
2105
- call_user_func(array($this, $function));
2106
- }
2107
-
2108
- add_action($hook, array($this, $function));
2109
- }
2110
-
2111
- /********** RSS FUNCTION **********/
2112
-
2113
- /**
2114
- * Loads an RSS feed and returns it as an object.
2115
- *
2116
- * @since 0.1
2117
- * @uses get_user_agent() Hooks into WordPress's http_header_useragent filter.
2118
- *
2119
- * @param string $url The URL of the RSS feed to load.
2120
- * @return object $rss The RSS object.
2121
- */
2122
- function load_rss($url) {
2123
- add_filter('http_headers_useragent', 'su_get_user_agent');
2124
- require_once (ABSPATH . WPINC . '/rss.php');
2125
- $rss = fetch_rss($url);
2126
- remove_filter('http_headers_useragent', 'su_get_user_agent');
2127
- return $rss;
2128
- }
2129
-
2130
- /**
2131
- * Loads a webpage and returns its HTML as a string.
2132
- *
2133
- * @since 0.1
2134
- *
2135
- * @param string $url The URL of the webpage to load.
2136
- * @return string The HTML of the URL.
2137
- */
2138
- function load_webpage($url) {
2139
-
2140
- $options = array();
2141
- $options['headers'] = array(
2142
- 'User-Agent' => su_get_user_agent()
2143
- );
2144
-
2145
- $response = wp_remote_request($url, $options);
2146
-
2147
- if ( is_wp_error( $response ) ) return false;
2148
- if ( 200 != $response['response']['code'] ) return false;
2149
-
2150
- return trim( $response['body'] );
2151
- }
2152
- }
2153
-
2154
- class SU_Widget {
2155
-
2156
- function get_title() { return ''; }
2157
- function get_section() { return 'normal'; }
2158
- function get_priority(){ return 'core'; }
2159
-
2160
- function content() { }
2161
- }
2162
-
2163
-
2164
- /********** CONSTANTS **********/
2165
 
2166
  define('SU_MODULE_ENABLED', 10);
2167
  define('SU_MODULE_SILENCED', 5);
@@ -2173,71 +53,17 @@ define('SU_RESULT_WARNING', 0);
2173
  define('SU_RESULT_ERROR', -1);
2174
 
2175
 
2176
- /********** INDEPENDENTLY-OPERABLE FUNCTIONS **********/
2177
-
2178
- /**
2179
- * Returns the plugin's User-Agent value.
2180
- * Can be used as a WordPress filter.
2181
- *
2182
- * @since 0.1
2183
- * @uses SU_USER_AGENT
2184
- *
2185
- * @return string The user agent.
2186
- */
2187
- function su_get_user_agent() {
2188
- return SU_USER_AGENT;
2189
- }
2190
 
2191
- /**
2192
- * Records an event in the debug log file.
2193
- * Usage: su_debug_log(__FILE__, __CLASS__, __FUNCTION__, __LINE__, "Message");
2194
- *
2195
- * @since 0.1
2196
- * @uses SU_VERSION
2197
- *
2198
- * @param string $file The value of __FILE__
2199
- * @param string $class The value of __CLASS__
2200
- * @param string $function The value of __FUNCTION__
2201
- * @param string $line The value of __LINE__
2202
- * @param string $message The message to log.
2203
- */
2204
- function su_debug_log($file, $class, $function, $line, $message) {
2205
- global $seo_ultimate;
2206
- if (isset($seo_ultimate->modules['settings']) && $seo_ultimate->modules['settings']->get_setting('debug_mode') === true) {
2207
-
2208
- $date = date("Y-m-d H:i:s");
2209
- $version = SU_VERSION;
2210
- $message = str_replace("\r\n", "\n", $message);
2211
- $message = str_replace("\n", "\r\n", $message);
2212
-
2213
- $log = "Date: $date\r\nVersion: $version\r\nFile: $file\r\nClass: $class\r\nFunction: $function\r\nLine: $line\r\nMessage: $message\r\n\r\n";
2214
- $logfile = trailingslashit(dirname(__FILE__))."seo-ultimate.log";
2215
-
2216
- @error_log($log, 3, $logfile);
2217
- }
2218
- }
2219
-
2220
-
2221
- /********** CLASS FUNCTION ALIASES **********/
2222
-
2223
- /**
2224
- * Launches the uninstallation process.
2225
- * WordPress will call this when the plugin is uninstalled, as instructed by the register_uninstall_hook() call in {@link SEO_Ultimate::__construct()}.
2226
- *
2227
- * @since 0.1
2228
- * @uses $seo_ultimate
2229
- * @uses SEO_Ultimate::uninstall()
2230
- */
2231
- function su_uninstall() {
2232
- global $seo_ultimate;
2233
- $seo_ultimate->uninstall();
2234
- }
2235
 
2236
 
2237
  /********** PLUGIN FILE LOAD HANDLER **********/
2238
 
2239
  //If we're running WordPress, then initialize the main class defined above.
2240
- //Show CSS or JavaScript if requested.
2241
  //Or, show a blank page on direct load.
2242
 
2243
  global $seo_ultimate;
2
  /*
3
  Plugin Name: SEO Ultimate
4
  Plugin URI: http://www.seodesignsolutions.com/wordpress-seo/
5
+ Description: This all-in-one SEO plugin can rewrite title tags, edit meta data, add noindex to pages, and insert canonical tags (with many more features coming soon).
6
+ Version: 0.3
7
  Author: SEO Design Solutions
8
  Author URI: http://www.seodesignsolutions.com/
9
  Text Domain: seo-ultimate
12
  /**
13
  * The main SEO Ultimate plugin file.
14
  * @package SeoUltimate
15
+ * @version 0.3
16
  * @link http://www.seodesignsolutions.com/wordpress-seo/ SEO Ultimate Homepage
17
  */
18
 
38
 
39
  define("SU_PLUGIN_NAME", "SEO Ultimate");
40
  define("SU_PLUGIN_URI", "http://www.seodesignsolutions.com/wordpress-seo/");
41
+ define("SU_VERSION", "0.3");
42
  define("SU_AUTHOR", "SEO Design Solutions");
43
  define("SU_AUTHOR_URI", "http://www.seodesignsolutions.com/");
44
+ define("SU_USER_AGENT", "SeoUltimate/0.3");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  define('SU_MODULE_ENABLED', 10);
47
  define('SU_MODULE_SILENCED', 5);
53
  define('SU_RESULT_ERROR', -1);
54
 
55
 
56
+ /********** INCLUDES **********/
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ require('class.seo-ultimate.php');
59
+ require('class.su-module.php');
60
+ require('class.su-widget.php');
61
+ require('functions.php');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
 
64
  /********** PLUGIN FILE LOAD HANDLER **********/
65
 
66
  //If we're running WordPress, then initialize the main class defined above.
 
67
  //Or, show a blank page on direct load.
68
 
69
  global $seo_ultimate;
seo-ultimate.pot ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR SEO Design Solutions
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/seo-ultimate\n"
11
+ "POT-Creation-Date: 2009-06-11 21:39+0000\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "MIME-Version: 1.0\n"
16
+ "Content-Type: text/plain; charset=CHARSET\n"
17
+ "Content-Transfer-Encoding: 8bit\n"
18
+
19
+ #. #-#-#-#-# plugin.pot (PACKAGE VERSION) #-#-#-#-#
20
+ #. Plugin Name of an extension
21
+ #: class.seo-ultimate.php:581 modules/settings.php:16
22
+ msgid "SEO Ultimate"
23
+ msgstr ""
24
+
25
+ #: class.seo-ultimate.php:581
26
+ msgid "SEO"
27
+ msgstr ""
28
+
29
+ #: class.seo-ultimate.php:767
30
+ #, php-format
31
+ msgid "%s Help"
32
+ msgstr ""
33
+
34
+ #: class.seo-ultimate.php:787
35
+ msgid "SEO Settings Help"
36
+ msgstr ""
37
+
38
+ #: class.seo-ultimate.php:789
39
+ msgid "The SEO Settings box lets you customize these settings:"
40
+ msgstr ""
41
+
42
+ #: class.seo-ultimate.php:791
43
+ msgid "(The SEO Settings box is part of the SEO Ultimate plugin.)"
44
+ msgstr ""
45
+
46
+ #: class.seo-ultimate.php:838
47
+ #, php-format
48
+ msgid ""
49
+ "SEO Ultimate includes the functionality of %1$s. You may want to deactivate %"
50
+ "1$s to avoid plugin conflicts."
51
+ msgstr ""
52
+
53
+ #: class.seo-ultimate.php:906
54
+ msgid "SEO Settings"
55
+ msgstr ""
56
+
57
+ #: class.su-module.php:408
58
+ #, php-format
59
+ msgid "%1$s | %2$s %3$s by %4$s"
60
+ msgstr ""
61
+
62
+ #: class.su-module.php:443
63
+ msgid "Settings updated."
64
+ msgstr ""
65
+
66
+ #: class.su-module.php:468
67
+ msgid "Save Changes"
68
+ msgstr ""
69
+
70
+ #: class.su-module.php:558
71
+ msgid ""
72
+ "Are you sure you want to replace the textbox contents with this default "
73
+ "value?"
74
+ msgstr ""
75
+
76
+ #: class.su-module.php:571
77
+ msgid "Reset"
78
+ msgstr ""
79
+
80
+ #: class.su-module.php:921
81
+ msgid "Date"
82
+ msgstr ""
83
+
84
+ #: class.su-module.php:922
85
+ msgid "IP Address"
86
+ msgstr ""
87
+
88
+ #: class.su-module.php:923
89
+ msgid "Browser"
90
+ msgstr ""
91
+
92
+ #: class.su-module.php:924
93
+ msgid "URL Requested"
94
+ msgstr ""
95
+
96
+ #: class.su-module.php:925
97
+ msgid "Redirected To"
98
+ msgstr ""
99
+
100
+ #: class.su-module.php:926
101
+ msgid "Status Code"
102
+ msgstr ""
103
+
104
+ #: class.su-module.php:958
105
+ #, php-format
106
+ msgid "%1$s<br />%2$s"
107
+ msgstr ""
108
+
109
+ #: modules/canonical.php:13
110
+ msgid "Canonicalizer"
111
+ msgstr ""
112
+
113
+ #: modules/canonical.php:23
114
+ msgid "Generate <code>&lt;link rel=&quot;canonical&quot; /&gt;</code> tags."
115
+ msgstr ""
116
+
117
+ #: modules/meta.php:13
118
+ msgid "Meta Editor"
119
+ msgstr ""
120
+
121
+ #: modules/meta.php:30
122
+ msgid "Blog Homepage Meta Description"
123
+ msgstr ""
124
+
125
+ #: modules/meta.php:31
126
+ msgid "Blog Homepage Meta Keywords"
127
+ msgstr ""
128
+
129
+ #: modules/meta.php:34
130
+ msgid "Use this blog&#8217s tagline as the default homepage description."
131
+ msgstr ""
132
+
133
+ #: modules/meta.php:35
134
+ msgid "Default Values"
135
+ msgstr ""
136
+
137
+ #: modules/meta.php:37
138
+ msgid ""
139
+ "Don&#8217t use this site&#8217s Open Directory description in search results."
140
+ msgstr ""
141
+
142
+ #: modules/meta.php:38
143
+ msgid ""
144
+ "Don&#8217t use this site&#8217s Yahoo! Directory description in search "
145
+ "results."
146
+ msgstr ""
147
+
148
+ #: modules/meta.php:39
149
+ msgid "Don&#8217t cache or archive this site."
150
+ msgstr ""
151
+
152
+ #: modules/meta.php:40
153
+ msgid "Spider Instructions"
154
+ msgstr ""
155
+
156
+ #: modules/meta.php:42
157
+ msgid "Google Webmaster Tools:"
158
+ msgstr ""
159
+
160
+ #: modules/meta.php:43
161
+ msgid "Yahoo! Site Explorer:"
162
+ msgstr ""
163
+
164
+ #: modules/meta.php:44
165
+ msgid "Bing Webmaster Center:"
166
+ msgstr ""
167
+
168
+ #: modules/meta.php:45
169
+ msgid "Verification Codes"
170
+ msgstr ""
171
+
172
+ #: modules/meta.php:46
173
+ msgid "Custom &lt;head&gt; HTML"
174
+ msgstr ""
175
+
176
+ #: modules/meta.php:55
177
+ msgid "Description:"
178
+ msgstr ""
179
+
180
+ #: modules/meta.php:58
181
+ #, php-format
182
+ msgid "You&#8217;ve entered %s characters. Most search engines use up to 160."
183
+ msgstr ""
184
+
185
+ #: modules/meta.php:60
186
+ msgid "Keywords:<br /><em>(separate with commas)"
187
+ msgstr ""
188
+
189
+ #: modules/meta.php:117
190
+ msgid "Custom Header Code"
191
+ msgstr ""
192
+
193
+ #: modules/meta.php:166
194
+ msgid ""
195
+ "<strong>Description:</strong> &mdash; The value of the meta description tag. "
196
+ "The description will often appear underneath the title in search engine "
197
+ "results. "
198
+ msgstr ""
199
+
200
+ #: modules/meta.php:168
201
+ msgid ""
202
+ "<strong>Keywords:</strong> &mdash; The value of the meta keywords tag. The "
203
+ "keywords list gives search engines a hint as to what this post/page is "
204
+ "about. "
205
+ msgstr ""
206
+
207
+ #: modules/noindex.php:13
208
+ msgid "Noindex Manager"
209
+ msgstr ""
210
+
211
+ #: modules/noindex.php:33
212
+ msgid ""
213
+ "Note: The current <a href='options-privacy.php'>privacy settings</a> will "
214
+ "block indexing of the entire site, regardless of which options are set below."
215
+ msgstr ""
216
+
217
+ #: modules/noindex.php:36
218
+ msgid "Prevent indexing of..."
219
+ msgstr ""
220
+
221
+ #: modules/noindex.php:37
222
+ msgid "Administration back-end pages"
223
+ msgstr ""
224
+
225
+ #: modules/noindex.php:38
226
+ msgid "Author archives"
227
+ msgstr ""
228
+
229
+ #: modules/noindex.php:39
230
+ msgid "Blog search pages"
231
+ msgstr ""
232
+
233
+ #: modules/noindex.php:40
234
+ msgid "Category archives"
235
+ msgstr ""
236
+
237
+ #: modules/noindex.php:41
238
+ msgid "Comment feeds"
239
+ msgstr ""
240
+
241
+ #: modules/noindex.php:43
242
+ msgid "Date-based archives"
243
+ msgstr ""
244
+
245
+ #: modules/noindex.php:44
246
+ msgid "Subpages of the homepage"
247
+ msgstr ""
248
+
249
+ #: modules/noindex.php:45
250
+ msgid "Tag archives"
251
+ msgstr ""
252
+
253
+ #: modules/noindex.php:46
254
+ msgid "User login/registration pages"
255
+ msgstr ""
256
+
257
+ #: modules/sds-blog.php:13
258
+ msgid "SEO Design Solutions Whitepapers"
259
+ msgstr ""
260
+
261
+ #: modules/sds-blog.php:14
262
+ msgid "Whitepapers"
263
+ msgstr ""
264
+
265
+ #: modules/sds-blog.php:38
266
+ msgid ""
267
+ "Search engine optimization articles from the company behind the SEO Ultimate "
268
+ "plugin."
269
+ msgstr ""
270
+
271
+ #: modules/sds-blog.php:89
272
+ msgid ""
273
+ "The articles below are loaded from the SEO Design Solutions website. Click "
274
+ "on an article&#8217s title to read it."
275
+ msgstr ""
276
+
277
+ #: modules/settings.php:15
278
+ msgid "SEO Ultimate Plugin Settings"
279
+ msgstr ""
280
+
281
+ #: modules/settings.php:50
282
+ msgid "Plugin Settings"
283
+ msgstr ""
284
+
285
+ #: modules/settings.php:52
286
+ msgid "Enable attribution link"
287
+ msgstr ""
288
+
289
+ #: modules/settings.php:53
290
+ msgid "Notify me about unnecessary active plugins"
291
+ msgstr ""
292
+
293
+ #: modules/settings.php:55
294
+ msgid "Insert comments around HTML code insertions"
295
+ msgstr ""
296
+
297
+ #: modules/stats.php:13 modules/stats.php:14
298
+ msgid "Modules"
299
+ msgstr ""
300
+
301
+ #: modules/titles.php:13
302
+ msgid "Title Rewriter"
303
+ msgstr ""
304
+
305
+ #: modules/titles.php:25
306
+ msgid "{blog}"
307
+ msgstr ""
308
+
309
+ #: modules/titles.php:26
310
+ msgid "{post} | {blog}"
311
+ msgstr ""
312
+
313
+ #: modules/titles.php:27
314
+ msgid "{page} | {blog}"
315
+ msgstr ""
316
+
317
+ #: modules/titles.php:28
318
+ msgid "{category} | {blog}"
319
+ msgstr ""
320
+
321
+ #: modules/titles.php:29
322
+ msgid "{tag} | {blog}"
323
+ msgstr ""
324
+
325
+ #: modules/titles.php:30
326
+ msgid "Archives for {month} {day}, {year} | {blog}"
327
+ msgstr ""
328
+
329
+ #: modules/titles.php:31
330
+ msgid "Archives for {month} {year} | {blog}"
331
+ msgstr ""
332
+
333
+ #: modules/titles.php:32
334
+ msgid "Archives for {year} | {blog}"
335
+ msgstr ""
336
+
337
+ #: modules/titles.php:33
338
+ msgid "Posts by {author} | {blog}"
339
+ msgstr ""
340
+
341
+ #: modules/titles.php:34
342
+ msgid "Search Results for {query} | {blog}"
343
+ msgstr ""
344
+
345
+ #: modules/titles.php:35
346
+ msgid "404 Not Found | {blog}"
347
+ msgstr ""
348
+
349
+ #: modules/titles.php:36
350
+ msgid "{title} - Page {num}"
351
+ msgstr ""
352
+
353
+ #: modules/titles.php:42
354
+ msgid "Blog Homepage Title"
355
+ msgstr ""
356
+
357
+ #: modules/titles.php:43
358
+ msgid "Post Title Format"
359
+ msgstr ""
360
+
361
+ #: modules/titles.php:44
362
+ msgid "Page Title Format"
363
+ msgstr ""
364
+
365
+ #: modules/titles.php:45
366
+ msgid "Category Title Format"
367
+ msgstr ""
368
+
369
+ #: modules/titles.php:46
370
+ msgid "Tag Title Format"
371
+ msgstr ""
372
+
373
+ #: modules/titles.php:47
374
+ msgid "Day Archive Title Format"
375
+ msgstr ""
376
+
377
+ #: modules/titles.php:48
378
+ msgid "Month Archive Title Format"
379
+ msgstr ""
380
+
381
+ #: modules/titles.php:49
382
+ msgid "Year Archive Title Format"
383
+ msgstr ""
384
+
385
+ #: modules/titles.php:50
386
+ msgid "Author Archive Title Format"
387
+ msgstr ""
388
+
389
+ #: modules/titles.php:51
390
+ msgid "Search Title Format"
391
+ msgstr ""
392
+
393
+ #: modules/titles.php:52
394
+ msgid "404 Title Format"
395
+ msgstr ""
396
+
397
+ #: modules/titles.php:53
398
+ msgid "Pagination Title Format"
399
+ msgstr ""
400
+
401
+ #: modules/titles.php:247
402
+ msgid ""
403
+ "<strong>Title Tag</strong> &mdash; The exact contents of the &lt;title&gt; "
404
+ "tag. The title appears in visitors' title bars and in search engine result "
405
+ "titles. "
406
+ msgstr ""
407
+
408
+ #. Plugin URI of an extension
409
+ msgid "http://www.seodesignsolutions.com/wordpress-seo/"
410
+ msgstr ""
411
+
412
+ #. Description of an extension
413
+ msgid ""
414
+ "This all-in-one SEO plugin can rewrite title tags, edit meta data, add "
415
+ "noindex to pages, and insert canonical tags (with many more features coming "
416
+ "soon)."
417
+ msgstr ""
418
+
419
+ #. Author of an extension
420
+ msgid "SEO Design Solutions"
421
+ msgstr ""
422
+
423
+ #. Author URI of an extension
424
+ msgid "http://www.seodesignsolutions.com/"
425
+ msgstr ""