SEO Ultimate - Version 0.1

Version Description

Download this release

Release Info

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

Version 0.1

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