+++ /dev/null
-<?php
-
-/*
-Plugin Name: Gaslightify
-Plugin URI: http://URI_Of_Page_Describing_Plugin_and_Updates
-Description: A gasline to a Wordpress website.
-Version: 1.0
-Author: Gaslight Media
-Author URI: http://www.gaslightmedia.com
-License: Gaslight Media
-*/
-
-//function basic_content_replace ($text){
-//$text = str_replace(‘Old’,’New’,$text)
-//return $text;
-//}
-//add_filter(‘the_content’,’basic_content_replace’);
-//add_filter(‘the_title’,’basic_content_replace’);
-
-add_action('the_generator', 'interface_client_safeguard');
-
-function interface_client_safeguard() {
- if ( current_user_can( 'manage_options' ) ) {
- /* A user with admin privileges */
-
- } else {
- /* A user without admin privileges */
- }
-}
-
-
-?>
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+Plugin Name: Gaslightify2
+Plugin URI: http://URI_Of_Page_Describing_Plugin_and_Updates
+Description: A gasline to a Wordpress website.
+Version: 1.0
+Author: Gaslight Media
+Author URI: http://www.gaslightmedia.com
+License: Gaslight Media
+*/
+
+//function basic_content_replace ($text){
+//$text = str_replace(‘Old’,’New’,$text)
+//return $text;
+//}
+//add_filter(‘the_content’,’basic_content_replace’);
+//add_filter(‘the_title’,’basic_content_replace’);
+
+add_action('admin_enqueue_scripts', 'interface_client_safeguard');
+
+function interface_client_safeguard() {
+ if ( current_user_can( 'manage_options' ) ) {
+ /* A user with admin privileges */
+
+ } else {
+ /* A user without admin privileges */
+ wp_register_style('Gaslightify2Style', plugins_url('css/Gaslightify2.css', __FILE__));
+ wp_enqueue_style('Gaslightify2Style');
+ wp_enqueue_script('GaslightifyJs', plugins_url('js/Gaslightify2.js', __FILE__),array(),'1.0.0', true);
+ }
+}
+
+function glm_custom_logo() {
+ echo '<style>
+#wp-admin-bar-wp-logo > .ab-item .ab-icon:before, #header-logo {
+ content: url('.plugins_url('img/icon-twitter3.gif', __FILE__).') !important;
+}
+</style>';
+}
+add_action('admin_head', 'glm_custom_logo');
+?>
--- /dev/null
+#screen-meta-links,
+select[name="seo_filter"],
+#menu-posts,
+#menu-comments,
+#menu-tools,
+#menu-users,
+#wp-admin-bar-edit-profile,
+#wp-admin-bar-user-info,
+#wp-admin-bar-wp-logo-default,
+#wp-admin-bar-wp-logo-external,
+#wp-admin-bar-comments,
+#wp-admin-bar-new-post,
+#wp-admin-bar-wpseo-menu,
+#footer-thankyou,
+#footer-upgrade {
+ display: none !important;
+}
--- /dev/null
+jQuery(document).ready(function(){
+ jQuery("#wp-admin-bar-my-account a").click(function(){
+ return false;
+ });
+});
//Our personal copy of the request vars, without any "magic quotes".\r
private $post = array();\r
private $get = array();\r
+ private $originalPost = array();\r
\r
function init(){\r
$this->sitewide_options = true;\r
\r
//Compatibility fix for bbPress.\r
$this->apply_bbpress_compat_fix();\r
+ //Compatibility fix for WooCommerce (woo).\r
+ $this->apply_woocommerce_compat_fix();\r
//Compatibility fix for WordPress Mu Domain Mapping.\r
$this->apply_wpmu_domain_mapping_fix();\r
\r
}\r
}\r
\r
+ //Remove consecutive submenu separators. This can happen if there are separators around a menu item\r
+ //that is not accessible to the current user.\r
+ foreach ($submenu as $parent => $items) {\r
+ $found_separator = false;\r
+ foreach ($items as $index => $item) {\r
+ //Separator have a dummy #anchor as a URL. See wsMenuEditorExtras::create_submenu_separator().\r
+ if (strpos($item[2], '#submenu-separator-') === 0) {\r
+ if ( $found_separator ) {\r
+ unset($submenu[$parent][$index]);\r
+ }\r
+ $found_separator = true;\r
+ } else {\r
+ $found_separator = false;\r
+ }\r
+ }\r
+ }\r
+\r
//Remove menus that have no accessible sub-menus and require privileges that the user does not have.\r
//Ensure the rest are visible. Run re-parent loop again.\r
foreach ( $menu as $id => $data ) {\r
$priority--;\r
}\r
\r
+ //TODO: Include more details like menu title and template ID for debugging purposes (log output).\r
$this->page_access_lookup[$item['url']][$priority] = $item['access_level'];\r
}\r
\r
$items = $topmenu['items'];\r
//Sort by position\r
uasort($items, 'ameMenuItem::compare_position');\r
- \r
+\r
foreach ($items as $item) {\r
//Skip missing and hidden items\r
if ( !empty($item['missing']) || !empty($item['hidden']) ) {\r
try {\r
$menu = ameMenu::load_json($post['data'], true);\r
} catch (InvalidMenuException $ex) {\r
- //Or redirect & display the error message\r
- wp_redirect( add_query_arg('message', 2, $url) );\r
- die();\r
+ $debugData = '';\r
+ $debugData .= "Exception:\n" . $ex->getMessage() . "\n\n";\r
+ $debugData .= "Used POST data:\n" . print_r($this->post, true) . "\n\n";\r
+ $debugData .= "Original POST:\n" . print_r($this->originalPost, true) . "\n\n";\r
+ $debugData .= "\$_POST global:\n" . print_r($_POST, true);\r
+\r
+ $debugData = sprintf(\r
+ "<textarea rows=\"30\" cols=\"100\">%s</textarea>",\r
+ htmlentities($debugData)\r
+ );\r
+\r
+ wp_die(\r
+ "Error: Failed to decode menu data!<br><br>\n"\r
+ . "Please send this debugging information to the developer: <br>"\r
+ . $debugData\r
+ );\r
+\r
+ return;\r
}\r
\r
//Save the custom menu\r
\r
$current_url = $this->parse_url($current_url);\r
\r
+ //Special case: if post_type is not specified for edit.php and post-new.php,\r
+ //WordPress assumes it is "post". Here we make this explicit.\r
+ if ( $this->endsWith($current_url['path'], '/wp-admin/edit.php') || $this->endsWith($current_url['path'], '/wp-admin/post-new.php') ) {\r
+ if ( !isset($current_url['params']['post_type']) ) {\r
+ $current_url['params']['post_type'] = 'post';\r
+ }\r
+ }\r
+\r
//Hook-based submenu pages can be accessed via both "parent-page.php?page=foo" and "admin.php?page=foo".\r
//WP has a private API function for determining the canonical parent page for the current request.\r
if ( $this->endsWith($current_url['path'], '/admin.php') && is_callable('get_admin_page_parent') ) {\r
}\r
}\r
\r
+ //Same as above - default post type is "post".\r
+ if ( $this->endsWith($item_url['path'], '/wp-admin/edit.php') || $this->endsWith($item_url['path'], '/wp-admin/post-new.php') ) {\r
+ if ( !isset($item_url['params']['post_type']) ) {\r
+ $item_url['params']['post_type'] = 'post';\r
+ }\r
+ }\r
+\r
//The current URL must match all query parameters of the item URL.\r
$different_params = array_diff_assoc($item_url['params'], $current_url['params']);\r
\r
}\r
}\r
\r
+ //Special case for CPTs: When the "Add New" menu is disabled by CPT settings (show_ui, etc), and someone goes\r
+ //to add a new item, WordPress highlights the "$CPT-Name" item as the current one. Lets do the same for\r
+ //consistency. See also: /wp-admin/post-new.php, lines #20 to #40.\r
+ if (\r
+ ($best_item === null)\r
+ && isset($current_url['params']['post_type'])\r
+ && (!empty($current_url['params']['post_type']))\r
+ && $this->endsWith($current_url['path'], '/wp-admin/post-new.php')\r
+ && isset($this->reverse_item_lookup['edit.php?post_type=' . $current_url['params']['post_type']])\r
+ ) {\r
+ $best_item = $this->reverse_item_lookup['edit.php?post_type=' . $current_url['params']['post_type']];\r
+ }\r
+\r
$cached_item = $best_item;\r
return $best_item;\r
}\r
* @return void\r
*/\r
function capture_request_vars(){\r
- $this->post = $_POST;\r
+ $this->post = $this->originalPost = $_POST;\r
$this->get = $_GET;\r
\r
if ( function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() ) {\r
}\r
}\r
\r
+ /**\r
+ * Compatibility fix for WooCommerce 2.2.1+.\r
+ * Summary: When AME is active, an unusable WooCommerce -> WooCommerce menu item shows up. Here we remove it.\r
+ *\r
+ * WooCommerce creates a top level "WooCommerce" menu with no callback. By default, WordPress automatically adds\r
+ * a submenu item with the same name. However, since the item doesn't have a callback, it is unusable and clicking\r
+ * it just triggers a "Cannot load woocommerce" error. So WooCommerce removes this item in an admin_head hook to\r
+ * hide it. With AME active, the item shows up anyway, and users get confused by the error.\r
+ *\r
+ * Fix it by removing the problematic menu item.\r
+ *\r
+ * Caution: If the user hides all WooCommerce submenus but not the top level menu, the WooCommerce menu will still\r
+ * show up but be inaccessible. This may be slightly counter-intuitive, but seems reasonable.\r
+ */\r
+ private function apply_woocommerce_compat_fix() {\r
+ if ( !isset($this->default_wp_submenu, $this->default_wp_submenu['woocommerce']) ) {\r
+ return;\r
+ }\r
+\r
+ $badSubmenuExists = isset($this->default_wp_submenu['woocommerce'][0])\r
+ && isset($this->default_wp_submenu['woocommerce'][0][2])\r
+ && ($this->default_wp_submenu['woocommerce'][0][2] === 'woocommerce');\r
+ $anotherSubmenuExists = isset($this->default_wp_submenu['woocommerce'][1]);\r
+\r
+ if ( $badSubmenuExists && $anotherSubmenuExists ) {\r
+ $this->default_wp_submenu['woocommerce'][0] = $this->default_wp_submenu['woocommerce'][1];\r
+ unset($this->default_wp_submenu['woocommerce'][1]);\r
+ }\r
+ }\r
+\r
/**\r
* Compatibility fix for WordPress Mu Domain Mapping 0.5.4.3.\r
*\r
$parent_file = 'users.php';\r
}\r
\r
+ //Special case: In WP 4.0+ the URL of the "Appearance -> Customize" item is different on every admin page.\r
+ //This is because the URL includes a "return" parameter that contains the current page's URL. It also makes\r
+ //the template ID different on every page, so it's impossible to identify the menu. To fix that, lets remove\r
+ //the "return" parameter from the ID.\r
+ if ( ($parent_file === 'themes.php') && (strpos($item_file, 'customize.php?') === 0) ) {\r
+ $item_file = remove_query_arg('return', $item_file);\r
+ }\r
+\r
return $parent_file . '>' . $item_file;\r
}\r
\r
$menu_url = is_array($item_slug) ? self::get($item_slug, 'file') : $item_slug;\r
$parent_url = !empty($parent_slug) ? $parent_slug : 'admin.php';\r
\r
+ //Workaround for WooCommerce 2.1.12: For some reason, it uses "&" instead of a plain "&" to separate\r
+ //query parameters. We need a plain URL, not a HTML-entity-encoded one.\r
+ //It is theoretically possible that another plugin might want to use a literal "&", but its very unlikely.\r
+ $menu_url = str_replace('&', '&', $menu_url);\r
+\r
if ( strpos($menu_url, '://') !== false ) {\r
return $menu_url;\r
}\r
}\r
$pageFile = self::remove_query_from($page_url);\r
\r
+ /*\r
+ * Special case: Absolute paths.\r
+ *\r
+ * - add_submenu_page() applies plugin_basename() to the menu slug, so we don't need to worry about plugin\r
+ * paths. However, absolute paths that *don't* point point to the plugins directory can be a problem.\r
+ *\r
+ * - If we blindly append $pageFile to another path, we'll get something like "C:\a\b/wp-admin/C:\c\d.php".\r
+ * PHP 5.2.5 has a known bug where calling file_exists() on that kind of an invalid filename will cause\r
+ * a timeout and a crash in some configurations. See: https://bugs.php.net/bug.php?id=44412\r
+ *\r
+ * - WP 3.9.2 and 4.0+ unintentionally break menu URLs like "foo.php?page=c:\a\b.php" because esc_url()\r
+ * interprets the part before the colon as an invalid protocol. As a result, such links have an empty URL\r
+ * on Windows (but they might still work on other OS).\r
+ *\r
+ * - Recent versions of WP won't let you load a PHP file from outside the plugins and mu-plugins directories\r
+ * with "admin.php?page=filename". See the validate_file() call in /wp-admin/admin.php. However, such filenames\r
+ * can still be used as unique slugs for menus with hook callbacks, so we shouldn't reject them outright.\r
+ * Related: https://core.trac.wordpress.org/ticket/10011\r
+ */\r
+ $allowPathConcatenation = (substr($pageFile, 1, 1) !== ':'); //Reject "C:\whatever" and similar.\r
+\r
//Check our hard-coded list of admin pages first. It's measurably faster than\r
//hitting the disk with is_file().\r
if ( isset(self::$known_wp_admin_files[$pageFile]) ) {\r
return false;\r
}\r
+\r
//Now actually check the filesystem.\r
- $adminFileExists = is_file(ABSPATH . '/wp-admin/' . $pageFile);\r
+ $adminFileExists = $allowPathConcatenation && is_file(ABSPATH . 'wp-admin/' . $pageFile);\r
if ( $adminFileExists ) {\r
return false;\r
}\r
return true;\r
}\r
\r
- $pluginFileExists = ($page_url != 'index.php') && is_file(WP_PLUGIN_DIR . '/' . $pageFile);\r
+ //Note: We don't need to call plugin_basename() on $pageFile because add_submenu_page() already did that.\r
+ $pluginFileExists = $allowPathConcatenation\r
+ && ($page_url != 'index.php')\r
+ && is_file(WP_PLUGIN_DIR . '/' . $pageFile);\r
if ( $pluginFileExists ) {\r
return true;\r
}\r
Plugin Name: Admin Menu Editor\r
Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/\r
Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more. \r
-Version: 1.4\r
+Version: 1.4.1\r
Author: Janis Elsts\r
Author URI: http://w-shadow.com/blog/\r
*/\r
Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW\r
Tags: admin, dashboard, menu, security, wpmu\r
Requires at least: 3.8\r
-Tested up to: 4.0-beta2\r
-Stable tag: 1.4\r
+Tested up to: 4.0\r
+Stable tag: 1.4.1\r
\r
Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more. \r
\r
\r
== Changelog ==\r
\r
+= 1.4.1 =\r
+* Fixed "Appearance -> Customize" always showing up as "new" and ignoring custom settings.\r
+* Fixed a WooCommerce 2.2.1+ compatibility issue that caused a superfluous "WooCommerce -> WooCommerce" submenu item to show up. Normally this item is invisible.\r
+* Fixed a bug where the plugin would fail to determine the current menu if the user tries to add a new item of a custom post type that doesn't have an "Add New" menu. Now it highlights the CPT parent menu instead.\r
+* Fixed a very obscure bug where certain old versions of PHP would crash if another plugin created a menu item using an absolute file name as the slug while AME was active. The crash was due to a known bug in PHP and only affected Windows systems with open_basedir enabled.\r
+* Added more debugging information for situations where the plugin can't save menu settings due to server configuration problems.\r
+* Other minor fixes.\r
+\r
= 1.4 = \r
* Added a special target page option: "< None >". It makes the selected menu item unclickable. This could be useful for creating menu headers and so on.\r
* Added a new menu editor colour scheme that's similar to the default WordPress admin colour scheme. Click the "Settings" button next to the menu editor page title to switch colour schemes.\r
NextGEN Gallery
by Photocrati Media
+= V2.0.66.29 - 09.17.2014 =
+* NEW: Added skip_excluding_globally_excluded_images property to displayed gallery objects
+* Fixed: SQL generation for random image selection
+* Fixed: Adjust regex for replacing displayed gallery placeholder images
+* Fixed: Removed filters to home_url needed previously for WMPL compatibility
+* Fixed: Use canonical redirects when appropriate
+* Fixed: Ability to override image files using XML-RPC
+
= V2.0.66.27 - 08.18.2014 =
* Fixed: Missing class.frame_communication_option_handerl.php error
* NEW: Spanish (es_ES) language thanks to Andrew Kurtis at WebHostingHub
* Changed: Updated Czech language thanks to Separatista; additional thanks to Martin Krizek for the original translation who was mistakenly unaccredited
* Changed: "Upgrade to Pro" page has new design, advertises for NextGEN Plus
-* Changed: Basic Albums templates now given the image counter <p> element the class 'ngg-album-gallery-image-counter' (by user request)
+* Changed: Basic Albums templates now given the image counter P element the class 'ngg-album-gallery-image-counter' (by user request)
* Changed: Gallery widgets now apply height:auto to their element; fixes compatibility with some themes
* Changed: Random galleries should be substantially faster now (1000% or more for large image tables)
* Fixed: Complete WPML compatibility
* Fixed: Multisite gallery path tooltip gave a wrong default setting
* Fixed: Flush 'all' caches when pope_module_list setting changes
* Fixed: Don't enqueue related images css in the admin
-* Fixed: Basic Slideshows fixes WP creating extraneous <p> element above the slideshow display
+* Fixed: Basic Slideshows fixes WP creating extraneous P element above the slideshow display
* Fixed: Basic Singlepic will now display images marked 'excluded' in the admin
* Fixed: Admin pages can now update when the "Save" button text has been translated
* Added : New filter function to add custom columns
* Bugfix : Fixed width for Thickbox in Manage gallery
* Bugfix : fixed width for media upload select box
-* Bugfix : Remove <p> tag in singlepic regex
+* Bugfix : Remove P tag in singlepic regex
* Bugfix : Correct format of shutter speed
* Bugfix : Album name in Short code not useable
/**
* Plugin Name: NextGEN Gallery by Photocrati
* Description: The most popular gallery plugin for WordPress and one of the most popular plugins of all time with over 9 million downloads.
- * Version: 2.0.66.27
+ * Version: 2.0.66.29
* Author: Photocrati Media
* Plugin URI: http://www.nextgen-gallery.com
* Author URI: http://www.photocrati.com
define('NGG_PRODUCT_URL', path_join(str_replace("\\", '/', NGG_PLUGIN_URL), 'products'));
define('NGG_MODULE_URL', path_join(str_replace("\\", '/', NGG_PRODUCT_URL), 'photocrati_nextgen/modules'));
define('NGG_PLUGIN_STARTED_AT', microtime());
- define('NGG_PLUGIN_VERSION', '2.0.66.27');
+ define('NGG_PLUGIN_VERSION', '2.0.66.29');
if (!defined('NGG_HIDE_STRICT_ERRORS')) {
define('NGG_HIDE_STRICT_ERRORS', TRUE);
*/
function substitute_placeholder_imgs($content)
{
- // Get some utilities
- $mapper = $this->get_registry()->get_utility('I_Displayed_Gallery_Mapper');
- $router = $this->get_registry()->get_utility('I_Router');
-
- // To match ATP entries we compare the stored url against a generic path
- // We must check HTTP and HTTPS as well as permalink and non-permalink forms
- $preview_url = parse_url($router->join_paths(
- $router->remove_url_segment('index.php', $router->get_base_url('root')),
- '/nextgen-attach_to_post/preview'
- ));
- $router->debug = TRUE;
- $preview_url = preg_quote($preview_url['host'] . $preview_url['path'], '#');
-
- $alt_preview_url = parse_url($router->join_paths(
- $router->remove_url_segment('index.php', $router->get_base_url('root')),
- 'index.php/nextgen-attach_to_post/preview'
- ));
- $alt_preview_url = preg_quote($alt_preview_url['host'] . $alt_preview_url['path'], '#');
-
// The placeholder MUST have a gallery instance id
- if (preg_match_all("#<img.*http(s)?://({$preview_url}|{$alt_preview_url})/id--(\\d+).*\\/>#mi", $content, $matches, PREG_SET_ORDER)) {
+ if (preg_match_all("#<img.*http(s)?://(.*)/" . NGG_ATTACH_TO_POST_SLUG . "/preview/id--(\\d+).*>#mi", $content, $matches, PREG_SET_ORDER))
+ {
+ $mapper = C_Displayed_Gallery_Mapper::get_instance();
foreach ($matches as $match) {
// Find the displayed gallery
$displayed_gallery_id = $match[3];
$display_settings = $displayed_gallery->display_settings;
// use this over get_included_entities() so we can display images marked 'excluded'
- $image = array_shift($displayed_gallery->get_entities(1, FALSE, FALSE, 'both'));
+ $displayed_gallery->skip_excluding_globally_excluded_images = TRUE;
+ $image = array_shift($displayed_gallery->get_entities(1, FALSE, FALSE, 'included'));
if (!$image)
return $this->object->render_partial("photocrati-nextgen_gallery_display#no_images_found", array(), $return);
* @param type $filename specifies the name of the file
* @return C_Image
*/
- function upload_base64_image($gallery, $data, $filename=FALSE, $image_id=FALSE)
+ function upload_base64_image($gallery, $data, $filename=FALSE, $image_id=FALSE, $override=FALSE)
{
$settings = C_NextGen_Settings::get_instance();
$memory_limit = intval(ini_get('memory_limit'));
// Prevent duplicate filenames: check if the filename exists and
// begin appending '-i' until we find an open slot
- if (!ini_get('safe_mode') && @file_exists($abs_filename))
+ if (!ini_get('safe_mode') && @file_exists($abs_filename) && !$override)
{
$file_exists = TRUE;
$i = 0;
$mapper->where(array("{$image_key} NOT IN %s", $this->object->exclusions));
}
- // Ensure that no images marked as excluded at the gallery level are
- // returned
- $mapper->where(array("exclude = %d", 0));
+ // Ensure that no images marked as excluded at the gallery level are returned
+ if (empty($this->object->skip_excluding_globally_excluded_images))
+ $mapper->where(array("exclude = %d", 0));
}
// When returns is "excluded", it's a little more complicated as the
// gallery created by randomly selecting X image ids that are then set as the gallery entity_ids
elseif ($this->object->source == 'random_images' && empty($this->object->entity_ids)) {
$table_name = $mapper->get_table_name();
- $mapper->_where_clauses[] = " /*NGG_NO_EXTRAS_TABLE*/ `{$image_key}` IN (SELECT `{$image_key}` FROM (SELECT `{$image_key}` FROM `{$table_name}` i ORDER BY RAND() LIMIT {$this->object->maximum_entity_count}) o) /*NGG_NO_EXTRAS_TABLE*/";
- }
+ $where_clauses = array();
+ $sub_where_sql = '';
+ foreach ($mapper->_where_clauses as $where) {
+ $where_clauses[] = '(' . $where . ')';
+ }
+ if ($where_clauses)
+ $sub_where_sql = 'WHERE ' . implode(' AND ', $where_clauses);
+ $mapper->_where_clauses = array(" /*NGG_NO_EXTRAS_TABLE*/ `{$image_key}` IN (SELECT `{$image_key}` FROM (SELECT `{$image_key}` FROM `{$table_name}` i {$sub_where_sql} ORDER BY RAND() LIMIT {$this->object->maximum_entity_count}) o) /*NGG_NO_EXTRAS_TABLE*/");
+ }
// Apply a sorting order
if ($sort_by) $mapper->order_by($sort_by, $sort_direction);
$password = strval($args[2]);
$data = $args[3];
$gallery_id = isset($data['gallery_id']) ? $data['gallery_id'] : $data['gallery'];
+ if (!isset($data['override'])) $data['override'] = FALSE;
+ if (!isset($data['overwrite']))$data['overwrite']= FALSE;
+ $data['override'] = $data['overwrite'];
// Authenticate the user
if ($this->_login($username, $password, $blog_id)) {
// Upload the image
$storage = C_Gallery_Storage::get_instance();
- $image = $storage->upload_base64_image($gallery, $data['bits'], $data['name'], $data['image_id']);
+ $image = $storage->upload_base64_image($gallery, $data['bits'], $data['name'], $data['image_id'], $data['override']);
if ($image) {
$storage = C_Gallery_Storage::get_instance();
$image->imageURL = $storage->get_image_url($image);
add_action('plugins_loaded', array(&$this, 'wpml'), PHP_INT_MAX);
add_action('plugins_loaded', array(&$this, 'wpml_translation_management'), PHP_INT_MAX);
- add_filter('home_url', array(&$this, 'wpml_home_url'), -1, 4);
add_filter('headway_gzip', array(&$this, 'headway_gzip'), (PHP_INT_MAX - 1));
add_filter('ckeditor_external_plugins', array(&$this, 'ckeditor_plugins'), 11);
add_filter('bp_do_redirect_canonical', array(&$this, 'fix_buddypress_routing'));
M_WordPress_Routing::$_use_old_slugs = FALSE;
}
- /**
- * WPML's home_url filter causes a conflict with NextGEN's url generation, but doesn't appear to be necessary for
- * WPML to function. This is necessary until we properly support WP_CONTENT_URL & WP_PLUGINS_URL.
- *
- * @param $url
- * @param $path
- * @param $orig_scheme
- * @param $blog_id
- * @return mixed
- */
- function wpml_home_url($url, $path, $orig_scheme, $blog_id)
- {
- if (!class_exists('SitePress'))
- return $url;
-
- global $wp_filter;
-
- if (empty($wp_filter['home_url'][1]))
- return $url;
-
- foreach ($wp_filter['home_url'][1] as $id => $filter) {
- if (!strpos($id, 'home_url'))
- continue;
- $object = $filter['function'][0];
- if (is_object($object) && get_class($object) != 'SitePress')
- continue;
- remove_filter('home_url', array($object, 'home_url'), 1);
- }
-
- return $url;
- }
-
/**
* CKEditor features a custom NextGEN shortcode generator that unfortunately relies on parts of the NextGEN
* 1.9x API that has been deprecated in NextGEN 2.0
***/
class M_WordPress_Routing extends C_Base_Module
{
- static $_use_canonical_redirect = NULL;
- static $_use_old_slugs = NULL;
+ static $_use_canonical_redirect = TRUE;
+ static $_use_old_slugs = TRUE;
function define()
{
// in the restore_request_uri() method
if (has_action('template_redirect', 'wp_old_slug_redirect')) {
remove_action( 'template_redirect', 'wp_old_slug_redirect');
- if (!is_null(self::$_use_canonical_redirect)) self::$_use_old_slugs = TRUE;
}
if (has_action('template_redirect', 'redirect_canonical')) {
remove_action( 'template_redirect', 'redirect_canonical');
- if (!is_null(self::$_use_canonical_redirect)) self::$_use_canonical_redirect = TRUE;
}
}
== Changelog ==
+= V2.0.66.29 - 09.17.2014 =
+* NEW: Added skip_excluding_globally_excluded_images property to displayed gallery objects
+* Fixed: SQL generation for random image selection
+* Fixed: Adjust regex for replacing displayed gallery placeholder images
+* Fixed: Removed filters to home_url needed previously for WMPL compatibility
+* Fixed: Use canonical redirects when appropriate
+* Fixed: Ability to override image files using XML-RPC
+
= V2.0.66.27 - 08.18.2014 =
* Fixed: Missing class.frame_communication_option_handerl.php error
* NEW: Spanish (es_ES) language thanks to Andrew Kurtis at WebHostingHub
* Changed: Updated Czech language thanks to Separatista; additional thanks to Martin Krizek for the original translation who was mistakenly unaccredited
* Changed: "Upgrade to Pro" page has new design, advertises for NextGEN Plus
-* Changed: Basic Albums templates now given the image counter <p> element the class 'ngg-album-gallery-image-counter' (by user request)
+* Changed: Basic Albums templates now given the image counter P element the class 'ngg-album-gallery-image-counter' (by user request)
* Changed: Gallery widgets now apply height:auto to their element; fixes compatibility with some themes
* Changed: Random galleries should be substantially faster now (1000% or more for large image tables)
* Fixed: Complete WPML compatibility
* Fixed: Multisite gallery path tooltip gave a wrong default setting
* Fixed: Flush 'all' caches when pope_module_list setting changes
* Fixed: Don't enqueue related images css in the admin
-* Fixed: Basic Slideshows fixes WP creating extraneous <p> element above the slideshow display
+* Fixed: Basic Slideshows fixes WP creating extraneous P element above the slideshow display
* Fixed: Basic Singlepic will now display images marked 'excluded' in the admin
* Fixed: Admin pages can now update when the "Save" button text has been translated
<?php foreach($results as $key => $v){ ?>
<tr><th>Time:</th><td><?php echo $v['timeAgo'] ?> ago -- <?php echo date(DATE_RFC822, $v['ctime']); ?> -- <?php echo $v['ctime']; ?> in Unixtime</td></tr>
<?php if($v['timeSinceLastHit']){ echo '<th>Secs since last hit:</th><td>' . $v['timeSinceLastHit'] . '</td></tr>'; } ?>
+<?php if(wfUtils::hasXSS($v['URL'])){ ?>
+<tr><th>URL:</th><td><span style="color: #F00;">Possible XSS code filtered out for your security</span></td></tr>
+<?php } else { ?>
<tr><th>URL:</th><td><a href="<?php echo $v['URL']; ?>" target="_blank"><?php echo $v['URL']; ?></a></td></tr>
+<?php } ?>
<tr><th>Type:</th><td><?php if($v['type'] == 'hit'){ echo 'Normal request'; } else if($v['type'] == '404'){ echo '<span style="color: #F00;">Page not found</span>'; } ?></td></tr>
<?php if($v['referer']){ ?><tr><th>Referrer:</th><td><a href="<?php echo $v['referer']; ?>" target="_blank"><?php echo $v['referer']; ?></a></td></tr><?php } ?>
<tr><th>Full Browser ID:</th><td><?php echo esc_html($v['UA']); ?></td></tr>
$append .= "Time created on server: " . date('Y-m-d H:i:s T') . ". ";
$append .= "Is HTTPS page: " . (self::isHTTPSPage() ? 'HTTPS' : 'no') . ". ";
$append .= "Page size: " . strlen($buffer) . " bytes. ";
- $append .= "Host: " . ($_SERVER['HTTP_HOST'] ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']) . ". ";
- $append .= "Request URI: " . $_SERVER['REQUEST_URI'] . " ";
+ $append .= "Host: " . ($_SERVER['HTTP_HOST'] ? htmlentities($_SERVER['HTTP_HOST']) : htmlentities($_SERVER['SERVER_NAME'])) . ". ";
+ $append .= "Request URI: " . htmlentities($_SERVER['REQUEST_URI']) . " ";
$appendGzip = $append . " Encoding: GZEncode -->\n";
$append .= " Encoding: Uncompressed -->\n";
}
$res['IP'] = wfUtils::inet_ntoa($res['IP']);
$res['extReferer'] = false;
if(isset( $res['referer'] ) && $res['referer']){
- if(! preg_match('/^https?:\/\/[a-z0-9\.\-]+\/[^\':<>\"\\\]*$/i', $res['referer'] )){ //filtering out XSS
+ if(wfUtils::hasXSS($res['referer'] )){ //filtering out XSS
$res['referer'] = '';
}
}
public static function getIP(){
//You can use the following examples to force Wordfence to think a visitor has a certain IP if you're testing. Remember to re-comment this out or you will break Wordfence badly.
//return '1.2.33.57';
- //return '4.2.3.14';
+ //return '4.22.23.114';
//return self::makeRandomIP();
$howGet = wfConfig::get('howGetIPs', false);
}
return true;
}
+ public static function hasXSS($URL){
+ if(! preg_match('/^https?:\/\/[a-z0-9\.\-]+\/[^\':<>\"\\\]*$/i', $URL)){
+ return true;
+ } else {
+ return false;
+ }
+ }
}
}
public static function initProtection(){
if(preg_match('/\/wp\-admin\/admin\-ajax\.php/', $_SERVER['REQUEST_URI'])){
- if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'revslider_show_image' && isset($_REQUEST['img']) && preg_match('/\.php$/i', $_REQUEST['img']) ){
+ if(
+ (isset($_GET['action']) && $_GET['action'] == 'revslider_show_image' && isset($_GET['img']) && preg_match('/\.php$/i', $_GET['img'])) ||
+ (isset($_POST['action']) && $_POST['action'] == 'revslider_show_image' && isset($_POST['img']) && preg_match('/\.php$/i', $_POST['img']))
+ ){
self::getLog()->do503(86400, "URL not allowed. Slider Revolution Hack attempt detected. #2");
exit(); //function above exits anyway
}
return array('err' => 1, 'errorMsg' => "The IP address " . htmlentities($IP) . " is whitelisted and can't be blocked or it is in a range of internal IP addresses that Wordfence does not block. You can remove this IP from the whitelist on the Wordfence options page.");
}
if(wfConfig::get('neverBlockBG') != 'treatAsOtherCrawlers'){ //Either neverBlockVerified or neverBlockUA is selected which means the user doesn't want to block google
- if(wfCrawl::verifyCrawlerPTR('/googlebot\.com$/i', $IP)){
+ if(wfCrawl::verifyCrawlerPTR('/\.googlebot\.com$/i', $IP)){
return array('err' => 1, 'errorMsg' => "The IP address you're trying to block belongs to Google. Your options are currently set to not block these crawlers. Change this in Wordfence options if you want to manually block Google.");
}
}
Tags: wordpress, security, performance, speed, caching, cache, caching plugin, wordpress cache, wordpress caching, wordpress security, security plugin, secure, anti-virus, malware, firewall, antivirus, virus, google safe browsing, phishing, scrapers, hacking, wordfence, securty, secrity, secure, two factor, cellphone sign-in, cellphone signin, cellphone, twofactor, security, secure, htaccess, login, log, users, login alerts, lock, chmod, maintenance, plugin, private, privacy, protection, permissions, 503, base64, injection, code, encode, script, attack, hack, hackers, block, blocked, prevent, prevention, RFI, XSS, CRLF, CSRF, SQL Injection, vulnerability, website security, WordPress security, security log, logging, HTTP log, error log, login security, personal security, infrastructure security, firewall security, front-end security, web server security, proxy security, reverse proxy security, secure website, secure login, two factor security, maximum login security, heartbleed, heart bleed, heartbleed vulnerability, openssl vulnerability, nginx, litespeed, php5-fpm, woocommerce support, woocommerce caching
Requires at least: 3.3.1
Tested up to: 4.0
-Stable tag: 5.2.3
+Stable tag: 5.2.4
Wordfence Security is a free enterprise class security and performance plugin that makes your site up to 50 times faster and more secure.
== Changelog ==
+= 5.2.4 =
+* Security release. Upgrade immediately.
+* This release fixes an XSS vunlerability on Wordfence "view all traffic from IP" page.
+* Also fixes a hard to exploit XSS which exists if you have your site as the default site on your web server, falcon enabled and debugging comments enabled.
+* Improves Revolution Slider proteciton.
+* Fixed bypass for fake googlebot blocking.
+
= 5.2.3 =
* Updated Geo IP country database to newest version (September 2014 edition)
* Security fix. Improved referrer sanitization in live traffic.
+++ /dev/null
-<?php
-/* Wordfence temporary file security header */
-echo "Nothing to see here!\n"; exit(0);
-?>a:18:{s:9:"cacheType";s:0:"";s:15:"firewallEnabled";s:1:"1";s:6:"apiKey";s:192:"9a57151a642820cab102741d5a92a91e788dc8d15823e8de2acf65c38ee9122d12d031e407fd6e07063ef5a2450379e96e3fc6d3152957ba67a0b99301da91a13a54962479db212e15db85e28ed9225d7b221f9ee3de6af39d16e984ebdd4fb4";s:9:"howGetIPs";s:0:"";s:9:"IPGetFail";s:0:"";s:11:"whitelisted";s:0:"";s:13:"cbl_countries";s:0:"";s:18:"cbl_bypassRedirURL";s:0:"";s:17:"cbl_bypassViewURL";s:0:"";s:11:"blockedTime";s:3:"300";s:18:"liveTrafficEnabled";s:1:"1";s:14:"disableCookies";s:1:"0";s:10:"tourClosed";s:1:"1";s:10:"autoUpdate";s:1:"0";s:16:"autoUpdateChoice";s:1:"1";s:11:"alertEmails";s:21:"dev@gaslightmedia.com";s:17:"actUpdateInterval";s:0:"";s:7:"debugOn";s:1:"0";}
\ No newline at end of file
Plugin URI: http://www.wordfence.com/
Description: Wordfence Security - Anti-virus, Firewall and High Speed Cache
Author: Wordfence
-Version: 5.2.3
+Version: 5.2.4
Author URI: http://www.wordfence.com/
*/
if(defined('WP_INSTALLING') && WP_INSTALLING){
return;
}
-define('WORDFENCE_VERSION', '5.2.3');
+define('WORDFENCE_VERSION', '5.2.4');
if(get_option('wordfenceActivated') != 1){
add_action('activated_plugin','wordfence_save_activation_error'); function wordfence_save_activation_error(){ update_option('wf_plugin_act_error', ob_get_contents()); }
}
--- /dev/null
+{
+ "exclude": [
+ "node_modules/**",
+ "vendor/**",
+ "*.min.css"
+ ],
+ "verbose": true,
+
+ "always-semicolon" : true,
+ "block-indent" : "\t",
+ "colon-space" : [
+ 0,
+ 1
+ ],
+ "color-case" : "lower",
+ "color-shorthand" : true,
+ "combinator-space" : [
+ 1,
+ 1
+ ],
+ "element-case" : "lower",
+ "eof-newline" : true,
+ "leading-zero" : true,
+ "quotes" : "double",
+ "remove-empty-rulesets" : true,
+ "rule-indent" : "\t",
+ "space-after-selector-delimiter": "\n",
+ "space-after-closing-brace" : "\n",
+ "space-after-opening-brace" : "\n",
+ "space-before-closing-brace" : "\n",
+ "space-before-opening-brace" : 1,
+ "space-before-colon" : 0,
+ "space-before-combinator" : 1,
+ "stick-brace" : 1,
+ "strip-spaces" : true,
+ "unitless-zero" : true,
+ "vendor-prefix-align" : false,
+ "sort-order" : [
+ "$variable",
+ "$include",
+
+ "display",
+ "visibility",
+ "float",
+ "clear",
+ "overflow",
+ "overflow-x",
+ "overflow-y",
+ "-ms-overflow-x",
+ "-ms-overflow-y",
+ "clip",
+ "zoom",
+ "flex-direction",
+ "flex-order",
+ "flex-pack",
+ "flex-align",
+
+ "position",
+ "z-index",
+ "top",
+ "right",
+ "bottom",
+ "left",
+
+ "-webkit-box-sizing",
+ "-moz-box-sizing",
+ "box-sizing",
+ "width",
+ "min-width",
+ "max-width",
+ "height",
+ "min-height",
+ "max-height",
+ "margin",
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left",
+ "padding",
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left",
+ "border",
+ "border-width",
+ "border-style",
+ "border-color",
+ "border-top",
+ "border-top-width",
+ "border-top-style",
+ "border-top-color",
+ "border-right",
+ "border-right-width",
+ "border-right-style",
+ "border-right-color",
+ "border-bottom",
+ "border-bottom-width",
+ "border-bottom-style",
+ "border-bottom-color",
+ "border-left",
+ "border-left-width",
+ "border-left-style",
+ "border-left-color",
+ "-webkit-border-radius",
+ "-moz-border-radius",
+ "border-radius",
+ "-webkit-border-top-left-radius",
+ "-moz-border-radius-topleft",
+ "border-top-left-radius",
+ "-webkit-border-top-right-radius",
+ "-moz-border-radius-topright",
+ "border-top-right-radius",
+ "-webkit-border-bottom-right-radius",
+ "-moz-border-radius-bottomright",
+ "border-bottom-right-radius",
+ "-webkit-border-bottom-left-radius",
+ "-moz-border-radius-bottomleft",
+ "border-bottom-left-radius",
+ "-webkit-border-image",
+ "-moz-border-image",
+ "-o-border-image",
+ "border-image",
+ "-webkit-border-image-source",
+ "-moz-border-image-source",
+ "-o-border-image-source",
+ "border-image-source",
+ "-webkit-border-image-slice",
+ "-moz-border-image-slice",
+ "-o-border-image-slice",
+ "border-image-slice",
+ "-webkit-border-image-width",
+ "-moz-border-image-width",
+ "-o-border-image-width",
+ "border-image-width",
+ "-webkit-border-image-outset",
+ "-moz-border-image-outset",
+ "-o-border-image-outset",
+ "border-image-outset",
+ "-webkit-border-image-repeat",
+ "-moz-border-image-repeat",
+ "-o-border-image-repeat",
+ "border-image-repeat",
+ "table-layout",
+ "empty-cells",
+ "caption-side",
+ "border-spacing",
+ "border-collapse",
+
+ "outline",
+ "outline-width",
+ "outline-style",
+ "outline-color",
+ "outline-offset",
+ "opacity",
+ "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
+ "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha",
+ "-ms-interpolation-mode",
+ "color",
+ "background",
+ "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
+ "background-color",
+ "background-image",
+ "background-repeat",
+ "background-attachment",
+ "background-position",
+ "background-position-x",
+ "-ms-background-position-x",
+ "background-position-y",
+ "-ms-background-position-y",
+ "-webkit-background-clip",
+ "-moz-background-clip",
+ "background-clip",
+ "background-origin",
+ "-webkit-background-size",
+ "-moz-background-size",
+ "-o-background-size",
+ "background-size",
+ "box-decoration-break",
+ "-webkit-box-shadow",
+ "-moz-box-shadow",
+ "box-shadow",
+ "filter:progid:DXImageTransform.Microsoft.gradient",
+ "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient",
+ "text-shadow",
+ "font",
+ "font-family",
+ "font-size",
+ "font-weight",
+ "font-style",
+ "font-variant",
+ "font-size-adjust",
+ "font-stretch",
+ "font-effect",
+ "font-emphasize",
+ "font-emphasize-position",
+ "font-emphasize-style",
+ "font-smooth",
+ "line-height",
+ "text-align",
+ "-webkit-text-align-last",
+ "-moz-text-align-last",
+ "-ms-text-align-last",
+ "text-align-last",
+ "vertical-align",
+ "white-space",
+ "text-decoration",
+ "text-emphasis",
+ "text-emphasis-color",
+ "text-emphasis-style",
+ "text-emphasis-position",
+ "text-indent",
+ "-ms-text-justify",
+ "text-justify",
+ "letter-spacing",
+ "word-spacing",
+ "-ms-writing-mode",
+ "text-outline",
+ "text-transform",
+ "text-wrap",
+ "text-overflow",
+ "-ms-text-overflow",
+ "text-overflow-ellipsis",
+ "text-overflow-mode",
+ "-ms-word-wrap",
+ "word-wrap",
+ "word-break",
+ "-ms-word-break",
+ "-moz-tab-size",
+ "-o-tab-size",
+ "tab-size",
+ "-webkit-hyphens",
+ "-moz-hyphens",
+ "hyphens",
+
+ "list-style",
+ "list-style-position",
+ "list-style-type",
+ "list-style-image",
+ "content",
+ "quotes",
+ "counter-reset",
+ "counter-increment",
+ "resize",
+ "cursor",
+ "-webkit-user-select",
+ "-moz-user-select",
+ "-ms-user-select",
+ "user-select",
+ "nav-index",
+ "nav-up",
+ "nav-right",
+ "nav-down",
+ "nav-left",
+ "-webkit-transition",
+ "-moz-transition",
+ "-ms-transition",
+ "-o-transition",
+ "transition",
+ "-webkit-transition-delay",
+ "-moz-transition-delay",
+ "-ms-transition-delay",
+ "-o-transition-delay",
+ "transition-delay",
+ "-webkit-transition-timing-function",
+ "-moz-transition-timing-function",
+ "-ms-transition-timing-function",
+ "-o-transition-timing-function",
+ "transition-timing-function",
+ "-webkit-transition-duration",
+ "-moz-transition-duration",
+ "-ms-transition-duration",
+ "-o-transition-duration",
+ "transition-duration",
+ "-webkit-transition-property",
+ "-moz-transition-property",
+ "-ms-transition-property",
+ "-o-transition-property",
+ "transition-property",
+ "-webkit-transform",
+ "-moz-transform",
+ "-ms-transform",
+ "-o-transform",
+ "transform",
+ "-webkit-transform-origin",
+ "-moz-transform-origin",
+ "-ms-transform-origin",
+ "-o-transform-origin",
+ "transform-origin",
+ "-webkit-animation",
+ "-moz-animation",
+ "-ms-animation",
+ "-o-animation",
+ "animation",
+ "-webkit-animation-name",
+ "-moz-animation-name",
+ "-ms-animation-name",
+ "-o-animation-name",
+ "animation-name",
+ "-webkit-animation-duration",
+ "-moz-animation-duration",
+ "-ms-animation-duration",
+ "-o-animation-duration",
+ "animation-duration",
+ "-webkit-animation-play-state",
+ "-moz-animation-play-state",
+ "-ms-animation-play-state",
+ "-o-animation-play-state",
+ "animation-play-state",
+ "-webkit-animation-timing-function",
+ "-moz-animation-timing-function",
+ "-ms-animation-timing-function",
+ "-o-animation-timing-function",
+ "animation-timing-function",
+ "-webkit-animation-delay",
+ "-moz-animation-delay",
+ "-ms-animation-delay",
+ "-o-animation-delay",
+ "animation-delay",
+ "-webkit-animation-iteration-count",
+ "-moz-animation-iteration-count",
+ "-ms-animation-iteration-count",
+ "-o-animation-iteration-count",
+ "animation-iteration-count",
+ "-webkit-animation-direction",
+ "-moz-animation-direction",
+ "-ms-animation-direction",
+ "-o-animation-direction",
+ "animation-direction",
+ "pointer-events"
+ ]
+}
\ No newline at end of file
--- /dev/null
+Thanks for reading our conribution guidelines! What do you want to do:
+
+* [File a bug / an issue](#filing-issue)
+* [Contribute to WordPress SEO](#contribute)
+
+<a name="filing-issue"></a>
+#Filing issues
+
+__Please Note:__ GitHub is for bug reports and code contributions only - if you have a support question or a request for a customisation don't post here, go to our [Support Forum](http://wordpress.org/support/plugin/wordpress-seo) instead.
+
+For localization, please refer to [translate.yoast.com](http://translate.yoast.com/projects/wordpress-seo), though bugs with strings that can't be translated are welcome here.
+
+## How to write a useful bug report
+If you think you have found a bug (we acknowledge that that's a possibility), please make sure you're using the latest version of the plugin. If possible check out the latest version from GitHub and see if the bug still exists there.
+
+A useful bug report explains:
+
+1. What you were trying to achieve.
+2. What you were expecting to happen.
+3. What actually happened, illustrated with screenshots if possible.
+
+Your bug report should also contain your WordPress version and if there are any errors, the _exact_ error text, including line numbers.
+
+### Blank / white screen
+If you're getting a blank screen and you report just that, we can do _absolutely_ nothing. By default, your WordPress install suppresses all errors, to prevent information leaks, but we need those errors to be able to help you. If you apply the small piece of code in [this post on WP_DEBUG](https://yoast.com/wordpress-debug/) to your site, you should be able to open the URL that gave you a white screen, append `?debug=debug` to the URL and get the actual error.
+
+That error will help us, without that error, we're completely in the dark about your white page problem...
+
+### Interface errors
+If you're reporting a bug about specific interface elements not working as expected, there's probably an error showing in your browsers JavaScript console. Please open your browsers console and copy the exact error showing there, or make a screenshot. If you don't know how to open your browsers console, here is info for [Chrome](https://developer.chrome.com/devtools/docs/console) and [Firefox](https://developer.mozilla.org/en/docs/Tools/Web_Console). For IE, some Googling will help but it changes with every version.
+
+<a name="contribute"></a>
+#Contribute To WordPress SEO
+
+Community made patches, localisations, bug reports and contributions are very welcome and help make WordPress SEO remains the #1 SEO plugin for WordPress.
+
+When contributing please ensure you follow the guidelines below so that we can keep on top of things.
+
+## Getting Started
+
+If there is no ticket for your issue, submit it first, following the above guidelines.
+
+## Making Changes
+
+* Fork the repository on GitHub.
+* Make the changes to your forked repository.
+ * Ensure you stick to the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/) and have properly documented any new functions, actions and filters following the [documentation standards](http://make.wordpress.org/core/handbook/inline-documentation-standards/php-documentation-standards/).
+* When committing, reference your issue (if present) and include a note about the fix.
+* Push the changes to your fork and submit a pull request to the 'master' branch of the WordPress SEO repository.
+
+At this point you're waiting on us to merge your pull request. We'll review all pull requests, and make suggestions and changes if necessary.
+
+# Additional Resources
+* [WordPress SEO API](https://yoast.com/wordpress/plugins/seo/api/)
+* [General GitHub Documentation](http://help.github.com/)
+* [GitHub Pull Request documentation](http://help.github.com/send-pull-requests/)
--- /dev/null
+WordPress SEO by Yoast
+======================
+
+[](https://travis-ci.org/Yoast/wordpress-seo)
+[](https://packagist.org/packages/yoast/wordpress-seo)
+[](https://packagist.org/packages/yoast/wordpress-seo)
+
+Welcome to the WordPress SEO Github repository
+----------------------------------------------
+
+While the documenation for the [WordPress SEO plugin](https://yoast.com/wordpress/seo/) can be found on yoast.com, here
+you can browse the source of the project, find and discuss open issues and even
+[contribute yourself](https://github.com/yoast/wordpress-seo/blob/master/CONTRIBUTING.md).
+
+Installation
+------------
+
+Here's a [guide on how to install WordPress SEO in your WordPress site](https://yoast.com/wordpress/seo/installation/).
+If you want to run the Git version though, you have two options:
+
+* You can clone the GitHub repository: https://github.com/yoast/wordpress-seo.git
+* Downloading it directly unfortunately won't work as the .zip file doesn't contain the submodules.
+
+This will download the latest development version of WordPress SEO by Yoast. While this version is usually stable,
+it is not recommended for use in a production environment.
+
+Bugs
+----
+If you find an issue, [let us know here](https://github.com/yoast/wordpress-seo/issues/new)!
+
+Support
+-------
+This is a developer's portal for WordPress SEO by Yoast and should not be used for support. Please visit the
+[support forums](https://wordpress.org/support/plugin/wordpress-seo).
+
+Contributions
+-------------
+Anyone is welcome to contribute to WordPress SEO. Please
+[read the guidelines](https://github.com/yoast/wordpress-seo/blob/master/CONTRIBUTING.md) for contributing to this
+repository.
+
+There are various ways you can contribute:
+
+* [Raise an issue](https://github.com/yoast/wordpress-seo/issues) on GitHub.
+* Send us a Pull Request with your bug fixes and/or new features.
+* [Translate WordPress SEO by Yoast into different languages](http://translate.yoast.com/projects/wordpress-seo/).
+* Provide feedback and [suggestions on enhancements](https://github.com/yoast/wordpress-seo/issues?direction=desc&labels=Enhancement&page=1&sort=created&state=open).
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'Yoast_TextStatistics' ) ) {
+ /**
+ * Modified (Reduced) TextStatistics Class
+ *
+ * Mostly removed functionality that isn't needed within the WordPress SEO plugin.
+ *
+ * @link http://code.google.com/p/php-text-statistics/
+ * @link https://github.com/DaveChild/Text-Statistics (new repo location)
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD license
+ *
+ * @todo [JRF => whomever] Research if a class/library can be found which will offer
+ * this functionality to a broader scope of languages/charsets.
+ * Now basically limited to English.
+ */
+ class Yoast_TextStatistics {
+
+ /**
+ * @var string $strEncoding Used to hold character encoding to be used by object, if set
+ */
+ protected $strEncoding = '';
+
+ /**
+ * @var string $blnMbstring Efficiency: Is the MB String extension loaded ?
+ */
+ protected $blnMbstring = true;
+
+ /**
+ * @var bool $normalize Should the result be normalized ?
+ */
+ public $normalize = true;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $strEncoding Optional character encoding.
+ */
+ public function __construct( $strEncoding = '' ) {
+ if ( $strEncoding <> '' ) {
+ // Encoding is given. Use it!
+ $this->strEncoding = $strEncoding;
+ }
+ $this->blnMbstring = extension_loaded( 'mbstring' );
+ }
+
+ /**
+ * Gives the Flesch-Kincaid Reading Ease of text entered rounded to one digit
+ *
+ * @param string $strText Text to be checked
+ * @return int|float
+ */
+ public function flesch_kincaid_reading_ease( $strText ) {
+ $strText = $this->clean_text( $strText );
+ $score = wpseo_calc( wpseo_calc( 206.835, '-', wpseo_calc( 1.015, '*', $this->average_words_per_sentence( $strText ) ) ), '-', wpseo_calc( 84.6, '*', $this->average_syllables_per_word( $strText ) ) );
+
+ return $this->normalize_score( $score, 0, 100 );
+ }
+
+ /**
+ * Gives string length.
+ *
+ * @param string $strText Text to be measured
+ *
+ * @return int
+ */
+ public function text_length( $strText ) {
+ if ( ! $this->blnMbstring ) {
+ return strlen( $strText );
+ }
+
+ try {
+ if ( $this->strEncoding == '' ) {
+ $intTextLength = mb_strlen( $strText );
+ } else {
+ $intTextLength = mb_strlen( $strText, $this->strEncoding );
+ }
+ } catch ( Exception $e ) {
+ $intTextLength = strlen( $strText );
+ }
+
+ return $intTextLength;
+ }
+
+ /**
+ * Gives letter count (ignores all non-letters). Tries mb_strlen and if that fails uses regular strlen.
+ *
+ * @param string $strText Text to be measured
+ *
+ * @return int
+ */
+ public function letter_count( $strText ) {
+ $strText = $this->clean_text( $strText ); // To clear out newlines etc
+ $strText = preg_replace( '`[^A-Za-z]+`', '', $strText );
+
+ if ( ! $this->blnMbstring ) {
+ return strlen( $strText );
+ }
+
+ try {
+ if ( $this->strEncoding == '' ) {
+ $intTextLength = mb_strlen( $strText );
+ } else {
+ $intTextLength = mb_strlen( $strText, $this->strEncoding );
+ }
+ } catch ( Exception $e ) {
+ $intTextLength = strlen( $strText );
+ }
+
+ return $intTextLength;
+ }
+
+ /**
+ * Trims, removes line breaks, multiple spaces and generally cleans text before processing.
+ *
+ * @param string $strText Text to be transformed
+ * @return string
+ */
+ protected function clean_text( $strText ) {
+ static $clean = array();
+
+ $key = sha1( $strText );
+
+ if ( isset( $clean[ $key ] ) ) {
+ return $clean[ $key ];
+ }
+
+ // all these tags should be preceeded by a full stop.
+ $fullStopTags = array( 'li', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dd' );
+ foreach ( $fullStopTags as $tag ) {
+ $strText = str_ireplace( '</' . $tag . '>', '.', $strText );
+ }
+ $strText = strip_tags( $strText );
+ $strText = preg_replace( '`[",:;\(\)-]`', ' ', $strText ); // Replace commas, hyphens etc (count them as spaces)
+ $strText = preg_replace( '`[\.!?]`', '.', $strText ); // Unify terminators
+ $strText = trim( $strText ) . '.'; // Add final terminator, just in case it's missing.
+ $strText = preg_replace( '`[ ]*(\n|\r\n|\r)[ ]*`', ' ', $strText ); // Replace new lines with spaces
+ $strText = preg_replace( '`([\.])[\. ]+`', '$1', $strText ); // Check for duplicated terminators
+ $strText = trim( preg_replace( '`[ ]*([\.])`', '$1 ', $strText ) ); // Pad sentence terminators
+ $strText = preg_replace( '` [0-9]+ `', ' ', ' ' . $strText . ' ' ); // Remove "words" comprised only of numbers
+ $strText = preg_replace( '`[ ]+`', ' ', $strText ); // Remove multiple spaces
+ $strText = preg_replace_callback( '`\. [^ ]+?`', create_function( '$matches', 'return strtolower( $matches[0] );' ), $strText ); // Lower case all words following terminators (for gunning fog score)
+
+ $strText = trim( $strText );
+
+ // Cache it and return
+ $clean[ $key ] = $strText;
+ return $strText;
+ }
+
+ /**
+ * Converts string to lower case. Tries mb_strtolower and if that fails uses regular strtolower.
+ *
+ * @param string $strText Text to be transformed
+ * @return string
+ */
+ protected function lower_case( $strText ) {
+
+ if ( ! $this->blnMbstring ) {
+ return strtolower( $strText );
+ }
+
+ try {
+ if ( $this->strEncoding == '' ) {
+ $strLowerCaseText = mb_strtolower( $strText );
+ } else {
+ $strLowerCaseText = mb_strtolower( $strText, $this->strEncoding );
+ }
+ } catch ( Exception $e ) {
+ $strLowerCaseText = strtolower( $strText );
+ }
+
+ return $strLowerCaseText;
+ }
+
+ /**
+ * Converts string to upper case. Tries mb_strtoupper and if that fails uses regular strtoupper.
+ *
+ * @param string $strText Text to be transformed
+ * @return string
+ */
+ protected function upper_case( $strText ) {
+ if ( ! $this->blnMbstring ) {
+ return strtoupper( $strText );
+ }
+
+ try {
+ if ( $this->strEncoding == '' ) {
+ $strUpperCaseText = mb_strtoupper( $strText );
+ } else {
+ $strUpperCaseText = mb_strtoupper( $strText, $this->strEncoding );
+ }
+ } catch ( Exception $e ) {
+ $strUpperCaseText = strtoupper( $strText );
+ }
+
+ return $strUpperCaseText;
+ }
+
+ /**
+ * Returns sentence count for text.
+ *
+ * @param string $strText Text to be measured
+ * @return int
+ */
+ public function sentence_count( $strText ) {
+ if ( strlen( trim( $strText ) ) == 0 ) {
+ return 0;
+ }
+
+ $strText = $this->clean_text( $strText );
+ // Will be tripped up by "Mr." or "U.K.". Not a major concern at this point.
+ // [JRF] Will also be tripped up by ... or ?!
+ // @todo [JRF => whomever] May be replace with something along the lines of this - will at least provide better count in ... and ?! situations:
+ // $intSentences = max( 1, preg_match_all( '`[^\.!?]+[\.!?]+([\s]+|$)`u', $strText, $matches ) ); [/JRF]
+ $intSentences = max( 1, $this->text_length( preg_replace( '`[^\.!?]`', '', $strText ) ) );
+ return $intSentences;
+ }
+
+ /**
+ * Returns word count for text.
+ *
+ * @param string $strText Text to be measured
+ * @return int
+ */
+ public function word_count( $strText ) {
+ if ( strlen( trim( $strText ) ) == 0 ) {
+ return 0;
+ }
+
+ $strText = $this->clean_text( $strText );
+ // Will be tripped by em dashes with spaces either side, among other similar characters
+ $intWords = 1 + $this->text_length( preg_replace( '`[^ ]`', '', $strText ) ); // Space count + 1 is word count
+ return $intWords;
+ }
+
+ /**
+ * Returns average words per sentence for text.
+ *
+ * @param string $strText Text to be measured
+ * @return int|float
+ */
+ public function average_words_per_sentence( $strText ) {
+ $strText = $this->clean_text( $strText );
+ $intSentenceCount = $this->sentence_count( $strText );
+ $intWordCount = $this->word_count( $strText );
+ return ( wpseo_calc( $intWordCount, '/', $intSentenceCount ) );
+ }
+
+ /**
+ * Returns average syllables per word for text.
+ *
+ * @param string $strText Text to be measured
+ * @return int|float
+ */
+ public function average_syllables_per_word( $strText ) {
+ $strText = $this->clean_text( $strText );
+ $intSyllableCount = 0;
+ $intWordCount = $this->word_count( $strText );
+ $arrWords = explode( ' ', $strText );
+ for ( $i = 0; $i < $intWordCount; $i++ ) {
+ $intSyllableCount += $this->syllable_count( $arrWords[ $i ] );
+ }
+ return ( wpseo_calc( $intSyllableCount, '/', $intWordCount ) );
+ }
+
+ /**
+ * Returns the number of syllables in the word.
+ * Based in part on Greg Fast's Perl module Lingua::EN::Syllables
+ *
+ * @param string $strWord Word to be measured
+ * @return int
+ */
+ public function syllable_count( $strWord ) {
+ if ( strlen( trim( $strWord ) ) == 0 ) {
+ return 0;
+ }
+
+ // Should be no non-alpha characters
+ $strWord = preg_replace( '`[^A-Za-z]`', '', $strWord );
+
+ $intSyllableCount = 0;
+ $strWord = $this->lower_case( $strWord );
+
+ // Specific common exceptions that don't follow the rule set below are handled individually
+ // Array of problem words (with word as key, syllable count as value)
+ $arrProblemWords = array(
+ 'simile' => 3,
+ 'forever' => 3,
+ 'shoreline' => 2,
+ );
+ if ( isset( $arrProblemWords[ $strWord ] ) ) {
+ $intSyllableCount = $arrProblemWords[ $strWord ];
+ }
+ if ( $intSyllableCount > 0 ) {
+ return $intSyllableCount;
+ }
+
+ // These syllables would be counted as two but should be one
+ $arrSubSyllables = array(
+ 'cial',
+ 'tia',
+ 'cius',
+ 'cious',
+ 'giu',
+ 'ion',
+ 'iou',
+ 'sia$',
+ '[^aeiuoyt]{2,}ed$',
+ '.ely$',
+ '[cg]h?e[rsd]?$',
+ 'rved?$',
+ '[aeiouy][dt]es?$',
+ '[aeiouy][^aeiouydt]e[rsd]?$',
+ // Sorts out deal, deign etc
+ '^[dr]e[aeiou][^aeiou]+$',
+ // Purse, hearse
+ '[aeiouy]rse$',
+ );
+
+ // These syllables would be counted as one but should be two
+ $arrAddSyllables = array(
+ 'ia',
+ 'riet',
+ 'dien',
+ 'iu',
+ 'io',
+ 'ii',
+ '[aeiouym]bl$',
+ '[aeiou]{3}',
+ '^mc',
+ 'ism$',
+ '([^aeiouy])\1l$',
+ '[^l]lien',
+ '^coa[dglx].',
+ '[^gq]ua[^auieo]',
+ 'dnt$',
+ 'uity$',
+ 'ie(r|st)$',
+ );
+
+ // Single syllable prefixes and suffixes
+ $arrPrefixSuffix = array(
+ '`^un`',
+ '`^fore`',
+ '`ly$`',
+ '`less$`',
+ '`ful$`',
+ '`ers?$`',
+ '`ings?$`',
+ );
+
+ // Remove prefixes and suffixes and count how many were taken
+ $strWord = preg_replace( $arrPrefixSuffix, '', $strWord, -1, $intPrefixSuffixCount );
+
+ // Removed non-word characters from word
+ $strWord = preg_replace( '`[^a-z]`is', '', $strWord );
+ $arrWordParts = preg_split( '`[^aeiouy]+`', $strWord );
+ $intWordPartCount = 0;
+ foreach ( $arrWordParts as $strWordPart ) {
+ if ( $strWordPart <> '' ) {
+ $intWordPartCount++;
+ }
+ }
+
+ // Some syllables do not follow normal rules - check for them
+ // Thanks to Joe Kovar for correcting a bug in the following lines
+ $intSyllableCount = $intWordPartCount + $intPrefixSuffixCount;
+ foreach ( $arrSubSyllables as $strSyllable ) {
+ $intSyllableCount -= preg_match( '`' . $strSyllable . '`', $strWord );
+ }
+ foreach ( $arrAddSyllables as $strSyllable ) {
+ $intSyllableCount += preg_match( '`' . $strSyllable . '`', $strWord );
+ }
+ $intSyllableCount = ( $intSyllableCount == 0 ) ? 1 : $intSyllableCount;
+ return $intSyllableCount;
+ }
+
+ /**
+ * Normalizes score according to min & max allowed. If score larger
+ * than max, max is returned. If score less than min, min is returned.
+ * Also rounds result to specified precision.
+ * Thanks to github.com/lvil.
+ *
+ * @param int|float $score Initial score
+ * @param int $min Minimum score allowed
+ * @param int $max Maximum score allowed
+ * @return int|float
+ */
+ public function normalize_score( $score, $min, $max, $dps = 1 ) {
+ $score = wpseo_calc( $score, '+', 0, true, $dps ); // Round
+ if ( ! $this->normalize ) {
+ return $score;
+ }
+
+ if ( $score > $max ) {
+ $score = $max;
+ } elseif ( $score < $min ) {
+ $score = $min;
+ }
+
+ return $score;
+ }
+
+ } /* End of class */
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+/**
+ * Function used from AJAX calls, takes it variables from $_POST, dies on exit.
+ */
+function wpseo_set_option() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ die( '-1' );
+ }
+
+ check_ajax_referer( 'wpseo-setoption' );
+
+ $option = sanitize_text_field( $_POST['option'] );
+ if ( $option !== 'page_comments' ) {
+ die( '-1' );
+ }
+
+ update_option( $option, 0 );
+ die( '1' );
+}
+
+add_action( 'wp_ajax_wpseo_set_option', 'wpseo_set_option' );
+
+/**
+ * Function used to remove the admin notices for several purposes, dies on exit.
+ */
+function wpseo_set_ignore() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ die( '-1' );
+ }
+
+ check_ajax_referer( 'wpseo-ignore' );
+
+ $options = get_option( 'wpseo' );
+ $ignore_key = sanitize_text_field( $_POST['option'] );
+ $options[ 'ignore_' . $ignore_key ] = true;
+ update_option( 'wpseo', $options );
+ die( '1' );
+}
+
+add_action( 'wp_ajax_wpseo_set_ignore', 'wpseo_set_ignore' );
+
+/**
+ * Function used to delete blocking files, dies on exit.
+ */
+function wpseo_kill_blocking_files() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ die( '-1' );
+ }
+
+ check_ajax_referer( 'wpseo-blocking-files' );
+
+ $message = 'There were no files to delete.';
+ $options = get_option( 'wpseo' );
+ if ( is_array( $options['blocking_files'] ) && $options['blocking_files'] !== array() ) {
+ $message = 'success';
+ $files_removed = 0;
+ foreach ( $options['blocking_files'] as $k => $file ) {
+ if ( ! @unlink( $file ) ) {
+ $message = __( 'Some files could not be removed. Please remove them via FTP.', 'wordpress-seo' );
+ }
+ else {
+ unset( $options['blocking_files'][ $k ] );
+ $files_removed++;
+ }
+ }
+ if ( $files_removed > 0 ) {
+ update_option( 'wpseo', $options );
+ }
+ }
+
+ die( $message );
+}
+
+add_action( 'wp_ajax_wpseo_kill_blocking_files', 'wpseo_kill_blocking_files' );
+
+/**
+ * Retrieve the suggestions from the Google Suggest API and return them to be
+ * used in the suggest box within the plugin. Dies on exit.
+ */
+function wpseo_get_suggest() {
+ check_ajax_referer( 'wpseo-get-suggest' );
+
+ $term = urlencode( $_GET['term'] );
+ $result = wp_remote_get( 'https://www.google.com/complete/search?output=toolbar&q=' . $term );
+
+ $return_arr = array();
+
+ if ( ! is_wp_error( $result ) ) {
+ preg_match_all( '`suggestion data="([^"]+)"/>`u', $result['body'], $matches );
+
+ if ( isset( $matches[1] ) && ( is_array( $matches[1] ) && $matches[1] !== array() ) ) {
+ foreach ( $matches[1] as $match ) {
+ $return_arr[] = html_entity_decode( $match, ENT_COMPAT, 'UTF-8' );
+ }
+ }
+ }
+ echo json_encode( $return_arr );
+ die();
+}
+
+add_action( 'wp_ajax_wpseo_get_suggest', 'wpseo_get_suggest' );
+
+/**
+ * Used in the editor to replace vars for the snippet preview
+ */
+function wpseo_ajax_replace_vars() {
+ check_ajax_referer( 'wpseo-replace-vars' );
+
+ $post = get_post( $_POST['post_id'] );
+ $omit = array( 'excerpt', 'excerpt_only', 'title' );
+ echo wpseo_replace_vars( stripslashes( $_POST['string'] ), $post, $omit );
+ die;
+}
+
+add_action( 'wp_ajax_wpseo_replace_vars', 'wpseo_ajax_replace_vars' );
+
+/**
+ * Save an individual SEO title from the Bulk Editor.
+ */
+function wpseo_save_title() {
+ check_ajax_referer( 'wpseo-bulk-editor' );
+
+ $new_title = $_POST['new_title'];
+ $id = intval( $_POST['wpseo_post_id'] );
+ $original_title = $_POST['existing_title'];
+
+ $results = wpseo_upsert_new_title( $id, $new_title, $original_title );
+
+ echo json_encode( $results );
+ die();
+}
+
+add_action( 'wp_ajax_wpseo_save_title', 'wpseo_save_title' );
+
+/**
+ * Helper function for updating an existing seo title or create a new one
+ * if it doesn't already exist.
+ */
+function wpseo_upsert_new_title( $post_id, $new_title, $original_title ) {
+
+ $meta_key = WPSEO_Meta::$meta_prefix . 'title';
+ $return_key = 'title';
+ return wpseo_upsert_meta( $post_id, $new_title, $original_title, $meta_key, $return_key );
+}
+
+/**
+ * Helper function to update a post's meta data, returning relevant information
+ * about the information updated and the results or the meta update.
+ */
+function wpseo_upsert_meta( $post_id, $new_meta_value, $orig_meta_value, $meta_key, $return_key ) {
+
+ $post_id = intval( $post_id );
+ $sanitized_new_meta_value = wp_strip_all_tags( $new_meta_value );
+ $orig_meta_value = wp_strip_all_tags( $orig_meta_value );
+
+ $upsert_results = array(
+ 'status' => 'success',
+ 'post_id' => $post_id,
+ "new_{$return_key}" => $new_meta_value,
+ "original_{$return_key}" => $orig_meta_value,
+ );
+
+ $the_post = get_post( $post_id );
+ if ( empty( $the_post ) ) {
+
+ $upsert_results['status'] = 'failure';
+ $upsert_results['results'] = __( 'Post doesn\'t exist.', 'wordpress-seo' );
+
+ return $upsert_results;
+ }
+
+ $post_type_object = get_post_type_object( $the_post->post_type );
+ if ( ! $post_type_object ) {
+
+ $upsert_results['status'] = 'failure';
+ $upsert_results['results'] = sprintf( __( 'Post has an invalid Post Type: %s.', 'wordpress-seo' ), $the_post->post_type );
+
+ return $upsert_results;
+ }
+
+ if ( ! current_user_can( $post_type_object->cap->edit_posts ) ) {
+
+ $upsert_results['status'] = 'failure';
+ $upsert_results['results'] = sprintf( __( 'You can\'t edit %s.', 'wordpress-seo' ), $post_type_object->label );
+
+ return $upsert_results;
+ }
+
+ if ( ! current_user_can( $post_type_object->cap->edit_others_posts ) && $the_post->post_author != get_current_user_id() ) {
+
+ $upsert_results['status'] = 'failure';
+ $upsert_results['results'] = sprintf( __( 'You can\'t edit %s that aren\'t yours.', 'wordpress-seo' ), $post_type_object->label );
+
+ return $upsert_results;
+
+ }
+
+ if ( $sanitized_new_meta_value === $orig_meta_value && $sanitized_new_meta_value !== $new_meta_value ) {
+ $upsert_results['status'] = 'failure';
+ $upsert_results['results'] = __( 'You have used HTML in your value which is not allowed.', 'wordpress-seo' );
+
+ return $upsert_results;
+ }
+
+ $res = update_post_meta( $post_id, $meta_key, $sanitized_new_meta_value );
+
+ $upsert_results['status'] = ( $res !== false ) ? 'success' : 'failure';
+ $upsert_results['results'] = $res;
+
+ return $upsert_results;
+}
+
+/**
+ * Save all titles sent from the Bulk Editor.
+ */
+function wpseo_save_all_titles() {
+ check_ajax_referer( 'wpseo-bulk-editor' );
+
+ $new_titles = $_POST['titles'];
+ $original_titles = $_POST['existing_titles'];
+
+ $results = array();
+
+ if ( is_array( $new_titles ) && $new_titles !== array() ) {
+ foreach ( $new_titles as $id => $new_title ) {
+ $original_title = $original_titles[ $id ];
+ $results[] = wpseo_upsert_new_title( $id, $new_title, $original_title );
+ }
+ }
+ echo json_encode( $results );
+ die();
+}
+
+add_action( 'wp_ajax_wpseo_save_all_titles', 'wpseo_save_all_titles' );
+
+/**
+ * Save an individual meta description from the Bulk Editor.
+ */
+function wpseo_save_description() {
+ check_ajax_referer( 'wpseo-bulk-editor' );
+
+ $new_metadesc = $_POST['new_metadesc'];
+ $id = intval( $_POST['wpseo_post_id'] );
+ $original_metadesc = $_POST['existing_metadesc'];
+
+ $results = wpseo_upsert_new_description( $id, $new_metadesc, $original_metadesc );
+
+ echo json_encode( $results );
+ die();
+}
+
+add_action( 'wp_ajax_wpseo_save_desc', 'wpseo_save_description' );
+
+/**
+ * Helper function to create or update a post's meta description.
+ */
+function wpseo_upsert_new_description( $post_id, $new_metadesc, $original_metadesc ) {
+
+ $meta_key = WPSEO_Meta::$meta_prefix . 'metadesc';
+ $return_key = 'metadesc';
+ return wpseo_upsert_meta( $post_id, $new_metadesc, $original_metadesc, $meta_key, $return_key );
+}
+
+/**
+ * Save all description sent from the Bulk Editor.
+ */
+function wpseo_save_all_descriptions() {
+ check_ajax_referer( 'wpseo-bulk-editor' );
+
+ $new_metadescs = $_POST['metadescs'];
+ $original_metadescs = $_POST['existing_metadescs'];
+
+ $results = array();
+
+ if ( is_array( $new_metadescs ) && $new_metadescs !== array() ) {
+ foreach ( $new_metadescs as $id => $new_metadesc ) {
+ $original_metadesc = $original_metadescs[ $id ];
+ $results[] = wpseo_upsert_new_description( $id, $new_metadesc, $original_metadesc );
+ }
+ }
+ echo json_encode( $results );
+ die();
+}
+
+add_action( 'wp_ajax_wpseo_save_all_descs', 'wpseo_save_all_descriptions' );
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Admin' ) ) {
+ /**
+ * Class that holds most of the admin functionality for WP SEO.
+ */
+ class WPSEO_Admin {
+
+ /**
+ * Class constructor
+ */
+ function __construct() {
+ $options = WPSEO_Options::get_all();
+
+ if ( is_multisite() ) {
+ WPSEO_Options::maybe_set_multisite_defaults( false );
+ }
+
+ if ( $options['stripcategorybase'] === true ) {
+ add_action( 'created_category', array( $this, 'schedule_rewrite_flush' ) );
+ add_action( 'edited_category', array( $this, 'schedule_rewrite_flush' ) );
+ add_action( 'delete_category', array( $this, 'schedule_rewrite_flush' ) );
+ }
+
+ // Needs the lower than default priority so other plugins can hook underneath it without issue.
+ add_action( 'admin_menu', array( $this, 'register_settings_page' ), 5 );
+ add_action( 'network_admin_menu', array( $this, 'register_network_settings_page' ) );
+
+ add_filter( 'plugin_action_links_' . WPSEO_BASENAME, array( $this, 'add_action_link' ), 10, 2 );
+
+ add_action( 'admin_enqueue_scripts', array( $this, 'config_page_scripts' ) );
+
+ if ( '0' == get_option( 'blog_public' ) ) {
+ add_action( 'admin_footer', array( $this, 'blog_public_warning' ) );
+ }
+
+ if ( ( ( isset( $options['theme_has_description'] ) && $options['theme_has_description'] === true ) || $options['theme_description_found'] !== '' ) && $options['ignore_meta_description_warning'] !== true ) {
+ add_action( 'admin_footer', array( $this, 'meta_description_warning' ) );
+ }
+
+ if ( $options['cleanslugs'] === true ) {
+ add_filter( 'name_save_pre', array( $this, 'remove_stopwords_from_slug' ), 0 );
+ }
+
+ add_action( 'show_user_profile', array( $this, 'user_profile' ) );
+ add_action( 'edit_user_profile', array( $this, 'user_profile' ) );
+ add_action( 'personal_options_update', array( $this, 'process_user_option_update' ) );
+ add_action( 'edit_user_profile_update', array( $this, 'process_user_option_update' ) );
+ add_action( 'personal_options_update', array( $this, 'update_user_profile' ) );
+ add_action( 'edit_user_profile_update', array( $this, 'update_user_profile' ) );
+
+ add_filter( 'user_contactmethods', array( $this, 'update_contactmethods' ), 10, 1 );
+
+ add_action( 'after_switch_theme', array( $this, 'switch_theme' ) );
+ add_action( 'switch_theme', array( $this, 'switch_theme' ) );
+
+ add_filter( 'set-screen-option', array( $this, 'save_bulk_edit_options' ), 10, 3 );
+
+ add_filter( 'upgrader_post_install', array( $this, 'remove_transients_on_update' ), 10, 1 );
+ }
+
+ /**
+ * Schedules a rewrite flush to happen at shutdown
+ */
+ function schedule_rewrite_flush() {
+ add_action( 'shutdown', 'flush_rewrite_rules' );
+ }
+
+
+ /**
+ * Register the menu item and its sub menu's.
+ *
+ * @global array $submenu used to change the label on the first item.
+ */
+ function register_settings_page() {
+ if ( WPSEO_Options::grant_access() !== true ) {
+ return;
+ }
+
+ // Base 64 encoded SVG image
+ $icon_svg = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCIgWw0KCTwhRU5USVRZIG5zX2Zsb3dzICJodHRwOi8vbnMuYWRvYmUuY29tL0Zsb3dzLzEuMC8iPg0KCTwhRU5USVRZIG5zX2V4dGVuZCAiaHR0cDovL25zLmFkb2JlLmNvbS9FeHRlbnNpYmlsaXR5LzEuMC8iPg0KCTwhRU5USVRZIG5zX2FpICJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlSWxsdXN0cmF0b3IvMTAuMC8iPg0KCTwhRU5USVRZIG5zX2dyYXBocyAiaHR0cDovL25zLmFkb2JlLmNvbS9HcmFwaHMvMS4wLyI+DQpdPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYWFnXzEiIHhtbG5zOng9IiZuc19leHRlbmQ7IiB4bWxuczppPSImbnNfYWk7IiB4bWxuczpncmFwaD0iJm5zX2dyYXBoczsiDQoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOmE9Imh0dHA6Ly9ucy5hZG9iZS5jb20vQWRvYmVTVkdWaWV3ZXJFeHRlbnNpb25zLzMuMC8iDQoJIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDAgMzEuODkiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQwIDMxLjg5IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTQwLDEyLjUyNEM0MCw1LjYwOCwzMS40NjksMCwyMCwwQzguNTMsMCwwLDUuNjA4LDAsMTIuNTI0YzAsNS41Niw1LjI0MywxMC4yNzIsMTMuNTU3LDExLjkwN3YtNC4wNjUNCgljMCwwLDAuMDQtMS0wLjI4LTEuOTJjLTAuMzItMC45MjEtMS43Ni0zLjAwMS0xLjc2LTUuMTIxYzAtMi4xMjEsMi41NjEtOS41NjMsNS4xMjItMTAuNDQ0Yy0wLjQsMS4yMDEtMC4zMiw3LjY4My0wLjMyLDcuNjgzDQoJczEuNCwyLjcyLDQuNjQxLDIuNzJjMy4yNDIsMCw0LjUxMS0xLjc2LDQuNzE1LTIuMmMwLjIwNi0wLjQ0LDAuODQ2LTguNzIzLDAuODQ2LTguNzIzczQuMDgyLDQuNDAyLDMuNjgyLDkuMzYzDQoJYy0wLjQwMSw0Ljk2Mi00LjQ4Miw3LjIwMy02LjEyMiw5LjEyM2MtMS4yODYsMS41MDUtMi4yMjQsMy4xMy0yLjYyOSw0LjE2OGMwLjgwMS0wLjAzNCwxLjU4Ny0wLjA5OCwyLjM2MS0wLjE4NGw5LjE1MSw3LjA1OQ0KCWwtNC44ODQtNy44M0MzNS41MzUsMjIuMTYxLDQwLDE3LjcxMyw0MCwxMi41MjR6Ii8+DQo8L2c+DQo8L3N2Zz4=';
+
+ // Add main page
+ $admin_page = add_menu_page( __( 'Yoast WordPress SEO:', 'wordpress-seo' ) . ' ' . __( 'General Settings', 'wordpress-seo' ), __( 'SEO', 'wordpress-seo' ), 'manage_options', 'wpseo_dashboard', array(
+ $this,
+ 'load_page',
+ ), $icon_svg, '99.31337' );
+
+ /**
+ * Filter: 'wpseo_manage_options_capability' - Allow changing the capability users need to view the settings pages
+ *
+ * @api string unsigned The capability
+ */
+ $manage_options_cap = apply_filters( 'wpseo_manage_options_capability', 'manage_options' );
+
+ // Sub menu pages
+ $submenu_pages = array(
+ array(
+ 'wpseo_dashboard',
+ '',
+ __( 'Titles & Metas', 'wordpress-seo' ),
+ $manage_options_cap,
+ 'wpseo_titles',
+ array( $this, 'load_page' ),
+ array( array( $this, 'title_metas_help_tab' ) ),
+ ),
+ array(
+ 'wpseo_dashboard',
+ '',
+ __( 'Social', 'wordpress-seo' ),
+ $manage_options_cap,
+ 'wpseo_social',
+ array( $this, 'load_page' ),
+ null,
+ ),
+ array(
+ 'wpseo_dashboard',
+ '',
+ __( 'XML Sitemaps', 'wordpress-seo' ),
+ $manage_options_cap,
+ 'wpseo_xml',
+ array( $this, 'load_page' ),
+ null,
+ ),
+ array(
+ 'wpseo_dashboard',
+ '',
+ __( 'Permalinks', 'wordpress-seo' ),
+ $manage_options_cap,
+ 'wpseo_permalinks',
+ array( $this, 'load_page' ),
+ null,
+ ),
+ array(
+ 'wpseo_dashboard',
+ '',
+ __( 'Internal Links', 'wordpress-seo' ),
+ $manage_options_cap,
+ 'wpseo_internal-links',
+ array( $this, 'load_page' ),
+ null,
+ ),
+ array(
+ 'wpseo_dashboard',
+ '',
+ __( 'RSS', 'wordpress-seo' ),
+ $manage_options_cap,
+ 'wpseo_rss',
+ array( $this, 'load_page' ),
+ null,
+ ),
+ array(
+ 'wpseo_dashboard',
+ '',
+ __( 'Import & Export', 'wordpress-seo' ),
+ $manage_options_cap,
+ 'wpseo_import',
+ array( $this, 'load_page' ),
+ null,
+ ),
+ array(
+ 'wpseo_dashboard',
+ '',
+ __( 'Bulk Editor', 'wordpress-seo' ),
+ 'wpseo_bulk_edit',
+ 'wpseo_bulk-editor',
+ array( $this, 'load_page' ),
+ array( array( $this, 'bulk_edit_options' ) ),
+ ),
+ );
+
+ // Check where to add the edit files page
+ if ( wpseo_allow_system_file_edit() === true && ! is_multisite() ) {
+ $submenu_pages[] = array(
+ 'wpseo_dashboard',
+ '',
+ __( 'Edit Files', 'wordpress-seo' ),
+ $manage_options_cap,
+ 'wpseo_files',
+ array( $this, 'load_page' ),
+ );
+ }
+
+ // Add Extension submenu page
+ $submenu_pages[] = array(
+ 'wpseo_dashboard',
+ '',
+ '<span style="color:#f18500">' . __( 'Extensions', 'wordpress-seo' ) . '</span>',
+ $manage_options_cap,
+ 'wpseo_licenses',
+ array( $this, 'load_page' ),
+ null,
+ );
+
+ // Allow submenu pages manipulation
+ $submenu_pages = apply_filters( 'wpseo_submenu_pages', $submenu_pages );
+
+ // Loop through submenu pages and add them
+ if ( count( $submenu_pages ) ) {
+ foreach ( $submenu_pages as $submenu_page ) {
+
+ // Add submenu page
+ $admin_page = add_submenu_page( $submenu_page[0], $submenu_page[2] . ' - ' . __( 'Yoast WordPress SEO:', 'wordpress-seo' ), $submenu_page[2], $submenu_page[3], $submenu_page[4], $submenu_page[5] );
+
+ // Check if we need to hook
+ if ( isset( $submenu_page[6] ) && null != $submenu_page[6] && is_array( $submenu_page[6] ) && count( $submenu_page[6] ) > 0 ) {
+ foreach ( $submenu_page[6] as $submenu_page_action ) {
+ add_action( 'load-' . $admin_page, $submenu_page_action );
+ }
+ }
+ }
+ }
+
+ global $submenu;
+ if ( isset( $submenu['wpseo_dashboard'] ) && current_user_can( $manage_options_cap ) ) {
+ $submenu['wpseo_dashboard'][0][0] = __( 'Dashboard', 'wordpress-seo' );
+ }
+ }
+
+ /**
+ * Adds contextual help to the titles & metas page.
+ */
+ function title_metas_help_tab() {
+ $screen = get_current_screen();
+
+ $screen->set_help_sidebar(
+ '<p><strong>' . __( 'For more information:', 'wordpress-seo' ) . '</strong></p>' .
+ '<p><a target="_blank" href="https://yoast.com/articles/wordpress-seo/#titles">' . __( 'Title optimization', 'wordpress-seo' ) . '</a></p>' .
+ '<p><a target="_blank" href="https://yoast.com/google-page-title/">' . __( 'Why Google won\'t display the right page title', 'wordpress-seo' ) . '</a></p>'
+ );
+
+ $screen->add_help_tab(
+ array(
+ 'id' => 'basic-help',
+ 'title' => __( 'Template explanation', 'wordpress-seo' ),
+ 'content' => '<p>' . __( 'The title & metas settings for WordPress SEO are made up of variables that are replaced by specific values from the page when the page is displayed. The tabs on the left explain the available variables.', 'wordpress-seo' ) . '</p>',
+ )
+ );
+
+
+ $screen->add_help_tab(
+ array(
+ 'id' => 'title-vars',
+ 'title' => __( 'Basic Variables', 'wordpress-seo' ),
+ 'content' => "\n\t\t<h2>" . __( 'Basic Variables', 'wordpress-seo' ) . "</h2>\n\t\t" . WPSEO_Replace_Vars::get_basic_help_texts(),
+ )
+ );
+
+ $screen->add_help_tab(
+ array(
+ 'id' => 'title-vars-advanced',
+ 'title' => __( 'Advanced Variables', 'wordpress-seo' ),
+ 'content' => "\n\t\t<h2>" . __( 'Advanced Variables', 'wordpress-seo' ) . "</h2>\n\t\t" . WPSEO_Replace_Vars::get_advanced_help_texts(),
+ )
+ );
+ }
+
+ /**
+ * Register the settings page for the Network settings.
+ */
+ function register_network_settings_page() {
+ if ( WPSEO_Options::grant_access() ) {
+ // Base 64 encoded SVG image
+ $icon_svg = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCIgWw0KCTwhRU5USVRZIG5zX2Zsb3dzICJodHRwOi8vbnMuYWRvYmUuY29tL0Zsb3dzLzEuMC8iPg0KCTwhRU5USVRZIG5zX2V4dGVuZCAiaHR0cDovL25zLmFkb2JlLmNvbS9FeHRlbnNpYmlsaXR5LzEuMC8iPg0KCTwhRU5USVRZIG5zX2FpICJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlSWxsdXN0cmF0b3IvMTAuMC8iPg0KCTwhRU5USVRZIG5zX2dyYXBocyAiaHR0cDovL25zLmFkb2JlLmNvbS9HcmFwaHMvMS4wLyI+DQpdPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYWFnXzEiIHhtbG5zOng9IiZuc19leHRlbmQ7IiB4bWxuczppPSImbnNfYWk7IiB4bWxuczpncmFwaD0iJm5zX2dyYXBoczsiDQoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOmE9Imh0dHA6Ly9ucy5hZG9iZS5jb20vQWRvYmVTVkdWaWV3ZXJFeHRlbnNpb25zLzMuMC8iDQoJIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNDAgMzEuODkiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDQwIDMxLjg5IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTQwLDEyLjUyNEM0MCw1LjYwOCwzMS40NjksMCwyMCwwQzguNTMsMCwwLDUuNjA4LDAsMTIuNTI0YzAsNS41Niw1LjI0MywxMC4yNzIsMTMuNTU3LDExLjkwN3YtNC4wNjUNCgljMCwwLDAuMDQtMS0wLjI4LTEuOTJjLTAuMzItMC45MjEtMS43Ni0zLjAwMS0xLjc2LTUuMTIxYzAtMi4xMjEsMi41NjEtOS41NjMsNS4xMjItMTAuNDQ0Yy0wLjQsMS4yMDEtMC4zMiw3LjY4My0wLjMyLDcuNjgzDQoJczEuNCwyLjcyLDQuNjQxLDIuNzJjMy4yNDIsMCw0LjUxMS0xLjc2LDQuNzE1LTIuMmMwLjIwNi0wLjQ0LDAuODQ2LTguNzIzLDAuODQ2LTguNzIzczQuMDgyLDQuNDAyLDMuNjgyLDkuMzYzDQoJYy0wLjQwMSw0Ljk2Mi00LjQ4Miw3LjIwMy02LjEyMiw5LjEyM2MtMS4yODYsMS41MDUtMi4yMjQsMy4xMy0yLjYyOSw0LjE2OGMwLjgwMS0wLjAzNCwxLjU4Ny0wLjA5OCwyLjM2MS0wLjE4NGw5LjE1MSw3LjA1OQ0KCWwtNC44ODQtNy44M0MzNS41MzUsMjIuMTYxLDQwLDE3LjcxMyw0MCwxMi41MjR6Ii8+DQo8L2c+DQo8L3N2Zz4=';
+ add_menu_page( __( 'Yoast WordPress SEO:', 'wordpress-seo' ) . ' ' . __( 'MultiSite Settings', 'wordpress-seo' ), __( 'SEO', 'wordpress-seo' ), 'delete_users', 'wpseo_dashboard', array(
+ $this,
+ 'network_config_page',
+ ), $icon_svg );
+
+ if ( wpseo_allow_system_file_edit() === true ) {
+ add_submenu_page( 'wpseo_dashboard', __( 'Yoast WordPress SEO:', 'wordpress-seo' ) . ' ' . __( 'Edit Files', 'wordpress-seo' ), __( 'Edit Files', 'wordpress-seo' ), 'delete_users', 'wpseo_files', array(
+ $this,
+ 'load_page',
+ ) );
+ }
+
+ // Add Extension submenu page
+ add_submenu_page( 'wpseo_dashboard', __( 'Yoast WordPress SEO:', 'wordpress-seo' ) . ' ' . __( 'Extensions', 'wordpress-seo' ), __( 'Extensions', 'wordpress-seo' ), 'delete_users', 'wpseo_licenses', array(
+ $this,
+ 'load_page',
+ ) );
+ }
+ }
+
+
+ /**
+ * Load the form for a WPSEO admin page
+ */
+ function load_page() {
+ if ( isset( $_GET['page'] ) ) {
+ switch ( $_GET['page'] ) {
+ case 'wpseo_titles':
+ require_once( WPSEO_PATH . 'admin/pages/metas.php' );
+ break;
+
+ case 'wpseo_social':
+ require_once( WPSEO_PATH . 'admin/pages/social.php' );
+ break;
+
+ case 'wpseo_xml':
+ require_once( WPSEO_PATH . 'admin/pages/xml-sitemaps.php' );
+ break;
+
+ case 'wpseo_permalinks':
+ require_once( WPSEO_PATH . 'admin/pages/permalinks.php' );
+ break;
+
+ case 'wpseo_internal-links':
+ require_once( WPSEO_PATH . 'admin/pages/internal-links.php' );
+ break;
+
+ case 'wpseo_rss':
+ require_once( WPSEO_PATH . 'admin/pages/rss.php' );
+ break;
+
+ case 'wpseo_import':
+ require_once( WPSEO_PATH . 'admin/pages/import.php' );
+ break;
+
+ case 'wpseo_files':
+ require_once( WPSEO_PATH . 'admin/pages/files.php' );
+ break;
+
+ case 'wpseo_bulk-editor':
+ require_once( WPSEO_PATH . 'admin/pages/bulk-editor.php' );
+ break;
+
+ case 'wpseo_licenses':
+ require_once( WPSEO_PATH . 'admin/pages/licenses.php' );
+ break;
+
+ case 'wpseo_dashboard':
+ default:
+ require_once( WPSEO_PATH . 'admin/pages/dashboard.php' );
+ break;
+ }
+ }
+ }
+
+
+ /**
+ * Loads the form for the network configuration page.
+ */
+ function network_config_page() {
+ require_once( WPSEO_PATH . 'admin/pages/network.php' );
+ }
+
+
+ /**
+ * Adds the ability to choose how many posts are displayed per page
+ * on the bulk edit pages.
+ */
+ function bulk_edit_options() {
+ $option = 'per_page';
+ $args = array(
+ 'label' => __( 'Posts', 'wordpress-seo' ),
+ 'default' => 10,
+ 'option' => 'wpseo_posts_per_page',
+ );
+ add_screen_option( $option, $args );
+ }
+
+ /**
+ * Saves the posts per page limit for bulk edit pages.
+ */
+ function save_bulk_edit_options( $status, $option, $value ) {
+ if ( 'wpseo_posts_per_page' === $option && ( $value > 0 && $value < 1000 ) ) {
+ return $value;
+ }
+
+ return $status;
+ }
+
+ /**
+ * Display an error message when the blog is set to private.
+ */
+ function blog_public_warning() {
+ if ( ( function_exists( 'is_network_admin' ) && is_network_admin() ) || WPSEO_Options::grant_access() !== true ) {
+ return;
+ }
+
+ $options = get_option( 'wpseo' );
+ if ( $options['ignore_blog_public_warning'] === true ) {
+ return;
+ }
+ echo '<div id="robotsmessage" class="error">';
+ echo '<p><strong>' . __( 'Huge SEO Issue: You\'re blocking access to robots.', 'wordpress-seo' ) . '</strong> ' . sprintf( __( 'You must %sgo to your Reading Settings%s and uncheck the box for Search Engine Visibility.', 'wordpress-seo' ), '<a href="' . esc_url( admin_url( 'options-reading.php' ) ) . '">', '</a>' ) . ' <a href="javascript:wpseo_setIgnore(\'blog_public_warning\',\'robotsmessage\',\'' . esc_js( wp_create_nonce( 'wpseo-ignore' ) ) . '\');" class="button">' . __( 'I know, don\'t bug me.', 'wordpress-seo' ) . '</a></p></div>';
+ }
+
+ /**
+ * Display an error message when the theme contains a meta description tag.
+ *
+ * @since 1.4.14
+ */
+ function meta_description_warning() {
+ if ( ( function_exists( 'is_network_admin' ) && is_network_admin() ) || WPSEO_Options::grant_access() !== true ) {
+ return;
+ }
+
+ // No need to double display it on the dashboard
+ if ( isset( $_GET['page'] ) && 'wpseo_dashboard' === $_GET['page'] ) {
+ return;
+ }
+
+ $options = get_option( 'wpseo' );
+ if ( true === $options['ignore_meta_description_warning'] ) {
+ return;
+ }
+
+ echo '<div id="metamessage" class="error">';
+ echo '<p><strong>' . __( 'SEO Issue:', 'wordpress-seo' ) . '</strong> ' . sprintf( __( 'Your theme contains a meta description, which blocks WordPress SEO from working properly. Please visit the %sSEO Dashboard%s to fix this.', 'wordpress-seo' ), '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_dashboard' ) ) . '">', '</a>' ) . ' <a href="javascript:wpseo_setIgnore(\'meta_description_warning\',\'metamessage\',\'' . esc_js( wp_create_nonce( 'wpseo-ignore' ) ) . '\');" class="button">' . __( 'I know, don\'t bug me.', 'wordpress-seo' ) . '</a></p></div>';
+ }
+
+ /**
+ * Add a link to the settings page to the plugins list
+ *
+ * @staticvar string $this_plugin holds the directory & filename for the plugin
+ *
+ * @param array $links array of links for the plugins, adapted when the current plugin is found.
+ * @param string $file the filename for the current plugin, which the filter loops through.
+ *
+ * @return array $links
+ */
+ function add_action_link( $links, $file ) {
+ if ( WPSEO_BASENAME === $file && WPSEO_Options::grant_access() ) {
+ $settings_link = '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_dashboard' ) ) . '">' . __( 'Settings', 'wordpress-seo' ) . '</a>';
+ array_unshift( $links, $settings_link );
+ }
+
+ if ( class_exists( 'Yoast_Product_WPSEO_Premium' ) ) {
+ $license_manager = new Yoast_Plugin_License_Manager( new Yoast_Product_WPSEO_Premium() );
+ if ( $license_manager->license_is_valid() ) {
+ return $links;
+ }
+ }
+
+ // add link to premium support landing page
+ $premium_link = '<a href="https://yoast.com/wordpress/plugins/seo-premium/support/#utm_source=wordpress-seo-settings-link&utm_medium=text-link&utm_campaign=support-link">' . __( 'Premium Support', 'wordpress-seo' ) . '</a>';
+ array_unshift( $links, $premium_link );
+
+ // add link to docs
+ $faq_link = '<a href="https://yoast.com/wordpress/plugins/seo/faq/">' . __( 'FAQ', 'wordpress-seo' ) . '</a>';
+ array_unshift( $links, $faq_link );
+
+ return $links;
+ }
+
+ /**
+ * Enqueues the (tiny) global JS needed for the plugin.
+ */
+ function config_page_scripts() {
+ if ( WPSEO_Options::grant_access() ) {
+ wp_enqueue_script( 'wpseo-admin-global-script', plugins_url( 'js/wp-seo-admin-global' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_FILE ), array( 'jquery' ), WPSEO_VERSION, true );
+ }
+ }
+
+
+ /**
+ * Updates the user metas that (might) have been set on the user profile page.
+ *
+ * @param int $user_id of the updated user
+ */
+ function process_user_option_update( $user_id ) {
+ if ( isset( $_POST['wpseo_author_title'] ) ) {
+ check_admin_referer( 'wpseo_user_profile_update', 'wpseo_nonce' );
+ update_user_meta( $user_id, 'wpseo_title', ( isset( $_POST['wpseo_author_title'] ) ? WPSEO_Option::sanitize_text_field( $_POST['wpseo_author_title'] ) : '' ) );
+ update_user_meta( $user_id, 'wpseo_metadesc', ( isset( $_POST['wpseo_author_metadesc'] ) ? WPSEO_Option::sanitize_text_field( $_POST['wpseo_author_metadesc'] ) : '' ) );
+ update_user_meta( $user_id, 'wpseo_metakey', ( isset( $_POST['wpseo_author_metakey'] ) ? WPSEO_Option::sanitize_text_field( $_POST['wpseo_author_metakey'] ) : '' ) );
+ update_user_meta( $user_id, 'wpseo_excludeauthorsitemap', ( isset( $_POST['wpseo_author_exclude'] ) ? WPSEO_Option::sanitize_text_field( $_POST['wpseo_author_exclude'] ) : '' ) );
+ }
+ }
+
+ /**
+ * Filter the $contactmethods array and add Facebook, Google+ and Twitter.
+ *
+ * These are used with the Facebook author, rel="author" and Twitter cards implementation.
+ *
+ * @param array $contactmethods currently set contactmethods.
+ *
+ * @return array $contactmethods with added contactmethods.
+ */
+ function update_contactmethods( $contactmethods ) {
+ // Add Google+
+ $contactmethods['googleplus'] = __( 'Google+', 'wordpress-seo' );
+ // Add Twitter
+ $contactmethods['twitter'] = __( 'Twitter username (without @)', 'wordpress-seo' );
+ // Add Facebook
+ $contactmethods['facebook'] = __( 'Facebook profile URL', 'wordpress-seo' );
+
+ return $contactmethods;
+ }
+
+ /**
+ * Add the inputs needed for SEO values to the User Profile page
+ *
+ * @param object $user
+ */
+ function user_profile( $user ) {
+
+ if ( ! current_user_can( 'edit_users' ) ) {
+ return;
+ }
+
+ $options = WPSEO_Options::get_all();
+
+ wp_nonce_field( 'wpseo_user_profile_update', 'wpseo_nonce' );
+ ?>
+ <h3 id="wordpress-seo"><?php _e( 'WordPress SEO settings', 'wordpress-seo' ); ?></h3>
+ <table class="form-table">
+ <tr>
+ <th>
+ <label for="wpseo_author_title"><?php _e( 'Title to use for Author page', 'wordpress-seo' ); ?></label>
+ </th>
+ <td><input class="regular-text" type="text" id="wpseo_author_title" name="wpseo_author_title"
+ value="<?php echo esc_attr( get_the_author_meta( 'wpseo_title', $user->ID ) ); ?>" />
+ </td>
+ </tr>
+ <tr>
+ <th>
+ <label for="wpseo_author_metadesc"><?php _e( 'Meta description to use for Author page', 'wordpress-seo' ); ?></label>
+ </th>
+ <td>
+ <textarea rows="3" cols="30" id="wpseo_author_metadesc" name="wpseo_author_metadesc"><?php echo esc_textarea( get_the_author_meta( 'wpseo_metadesc', $user->ID ) ); ?></textarea>
+ </td>
+ </tr>
+ <?php if ( $options['usemetakeywords'] === true ) { ?>
+ <tr>
+ <th>
+ <label for="wpseo_author_metakey"><?php _e( 'Meta keywords to use for Author page', 'wordpress-seo' ); ?></label>
+ </th>
+ <td>
+ <input class="regular-text" type="text" id="wpseo_author_metakey" name="wpseo_author_metakey" value="<?php echo esc_attr( get_the_author_meta( 'wpseo_metakey', $user->ID ) ); ?>" />
+ </td>
+ </tr>
+ <?php } ?>
+ <tr>
+ <th>
+ <label for="wpseo_author_exclude"><?php _e( 'Exclude user from Author-sitemap', 'wordpress-seo' ); ?></label>
+ </th>
+ <td>
+ <input class="checkbox double" type="checkbox" id="wpseo_author_exclude" name="wpseo_author_exclude" value="on" <?php echo ( ( esc_attr( get_the_author_meta( 'wpseo_excludeauthorsitemap', $user->ID ) ) == 'on' ) ? 'checked' : '' ); ?> />
+ </td>
+ </tr>
+ </table>
+ <br /><br />
+ <?php
+ }
+
+ /**
+ * Cleans stopwords out of the slug, if the slug hasn't been set yet.
+ *
+ * @since 1.1.7
+ *
+ * @param string $slug if this isn't empty, the function will return an unaltered slug.
+ *
+ * @return string $clean_slug cleaned slug
+ */
+ function remove_stopwords_from_slug( $slug ) {
+ // Don't change an existing slug
+ if ( isset( $slug ) && $slug !== '' ) {
+ return $slug;
+ }
+
+ if ( ! isset( $_POST['post_title'] ) ) {
+ return $slug;
+ }
+
+ // Lowercase the slug and strip slashes
+ $clean_slug = sanitize_title( stripslashes( $_POST['post_title'] ) );
+
+ // Turn it to an array and strip stopwords by comparing against an array of stopwords
+ $clean_slug_array = array_diff( explode( '-', $clean_slug ), $this->stopwords() );
+
+ // Turn the sanitized array into a string
+ $clean_slug = join( '-', $clean_slug_array );
+
+ return $clean_slug;
+ }
+
+ /**
+ * Returns the stopwords for the current language
+ *
+ * @since 1.1.7
+ *
+ * @return array $stopwords array of stop words to check and / or remove from slug
+ */
+ function stopwords() {
+ /* translators: this should be an array of stopwords for your language, separated by comma's. */
+ $stopwords = explode( ',', __( "a,about,above,after,again,against,all,am,an,and,any,are,aren't,as,at,be,because,been,before,being,below,between,both,but,by,can't,cannot,could,couldn't,did,didn't,do,does,doesn't,doing,don't,down,during,each,few,for,from,further,had,hadn't,has,hasn't,have,haven't,having,he,he'd,he'll,he's,her,here,here's,hers,herself,him,himself,his,how,how's,i,i'd,i'll,i'm,i've,if,in,into,is,isn't,it,it's,its,itself,let's,me,more,most,mustn't,my,myself,no,nor,not,of,off,on,once,only,or,other,ought,our,ours,ourselves,out,over,own,same,shan't,she,she'd,she'll,she's,should,shouldn't,so,some,such,than,that,that's,the,their,theirs,them,themselves,then,there,there's,these,they,they'd,they'll,they're,they've,this,those,through,to,too,under,until,up,very,was,wasn't,we,we'd,we'll,we're,we've,were,weren't,what,what's,when,when's,where,where's,which,while,who,who's,whom,why,why's,with,won't,would,wouldn't,you,you'd,you'll,you're,you've,your,yours,yourself,yourselves", 'wordpress-seo' ) );
+
+ /**
+ * Allows filtering of the stop words list
+ * Especially useful for users on a language in which WPSEO is not available yet
+ * and/or users who want to turn off stop word filtering
+ * @api array $stopwords Array of all lowercase stopwords to check and/or remove from slug
+ */
+ $stopwords = apply_filters( 'wpseo_stopwords', $stopwords );
+
+ return $stopwords;
+ }
+
+
+ /**
+ * Check whether the stopword appears in the string
+ *
+ * @param string $haystack The string to be checked for the stopword
+ * @param bool $checkingUrl Whether or not we're checking a URL
+ *
+ * @return bool|mixed
+ */
+ function stopwords_check( $haystack, $checkingUrl = false ) {
+ $stopWords = $this->stopwords();
+
+ if ( is_array( $stopWords ) && $stopWords !== array() ) {
+ foreach ( $stopWords as $stopWord ) {
+ // If checking a URL remove the single quotes
+ if ( $checkingUrl ) {
+ $stopWord = str_replace( "'", '', $stopWord );
+ }
+
+ // Check whether the stopword appears as a whole word
+ // @todo [JRF => whomever] check whether the use of \b (=word boundary) would be more efficient ;-)
+ $res = preg_match( "`(^|[ \n\r\t\.,'\(\)\"\+;!?:])" . preg_quote( $stopWord, '`' ) . "($|[ \n\r\t\.,'\(\)\"\+;!?:])`iu", $haystack, $match );
+ if ( $res > 0 ) {
+ return $stopWord;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Log the timestamp when a user profile has been updated
+ */
+ function update_user_profile( $user_id ) {
+ if ( current_user_can( 'edit_user', $user_id ) ) {
+ update_user_meta( $user_id, '_yoast_wpseo_profile_updated', time() );
+ }
+ }
+
+ /**
+ * Log the updated timestamp for user profiles when theme is changed
+ */
+ function switch_theme() {
+ $users = get_users( array( 'who' => 'authors' ) );
+ if ( is_array( $users ) && $users !== array() ) {
+ foreach ( $users as $user ) {
+ update_user_meta( $user->ID, '_yoast_wpseo_profile_updated', time() );
+ }
+ }
+ }
+
+ /**
+ * This method will remove the sitemap transients on upgrade
+ *
+ * @param boolean $response
+ *
+ * @return boolean $response
+ */
+ function remove_transients_on_update( $response ) {
+
+ global $wpdb;
+
+ $results = $wpdb->get_results(
+ "
+ SELECT option_name
+ FROM {$wpdb->options}
+ WHERE option_name LIKE '%_transient_wpseo_sitemap_cache%'
+ "
+ );
+
+ foreach ( $results as $result ) {
+ $transient_name = substr( $result->option_name, 11 );
+ delete_transient( $transient_name );
+ }
+
+ return $response;
+ }
+
+
+
+
+ /********************** DEPRECATED METHODS **********************/
+
+ /**
+ * Check whether the current user is allowed to access the configuration.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Options::grant_access()
+ * @see WPSEO_Options::grant_access()
+ *
+ * @return boolean
+ */
+ function grant_access() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Options::grant_access()' );
+
+ return WPSEO_Options::grant_access();
+ }
+
+ /**
+ * Check whether the current user is allowed to access the configuration.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use wpseo_do_upgrade()
+ * @see wpseo_do_upgrade()
+ *
+ * @return boolean
+ */
+ function maybe_upgrade() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'wpseo_do_upgrade' );
+ wpseo_do_upgrade();
+ }
+
+ /**
+ * Clears the cache
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Options::clear_cache()
+ * @see WPSEO_Options::clear_cache()
+ */
+ function clear_cache() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Options::clear_cache()' );
+ WPSEO_Options::clear_cache();
+ }
+
+ /**
+ * Clear rewrites
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Options::clear_rewrites()
+ * @see WPSEO_Options::clear_rewrites()
+ */
+ function clear_rewrites() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Options::clear_rewrites()' );
+ WPSEO_Options::clear_rewrites();
+ }
+
+ /**
+ * Register all the options needed for the configuration pages.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Option::register_setting() on each individual option
+ * @see WPSEO_Option::register_setting()
+ */
+ function options_init() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Option::register_setting()' );
+ }
+
+ /**
+ * Initialize default values for a new multisite blog.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Options::set_multisite_defaults()
+ * @see WPSEO_Options::set_multisite_defaults()
+ */
+ function multisite_defaults() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Options::set_multisite_defaults()' );
+ WPSEO_Options::set_multisite_defaults();
+ }
+
+ /**
+ * Loads the form for the import/export page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function import_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ /**
+ * Loads the form for the titles & metas page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function titles_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ /**
+ * Loads the form for the permalinks page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function permalinks_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ /**
+ * Loads the form for the internal links / breadcrumbs page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function internallinks_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ /**
+ * Loads the form for the file edit page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function files_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ /**
+ * Loads the form for the RSS page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function rss_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ /**
+ * Loads the form for the XML Sitemaps page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function xml_sitemaps_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ /**
+ * Loads the form for the Dashboard page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function config_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ /**
+ * Loads the form for the Social Settings page.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Admin::load_page()
+ */
+ function social_page() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Admin::load_page()' );
+ $this->load_page();
+ }
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_Bulk_Description_List_Table' ) ) {
+ /**
+ *
+ */
+ class WPSEO_Bulk_Description_List_Table extends WPSEO_Bulk_List_Table {
+
+
+ /**
+ * Current type for this class will be (meta) description.
+ *
+ * @var string
+ */
+ protected $page_type = 'description';
+
+ /**
+ * Settings with are used in __construct
+ *
+ * @var array
+ */
+ protected $settings = array(
+ 'singular' => 'wpseo_bulk_description',
+ 'plural' => 'wpseo_bulk_descriptions',
+ 'ajax' => true,
+ );
+
+ /**
+ * The columns shown on the table
+ *
+ * @return array
+ */
+ function get_columns() {
+ return $columns = array(
+ 'col_page_title' => __( 'WP Page Title', 'wordpress-seo' ),
+ 'col_post_type' => __( 'Post Type', 'wordpress-seo' ),
+ 'col_post_status' => __( 'Post Status', 'wordpress-seo' ),
+ 'col_post_date' => __( 'Publication date', 'wordpress-seo' ),
+ 'col_page_slug' => __( 'Page URL/Slug', 'wordpress-seo' ),
+ 'col_existing_yoast_seo_metadesc' => __( 'Existing Yoast Meta Description', 'wordpress-seo' ),
+ 'col_new_yoast_seo_metadesc' => __( 'New Yoast Meta Description', 'wordpress-seo' ),
+ 'col_row_action' => __( 'Action', 'wordpress-seo' ),
+ );
+ }
+
+ /**
+ * Method for setting the meta data, which belongs to the records that will be shown on the current page
+ *
+ * This method will loop through the current items ($this->items) for getting the post_id. With this data
+ * ($needed_ids) the method will query the meta-data table for getting the metadescription.
+ *
+ */
+ function get_meta_data() {
+
+ global $wpdb;
+
+ $needed_ids = array();
+ foreach ( $this->items AS $item ) {
+ $needed_ids[] = $item->ID;
+ }
+
+ $post_ids = "'" . implode( "', '", $needed_ids ) . "'";
+ $meta_data = $wpdb->get_results(
+ "
+ SELECT *
+ FROM {$wpdb->postmeta}
+ WHERE post_id IN({$post_ids}) && meta_key = '" . WPSEO_Meta::$meta_prefix . "metadesc'
+ "
+ );
+
+ foreach ( $meta_data AS $row ) {
+ $this->meta_data[$row->post_id][$row->meta_key] = $row->meta_value;
+ }
+
+ // Little housekeeping
+ unset( $needed_ids, $post_ids, $meta_data );
+
+ }
+
+
+ } /* End of class */
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_Bulk_List_Table' ) ) {
+ /**
+ *
+ */
+ class WPSEO_Bulk_List_Table extends WP_List_Table {
+
+ /*
+ * Array of post types for which the current user has `edit_others_posts` capabilities.
+ */
+ private $all_posts;
+
+ /*
+ * Array of post types for which the current user has `edit_posts` capabilities, but not `edit_others_posts`.
+ */
+ private $own_posts;
+
+ /**
+ * Saves all the metadata into this array.
+ *
+ * @var array
+ */
+ protected $meta_data = array();
+
+ /**
+ * The current requested page_url
+ *
+ * @var string
+ */
+ private $request_url;
+
+ /**
+ * The current page (depending on $_GET['paged']) if current tab is for current page_type, else it will be 1
+ *
+ * @var integer
+ */
+ private $current_page;
+
+ /**
+ * The current post filter, if is used (depending on $_GET['post_type_filter'])
+ *
+ * @var string
+ */
+ private $current_filter;
+
+ /**
+ * The current post status, if is used (depending on $_GET['post_status'])
+ *
+ * @var string
+ */
+ private $current_status;
+
+ /**
+ * The current sorting, if used (depending on $_GET['order'] and $_GET['orderby'])
+ *
+ * @var string
+ */
+ private $current_order;
+
+ /**
+ * The page_type for current class instance (for example: title / description).
+ *
+ * @var string
+ */
+ protected $page_type;
+
+ /**
+ * Based on the page_type ($this->page_type) there will be constructed an url part, for subpages and
+ * navigation
+ *
+ * @var string
+ */
+ protected $page_url;
+
+ /**
+ * The settings which will be used in the __construct.
+ * @var
+ */
+ protected $settings;
+
+ /**
+ * Class constructor
+ */
+ function __construct() {
+ parent::__construct( $this->settings );
+
+ $this->request_url = $_SERVER['REQUEST_URI'];
+ $this->current_page = ( ! empty( $_GET['paged'] ) ) ? $_GET['paged'] : 1;
+ $this->current_filter = ( ! empty( $_GET['post_type_filter'] ) ) ? $_GET['post_type_filter'] : 1;
+ $this->current_status = ( ! empty( $_GET['post_status'] ) ) ? $_GET['post_status'] : 1;
+ $this->current_order = array(
+ 'order' => ( ! empty( $_GET['order'] ) ) ? $_GET['order'] : 'asc',
+ 'orderby' => ( ! empty( $_GET['orderby'] ) ) ? $_GET['orderby'] : 'post_title',
+ );
+ $this->page_url = "&type={$this->page_type}#top#{$this->page_type}";
+
+ $this->populate_editable_post_types();
+
+ }
+
+ /**
+ * Used in the constructor to build a reference list of post types the current user can edit.
+ */
+ protected function populate_editable_post_types() {
+ $post_types = get_post_types( array( 'public' => true, 'exclude_from_search' => false ), 'object' );
+
+ $this->all_posts = array();
+ $this->own_posts = array();
+
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ foreach ( $post_types as $post_type ) {
+ if ( ! current_user_can( $post_type->cap->edit_posts ) ) {
+ continue;
+ }
+
+ if ( current_user_can( $post_type->cap->edit_others_posts ) ) {
+ $this->all_posts[] = esc_sql( $post_type->name );
+ } else {
+ $this->own_posts[] = esc_sql( $post_type->name );
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Will shown the navigation for the table like pagenavigation and pagefilter;
+ *
+ *
+ * @param $which
+ */
+ function display_tablenav( $which ) {
+ $post_status = '';
+ if ( ! empty( $_GET['post_status'] ) ) {
+ $post_status = sanitize_text_field( $_GET['post_status'] );
+ }
+ ?>
+ <div class="tablenav <?php echo esc_attr( $which ); ?>">
+
+ <?php if ( 'top' === $which ) { ?>
+ <form id="posts-filter" action="" method="get">
+ <input type="hidden" name="page" value="wpseo_bulk-editor" />
+ <input type="hidden" name="type" value="<?php echo $this->page_type; ?>" />
+ <input type="hidden" name="orderby" value="<?php echo $_GET['orderby']; ?>" />
+ <input type="hidden" name="order" value="<?php echo $_GET['order']; ?>" />
+ <input type="hidden" name="post_type_filter" value="<?php echo $_GET['post_type_filter']; ?>" />
+ <?php if ( ! empty( $post_status ) ) { ?>
+ <input type="hidden" name="post_status" value="<?php echo esc_attr( $post_status ); ?>" />
+ <?php } ?>
+ <?php } ?>
+
+ <?php
+ $this->extra_tablenav( $which );
+ $this->pagination( $which );
+ ?>
+
+ <br class="clear" />
+ <?php if ( 'top' === $which ) { ?>
+ </form>
+ <?php } ?>
+ </div>
+
+ <?php
+ }
+
+ /**
+ * This function builds the base sql subquery used in this class.
+ *
+ * This function takes into account the post types in which the current user can
+ * edit all posts, and the ones the current user can only edit his/her own.
+ *
+ * @return string $subquery The subquery, which should always be used in $wpdb->prepare(), passing the current user_id in as the first parameter.
+ */
+ function get_base_subquery() {
+ global $wpdb;
+
+ $all_posts_string = "'" . implode( "', '", $this->all_posts ) . "'";
+ $own_posts_string = "'" . implode( "', '", $this->own_posts ) . "'";
+
+ $post_author = esc_sql( (int) get_current_user_id() );
+
+ $subquery = "(
+ SELECT *
+ FROM {$wpdb->posts}
+ WHERE post_type IN ({$all_posts_string})
+ UNION ALL
+ SELECT *
+ FROM {$wpdb->posts}
+ WHERE post_type IN ({$own_posts_string}) AND post_author = {$post_author}
+ ) sub_base";
+
+ return $subquery;
+ }
+
+
+ /**
+ * @return array
+ */
+ function get_views() {
+ global $wpdb;
+
+ $status_links = array();
+
+ $states = get_post_stati( array( 'show_in_admin_all_list' => true ) );
+ $states['trash'] = 'trash';
+ $states = esc_sql( $states );
+ $all_states = "'" . implode( "', '", $states ) . "'";
+
+ $subquery = $this->get_base_subquery();
+
+ $total_posts = $wpdb->get_var(
+ "
+ SELECT COUNT(ID) FROM {$subquery}
+ WHERE post_status IN ({$all_states})
+ "
+ );
+
+
+ $class = empty( $_GET['post_status'] ) ? ' class="current"' : '';
+ $status_links['all'] = '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_bulk-editor' . $this->page_url ) ) . '"' . $class . '>' . sprintf( _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $total_posts, 'posts', 'wordpress-seo' ), number_format_i18n( $total_posts ) ) . '</a>';
+
+ $post_stati = get_post_stati( array( 'show_in_admin_all_list' => true ), 'objects' );
+ if ( is_array( $post_stati ) && $post_stati !== array() ) {
+ foreach ( $post_stati as $status ) {
+
+ $status_name = esc_sql( $status->name );
+
+ $total = $wpdb->get_var(
+ $wpdb->prepare(
+ "
+ SELECT COUNT(ID) FROM {$subquery}
+ WHERE post_status = %s
+ ",
+ $status_name
+ )
+ );
+
+ if ( $total == 0 ) {
+ continue;
+ }
+
+ $class = '';
+ if ( isset( $_GET['post_status'] ) && $status_name == $_GET['post_status'] ) {
+ $class = ' class="current"';
+ }
+
+ $status_links[$status_name] = '<a href="' . esc_url( add_query_arg( array( 'post_status' => $status_name ), admin_url( 'admin.php?page=wpseo_bulk-editor' . $this->page_url ) ) ) . '"' . $class . '>' . sprintf( translate_nooped_plural( $status->label_count, $total ), number_format_i18n( $total ) ) . '</a>';
+ }
+ }
+ unset( $post_stati, $status, $status_name, $total, $class );
+
+ $trashed_posts = $wpdb->get_var(
+ "
+ SELECT COUNT(ID) FROM {$subquery}
+ WHERE post_status IN ('trash')
+ "
+ );
+
+ $class = ( isset( $_GET['post_status'] ) && 'trash' == $_GET['post_status'] ) ? 'class="current"' : '';
+ $status_links['trash'] = '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_bulk-editor&post_status=trash' . $this->page_url ) ) . '"' . $class . '>' . sprintf( _nx( 'Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>', $trashed_posts, 'posts', 'wordpress-seo' ), number_format_i18n( $trashed_posts ) ) . '</a>';
+
+ return $status_links;
+ }
+
+
+ /**
+ * @param $which
+ */
+ function extra_tablenav( $which ) {
+
+ if ( 'top' === $which ) {
+ $post_types = get_post_types( array( 'public' => true, 'exclude_from_search' => false ) );
+
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ global $wpdb;
+
+ echo '<div class="alignleft actions">';
+
+ $post_types = esc_sql( $post_types );
+ $post_types = "'" . implode( "', '", $post_types ) . "'";
+
+ $states = get_post_stati( array( 'show_in_admin_all_list' => true ) );
+ $states['trash'] = 'trash';
+ $states = esc_sql( $states );
+ $all_states = "'" . implode( "', '", $states ) . "'";
+
+ $subquery = $this->get_base_subquery();
+
+ $post_types = $wpdb->get_results(
+ "
+ SELECT DISTINCT post_type FROM {$subquery}
+ WHERE post_status IN ({$all_states})
+ ORDER BY 'post_type' ASC
+ "
+ );
+
+ $selected = ! empty( $_GET['post_type_filter'] ) ? sanitize_text_field( $_GET['post_type_filter'] ) : - 1;
+
+ $options = '<option value="-1">Show All Post Types</option>';
+
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ foreach ( $post_types as $post_type ) {
+ $obj = get_post_type_object( $post_type->post_type );
+ $options .= sprintf( '<option value="%2$s" %3$s>%1$s</option>', $obj->labels->name, $post_type->post_type, selected( $selected, $post_type->post_type, false ) );
+ }
+ }
+
+ echo sprintf( '<select name="post_type_filter">%1$s</select>', $options );
+ submit_button( __( 'Filter', 'wordpress-seo' ), 'button', false, false, array( 'id' => 'post-query-submit' ) );
+ echo '</div>';
+ }
+ } elseif ( 'bottom' === $which ) {
+
+ }
+
+ }
+
+ /**
+ *
+ * @return array
+ */
+ function get_sortable_columns() {
+ return $sortable = array(
+ 'col_page_title' => array( 'post_title', true ),
+ 'col_post_type' => array( 'post_type', false ),
+ 'col_post_date' => array( 'post_date', false ),
+ );
+ }
+
+ /**
+ * Sets the correct pagenumber and pageurl for the navigation
+ *
+ * @param string $page_type
+ */
+ function prepare_page_navigation() {
+
+ $request_url = $this->request_url . $this->page_url;
+
+ $current_page = $this->current_page;
+ $current_filter = $this->current_filter;
+ $current_status = $this->current_status;
+ $current_order = $this->current_order;
+
+ // If current type doesn't compare with objects page_type, than we have to unset some vars in the requested url (which will be use for internal table urls)
+ if ( $_GET['type'] != $this->page_type ) {
+ $request_url = remove_query_arg( 'paged', $request_url ); // page will be set with value 1 below.
+ $request_url = remove_query_arg( 'post_type_filter', $request_url );
+ $request_url = remove_query_arg( 'post_status', $request_url );
+ $request_url = remove_query_arg( 'orderby', $request_url );
+ $request_url = remove_query_arg( 'order', $request_url );
+ $request_url = add_query_arg( 'pages', 1, $request_url );
+
+ $current_page = 1;
+ $current_filter = '-1';
+ $current_status = '';
+ $current_order = array( 'orderby' => 'post_title', 'order' => 'asc' );
+
+ }
+
+ $_SERVER['REQUEST_URI'] = $request_url;
+
+ $_GET['paged'] = $current_page;
+ $_REQUEST['paged'] = $current_page;
+ $_REQUEST['post_type_filter'] = $current_filter;
+ $_GET['post_type_filter'] = $current_filter;
+ $_GET['post_status'] = $current_status;
+ $_GET['orderby'] = $current_order['orderby'];
+ $_GET['order'] = $current_order['order'];
+
+ }
+
+ /**
+ * Preparing the requested pagerows and setting the needed variables
+ */
+ function prepare_items() {
+ global $wpdb;
+
+ // Filter Block
+
+ $post_types = null;
+ $post_type_clause = '';
+
+ if ( ! empty( $_GET['post_type_filter'] ) && get_post_type_object( sanitize_text_field( $_GET['post_type_filter'] ) ) ) {
+ $post_types = esc_sql( sanitize_text_field( $_GET['post_type_filter'] ) );
+ $post_type_clause = "AND post_type IN ('{$post_types}')";
+ }
+
+ // Order By block
+ $orderby = ! empty( $_GET['orderby'] ) ? esc_sql( sanitize_text_field( $_GET['orderby'] ) ) : 'post_title';
+ $order = 'ASC';
+
+ if ( ! empty( $_GET['order'] ) ) {
+ $order = esc_sql( strtoupper( sanitize_text_field( $_GET['order'] ) ) );
+ }
+
+ $states = get_post_stati( array( 'show_in_admin_all_list' => true ) );
+ $states['trash'] = 'trash';
+
+ if ( ! empty( $_GET['post_status'] ) ) {
+ $requested_state = sanitize_text_field( $_GET['post_status'] );
+ if ( in_array( $requested_state, $states ) ) {
+ $states = array( $requested_state );
+ }
+ }
+
+ $states = esc_sql( $states );
+ $all_states = "'" . implode( "', '", $states ) . "'";
+
+ $subquery = $this->get_base_subquery();
+
+ // Count the total number of needed items
+ $total_items = $wpdb->get_var(
+ "
+ SELECT COUNT(ID)
+ FROM {$subquery}
+ WHERE post_status IN ({$all_states}) $post_type_clause
+ "
+ );
+
+ // Get all needed results
+ $query = "
+ SELECT ID, post_title, post_type, post_status, post_modified, post_date
+ FROM {$subquery}
+ WHERE post_status IN ({$all_states}) $post_type_clause
+ ORDER BY {$orderby} {$order}
+ LIMIT %d,%d
+ ";
+
+ $per_page = $this->get_items_per_page( 'wpseo_posts_per_page', 10 );
+
+ $paged = ! empty( $_GET['paged'] ) ? esc_sql( sanitize_text_field( $_GET['paged'] ) ) : '';
+
+ if ( empty( $paged ) || ! is_numeric( $paged ) || $paged <= 0 ) {
+ $paged = 1;
+ }
+
+ $total_pages = ceil( $total_items / $per_page );
+
+ $offset = ( $paged - 1 ) * $per_page;
+
+ $this->set_pagination_args(
+ array(
+ 'total_items' => $total_items,
+ 'total_pages' => $total_pages,
+ 'per_page' => $per_page,
+ )
+ );
+
+ $columns = $this->get_columns();
+ $hidden = array();
+ $sortable = $this->get_sortable_columns();
+ $this->_column_headers = array( $columns, $hidden, $sortable );
+
+ $this->items = $wpdb->get_results(
+ $wpdb->prepare(
+ $query,
+ $offset,
+ $per_page
+ )
+ );
+
+ // Get the metadata for the current items ($this->items)
+ $this->get_meta_data();
+
+ }
+
+ /**
+ * Based on $this->items and the defined columns, the table rows will be displayed.
+ *
+ */
+ function display_rows() {
+
+ $records = $this->items;
+
+ list( $columns, $hidden ) = $this->get_column_info();
+
+
+ $date_format = get_option( 'date_format' );
+
+ if ( ( is_array( $records ) && $records !== array() ) && ( is_array( $columns ) && $columns !== array() ) ) {
+ foreach ( $records as $rec ) {
+
+ // Fill meta data if exists in $this->meta_data
+ $meta_data = ( ! empty( $this->meta_data[$rec->ID] ) ) ? $this->meta_data[$rec->ID] : array();
+
+ echo '<tr id="record_' . $rec->ID . '">';
+
+ foreach ( $columns as $column_name => $column_display_name ) {
+
+ $class = sprintf( 'class="%1$s column-%1$s"', $column_name );
+ $style = '';
+
+ if ( in_array( $column_name, $hidden ) ) {
+ $style = ' style="display:none;"';
+ }
+
+ $attributes = $class . $style;
+
+ switch ( $column_name ) {
+ case 'col_page_title':
+ echo sprintf( '<td %2$s><strong>%1$s</strong>', stripslashes( $rec->post_title ), $attributes );
+
+ $post_type_object = get_post_type_object( $rec->post_type );
+ $can_edit_post = current_user_can( $post_type_object->cap->edit_post, $rec->ID );
+
+ $actions = array();
+
+ if ( $can_edit_post && 'trash' != $rec->post_status ) {
+ $actions['edit'] = '<a href="' . esc_url( get_edit_post_link( $rec->ID, true ) ) . '" title="' . esc_attr( __( 'Edit this item', 'wordpress-seo' ) ) . '">' . __( 'Edit', 'wordpress-seo' ) . '</a>';
+ }
+
+ if ( $post_type_object->public ) {
+ if ( in_array( $rec->post_status, array( 'pending', 'draft', 'future' ) ) ) {
+ if ( $can_edit_post ) {
+ $actions['view'] = '<a href="' . esc_url( add_query_arg( 'preview', 'true', get_permalink( $rec->ID ) ) ) . '" title="' . esc_attr( sprintf( __( 'Preview “%s”', 'wordpress-seo' ), $rec->post_title ) ) . '">' . __( 'Preview', 'wordpress-seo' ) . '</a>';
+ }
+ } elseif ( 'trash' != $rec->post_status ) {
+ $actions['view'] = '<a href="' . esc_url( get_permalink( $rec->ID ) ) . '" title="' . esc_attr( sprintf( __( 'View “%s”', 'wordpress-seo' ), $rec->post_title ) ) . '" rel="bookmark">' . __( 'View', 'wordpress-seo' ) . '</a>';
+ }
+ }
+
+ echo $this->row_actions( $actions );
+ echo '</td>';
+ break;
+
+ case 'col_page_slug':
+ $permalink = get_permalink( $rec->ID );
+ $display_slug = str_replace( get_bloginfo( 'url' ), '', $permalink );
+ echo sprintf( '<td %2$s><a href="%3$s" target="_blank">%1$s</a></td>', stripslashes( $display_slug ), $attributes, esc_url( $permalink ) );
+ break;
+
+ case 'col_post_type':
+ $post_type = get_post_type_object( $rec->post_type );
+ echo sprintf( '<td %2$s>%1$s</td>', $post_type->labels->singular_name, $attributes );
+ break;
+
+ case 'col_post_status':
+ $post_status = get_post_status_object( $rec->post_status );
+ echo sprintf( '<td %2$s>%1$s</td>', $post_status->label, $attributes );
+ break;
+
+ case 'col_post_date':
+ $cell_value = date_i18n( $date_format, strtotime( $rec->post_date ) );
+ echo sprintf( '<td %2$s>%1$s</td>', $cell_value, $attributes );
+ break;
+
+ case 'col_existing_yoast_seo_title':
+ $cell_value = ( ( ! empty( $meta_data[WPSEO_Meta::$meta_prefix . 'title'] ) ) ? $meta_data[WPSEO_Meta::$meta_prefix . 'title'] : '' );
+ echo sprintf( '<td %2$s id="wpseo-existing-title-%3$s">%1$s</td>', $cell_value, $attributes, $rec->ID );
+ break;
+
+ case 'col_new_yoast_seo_title':
+ $input = sprintf( '<input type="text" id="%1$s" name="%1$s" class="wpseo-new-title" data-id="%2$s" />', 'wpseo-new-title-' . $rec->ID, $rec->ID );
+ echo sprintf( '<td %2$s>%1$s</td>', $input, $attributes );
+ break;
+
+ case 'col_new_yoast_seo_metadesc' :
+ $input = sprintf( '<textarea id="%1$s" name="%1$s" class="wpseo-new-metadesc" data-id="%2$s"></textarea>', 'wpseo-new-metadesc-' . $rec->ID, $rec->ID );
+ echo sprintf( '<td %2$s>%1$s</td>', $input, $attributes );
+ break;
+
+
+ case 'col_existing_yoast_seo_metadesc':
+ $cell_value = ( ( ! empty( $meta_data[WPSEO_Meta::$meta_prefix . 'metadesc'] ) ) ? $meta_data[WPSEO_Meta::$meta_prefix . 'metadesc'] : '' );
+ echo sprintf( '<td %2$s id="wpseo-existing-metadesc-%3$s">%1$s</td>', $cell_value, $attributes, $rec->ID );
+ break;
+
+ case 'col_row_action':
+ $actions = sprintf( '<a href="#" class="wpseo-save" data-id="%1$s">Save</a> | <a href="#" class="wpseo-save-all">Save All</a>', $rec->ID );
+ echo sprintf( '<td %2$s>%1$s</td>', $actions, $attributes );
+ break;
+ }
+ }
+
+ echo '</tr>';
+ }
+ }
+ }
+ } /* End of class */
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_Bulk_Title_Editor_List_Table' ) ) {
+ /**
+ *
+ */
+ class WPSEO_Bulk_Title_Editor_List_Table extends WPSEO_Bulk_List_Table {
+
+ /**
+ * Current type for this class will be title
+ *
+ * @var string
+ */
+ protected $page_type = 'title';
+
+
+ /**
+ * Settings with are used in __construct
+ *
+ * @var array
+ */
+ protected $settings = array(
+ 'singular' => 'wpseo_bulk_title',
+ 'plural' => 'wpseo_bulk_titles',
+ 'ajax' => true,
+ );
+
+ /**
+ * The columns shown on the table
+ *
+ * @return array
+ */
+ function get_columns() {
+ return $columns = array(
+ 'col_page_title' => __( 'WP Page Title', 'wordpress-seo' ),
+ 'col_post_type' => __( 'Post Type', 'wordpress-seo' ),
+ 'col_post_status' => __( 'Post Status', 'wordpress-seo' ),
+ 'col_post_date' => __( 'Publication date', 'wordpress-seo' ),
+ 'col_page_slug' => __( 'Page URL/Slug', 'wordpress-seo' ),
+ 'col_existing_yoast_seo_title' => __( 'Existing Yoast SEO Title', 'wordpress-seo' ),
+ 'col_new_yoast_seo_title' => __( 'New Yoast SEO Title', 'wordpress-seo' ),
+ 'col_row_action' => __( 'Action', 'wordpress-seo' ),
+ );
+ }
+
+ /**
+ * Method for setting the meta data, which belongs to the records that will be shown on the current page
+ *
+ * This method will loop through the current items ($this->items) for getting the post_id. With this data
+ * ($needed_ids) the method will query the meta-data table for getting the title.
+ *
+ */
+ function get_meta_data() {
+
+ global $wpdb;
+
+ $needed_ids = array();
+ foreach ( $this->items AS $item ) {
+ $needed_ids[] = $item->ID;
+ }
+
+ $post_ids = "'" . implode( "', '", $needed_ids ) . "'";
+ $meta_data = $wpdb->get_results(
+ "
+ SELECT *
+ FROM {$wpdb->postmeta}
+ WHERE post_id IN({$post_ids}) && meta_key = '" . WPSEO_Meta::$meta_prefix . "title'
+ "
+ );
+
+ foreach ( $meta_data AS $row ) {
+ $this->meta_data[$row->post_id][$row->meta_key] = $row->meta_value;
+ }
+
+
+ // Little housekeeping
+ unset( $needed_ids, $post_ids, $meta_data );
+
+ }
+
+ } /* End of class */
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_Admin_Pages' ) ) {
+ /**
+ * class WPSEO_Admin_Pages
+ *
+ * Class with functionality for the WP SEO admin pages.
+ */
+ class WPSEO_Admin_Pages {
+
+ /**
+ * @var string $currentoption The option in use for the current admin page.
+ */
+ var $currentoption = 'wpseo';
+
+ /**
+ * @var array $adminpages Array of admin pages that the plugin uses.
+ */
+ var $adminpages = array(
+ 'wpseo_dashboard',
+ 'wpseo_rss',
+ 'wpseo_files',
+ 'wpseo_permalinks',
+ 'wpseo_internal-links',
+ 'wpseo_import',
+ 'wpseo_titles',
+ 'wpseo_xml',
+ 'wpseo_social',
+ 'wpseo_bulk-editor',
+ 'wpseo_licenses',
+ 'wpseo_network_licenses',
+ );
+
+ /**
+ * Class constructor, which basically only hooks the init function on the init hook
+ */
+ function __construct() {
+ add_action( 'init', array( $this, 'init' ), 20 );
+ }
+
+ /**
+ * Make sure the needed scripts are loaded for admin pages
+ */
+ function init() {
+ if ( isset( $_GET['wpseo_reset_defaults'] ) && wp_verify_nonce( $_GET['nonce'], 'wpseo_reset_defaults' ) && current_user_can( 'manage_options' ) ) {
+ WPSEO_Options::reset();
+ wp_redirect( admin_url( 'admin.php?page=wpseo_dashboard' ) );
+ }
+
+ $this->adminpages = apply_filters( 'wpseo_admin_pages', $this->adminpages );
+
+ if ( WPSEO_Options::grant_access() ) {
+ add_action( 'admin_enqueue_scripts', array( $this, 'config_page_scripts' ) );
+ add_action( 'admin_enqueue_scripts', array( $this, 'config_page_styles' ) );
+ }
+ }
+
+ /**
+ * Generates the sidebar for admin pages.
+ */
+ function admin_sidebar() {
+
+ // No banners in Premium
+ if ( class_exists( 'WPSEO_Product_Premium' ) ) {
+ $license_manager = new Yoast_Plugin_License_Manager( new WPSEO_Product_Premium() );
+ if ( $license_manager->license_is_valid() ) {
+ return;
+ }
+ }
+
+ $service_banners = array(
+ array(
+ 'url' => 'https://yoast.com/hire-us/website-review/#utm_source=wordpress-seo-config&utm_medium=banner&utm_campaign=website-review-banner',
+ 'img' => 'banner-website-review.png',
+ 'alt' => 'Website Review banner',
+ ),
+ );
+
+ $plugin_banners = array(
+ array(
+ 'url' => 'https://yoast.com/wordpress/plugins/seo-premium/#utm_source=wordpress-seo-config&utm_medium=banner&utm_campaign=premium-seo-banner',
+ 'img' => 'banner-premium-seo.png',
+ 'alt' => 'Banner WordPress SEO Premium',
+ ),
+ );
+
+ if ( ! class_exists( 'wpseo_Video_Sitemap' ) ) {
+ $plugin_banners[] = array(
+ 'url' => 'https://yoast.com/wordpress/plugins/video-seo/#utm_source=wordpress-seo-config&utm_medium=banner&utm_campaign=video-seo-banner',
+ 'img' => 'banner-video-seo.png',
+ 'alt' => 'Banner WordPress SEO Video SEO extension',
+ );
+ }
+
+ if ( class_exists( 'Woocommerce' ) && ! class_exists( 'Yoast_WooCommerce_SEO' ) ) {
+ $plugin_banners[] = array(
+ 'url' => 'https://yoast.com/wordpress/plugins/yoast-woocommerce-seo/#utm_source=wordpress-seo-config&utm_medium=banner&utm_campaign=woocommerce-seo-banner',
+ 'img' => 'banner-woocommerce-seo.png',
+ 'alt' => 'Banner WooCommerce SEO plugin',
+ );
+ }
+
+ if ( ! defined( 'WPSEO_LOCAL_VERSION' ) ) {
+ $plugin_banners[] = array(
+ 'url' => 'https://yoast.com/wordpress/plugins/local-seo/#utm_source=wordpress-seo-config&utm_medium=banner&utm_campaign=local-seo-banner',
+ 'img' => 'banner-local-seo.png',
+ 'alt' => 'Banner Local SEO plugin',
+ );
+ }
+
+ if ( ! class_exists( 'WPSEO_News' ) ) {
+ $plugin_banners[] = array(
+ 'url' => 'https://yoast.com/wordpress/plugins/news-seo/#utm_source=wordpress-seo-config&utm_medium=banner&utm_campaign=news-seo-banner',
+ 'img' => 'banner-news-seo.png',
+ 'alt' => 'Banner News SEO',
+ );
+ }
+
+ shuffle( $service_banners );
+ shuffle( $plugin_banners );
+ ?>
+ <div class="wpseo_content_cell" id="sidebar-container">
+ <div id="sidebar">
+ <?php
+
+ $service_banner = $service_banners[0];
+
+ echo '<a target="_blank" href="' . esc_url( $service_banner['url'] ) . '"><img width="261" height="190" src="' . plugins_url( 'images/' . $service_banner['img'], WPSEO_FILE ) . '" alt="' . esc_attr( $service_banner['alt'] ) . '"/></a><br/><br/>';
+
+ $i = 0;
+ foreach ( $plugin_banners as $banner ) {
+ if ( $i == 2 ) {
+ break;
+ }
+ echo '<a target="_blank" href="' . esc_url( $banner['url'] ) . '"><img width="261" src="' . plugins_url( 'images/' . $banner['img'], WPSEO_FILE ) . '" alt="' . esc_attr( $banner['alt'] ) . '"/></a><br/><br/>';
+ $i ++;
+ }
+ ?>
+ <?php
+ echo __( 'Remove these ads?', 'wordpress-seo' ) . '<br/>';
+ echo '<a target="_blank" href="https://yoast.com/wordpress/plugins/seo-premium/#utm_source=wordpress-seo-config&utm_medium=textlink&utm_campaign=remove-ads-link">' . __( 'Upgrade to WordPress SEO Premium »', 'wordpress-seo' ) . '</a><br/><br/>';
+ ?>
+ </div>
+ </div>
+ <?php
+ }
+
+ /**
+ * Generates the header for admin pages
+ *
+ * @param bool $form Whether or not the form start tag should be included.
+ * @param string $option The long name of the option to use for the current page.
+ * @param string $optionshort The short name of the option to use for the current page.
+ * @param bool $contains_files Whether the form should allow for file uploads.
+ */
+ function admin_header( $form = true, $option = 'yoast_wpseo_options', $optionshort = 'wpseo', $contains_files = false ) {
+ ?>
+ <div class="wrap wpseo-admin-page page-<?php echo $optionshort; ?>">
+ <?php
+ /**
+ * Display the updated/error messages
+ * Only needed as our settings page is not under options, otherwise it will automatically be included
+ * @see settings_errors()
+ */
+ require_once( ABSPATH . 'wp-admin/options-head.php' );
+ ?>
+ <h2 id="wpseo-title"><?php echo esc_html( get_admin_page_title() ); ?></h2>
+ <div class="wpseo_content_wrapper">
+ <div class="wpseo_content_cell" id="wpseo_content_top">
+ <div class="metabox-holder">
+ <div class="meta-box-sortables">
+ <?php
+ if ( $form === true ) {
+ echo '<form action="' . esc_url( admin_url( 'options.php' ) ) . '" method="post" id="wpseo-conf"' . ( $contains_files ? ' enctype="multipart/form-data"' : '' ) . ' accept-charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '">';
+ settings_fields( $option );
+ }
+ $this->currentoption = $optionshort;
+ }
+
+ /**
+ * Generates the footer for admin pages
+ *
+ * @param bool $submit Whether or not a submit button and form end tag should be shown.
+ * @param bool $show_sidebar Whether or not to show the banner sidebar - used by premium plugins to disable it
+ */
+ function admin_footer( $submit = true, $show_sidebar = true ) {
+ if ( $submit ) {
+ submit_button();
+
+ echo '
+ </form>';
+ }
+
+ echo '
+ </div><!-- end of div meta-box-sortables -->
+ </div><!-- end of div metabox-holder -->
+ </div><!-- end of div wpseo_content_top -->';
+
+ if ( $show_sidebar ) {
+ $this->admin_sidebar();
+ }
+
+ echo '</div><!-- end of div wpseo_content_wrapper -->';
+
+
+ /* Add the current settings array to the page for debugging purposes,
+ but not for a limited set of pages were it wouldn't make sense */
+ $excluded = array(
+ 'wpseo_import',
+ 'wpseo_files',
+ 'bulk_title_editor_page',
+ 'bulk_description_editor_page',
+ );
+
+ if ( ( WP_DEBUG === true || ( defined( 'WPSEO_DEBUG' ) && WPSEO_DEBUG === true ) ) && isset( $_GET['page'] ) && ! in_array( $_GET['page'], $excluded, true ) ) {
+ $xdebug = ( extension_loaded( 'xdebug' ) ? true : false );
+ echo '
+ <div id="poststuff">
+ <div id="wpseo-debug-info" class="postbox">
+
+ <h3 class="hndle"><span>' . __( 'Debug Information', 'wordpress-seo' ) . '</span></h3>
+ <div class="inside">
+ <h4>' . esc_html( __( 'Current option:', 'wordpress-seo' ) ) . ' <span class="wpseo-debug">' . esc_html( $this->currentoption ) . '</span></h4>
+ ' . ( $xdebug ? '' : '<pre>' );
+ var_dump( $this->get_option( $this->currentoption ) );
+ echo '
+ ' . ( $xdebug ? '' : '</pre>' ) . '
+ </div>
+ </div>
+ </div>';
+ }
+
+ echo '
+ </div><!-- end of wrap -->';
+ }
+
+ /**
+ * Deletes all post meta values with a given meta key from the database
+ *
+ * @todo [JRF => whomever] This method does not seem to be used anywhere. Double-check before removal.
+ *
+ * @param string $meta_key Key to delete all meta values for.
+ */
+ /*function delete_meta( $meta_key ) {
+ global $wpdb;
+ $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->postmeta WHERE meta_key = %s", $meta_key ) );
+ }*/
+
+ /**
+ * Exports the current site's WP SEO settings.
+ *
+ * @param bool $include_taxonomy Whether to include the taxonomy metadata the plugin creates.
+ *
+ * @return bool|string $return False when failed, the URL to the export file when succeeded.
+ */
+ function export_settings( $include_taxonomy ) {
+ $content = '; ' . __( 'This is a settings export file for the WordPress SEO plugin by Yoast.com', 'wordpress-seo' ) . " - https://yoast.com/wordpress/plugins/seo/ \r\n";
+
+ $optarr = WPSEO_Options::get_option_names();
+
+ foreach ( $optarr as $optgroup ) {
+ $content .= "\n" . '[' . $optgroup . ']' . "\n";
+ $options = get_option( $optgroup );
+ if ( ! is_array( $options ) ) {
+ continue;
+ }
+ foreach ( $options as $key => $elem ) {
+ if ( is_array( $elem ) ) {
+ $elm_count = count( $elem );
+ for ( $i = 0; $i < $elm_count; $i ++ ) {
+ $content .= $key . '[] = "' . $elem[ $i ] . "\"\n";
+ }
+ } elseif ( is_string( $elem ) && $elem == '' ) {
+ $content .= $key . " = \n";
+ } elseif ( is_bool( $elem ) ) {
+ $content .= $key . ' = "' . ( ( $elem === true ) ? 'on' : 'off' ) . "\"\n";
+ } else {
+ $content .= $key . ' = "' . $elem . "\"\n";
+ }
+ }
+ }
+
+ if ( $include_taxonomy ) {
+ $content .= "\r\n\r\n[wpseo_taxonomy_meta]\r\n";
+ $content .= 'wpseo_taxonomy_meta = "' . urlencode( json_encode( get_option( 'wpseo_taxonomy_meta' ) ) ) . '"';
+ }
+
+ $dir = wp_upload_dir();
+
+ if ( ! $handle = fopen( $dir['path'] . '/settings.ini', 'w' ) ) {
+ die();
+ }
+
+ if ( ! fwrite( $handle, $content ) ) {
+ die();
+ }
+
+ fclose( $handle );
+
+ chdir( $dir['path'] );
+ $zip = new PclZip( './settings.zip' );
+ if ( $zip->create( './settings.ini' ) == 0 ) {
+ return false;
+ }
+
+ return $dir['url'] . '/settings.zip';
+ }
+
+ /**
+ * Loads the required styles for the config page.
+ */
+ function config_page_styles() {
+ global $pagenow;
+ if ( $pagenow === 'admin.php' && isset( $_GET['page'] ) && in_array( $_GET['page'], $this->adminpages ) ) {
+ wp_enqueue_style( 'dashboard' );
+ wp_enqueue_style( 'thickbox' );
+ wp_enqueue_style( 'global' );
+ wp_enqueue_style( 'wp-admin' );
+ wp_enqueue_style( 'yoast-admin-css', plugins_url( 'css/yst_plugin_tools' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
+
+ if ( is_rtl() ) {
+ wp_enqueue_style( 'wpseo-rtl', plugins_url( 'css/wpseo-rtl' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
+ }
+ }
+ }
+
+ /**
+ * Loads the required scripts for the config page.
+ */
+ function config_page_scripts() {
+ global $pagenow;
+
+ if ( $pagenow == 'admin.php' && isset( $_GET['page'] ) && in_array( $_GET['page'], $this->adminpages ) ) {
+ wp_enqueue_script( 'wpseo-admin-script', plugins_url( 'js/wp-seo-admin' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_FILE ), array(
+ 'jquery',
+ 'jquery-ui-core',
+ ), WPSEO_VERSION, true );
+ wp_enqueue_script( 'dashboard' );
+ wp_enqueue_script( 'thickbox' );
+ }
+
+ if ( $pagenow == 'admin.php' && isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'wpseo_social' ) ) ) {
+ wp_enqueue_media();
+ wp_enqueue_script( 'wpseo-admin-media', plugins_url( 'js/wp-seo-admin-media' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_FILE ), array(
+ 'jquery',
+ 'jquery-ui-core',
+ ), WPSEO_VERSION, true );
+ wp_localize_script( 'wpseo-admin-media', 'wpseoMediaL10n', $this->localize_media_script() );
+ }
+
+ if ( $pagenow == 'admin.php' && isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'wpseo_bulk-editor' ) ) ) {
+ wp_enqueue_script( 'wpseo-bulk-editor', plugins_url( 'js/wp-seo-bulk-editor' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_FILE ), array( 'jquery' ), WPSEO_VERSION, true );
+ }
+ }
+
+ /**
+ * Pass some variables to js for upload module.
+ *
+ * @return array
+ */
+ public function localize_media_script() {
+ return array(
+ 'choose_image' => __( 'Use Image', 'wordpress-seo' ),
+ );
+ }
+
+ /**
+ * Retrieve options based on whether we're on multisite or not.
+ *
+ * @since 1.2.4
+ *
+ * @param string $option The option to retrieve.
+ *
+ * @return array
+ */
+ function get_option( $option ) {
+ if ( is_network_admin() ) {
+ return get_site_option( $option );
+ } else {
+ return get_option( $option );
+ }
+ }
+
+ /**
+ * Create a Checkbox input field.
+ *
+ * @param string $var The variable within the option to create the checkbox for.
+ * @param string $label The label to show for the variable.
+ * @param bool $label_left Whether the label should be left (true) or right (false).
+ * @param string $option The option the variable belongs to.
+ *
+ * @return string
+ */
+ function checkbox( $var, $label, $label_left = false, $option = '' ) {
+ if ( empty( $option ) ) {
+ $option = $this->currentoption;
+ }
+
+ $options = $this->get_option( $option );
+
+ if ( ! isset( $options[ $var ] ) ) {
+ $options[ $var ] = false;
+ }
+
+ if ( $options[ $var ] === true ) {
+ $options[ $var ] = 'on';
+ }
+
+ if ( $label_left !== false ) {
+ if ( ! empty( $label_left ) ) {
+ $label_left .= ':';
+ }
+ $output_label = '<label class="checkbox" for="' . esc_attr( $var ) . '">' . $label_left . '</label>';
+ $class = 'checkbox';
+ } else {
+ $output_label = '<label for="' . esc_attr( $var ) . '">' . $label . '</label>';
+ $class = 'checkbox double';
+ }
+
+ $output_input = '<input class="' . esc_attr( $class ) . '" type="checkbox" id="' . esc_attr( $var ) . '" name="' . esc_attr( $option ) . '[' . esc_attr( $var ) . ']" value="on"' . checked( $options[ $var ], 'on', false ) . '/>';
+
+ if ( $label_left !== false ) {
+ $output = $output_label . $output_input . '<label class="checkbox" for="' . esc_attr( $var ) . '">' . $label . '</label>';
+ } else {
+ $output = $output_input . $output_label;
+ }
+
+ return $output . '<br class="clear" />';
+ }
+
+ /**
+ * Create a Text input field.
+ *
+ * @param string $var The variable within the option to create the text input field for.
+ * @param string $label The label to show for the variable.
+ * @param string $option The option the variable belongs to.
+ *
+ * @return string
+ */
+ function textinput( $var, $label, $option = '' ) {
+ if ( empty( $option ) ) {
+ $option = $this->currentoption;
+ }
+
+ $options = $this->get_option( $option );
+ $val = ( isset( $options[ $var ] ) ) ? $options[ $var ] : '';
+
+ return '<label class="textinput" for="' . esc_attr( $var ) . '">' . $label . ':</label><input class="textinput" type="text" id="' . esc_attr( $var ) . '" name="' . esc_attr( $option ) . '[' . esc_attr( $var ) . ']" value="' . esc_attr( $val ) . '"/>' . '<br class="clear" />';
+ }
+
+ /**
+ * Create a textarea.
+ *
+ * @param string $var The variable within the option to create the textarea for.
+ * @param string $label The label to show for the variable.
+ * @param string $option The option the variable belongs to.
+ * @param string $class The CSS class to assign to the textarea.
+ *
+ * @return string
+ */
+ function textarea( $var, $label, $option = '', $class = '' ) {
+ if ( empty( $option ) ) {
+ $option = $this->currentoption;
+ }
+
+ $options = $this->get_option( $option );
+ $val = ( isset( $options[ $var ] ) ) ? $options[ $var ] : '';
+
+ return '<label class="textinput" for="' . esc_attr( $var ) . '">' . esc_html( $label ) . ':</label><textarea class="textinput ' . esc_attr( $class ) . '" id="' . esc_attr( $var ) . '" name="' . esc_attr( $option ) . '[' . esc_attr( $var ) . ']">' . esc_textarea( $val ) . '</textarea>' . '<br class="clear" />';
+ }
+
+ /**
+ * Create a hidden input field.
+ *
+ * @param string $var The variable within the option to create the hidden input for.
+ * @param string $option The option the variable belongs to.
+ *
+ * @return string
+ */
+ function hidden( $var, $option = '' ) {
+ if ( empty( $option ) ) {
+ $option = $this->currentoption;
+ }
+
+ $options = $this->get_option( $option );
+
+ $val = ( isset( $options[ $var ] ) ) ? $options[ $var ] : '';
+ if ( is_bool( $val ) ) {
+ $val = ( $val === true ) ? 'true' : 'false';
+ }
+
+ return '<input type="hidden" id="hidden_' . esc_attr( $var ) . '" name="' . esc_attr( $option ) . '[' . esc_attr( $var ) . ']" value="' . esc_attr( $val ) . '"/>';
+ }
+
+ /**
+ * Create a Select Box.
+ *
+ * @param string $var The variable within the option to create the select for.
+ * @param string $label The label to show for the variable.
+ * @param array $values The select options to choose from.
+ * @param string $option The option the variable belongs to.
+ *
+ * @return string
+ */
+ function select( $var, $label, $values, $option = '' ) {
+ if ( ! is_array( $values ) || $values === array() ) {
+ return '';
+ }
+ if ( empty( $option ) ) {
+ $option = $this->currentoption;
+ }
+
+ $options = $this->get_option( $option );
+ $val = ( isset( $options[ $var ] ) ) ? $options[ $var ] : '';
+
+ $output = '<label class="select" for="' . esc_attr( $var ) . '">' . $label . ':</label>';
+ $output .= '<select class="select" name="' . esc_attr( $option ) . '[' . esc_attr( $var ) . ']" id="' . esc_attr( $var ) . '">';
+
+ foreach ( $values as $value => $label ) {
+ if ( ! empty( $label ) ) {
+ $output .= '<option value="' . esc_attr( $value ) . '"' . selected( $val, $value, false ) . '>' . $label . '</option>';
+ }
+ }
+ $output .= '</select>';
+
+ return $output . '<br class="clear"/>';
+ }
+
+ /**
+ * Create a File upload field.
+ *
+ * @param string $var The variable within the option to create the file upload field for.
+ * @param string $label The label to show for the variable.
+ * @param string $option The option the variable belongs to.
+ *
+ * @return string
+ */
+ function file_upload( $var, $label, $option = '' ) {
+ if ( empty( $option ) ) {
+ $option = $this->currentoption;
+ }
+
+ $options = $this->get_option( $option );
+
+ $val = '';
+ if ( isset( $options[ $var ] ) && is_array( $options[ $var ] ) ) {
+ $val = $options[ $var ]['url'];
+ }
+
+ $var_esc = esc_attr( $var );
+ $output = '<label class="select" for="' . $var_esc . '">' . esc_html( $label ) . ':</label>';
+ $output .= '<input type="file" value="' . esc_attr( $val ) . '" class="textinput" name="' . esc_attr( $option ) . '[' . $var_esc . ']" id="' . $var_esc . '"/>';
+
+ // Need to save separate array items in hidden inputs, because empty file inputs type will be deleted by settings API.
+ if ( ! empty( $options[ $var ] ) ) {
+ $output .= '<input class="hidden" type="hidden" id="' . $var_esc . '_file" name="wpseo_local[' . $var_esc . '][file]" value="' . esc_attr( $options[ $var ]['file'] ) . '"/>';
+ $output .= '<input class="hidden" type="hidden" id="' . $var_esc . '_url" name="wpseo_local[' . $var_esc . '][url]" value="' . esc_attr( $options[ $var ]['url'] ) . '"/>';
+ $output .= '<input class="hidden" type="hidden" id="' . $var_esc . '_type" name="wpseo_local[' . $var_esc . '][type]" value="' . esc_attr( $options[ $var ]['type'] ) . '"/>';
+ }
+ $output .= '<br class="clear"/>';
+
+ return $output;
+ }
+
+ /**
+ * Media input
+ *
+ * @param string $var
+ * @param string $label
+ * @param string $option
+ *
+ * @return string
+ */
+ function media_input( $var, $label, $option = '' ) {
+ if ( empty( $option ) ) {
+ $option = $this->currentoption;
+ }
+
+ $options = $this->get_option( $option );
+
+ $val = '';
+ if ( isset( $options[ $var ] ) ) {
+ $val = $options[ $var ];
+ }
+
+ $var_esc = esc_attr( $var );
+
+ $output = '<label class="select" for="wpseo_' . $var_esc . '">' . esc_html( $label ) . ':</label>';
+ $output .= '<input id="wpseo_' . $var_esc . '" type="text" size="36" name="' . esc_attr( $option ) . '[' . $var_esc . ']" value="' . esc_attr( $val ) . '" />';
+ $output .= '<input id="wpseo_' . $var_esc . '_button" class="wpseo_image_upload_button button" type="button" value="Upload Image" />';
+ $output .= '<br class="clear"/>';
+
+ return $output;
+ }
+
+ /**
+ * Create a Radio input field.
+ *
+ * @param string $var The variable within the option to create the file upload field for.
+ * @param array $values The radio options to choose from.
+ * @param string $label The label to show for the variable.
+ * @param string $option The option the variable belongs to.
+ *
+ * @return string
+ */
+ function radio( $var, $values, $label, $option = '' ) {
+ if ( ! is_array( $values ) || $values === array() ) {
+ return '';
+ }
+ if ( empty( $option ) ) {
+ $option = $this->currentoption;
+ }
+
+ $options = $this->get_option( $option );
+
+ if ( ! isset( $options[ $var ] ) ) {
+ $options[ $var ] = false;
+ }
+
+ $var_esc = esc_attr( $var );
+
+ $output = '<br/><div class="wpseo_radio_block" id="' . $var_esc . '">';
+ if ( is_string( $label ) && $label !== '' ) {
+ $output .= '<label class="select">' . $label . ':</label>';
+ }
+
+ foreach ( $values as $key => $value ) {
+ $key_esc = esc_attr( $key );
+ $output .= '<input type="radio" class="radio" id="' . $var_esc . '-' . $key_esc . '" name="' . esc_attr( $option ) . '[' . $var_esc . ']" value="' . $key_esc . '" ' . checked( $options[ $var ], $key_esc, false ) . ' /> <label class="radio" for="' . $var_esc . '-' . $key_esc . '">' . esc_html( $value ) . '</label>';
+ }
+ $output .= '<div class="clear"></div>';
+ $output .= '</div><br/>';
+
+ return $output;
+ }
+
+ /**
+ * Create a postbox widget.
+ *
+ * @param string $id ID of the postbox.
+ * @param string $title Title of the postbox.
+ * @param string $content Content of the postbox.
+ */
+ function postbox( $id, $title, $content ) {
+ ?>
+ <div id="<?php echo esc_attr( $id ); ?>" class="yoastbox">
+ <h2><?php echo $title; ?></h2>
+ <?php echo $content; ?>
+ </div>
+ <?php
+ }
+
+
+ /**
+ * Create a form table from an array of rows.
+ *
+ * @param array $rows Rows to include in the table.
+ *
+ * @return string
+ */
+ function form_table( $rows ) {
+ if ( ! is_array( $rows ) || $rows === array() ) {
+ return '';
+ }
+
+ $content = '<table class="form-table">';
+ foreach ( $rows as $row ) {
+ $content .= '<tr><th scope="row">';
+ if ( isset( $row['id'] ) && $row['id'] != '' ) {
+ $content .= '<label for="' . esc_attr( $row['id'] ) . '">' . esc_html( $row['label'] ) . ':</label>';
+ } else {
+ $content .= esc_html( $row['label'] );
+ }
+ if ( isset( $row['desc'] ) && $row['desc'] != '' ) {
+ $content .= '<br/><small>' . esc_html( $row['desc'] ) . '</small>';
+ }
+ $content .= '</th><td>';
+ $content .= $row['content'];
+ $content .= '</td></tr>';
+ }
+ $content .= '</table>';
+
+ return $content;
+ }
+
+
+
+ /********************** DEPRECATED METHODS **********************/
+
+ /**
+ * Resets the site to the default WordPress SEO settings and runs a title test to check
+ * whether force rewrite needs to be on.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Options::reset()
+ * @see WPSEO_Options::reset()
+ */
+ function reset_defaults() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Options::reset()' );
+ WPSEO_Options::reset();
+ }
+
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ *
+ * This code generates the metabox on the edit post / page as well as contains all page analysis functionality.
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Metabox' ) ) {
+ /**
+ * class WPSEO_Metabox
+ *
+ * The class that generates the metabox on the edit post / page as well as contains all page analysis functionality.
+ */
+ class WPSEO_Metabox extends WPSEO_Meta {
+
+ /**
+ * Property holding the Text statistics object
+ */
+ public $statistics;
+
+ /**
+ * Class constructor
+ */
+ function __construct() {
+ add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
+ add_action( 'wp_insert_post', array( $this, 'save_postdata' ) );
+ add_action( 'edit_attachment', array( $this, 'save_postdata' ) );
+ add_action( 'add_attachment', array( $this, 'save_postdata' ) );
+ add_action( 'admin_init', array( $this, 'setup_page_analysis' ) );
+ add_action( 'admin_init', array( $this, 'translate_meta_boxes' ) );
+ }
+
+ /**
+ * Translate text strings for use in the meta box
+ *
+ * IMPORTANT: if you want to add a new string (option) somewhere, make sure you add that array key to
+ * the main meta box definition array in the class WPSEO_Meta() as well!!!!
+ *
+ */
+ public static function translate_meta_boxes() {
+ self::$meta_fields['general']['snippetpreview']['title'] = __( 'Snippet Preview', 'wordpress-seo' );
+ self::$meta_fields['general']['snippetpreview']['help'] = sprintf( __( 'This is a rendering of what this post might look like in Google\'s search results.<br/><br/>Read %sthis post%s for more info.', 'wordpress-seo' ), '<a href="https://yoast.com/snippet-preview/#utm_source=wordpress-seo-metabox&utm_medium=inline-help&utm_campaign=snippet-preview">', '</a>' );
+
+ self::$meta_fields['general']['focuskw']['title'] = __( 'Focus Keyword', 'wordpress-seo' );
+ self::$meta_fields['general']['focuskw']['help'] = sprintf( __( 'Pick the main keyword or keyphrase that this post/page is about.<br/><br/>Read %sthis post%s for more info.', 'wordpress-seo' ), '<a href="https://yoast.com/focus-keyword/#utm_source=wordpress-seo-metabox&utm_medium=inline-help&utm_campaign=focus-keyword">', '</a>' );
+
+ self::$meta_fields['general']['title']['title'] = __( 'SEO Title', 'wordpress-seo' );
+ self::$meta_fields['general']['title']['description'] = '<p id="yoast_wpseo_title-length-warning">' . '<span class="wrong">' . __( 'Warning:', 'wordpress-seo' ) . '</span> ' . __( 'Title display in Google is limited to a fixed width, yours is too long.', 'wordpress-seo' ) . '</p>';
+ self::$meta_fields['general']['title']['help'] = __( 'The SEO Title defaults to what is generated based on this sites title template for this posttype.', 'wordpress-seo' );
+
+ self::$meta_fields['general']['metadesc']['title'] = __( 'Meta Description', 'wordpress-seo' );
+ self::$meta_fields['general']['metadesc']['description'] = sprintf( __( 'The <code>meta</code> description will be limited to %s chars%s, %s chars left.', 'wordpress-seo' ), self::$meta_length, self::$meta_length_reason, '<span id="yoast_wpseo_metadesc-length"></span>' ) . ' <div id="yoast_wpseo_metadesc_notice"></div>';
+ self::$meta_fields['general']['metadesc']['help'] = sprintf( __( 'The meta description is often shown as the black text under the title in a search result. For this to work it has to contain the keyword that was searched for.<br/><br/>Read %sthis post%s for more info.', 'wordpress-seo' ), '<a href="https://yoast.com/snippet-preview/#utm_source=wordpress-seo-metabox&utm_medium=inline-help&utm_campaign=focus-keyword">', '</a>' );
+
+ self::$meta_fields['general']['metakeywords']['title'] = __( 'Meta Keywords', 'wordpress-seo' );
+ self::$meta_fields['general']['metakeywords']['description'] = __( 'If you type something above it will override your %smeta keywords template%s.', 'wordpress-seo' );
+
+
+ self::$meta_fields['advanced']['meta-robots-noindex']['title'] = __( 'Meta Robots Index', 'wordpress-seo' );
+ if ( '0' == get_option( 'blog_public' ) ) {
+ self::$meta_fields['advanced']['meta-robots-noindex']['description'] = '<p class="error-message">' . __( 'Warning: even though you can set the meta robots setting here, the entire site is set to noindex in the sitewide privacy settings, so these settings won\'t have an effect.', 'wordpress-seo' ) . '</p>';
+ }
+ self::$meta_fields['advanced']['meta-robots-noindex']['options']['0'] = __( 'Default for post type, currently: %s', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-noindex']['options']['2'] = __( 'index', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-noindex']['options']['1'] = __( 'noindex', 'wordpress-seo' );
+
+ self::$meta_fields['advanced']['meta-robots-nofollow']['title'] = __( 'Meta Robots Follow', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-nofollow']['options']['0'] = __( 'Follow', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-nofollow']['options']['1'] = __( 'Nofollow', 'wordpress-seo' );
+
+ self::$meta_fields['advanced']['meta-robots-adv']['title'] = __( 'Meta Robots Advanced', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-adv']['description'] = __( 'Advanced <code>meta</code> robots settings for this page.', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-adv']['options']['-'] = __( 'Site-wide default: %s', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-adv']['options']['none'] = __( 'None', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-adv']['options']['noodp'] = __( 'NO ODP', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-adv']['options']['noydir'] = __( 'NO YDIR', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-adv']['options']['noimageindex'] = __( 'No Image Index', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-adv']['options']['noarchive'] = __( 'No Archive', 'wordpress-seo' );
+ self::$meta_fields['advanced']['meta-robots-adv']['options']['nosnippet'] = __( 'No Snippet', 'wordpress-seo' );
+
+ self::$meta_fields['advanced']['bctitle']['title'] = __( 'Breadcrumbs title', 'wordpress-seo' );
+ self::$meta_fields['advanced']['bctitle']['description'] = __( 'Title to use for this page in breadcrumb paths', 'wordpress-seo' );
+
+ self::$meta_fields['advanced']['sitemap-include']['title'] = __( 'Include in Sitemap', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-include']['description'] = __( 'Should this page be in the XML Sitemap at all times, regardless of Robots Meta settings?', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-include']['options']['-'] = __( 'Auto detect', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-include']['options']['always'] = __( 'Always include', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-include']['options']['never'] = __( 'Never include', 'wordpress-seo' );
+
+ self::$meta_fields['advanced']['sitemap-prio']['title'] = __( 'Sitemap Priority', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-prio']['description'] = __( 'The priority given to this page in the XML sitemap.', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-prio']['options']['-'] = __( 'Automatic prioritization', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-prio']['options']['1'] = __( '1 - Highest priority', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-prio']['options']['0.8'] .= __( 'Default for first tier pages', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-prio']['options']['0.6'] .= __( 'Default for second tier pages and posts', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-prio']['options']['0.5'] .= __( 'Medium priority', 'wordpress-seo' );
+ self::$meta_fields['advanced']['sitemap-prio']['options']['0.1'] .= __( 'Lowest priority', 'wordpress-seo' );
+
+ self::$meta_fields['advanced']['canonical']['title'] = __( 'Canonical URL', 'wordpress-seo' );
+ self::$meta_fields['advanced']['canonical']['description'] = sprintf( __( 'The canonical URL that this page should point to, leave empty to default to permalink. %sCross domain canonical%s supported too.', 'wordpress-seo' ), '<a target="_blank" href="http://googlewebmastercentral.blogspot.com/2009/12/handling-legitimate-cross-domain.html">', '</a>' );
+
+ self::$meta_fields['advanced']['redirect']['title'] = __( '301 Redirect', 'wordpress-seo' );
+ self::$meta_fields['advanced']['redirect']['description'] = __( 'The URL that this page should redirect to.', 'wordpress-seo' );
+
+ do_action( 'wpseo_tab_translate' );
+ }
+
+ /**
+ * Test whether the metabox should be hidden either by choice of the admin or because
+ * the post type is not a public post type
+ *
+ * @since 1.5.0
+ *
+ * @param string $post_type (optional) The post type to test, defaults to the current post post_type
+ *
+ * @return bool Whether or not the meta box (and associated columns etc) should be hidden
+ */
+ function is_metabox_hidden( $post_type = null ) {
+ if ( ! isset( $post_type ) ) {
+ if ( isset( $GLOBALS['post'] ) && ( is_object( $GLOBALS['post'] ) && isset( $GLOBALS['post']->post_type ) ) ) {
+ $post_type = $GLOBALS['post']->post_type;
+ } elseif ( isset( $_GET['post_type'] ) && $_GET['post_type'] !== '' ) {
+ $post_type = sanitize_text_field( $_GET['post_type'] );
+ }
+ }
+
+ if ( isset( $post_type ) ) {
+ // Don't make static as post_types may still be added during the run
+ $cpts = get_post_types( array( 'public' => true ), 'names' );
+ $options = get_option( 'wpseo_titles' );
+
+ return ( ( isset( $options[ 'hideeditbox-' . $post_type ] ) && $options[ 'hideeditbox-' . $post_type ] === true ) || in_array( $post_type, $cpts ) === false );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Sets up all the functionality related to the prominence of the page analysis functionality.
+ */
+ public function setup_page_analysis() {
+
+ if ( apply_filters( 'wpseo_use_page_analysis', true ) === true ) {
+
+ $post_types = get_post_types( array( 'public' => true ), 'names' );
+
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ foreach ( $post_types as $pt ) {
+ if ( $this->is_metabox_hidden( $pt ) === false ) {
+ add_filter( 'manage_' . $pt . '_posts_columns', array( $this, 'column_heading' ), 10, 1 );
+ add_action( 'manage_' . $pt . '_posts_custom_column', array(
+ $this,
+ 'column_content',
+ ), 10, 2 );
+ add_action( 'manage_edit-' . $pt . '_sortable_columns', array(
+ $this,
+ 'column_sort',
+ ), 10, 2 );
+ }
+ }
+ }
+ add_action( 'restrict_manage_posts', array( $this, 'posts_filter_dropdown' ) );
+ add_filter( 'request', array( $this, 'column_sort_orderby' ) );
+
+ add_action( 'post_submitbox_misc_actions', array( $this, 'publish_box' ) );
+ }
+ }
+
+ /**
+ * Get an instance of the text statistics class
+ *
+ * @return Yoast_TextStatistics
+ */
+ private function statistics() {
+ if ( ! isset( $this->statistics ) ) {
+ $this->statistics = new Yoast_TextStatistics( get_bloginfo( 'charset' ) );
+ }
+
+ return $this->statistics;
+ }
+
+ /**
+ * Returns post in metabox context
+ *
+ * @returns WP_Post
+ */
+ private function get_metabox_post() {
+ if ( isset( $_GET['post'] ) ) {
+ $post_id = (int) WPSEO_Option::validate_int( $_GET['post'] );
+ $post = get_post( $post_id );
+ } else {
+ global $post;
+ }
+
+ return $post;
+ }
+
+ /**
+ * Lowercase a sentence while preserving "weird" characters.
+ *
+ * This should work with Greek, Russian, Polish & French amongst other languages...
+ *
+ * @param string $string String to lowercase
+ *
+ * @return string
+ */
+ public function strtolower_utf8( $string ) {
+
+ // Prevent comparison between utf8 characters and html entities (é vs é)
+ $string = html_entity_decode( $string );
+
+ $convert_to = array(
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
+ 'v', 'w', 'x', 'y', 'z', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï',
+ 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж',
+ 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы',
+ 'ь', 'э', 'ю', 'я', 'ą', 'ć', 'ę', 'ł', 'ń', 'ó', 'ś', 'ź', 'ż',
+ );
+ $convert_from = array(
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
+ 'V', 'W', 'X', 'Y', 'Z', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï',
+ 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж',
+ 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ъ',
+ 'Ь', 'Э', 'Ю', 'Я', 'Ą', 'Ć', 'Ę', 'Ł', 'Ń', 'Ó', 'Ś', 'Ź', 'Ż',
+ );
+
+ return str_replace( $convert_from, $convert_to, $string );
+ }
+
+ /**
+ * Outputs the page analysis score in the Publish Box.
+ *
+ */
+ public function publish_box() {
+ if ( $this->is_metabox_hidden() === true ) {
+ return;
+ }
+
+ echo '<div class="misc-pub-section misc-yoast misc-pub-section-last">';
+
+ $post = $this->get_metabox_post();
+
+ if ( self::get_value( 'meta-robots-noindex', $post->ID ) === '1' ) {
+ $score_label = 'noindex';
+ $title = __( 'Post is set to noindex.', 'wordpress-seo' );
+ $score_title = $title;
+ } else {
+
+ $score = '';
+ $results = $this->calculate_results( $post );
+ if ( ! is_wp_error( $results ) && isset( $results['total'] ) ) {
+ $score = $results['total'];
+ unset( $results );
+ }
+
+ if ( $score === '' ) {
+ $score_label = 'na';
+ $title = __( 'No focus keyword set.', 'wordpress-seo' );
+ } else {
+ $score_label = wpseo_translate_score( $score );
+ }
+
+ $score_title = wpseo_translate_score( $score, false );
+ if ( ! isset( $title ) ) {
+ $title = $score_title;
+ }
+ }
+
+ echo '<div title="' . esc_attr( $title ) . '" class="' . esc_attr( 'wpseo-score-icon ' . $score_label ) . '"></div>';
+
+ echo __( 'SEO: ', 'wordpress-seo' ) . '<span class="wpseo-score-title">' . $score_title . '</span>';
+
+ echo ' <a class="wpseo_tablink scroll" href="#wpseo_linkdex">' . __( 'Check', 'wordpress-seo' ) . '</a>';
+
+ echo '</div>';
+ }
+
+ /**
+ * Adds the WordPress SEO meta box to the edit boxes in the edit post / page / cpt pages.
+ */
+ public function add_meta_box() {
+ $post_types = get_post_types( array( 'public' => true ) );
+
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ foreach ( $post_types as $post_type ) {
+ if ( $this->is_metabox_hidden( $post_type ) === false ) {
+ add_meta_box( 'wpseo_meta', __( 'WordPress SEO by Yoast', 'wordpress-seo' ), array(
+ $this,
+ 'meta_box',
+ ), $post_type, 'normal', apply_filters( 'wpseo_metabox_prio', 'high' ) );
+ }
+ }
+ }
+ }
+
+ /**
+ * Pass some variables to js for the edit / post page overview, snippet preview, etc.
+ *
+ * @return array
+ */
+ public function localize_script() {
+ $post = $this->get_metabox_post();
+
+ if ( ( ! is_object( $post ) || ! isset( $post->post_type ) ) || $this->is_metabox_hidden( $post->post_type ) === true ) {
+ return array();
+ }
+
+ $options = get_option( 'wpseo_titles' );
+
+ $date = '';
+ if ( isset( $options[ 'showdate-' . $post->post_type ] ) && $options[ 'showdate-' . $post->post_type ] === true ) {
+ $date = $this->get_post_date( $post );
+
+ self::$meta_length = self::$meta_length - ( strlen( $date ) + 5 );
+ self::$meta_length_reason = __( ' (because of date display)', 'wordpress-seo' );
+ }
+
+ self::$meta_length_reason = apply_filters( 'wpseo_metadesc_length_reason', self::$meta_length_reason, $post );
+ self::$meta_length = apply_filters( 'wpseo_metadesc_length', self::$meta_length, $post );
+
+ unset( $date );
+
+ $title_template = '';
+ if ( isset( $options[ 'title-' . $post->post_type ] ) && $options[ 'title-' . $post->post_type ] !== '' ) {
+ $title_template = $options[ 'title-' . $post->post_type ];
+ }
+
+ // If there's no title template set, use the default, otherwise title preview won't work.
+ if ( $title_template == '' ) {
+ $title_template = '%%title%% - %%sitename%%';
+ }
+
+ $metadesc_template = '';
+ if ( isset( $options[ 'metadesc-' . $post->post_type ] ) && $options[ 'metadesc-' . $post->post_type ] !== '' ) {
+ $metadesc_template = $options[ 'metadesc-' . $post->post_type ];
+ }
+
+ $sample_permalink = get_sample_permalink( $post->ID );
+ $sample_permalink = str_replace( '%page', '%post', $sample_permalink[0] );
+
+ $cached_replacement_vars = array();
+ foreach (
+ array(
+ 'date',
+ 'id',
+ 'sitename',
+ 'sitedesc',
+ 'sep',
+ 'page',
+ 'currenttime',
+ 'currentdate',
+ 'currentday',
+ 'currentmonth',
+ 'currentyear',
+ ) as $var
+ ) {
+ $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $post );
+ }
+
+ return array_merge( $cached_replacement_vars, array(
+ 'field_prefix' => self::$form_prefix,
+ 'keyword_header' => __( 'Your focus keyword was found in:', 'wordpress-seo' ),
+ 'article_header_text' => __( 'Article Heading: ', 'wordpress-seo' ),
+ 'page_title_text' => __( 'Page title: ', 'wordpress-seo' ),
+ 'page_url_text' => __( 'Page URL: ', 'wordpress-seo' ),
+ 'content_text' => __( 'Content: ', 'wordpress-seo' ),
+ 'meta_description_text' => __( 'Meta description: ', 'wordpress-seo' ),
+ 'choose_image' => __( 'Use Image', 'wordpress-seo' ),
+ 'wpseo_meta_desc_length' => self::$meta_length,
+ 'wpseo_title_template' => $title_template,
+ 'wpseo_metadesc_template' => $metadesc_template,
+ 'wpseo_permalink_template' => $sample_permalink,
+ 'wpseo_keyword_suggest_nonce' => wp_create_nonce( 'wpseo-get-suggest' ),
+ 'wpseo_replace_vars_nonce' => wp_create_nonce( 'wpseo-replace-vars' ),
+ 'no_parent_text' => __( '(no parent)', 'wordpress-seo' ),
+ ) );
+ }
+
+ /**
+ * Output a tab in the WP SEO Metabox
+ *
+ * @param string $id CSS ID of the tab.
+ * @param string $heading Heading for the tab.
+ * @param string $content Content of the tab. This content should be escaped.
+ */
+ public function do_tab( $id, $heading, $content ) {
+ ?>
+ <div class="wpseotab <?php echo esc_attr( $id ) ?>">
+ <h4 class="wpseo-heading"><?php echo esc_html( $heading ); ?></h4>
+ <table class="form-table">
+ <?php echo $content ?>
+ </table>
+ </div>
+ <?php
+ }
+
+ /**
+ * Output the meta box
+ */
+ function meta_box() {
+ $post = $this->get_metabox_post();
+ $options = WPSEO_Options::get_all();
+
+ ?>
+ <div class="wpseo-metabox-tabs-div">
+ <ul class="wpseo-metabox-tabs" id="wpseo-metabox-tabs">
+ <li class="general">
+ <a class="wpseo_tablink" href="#wpseo_general"><?php _e( 'General', 'wordpress-seo' ); ?></a></li>
+ <li id="linkdex" class="linkdex">
+ <a class="wpseo_tablink" href="#wpseo_linkdex"><?php _e( 'Page Analysis', 'wordpress-seo' ); ?></a>
+ </li>
+ <?php if ( current_user_can( 'manage_options' ) || $options['disableadvanced_meta'] === false ): ?>
+ <li class="advanced">
+ <a class="wpseo_tablink" href="#wpseo_advanced"><?php _e( 'Advanced', 'wordpress-seo' ); ?></a>
+ </li>
+ <?php endif; ?>
+ <?php do_action( 'wpseo_tab_header' ); ?>
+ </ul>
+ <?php
+ $content = '';
+ if ( is_object( $post ) && isset( $post->post_type ) ) {
+ foreach ( $this->get_meta_field_defs( 'general', $post->post_type ) as $key => $meta_field ) {
+ $content .= $this->do_meta_box( $meta_field, $key );
+ }
+ }
+ $this->do_tab( 'general', __( 'General', 'wordpress-seo' ), $content );
+
+ $this->do_tab( 'linkdex', __( 'Page Analysis', 'wordpress-seo' ), $this->linkdex_output( $post ) );
+
+ if ( current_user_can( 'manage_options' ) || $options['disableadvanced_meta'] === false ) {
+ $content = '';
+ foreach ( $this->get_meta_field_defs( 'advanced' ) as $key => $meta_field ) {
+ $content .= $this->do_meta_box( $meta_field, $key );
+ }
+ $this->do_tab( 'advanced', __( 'Advanced', 'wordpress-seo' ), $content );
+ }
+
+ do_action( 'wpseo_tab_content' );
+
+ echo '</div>';
+ }
+
+ /**
+ * Adds a line in the meta box
+ *
+ * @todo [JRF] check if $class is added appropriately everywhere
+ *
+ * @param array $meta_field_def Contains the vars based on which output is generated.
+ * @param string $key Internal key (without prefix)
+ *
+ * @return string
+ */
+ function do_meta_box( $meta_field_def, $key = '' ) {
+ $content = '';
+ $esc_form_key = esc_attr( self::$form_prefix . $key );
+ $post = $this->get_metabox_post();
+ $meta_value = self::get_value( $key, $post->ID );
+
+ $class = '';
+ if ( isset( $meta_field_def['class'] ) && $meta_field_def['class'] !== '' ) {
+ $class = ' ' . $meta_field_def['class'];
+ }
+
+ $placeholder = '';
+ if ( isset( $meta_field_def['placeholder'] ) && $meta_field_def['placeholder'] !== '' ) {
+ $placeholder = $meta_field_def['placeholder'];
+ }
+
+ switch ( $meta_field_def['type'] ) {
+ case 'snippetpreview':
+ $content .= $this->snippet();
+ break;
+
+ case 'text':
+ $ac = '';
+ if ( isset( $meta_field_def['autocomplete'] ) && $meta_field_def['autocomplete'] === false ) {
+ $ac = 'autocomplete="off" ';
+ }
+ if ( $placeholder !== '' ) {
+ $placeholder = ' placeholder="' . esc_attr( $placeholder ) . '"';
+ }
+ $content .= '<input type="text"' . $placeholder . '" id="' . $esc_form_key . '" ' . $ac . 'name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '" class="large-text' . $class . '"/><br />';
+ break;
+
+ case 'textarea':
+ $rows = 3;
+ if ( isset( $meta_field_def['rows'] ) && $meta_field_def['rows'] > 0 ) {
+ $rows = $meta_field_def['rows'];
+ }
+ $content .= '<textarea class="large-text' . $class . '" rows="' . esc_attr( $rows ) . '" id="' . $esc_form_key . '" name="' . $esc_form_key . '">' . esc_textarea( $meta_value ) . '</textarea>';
+ break;
+
+ case 'select':
+ if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== array() ) {
+ $content .= '<select name="' . $esc_form_key . '" id="' . $esc_form_key . '" class="yoast' . $class . '">';
+ foreach ( $meta_field_def['options'] as $val => $option ) {
+ $selected = selected( $meta_value, $val, false );
+ $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>';
+ }
+ $content .= '</select>';
+ }
+ break;
+
+ case 'multiselect':
+ if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== array() ) {
+
+ // Set $meta_value as $selectedarr
+ $selected_arr = $meta_value;
+
+ // If the multiselect field is 'meta-robots-adv' we should explode on ,
+ if ( 'meta-robots-adv' === $key ) {
+ $selected_arr = explode( ',', $meta_value );
+ }
+
+ if ( ! is_array( $selected_arr ) ) {
+ $selected_arr = (array) $selected_arr;
+ }
+
+ $options_count = count( $meta_field_def['options'] );
+
+ // @todo [JRF => whomever] verify height calculation for older WP versions, was 16x, for WP3.8 20x is more appropriate
+ $content .= '<select multiple="multiple" size="' . esc_attr( $options_count ) . '" style="height: ' . esc_attr( ( $options_count * 20 ) + 4 ) . 'px;" name="' . $esc_form_key . '[]" id="' . $esc_form_key . '" class="yoast' . $class . '">';
+ foreach ( $meta_field_def['options'] as $val => $option ) {
+ $selected = '';
+ if ( in_array( $val, $selected_arr ) ) {
+ $selected = ' selected="selected"';
+ }
+ $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>';
+ }
+ $content .= '</select>';
+ }
+ break;
+
+ case 'checkbox':
+ $checked = checked( $meta_value, 'on', false );
+ $expl = ( isset( $meta_field_def['expl'] ) ) ? esc_html( $meta_field_def['expl'] ) : '';
+ $content .= '<label for="' . $esc_form_key . '"><input type="checkbox" id="' . $esc_form_key . '" name="' . $esc_form_key . '" ' . $checked . ' value="on" class="yoast' . $class . '"/> ' . $expl . '</label><br />';
+ break;
+
+ case 'radio':
+ if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== array() ) {
+ foreach ( $meta_field_def['options'] as $val => $option ) {
+ $checked = checked( $meta_value, $val, false );
+ $content .= '<input type="radio" ' . $checked . ' id="' . $esc_form_key . '_' . esc_attr( $val ) . '" name="' . $esc_form_key . '" value="' . esc_attr( $val ) . '"/> <label for="' . $esc_form_key . '_' . esc_attr( $val ) . '">' . esc_html( $option ) . '</label> ';
+ }
+ }
+ break;
+
+ case 'upload':
+ $content .= '<input id="' . $esc_form_key . '" type="text" size="36" name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '" />';
+ $content .= '<input id="' . $esc_form_key . '_button" class="wpseo_image_upload_button button" type="button" value="Upload Image" />';
+ break;
+ }
+
+
+ $html = '';
+ if ( $content === '' ) {
+ $content = apply_filters( 'wpseo_do_meta_box_field_' . $key, $content, $meta_value, $esc_form_key, $meta_field_def, $key );
+ }
+
+ if ( $content !== '' ) {
+
+ $label = esc_html( $meta_field_def['title'] );
+ if ( in_array( $meta_field_def['type'], array(
+ 'snippetpreview',
+ 'radio',
+ 'checkbox',
+ ), true ) === false
+ ) {
+ $label = '<label for="' . $esc_form_key . '">' . $label . ':</label>';
+ }
+
+ $help = '';
+ if ( isset( $meta_field_def['help'] ) && $meta_field_def['help'] !== '' ) {
+ $help = '<img src="' . plugins_url( 'images/question-mark.png', WPSEO_FILE ) . '" class="alignright yoast_help" id="' . esc_attr( $key . 'help' ) . '" alt="' . esc_attr( $meta_field_def['help'] ) . '" />';
+ }
+
+ $html = '
+ <tr>
+ <th scope="row">' . $label . $help . '</th>
+ <td>';
+
+ $html .= $content;
+
+ if ( isset( $meta_field_def['description'] ) ) {
+ $html .= '<div>' . $meta_field_def['description'] . '</div>';
+ }
+
+ $html .= '
+ </td>
+ </tr>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Retrieve a post date when post is published, or return current date when it's not.
+ *
+ * @param object $post Post to retrieve the date for.
+ *
+ * @return string
+ */
+ function get_post_date( $post ) {
+ if ( isset( $post->post_date ) && $post->post_status == 'publish' ) {
+ $date = date_i18n( 'j M Y', strtotime( $post->post_date ) );
+ } else {
+ $date = date_i18n( 'j M Y' );
+ }
+
+ return (string) $date;
+ }
+
+ /**
+ * Generate a snippet preview.
+ *
+ * @return string
+ */
+ function snippet() {
+ $post = $this->get_metabox_post();
+
+ $options = WPSEO_Options::get_all();
+
+ $date = '';
+ if ( is_object( $post ) && isset( $options[ 'showdate-' . $post->post_type ] ) && $options[ 'showdate-' . $post->post_type ] === true ) {
+ $date = $this->get_post_date( $post );
+ }
+
+ $title = self::get_value( 'title', $post->ID );
+ $desc = self::get_value( 'metadesc', $post->ID );
+
+ $slug = ( is_object( $post ) && isset( $post->post_name ) ) ? $post->post_name : '';
+ if ( $slug !== '' ) {
+ $slug = sanitize_title( $title );
+ }
+
+ if ( is_string( $date ) && $date !== '' ) {
+ $datestr = '<span class="date">' . $date . ' - </span>';
+ } else {
+ $datestr = '';
+ }
+ $content = '<div id="wpseosnippet">
+ <a class="title" id="wpseosnippet_title" href="#">' . esc_html( $title ) . '</a>';
+
+ $content .= '<span class="url">' . str_replace( 'http://', '', get_bloginfo( 'url' ) ) . '/' . esc_html( $slug ) . '/</span>';
+
+ $content .= '<p class="desc">' . $datestr . '<span class="autogen"></span><span class="content">' . esc_html( $desc ) . '</span></p>';
+
+ $content .= '</div>';
+
+ $content = apply_filters( 'wpseo_snippet', $content, $post, compact( 'title', 'desc', 'date', 'slug' ) );
+
+ return $content;
+ }
+
+ /**
+ * Save the WP SEO metadata for posts.
+ *
+ * @internal $_POST parameters are validated via sanitize_post_meta()
+ *
+ * @param int $post_id
+ *
+ * @return bool|void Boolean false if invalid save post request
+ */
+ function save_postdata( $post_id ) {
+ if ( $post_id === null ) {
+ return false;
+ }
+
+ if ( wp_is_post_revision( $post_id ) ) {
+ $post_id = wp_is_post_revision( $post_id );
+ }
+
+ clean_post_cache( $post_id );
+ $post = get_post( $post_id );
+
+ if ( ! is_object( $post ) ) {
+ // non-existent post
+ return false;
+ }
+
+ $meta_boxes = apply_filters( 'wpseo_save_metaboxes', array() );
+ $meta_boxes = array_merge( $meta_boxes, $this->get_meta_field_defs( 'general', $post->post_type ), $this->get_meta_field_defs( 'advanced' ) );
+
+ foreach ( $meta_boxes as $key => $meta_box ) {
+ $data = null;
+ if ( 'checkbox' === $meta_box['type'] ) {
+ $data = isset( $_POST[ self::$form_prefix . $key ] ) ? 'on' : 'off';
+ } else {
+ if ( isset( $_POST[ self::$form_prefix . $key ] ) ) {
+ $data = $_POST[ self::$form_prefix . $key ];
+ }
+ }
+ if ( isset( $data ) ) {
+ self::set_value( $key, $data, $post_id );
+ }
+ }
+
+ do_action( 'wpseo_saved_postdata' );
+ }
+
+ /**
+ * Enqueues all the needed JS and CSS.
+ * @todo [JRF => whomever] create css/metabox-mp6.css file and add it to the below allowed colors array when done
+ */
+ public function enqueue() {
+ global $pagenow;
+ if ( ! in_array( $pagenow, array(
+ 'post-new.php',
+ 'post.php',
+ 'edit.php',
+ ), true ) || $this->is_metabox_hidden() === true
+ ) {
+ return;
+ }
+
+
+ $color = get_user_meta( get_current_user_id(), 'admin_color', true );
+ if ( '' == $color || in_array( $color, array( 'classic', 'fresh' ), true ) === false ) {
+ $color = 'fresh';
+ }
+
+
+ if ( $pagenow == 'edit.php' ) {
+ wp_enqueue_style( 'edit-page', plugins_url( 'css/edit-page' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
+ } else {
+ if ( 0 != get_queried_object_id() ) {
+ wp_enqueue_media( array( 'post' => get_queried_object_id() ) ); // enqueue files needed for upload functionality
+ }
+ wp_enqueue_style( 'metabox-tabs', plugins_url( 'css/metabox-tabs' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
+ wp_enqueue_style( "metabox-$color", plugins_url( 'css/metabox-' . esc_attr( $color ) . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
+
+ wp_enqueue_script( 'jquery-ui-autocomplete' );
+
+ wp_enqueue_script( 'jquery-qtip', plugins_url( 'js/jquery.qtip.min.js', WPSEO_FILE ), array( 'jquery' ), '1.0.0-RC3', true );
+ wp_enqueue_script( 'wp-seo-metabox', plugins_url( 'js/wp-seo-metabox' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_FILE ), array(
+ 'jquery',
+ 'jquery-ui-core',
+ 'jquery-ui-autocomplete',
+ ), WPSEO_VERSION, true );
+
+ wp_enqueue_script( 'wpseo-admin-media', plugins_url( 'js/wp-seo-admin-media' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_FILE ), array( 'jquery', 'jquery-ui-core' ), WPSEO_VERSION, true );
+ wp_localize_script( 'wpseo-admin-media', 'wpseoMediaL10n', $this->localize_media_script() );
+
+ // Text strings to pass to metabox for keyword analysis
+ wp_localize_script( 'wp-seo-metabox', 'wpseoMetaboxL10n', $this->localize_script() );
+ }
+ }
+
+ /**
+ * Pass some variables to js for upload module.
+ *
+ * @return array
+ */
+ public function localize_media_script() {
+ return array(
+ 'choose_image' => __( 'Use Image', 'wordpress-seo' ),
+ );
+ }
+
+ /**
+ * Adds a dropdown that allows filtering on the posts SEO Quality.
+ *
+ * @return bool
+ */
+ function posts_filter_dropdown() {
+ global $pagenow;
+ if ( $pagenow === 'upload.php' || $this->is_metabox_hidden() === true ) {
+ return;
+ }
+
+ $scores_array = array(
+ 'na' => __( 'SEO: No Focus Keyword', 'wordpress-seo' ),
+ 'bad' => __( 'SEO: Bad', 'wordpress-seo' ),
+ 'poor' => __( 'SEO: Poor', 'wordpress-seo' ),
+ 'ok' => __( 'SEO: OK', 'wordpress-seo' ),
+ 'good' => __( 'SEO: Good', 'wordpress-seo' ),
+ 'noindex' => __( 'SEO: Post Noindexed', 'wordpress-seo' )
+ );
+
+ echo '<select name="seo_filter">';
+ echo '<option value="">' . __( 'All SEO Scores', 'wordpress-seo' ) . '</option>';
+ foreach ( $scores_array as $val => $text ) {
+ $sel = '';
+ if ( isset( $_GET['seo_filter'] ) ) {
+ $sel = selected( $_GET['seo_filter'], $val, false );
+ }
+ echo '<option ' . $sel . 'value="' . $val . '">' . $text . '</option>';
+ }
+ echo '</select>';
+ }
+
+ /**
+ * Adds the column headings for the SEO plugin for edit posts / pages overview
+ *
+ * @param array $columns Already existing columns.
+ *
+ * @return array
+ */
+ function column_heading( $columns ) {
+ if ( $this->is_metabox_hidden() === true ) {
+ return $columns;
+ }
+
+ return array_merge( $columns, array(
+ 'wpseo-score' => __( 'SEO', 'wordpress-seo' ),
+ 'wpseo-title' => __( 'SEO Title', 'wordpress-seo' ),
+ 'wpseo-metadesc' => __( 'Meta Desc.', 'wordpress-seo' ),
+ 'wpseo-focuskw' => __( 'Focus KW', 'wordpress-seo' )
+ ) );
+ }
+
+ /**
+ * Display the column content for the given column
+ *
+ * @param string $column_name Column to display the content for.
+ * @param int $post_id Post to display the column content for.
+ */
+ function column_content( $column_name, $post_id ) {
+ if ( $this->is_metabox_hidden() === true ) {
+ return;
+ }
+
+ if ( $column_name === 'wpseo-score' ) {
+ $score = self::get_value( 'linkdex', $post_id );
+ if ( self::get_value( 'meta-robots-noindex', $post_id ) === '1' ) {
+ $score_label = 'noindex';
+ $title = __( 'Post is set to noindex.', 'wordpress-seo' );
+ self::set_value( 'linkdex', 0, $post_id );
+ } elseif ( $score !== '' ) {
+ $nr = wpseo_calc( $score, '/', 10, true );
+ $score_label = wpseo_translate_score( $nr );
+ $title = wpseo_translate_score( $nr, false );
+ unset( $nr );
+ } else {
+ $this->calculate_results( get_post( $post_id ) );
+ $score = self::get_value( 'linkdex', $post_id );
+ if ( $score === '' ) {
+ $score_label = 'na';
+ $title = __( 'Focus keyword not set.', 'wordpress-seo' );
+ } else {
+ $score_label = wpseo_translate_score( $score );
+ $title = wpseo_translate_score( $score, false );
+ }
+ }
+
+ echo '<div title="' . esc_attr( $title ) . '" class="wpseo-score-icon ' . esc_attr( $score_label ) . '"></div>';
+ }
+ if ( $column_name === 'wpseo-title' ) {
+ echo esc_html( apply_filters( 'wpseo_title', wpseo_replace_vars( $this->page_title( $post_id ), get_post( $post_id, ARRAY_A ) ) ) );
+ }
+ if ( $column_name === 'wpseo-metadesc' ) {
+ echo esc_html( apply_filters( 'wpseo_metadesc', wpseo_replace_vars( self::get_value( 'metadesc', $post_id ), get_post( $post_id, ARRAY_A ) ) ) );
+ }
+ if ( $column_name === 'wpseo-focuskw' ) {
+ $focuskw = self::get_value( 'focuskw', $post_id );
+ echo esc_html( $focuskw );
+ }
+ }
+
+ /**
+ * Indicate which of the SEO columns are sortable.
+ *
+ * @param array $columns appended with their orderby variable.
+ *
+ * @return array
+ */
+ function column_sort( $columns ) {
+ if ( $this->is_metabox_hidden() === true ) {
+ return $columns;
+ }
+
+ $columns['wpseo-score'] = 'wpseo-score';
+ $columns['wpseo-metadesc'] = 'wpseo-metadesc';
+ $columns['wpseo-focuskw'] = 'wpseo-focuskw';
+
+ return $columns;
+ }
+
+ /**
+ * Modify the query based on the seo_filter variable in $_GET
+ *
+ * @param array $vars Query variables.
+ *
+ * @return array
+ */
+ function column_sort_orderby( $vars ) {
+ if ( isset( $_GET['seo_filter'] ) ) {
+ $noindex = false;
+ $high = false;
+ switch ( $_GET['seo_filter'] ) {
+ case 'noindex':
+ $low = false;
+ $noindex = true;
+ break;
+ case 'na':
+ $low = 0;
+ $high = 0;
+ break;
+ case 'bad':
+ $low = 1;
+ $high = 34;
+ break;
+ case 'poor':
+ $low = 35;
+ $high = 54;
+ break;
+ case 'ok':
+ $low = 55;
+ $high = 74;
+ break;
+ case 'good':
+ $low = 75;
+ $high = 100;
+ break;
+ default:
+ $low = false;
+ $high = false;
+ $noindex = false;
+ break;
+ }
+ if ( $low !== false ) {
+ /* @internal DON'T touch the order of these without double-checking/adjusting
+ * the seo_score_posts_where() method below! */
+ $vars = array_merge(
+ $vars,
+ array(
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ array(
+ 'key' => self::$meta_prefix . 'linkdex',
+ 'value' => array( $low, $high ),
+ 'type' => 'numeric',
+ 'compare' => 'BETWEEN',
+ ),
+ array(
+ 'key' => self::$meta_prefix . 'meta-robots-noindex',
+ 'value' => 'needs-a-value-anyway',
+ 'compare' => 'NOT EXISTS',
+ ),
+ array(
+ 'key' => self::$meta_prefix . 'meta-robots-noindex',
+ 'value' => '1',
+ 'compare' => '!=',
+ ),
+ )
+ )
+ );
+
+ add_filter( 'posts_where', array( $this, 'seo_score_posts_where' ) );
+
+ } elseif ( $noindex ) {
+ $vars = array_merge(
+ $vars,
+ array(
+ 'meta_query' => array(
+ array(
+ 'key' => self::$meta_prefix . 'meta-robots-noindex',
+ 'value' => '1',
+ 'compare' => '=',
+ ),
+ )
+ )
+ );
+ }
+ }
+ if ( isset( $_GET['seo_kw_filter'] ) && $_GET['seo_kw_filter'] !== '' ) {
+ $vars = array_merge(
+ $vars, array(
+ 'post_type' => 'any',
+ 'meta_key' => self::$meta_prefix . 'focuskw',
+ 'meta_value' => sanitize_text_field( $_GET['seo_kw_filter'] ),
+ )
+ );
+ }
+ if ( isset( $vars['orderby'] ) && 'wpseo-score' === $vars['orderby'] ) {
+ $vars = array_merge(
+ $vars, array(
+ 'meta_key' => self::$meta_prefix . 'linkdex',
+ 'orderby' => 'meta_value_num',
+ )
+ );
+ }
+ if ( isset( $vars['orderby'] ) && 'wpseo-metadesc' === $vars['orderby'] ) {
+ $vars = array_merge(
+ $vars, array(
+ 'meta_key' => self::$meta_prefix . 'metadesc',
+ 'orderby' => 'meta_value',
+ )
+ );
+ }
+ if ( isset( $vars['orderby'] ) && 'wpseo-focuskw' === $vars['orderby'] ) {
+ $vars = array_merge(
+ $vars, array(
+ 'meta_key' => self::$meta_prefix . 'focuskw',
+ 'orderby' => 'meta_value',
+ )
+ );
+ }
+
+ return $vars;
+ }
+
+ /**
+ * Hacky way to get round the limitation that you can only have AND *or* OR relationship between
+ * meta key clauses and not a combination - which is what we need.
+ *
+ * @param string $where
+ *
+ * @return string
+ */
+ function seo_score_posts_where( $where ) {
+ global $wpdb;
+
+ /* Find the two mutually exclusive noindex clauses which should be changed from AND to OR relation */
+ $find = '`([\s]+AND[\s]+)((?:' . $wpdb->prefix . 'postmeta|mt[0-9]|mt1)\.post_id IS NULL[\s]+)AND([\s]+\([\s]*(?:' . $wpdb->prefix . 'postmeta|mt[0-9])\.meta_key = \'' . self::$meta_prefix . 'meta-robots-noindex\' AND CAST\([^\)]+\)[^\)]+\))`';
+
+ $replace = '$1( $2OR$3 )';
+
+ $new_where = preg_replace( $find, $replace, $where );
+
+ if ( $new_where ) {
+ return $new_where;
+ } else {
+ return $where;
+ }
+ }
+
+ /**
+ * Retrieve the page title.
+ *
+ * @param int $post_id Post to retrieve the title for.
+ *
+ * @return string
+ */
+ function page_title( $post_id ) {
+ $fixed_title = self::get_value( 'title', $post_id );
+ if ( $fixed_title !== '' ) {
+ return $fixed_title;
+ } else {
+ $post = get_post( $post_id );
+ $options = WPSEO_Options::get_all();
+ if ( is_object( $post ) && ( isset( $options[ 'title-' . $post->post_type ] ) && $options[ 'title-' . $post->post_type ] !== '' ) ) {
+ $title_template = $options[ 'title-' . $post->post_type ];
+ $title_template = str_replace( ' %%page%% ', ' ', $title_template );
+
+ return wpseo_replace_vars( $title_template, $post );
+ } else {
+ return wpseo_replace_vars( '%%title%%', $post );
+ }
+ }
+ }
+
+ /**
+ * Sort an array by a given key.
+ *
+ * @param array $array Array to sort, array is returned sorted.
+ * @param string $key Key to sort array by.
+ */
+ function aasort( &$array, $key ) {
+ $sorter = array();
+ $ret = array();
+ reset( $array );
+ foreach ( $array as $ii => $va ) {
+ $sorter[ $ii ] = $va[ $key ];
+ }
+ asort( $sorter );
+ foreach ( $sorter as $ii => $va ) {
+ $ret[ $ii ] = $array[ $ii ];
+ }
+ $array = $ret;
+ }
+
+ /**
+ * Output the page analysis results.
+ *
+ * @param object $post Post to output the page analysis results for.
+ *
+ * @return string
+ */
+ function linkdex_output( $post ) {
+ $results = $this->calculate_results( $post );
+
+ if ( is_wp_error( $results ) ) {
+ $error = $results->get_error_messages();
+
+ return '<tr><td><div class="wpseo_msg"><p><strong>' . esc_html( $error[0] ) . '</strong></p></div></td></tr>';
+ }
+ $output = '';
+
+ if ( is_array( $results ) && $results !== array() ) {
+
+ $output = '<table class="wpseoanalysis">';
+ $perc_score = absint( $results['total'] );
+ unset( $results['total'] ); // unset to prevent echoing it.
+
+ foreach ( $results as $result ) {
+ if ( is_array( $result ) ) {
+ $score = wpseo_translate_score( $result['val'] );
+ $output .= '<tr><td class="score"><div class="' . esc_attr( 'wpseo-score-icon ' . $score ) . '"></div></td><td>' . $result['msg'] . '</td></tr>';
+ }
+ }
+ $output .= '</table>';
+
+ if ( WP_DEBUG === true || ( defined( 'WPSEO_DEBUG' ) && WPSEO_DEBUG === true ) ) {
+ $output .= '<p><small>(' . $perc_score . '%)</small></p>';
+ }
+ }
+
+ $output = '<div class="wpseo_msg"><p>' . __( 'To update this page analysis, save as draft or update and check this tab again', 'wordpress-seo' ) . '.</p></div>' . $output;
+
+ unset( $results );
+
+ return $output;
+ }
+
+ /**
+ * Calculate the page analysis results for post.
+ *
+ * @todo [JRF => whomever] check whether the results of this method are always checked with is_wp_error()
+ * @todo [JRF => whomever] check the usage of this method as it's quite intense/heavy, see if it's only
+ * used when really necessary
+ * @todo [JRF => whomever] see if we can get rid of the passing by reference of $results as it makes
+ * the code obfuscated
+ *
+ * @param object $post Post to calculate the results for.
+ *
+ * @return array|WP_Error
+ */
+ function calculate_results( $post ) {
+ $options = WPSEO_Options::get_all();
+
+ if ( ! class_exists( 'DOMDocument' ) ) {
+ $result = new WP_Error( 'no-domdocument', sprintf( __( "Your hosting environment does not support PHP's %sDocument Object Model%s.", 'wordpress-seo' ), '<a href="http://php.net/manual/en/book.dom.php">', '</a>' ) . ' ' . __( "To enjoy all the benefits of the page analysis feature, you'll need to (get your host to) install it.", 'wordpress-seo' ) );
+
+ return $result;
+ }
+
+ if ( ! is_array( $post ) && ! is_object( $post ) ) {
+ $result = new WP_Error( 'no-post', __( 'No post content to analyse.', 'wordpress-seo' ) );
+
+ return $result;
+ } elseif ( self::get_value( 'focuskw', $post->ID ) === '' ) {
+ $result = new WP_Error( 'no-focuskw', sprintf( __( 'No focus keyword was set for this %s. If you do not set a focus keyword, no score can be calculated.', 'wordpress-seo' ), $post->post_type ) );
+
+ self::set_value( 'linkdex', 0, $post->ID );
+
+ return $result;
+ } elseif ( apply_filters( 'wpseo_use_page_analysis', true ) !== true ) {
+ $result = new WP_Error( 'page-analysis-disabled', sprintf( __( 'Page Analysis has been disabled.', 'wordpress-seo' ), $post->post_type ) );
+
+ return $result;
+ }
+
+ $results = array();
+ $job = array();
+
+ $sampleurl = $this->get_sample_permalink( $post );
+ $job['pageUrl'] = preg_replace( '`%(?:post|page)name%`', $sampleurl[1], $sampleurl[0] );
+ $job['pageSlug'] = urldecode( $post->post_name );
+ $job['keyword'] = self::get_value( 'focuskw', $post->ID );
+ $job['keyword_folded'] = $this->strip_separators_and_fold( $job['keyword'] );
+ $job['post_id'] = $post->ID;
+ $job['post_type'] = $post->post_type;
+
+ $dom = new domDocument;
+ $dom->strictErrorChecking = false;
+ $dom->preserveWhiteSpace = false;
+
+ /**
+ * Filter: 'wpseo_pre_analysis_post_content' - Make the post content filterable before calculating the page analysis
+ *
+ * @api string $post_content The post content
+ *
+ * @param object $post The post
+ */
+ $post_content = apply_filters( 'wpseo_pre_analysis_post_content', $post->post_content, $post );
+
+ // Check if the post content is not empty
+ if ( ! empty( $post_content ) ) {
+ @$dom->loadHTML( $post_content );
+ }
+
+ unset( $post_content );
+
+ $xpath = new DOMXPath( $dom );
+
+ // Check if this focus keyword has been used already.
+ $this->check_double_focus_keyword( $job, $results );
+
+ // Keyword
+ $this->score_keyword( $job['keyword'], $results );
+
+ // Title
+ $title = self::get_value( 'title', $post->ID );
+ if ( $title !== '' ) {
+ $job['title'] = $title;
+ } else {
+ if ( isset( $options[ 'title-' . $post->post_type ] ) && $options[ 'title-' . $post->post_type ] !== '' ) {
+ $title_template = $options[ 'title-' . $post->post_type ];
+ } else {
+ $title_template = '%%title%% - %%sitename%%';
+ }
+ $job['title'] = wpseo_replace_vars( $title_template, $post );
+ }
+ unset( $title );
+ $this->score_title( $job, $results );
+
+ // Meta description
+ $description = '';
+ $desc_meta = self::get_value( 'metadesc', $post->ID );
+ if ( $desc_meta !== '' ) {
+ $description = $desc_meta;
+ } elseif ( isset( $options[ 'metadesc-' . $post->post_type ] ) && $options[ 'metadesc-' . $post->post_type ] !== '' ) {
+ $description = wpseo_replace_vars( $options[ 'metadesc-' . $post->post_type ], $post );
+ }
+ unset( $desc_meta );
+
+ self::$meta_length = apply_filters( 'wpseo_metadesc_length', self::$meta_length, $post );
+
+ $this->score_description( $job, $results, $description, self::$meta_length );
+ unset( $description );
+
+ // Body
+ $body = $this->get_body( $post );
+ $firstp = $this->get_first_paragraph( $body );
+ $this->score_body( $job, $results, $body, $firstp );
+ unset( $firstp );
+
+ // URL
+ $this->score_url( $job, $results );
+
+ // Headings
+ $headings = $this->get_headings( $body );
+ $this->score_headings( $job, $results, $headings );
+ unset( $headings );
+
+ // Images
+ $imgs = array();
+ $imgs['count'] = substr_count( $body, '<img' );
+ $imgs = $this->get_images_alt_text( $post->ID, $body, $imgs );
+
+ // Check featured image
+ if ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail() ) {
+ $imgs['count'] += 1;
+
+ if ( empty( $imgs['alts'] ) ) {
+ $imgs['alts'] = array();
+ }
+
+ $imgs['alts'][] = $this->strtolower_utf8( get_post_meta( get_post_thumbnail_id( $post->ID ), '_wp_attachment_image_alt', true ) );
+ }
+
+ $this->score_images_alt_text( $job, $results, $imgs );
+ unset( $imgs );
+ unset( $body );
+
+ // Anchors
+ $anchors = $this->get_anchor_texts( $xpath );
+ $count = $this->get_anchor_count( $xpath );
+
+ $this->score_anchor_texts( $job, $results, $anchors, $count );
+ unset( $anchors, $count, $dom );
+
+ $results = apply_filters( 'wpseo_linkdex_results', $results, $job, $post );
+
+ $this->aasort( $results, 'val' );
+
+ $overall = 0;
+ $overall_max = 0;
+
+ foreach ( $results as $result ) {
+ $overall += $result['val'];
+ $overall_max += 9;
+ }
+
+ if ( $overall < 1 ) {
+ $overall = 1;
+ }
+ $score = wpseo_calc( wpseo_calc( $overall, '/', $overall_max ), '*', 100, true );
+
+ if ( ! is_wp_error( $score ) ) {
+ self::set_value( 'linkdex', absint( $score ), $post->ID );
+
+ $results['total'] = $score;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get sample permalink
+ *
+ * @param object $post
+ *
+ * @return array
+ */
+ function get_sample_permalink( $post ) {
+ if ( ! function_exists( 'get_sample_permalink' ) ) {
+ // Front-end post update
+ include_once( ABSPATH . 'wp-admin/includes/post.php' );
+ }
+
+ return get_sample_permalink( $post );
+ }
+
+ /**
+ * Save the score result to the results array.
+ *
+ * @param array $results The results array used to store results.
+ * @param int $scoreValue The score value.
+ * @param string $scoreMessage The score message.
+ * @param string $scoreLabel The label of the score to use in the results array.
+ * @param string $rawScore The raw score, to be used by other filters.
+ */
+ function save_score_result( &$results, $scoreValue, $scoreMessage, $scoreLabel, $rawScore = null ) {
+ $score = array(
+ 'val' => $scoreValue,
+ 'msg' => $scoreMessage,
+ 'raw' => $rawScore,
+ );
+ $results[ $scoreLabel ] = $score;
+ }
+
+ /**
+ * Clean up the input string.
+ *
+ * @param string $inputString String to clean up.
+ * @param bool $removeOptionalCharacters Whether or not to do a cleanup of optional chars too.
+ *
+ * @return string
+ */
+ function strip_separators_and_fold( $inputString, $removeOptionalCharacters = false ) {
+ $keywordCharactersAlwaysReplacedBySpace = array( ',', "'", '"', '?', '’', '“', '”', '|', '/' );
+ $keywordCharactersRemovedOrReplaced = array( '_', '-' );
+ $keywordWordsRemoved = array( ' a ', ' in ', ' an ', ' on ', ' for ', ' the ', ' and ' );
+
+ // lower
+ $inputString = $this->strtolower_utf8( $inputString );
+
+ // default characters replaced by space
+ $inputString = str_replace( $keywordCharactersAlwaysReplacedBySpace, ' ', $inputString );
+
+ // standardise whitespace
+ $inputString = wpseo_standardize_whitespace( $inputString );
+
+ // deal with the separators that can be either removed or replaced by space
+ if ( $removeOptionalCharacters ) {
+ // remove word separators with a space
+ $inputString = str_replace( $keywordWordsRemoved, ' ', $inputString );
+
+ $inputString = str_replace( $keywordCharactersRemovedOrReplaced, '', $inputString );
+ } else {
+ $inputString = str_replace( $keywordCharactersRemovedOrReplaced, ' ', $inputString );
+ }
+
+ // standardise whitespace again
+ $inputString = wpseo_standardize_whitespace( $inputString );
+
+ return trim( $inputString );
+ }
+
+ /**
+ * Check whether this focus keyword has been used for other posts before.
+ *
+ * @param array $job
+ * @param array $results
+ */
+ function check_double_focus_keyword( $job, &$results ) {
+ $posts = get_posts(
+ array(
+ 'meta_key' => self::$meta_prefix . 'focuskw',
+ 'meta_value' => $job['keyword'],
+ 'exclude' => $job['post_id'],
+ 'fields' => 'ids',
+ 'post_type' => 'any',
+ 'numberposts' => - 1,
+ )
+ );
+
+ if ( count( $posts ) == 0 ) {
+ $this->save_score_result( $results, 9, __( 'You\'ve never used this focus keyword before, very good.', 'wordpress-seo' ), 'keyword_overused' );
+ } elseif ( count( $posts ) == 1 ) {
+ $this->save_score_result( $results, 6, sprintf( __( 'You\'ve used this focus keyword %1$sonce before%2$s, be sure to make very clear which URL on your site is the most important for this keyword.', 'wordpress-seo' ), '<a href="' . esc_url( add_query_arg( array(
+ 'post' => $posts[0],
+ 'action' => 'edit',
+ ), admin_url( 'post.php' ) ) ) . '">', '</a>' ), 'keyword_overused' );
+ } else {
+ $this->save_score_result( $results, 1, sprintf( __( 'You\'ve used this focus keyword %3$s%4$d times before%2$s, it\'s probably a good idea to read %1$sthis post on cornerstone content%2$s and improve your keyword strategy.', 'wordpress-seo' ), '<a href="https://yoast.com/cornerstone-content-rank/">', '</a>', '<a href="' . esc_url( add_query_arg( array( 'seo_kw_filter' => $job['keyword'] ), admin_url( 'edit.php' ) ) ) . '">', count( $posts ) ), 'keyword_overused' );
+ }
+ }
+
+ /**
+ * Check whether the keyword contains stopwords.
+ *
+ * @param string $keyword The keyword to check for stopwords.
+ * @param array $results The results array.
+ */
+ function score_keyword( $keyword, &$results ) {
+ global $wpseo_admin;
+
+ $keywordStopWord = __( 'The keyword for this page contains one or more %sstop words%s, consider removing them. Found \'%s\'.', 'wordpress-seo' );
+
+ if ( $wpseo_admin->stopwords_check( $keyword ) !== false ) {
+ $this->save_score_result( $results, 5, sprintf( $keywordStopWord, '<a href="http://en.wikipedia.org/wiki/Stop_words">', '</a>', $wpseo_admin->stopwords_check( $keyword ) ), 'keyword_stopwords' );
+ }
+ }
+
+ /**
+ * Check whether the keyword is contained in the URL.
+ *
+ * @param array $job The job array holding both the keyword and the URLs.
+ * @param array $results The results array.
+ */
+ function score_url( $job, &$results ) {
+ global $wpseo_admin;
+
+ $urlGood = __( 'The keyword / phrase appears in the URL for this page.', 'wordpress-seo' );
+ $urlMedium = __( 'The keyword / phrase does not appear in the URL for this page. If you decide to rename the URL be sure to check the old URL 301 redirects to the new one!', 'wordpress-seo' );
+ $urlStopWords = __( 'The slug for this page contains one or more <a href="http://en.wikipedia.org/wiki/Stop_words">stop words</a>, consider removing them.', 'wordpress-seo' );
+ $longSlug = __( 'The slug for this page is a bit long, consider shortening it.', 'wordpress-seo' );
+
+ $needle = $this->strip_separators_and_fold( remove_accents( $job['keyword'] ) );
+ $haystack1 = $this->strip_separators_and_fold( $job['pageUrl'], true );
+ $haystack2 = $this->strip_separators_and_fold( $job['pageUrl'], false );
+
+ if ( stripos( $haystack1, $needle ) || stripos( $haystack2, $needle ) ) {
+ $this->save_score_result( $results, 9, $urlGood, 'url_keyword' );
+ } else {
+ $this->save_score_result( $results, 6, $urlMedium, 'url_keyword' );
+ }
+
+ // Check for Stop Words in the slug
+ if ( $wpseo_admin->stopwords_check( $job['pageSlug'], true ) !== false ) {
+ $this->save_score_result( $results, 5, $urlStopWords, 'url_stopword' );
+ }
+
+ // Check if the slug isn't too long relative to the length of the keyword
+ if ( ( $this->statistics()->text_length( $job['keyword'] ) + 20 ) < $this->statistics()->text_length( $job['pageSlug'] ) && 40 < $this->statistics()->text_length( $job['pageSlug'] ) ) {
+ $this->save_score_result( $results, 5, $longSlug, 'url_length' );
+ }
+ }
+
+ /**
+ * Check whether the keyword is contained in the title.
+ *
+ * @param array $job The job array holding both the keyword versions.
+ * @param array $results The results array.
+ */
+ function score_title( $job, &$results ) {
+ $scoreTitleMinLength = 40;
+ $scoreTitleMaxLength = 70;
+ $scoreTitleKeywordLimit = 0;
+
+ $scoreTitleMissing = __( 'Please create a page title.', 'wordpress-seo' );
+ $scoreTitleCorrectLength = __( 'The page title is more than 40 characters and less than the recommended 70 character limit.', 'wordpress-seo' );
+ $scoreTitleTooShort = __( 'The page title contains %d characters, which is less than the recommended minimum of 40 characters. Use the space to add keyword variations or create compelling call-to-action copy.', 'wordpress-seo' );
+ $scoreTitleTooLong = __( 'The page title contains %d characters, which is more than the viewable limit of 70 characters; some words will not be visible to users in your listing.', 'wordpress-seo' );
+ $scoreTitleKeywordMissing = __( 'The keyword / phrase %s does not appear in the page title.', 'wordpress-seo' );
+ $scoreTitleKeywordBeginning = __( 'The page title contains keyword / phrase, at the beginning which is considered to improve rankings.', 'wordpress-seo' );
+ $scoreTitleKeywordEnd = __( 'The page title contains keyword / phrase, but it does not appear at the beginning; try and move it to the beginning.', 'wordpress-seo' );
+
+ if ( $job['title'] == '' ) {
+ $this->save_score_result( $results, 1, $scoreTitleMissing, 'title' );
+ } else {
+ $job['title'] = wp_strip_all_tags( $job['title'] );
+
+ $length = $this->statistics()->text_length( $job['title'] );
+ if ( $length < $scoreTitleMinLength ) {
+ $this->save_score_result( $results, 6, sprintf( $scoreTitleTooShort, $length ), 'title_length' );
+ } elseif ( $length > $scoreTitleMaxLength ) {
+ $this->save_score_result( $results, 6, sprintf( $scoreTitleTooLong, $length ), 'title_length' );
+ } else {
+ $this->save_score_result( $results, 9, $scoreTitleCorrectLength, 'title_length' );
+ }
+
+ // @todo MA Keyword/Title matching is exact match with separators removed, but should extend to distributed match
+ $needle_position = stripos( $job['title'], $job['keyword_folded'] );
+
+ if ( $needle_position === false ) {
+ $needle_position = stripos( $job['title'], $job['keyword'] );
+ }
+
+ if ( $needle_position === false ) {
+ $this->save_score_result( $results, 2, sprintf( $scoreTitleKeywordMissing, $job['keyword_folded'] ), 'title_keyword' );
+ } elseif ( $needle_position <= $scoreTitleKeywordLimit ) {
+ $this->save_score_result( $results, 9, $scoreTitleKeywordBeginning, 'title_keyword' );
+ } else {
+ $this->save_score_result( $results, 6, $scoreTitleKeywordEnd, 'title_keyword' );
+ }
+ }
+ }
+
+ /**
+ * Check whether the document contains outbound links and whether it's anchor text matches the keyword.
+ *
+ * @param array $job The job array holding both the keyword versions.
+ * @param array $results The results array.
+ * @param array $anchor_texts The array holding all anchors in the document.
+ * @param array $count The number of anchors in the document, grouped by type.
+ */
+ function score_anchor_texts( $job, &$results, $anchor_texts, $count ) {
+ $scoreNoLinks = __( 'No outbound links appear in this page, consider adding some as appropriate.', 'wordpress-seo' );
+ $scoreKeywordInOutboundLink = __( 'You\'re linking to another page with the keyword you want this page to rank for, consider changing that if you truly want this page to rank.', 'wordpress-seo' );
+ $scoreLinksDofollow = __( 'This page has %s outbound link(s).', 'wordpress-seo' );
+ $scoreLinksNofollow = __( 'This page has %s outbound link(s), all nofollowed.', 'wordpress-seo' );
+ $scoreLinks = __( 'This page has %s nofollowed link(s) and %s normal outbound link(s).', 'wordpress-seo' );
+
+ if ( $count['external']['nofollow'] == 0 && $count['external']['dofollow'] == 0 ) {
+ $this->save_score_result( $results, 6, $scoreNoLinks, 'links' );
+ } else {
+ $found = false;
+ if ( is_array( $anchor_texts ) && $anchor_texts !== array() ) {
+ foreach ( $anchor_texts as $anchor_text ) {
+ if ( $this->strtolower_utf8( $anchor_text ) == $job['keyword_folded'] ) {
+ $found = true;
+ }
+ }
+ }
+ if ( $found ) {
+ $this->save_score_result( $results, 2, $scoreKeywordInOutboundLink, 'links_focus_keyword' );
+ }
+
+ if ( $count['external']['nofollow'] == 0 && $count['external']['dofollow'] > 0 ) {
+ $this->save_score_result( $results, 9, sprintf( $scoreLinksDofollow, $count['external']['dofollow'] ), 'links_number' );
+ } elseif ( $count['external']['nofollow'] > 0 && $count['external']['dofollow'] == 0 ) {
+ $this->save_score_result( $results, 7, sprintf( $scoreLinksNofollow, $count['external']['nofollow'] ), 'links_number' );
+ } else {
+ $this->save_score_result( $results, 8, sprintf( $scoreLinks, $count['external']['nofollow'], $count['external']['dofollow'] ), 'links_number' );
+ }
+ }
+ }
+
+ /**
+ * Retrieve the anchor texts used in the current document.
+ *
+ * @param object $xpath An XPATH object of the current document.
+ *
+ * @return array
+ */
+ function get_anchor_texts( &$xpath ) {
+ $query = '//a|//A';
+ $dom_objects = $xpath->query( $query );
+ $anchor_texts = array();
+ if ( is_object( $dom_objects ) && is_a( $dom_objects, 'DOMNodeList' ) && $dom_objects->length > 0 ) {
+ foreach ( $dom_objects as $dom_object ) {
+ if ( $dom_object->attributes->getNamedItem( 'href' ) ) {
+ $href = $dom_object->attributes->getNamedItem( 'href' )->textContent;
+ if ( substr( $href, 0, 4 ) == 'http' ) {
+ $anchor_texts['external'] = $dom_object->textContent;
+ }
+ }
+ }
+ }
+
+ return $anchor_texts;
+ }
+
+ /**
+ * Count the number of anchors and group them by type.
+ *
+ * @param object $xpath An XPATH object of the current document.
+ *
+ * @return array
+ */
+ function get_anchor_count( &$xpath ) {
+ $query = '//a|//A';
+ $dom_objects = $xpath->query( $query );
+
+ $count = array(
+ 'total' => 0,
+ 'internal' => array( 'nofollow' => 0, 'dofollow' => 0 ),
+ 'external' => array( 'nofollow' => 0, 'dofollow' => 0 ),
+ 'other' => array( 'nofollow' => 0, 'dofollow' => 0 ),
+ );
+
+ if ( is_object( $dom_objects ) && is_a( $dom_objects, 'DOMNodeList' ) && $dom_objects->length > 0 ) {
+ foreach ( $dom_objects as $dom_object ) {
+ $count['total'] ++;
+ if ( $dom_object->attributes->getNamedItem( 'href' ) ) {
+ $href = $dom_object->attributes->getNamedItem( 'href' )->textContent;
+ $wpurl = get_bloginfo( 'url' );
+ if ( wpseo_is_url_relative( $href ) === true || substr( $href, 0, strlen( $wpurl ) ) === $wpurl ) {
+ $type = 'internal';
+ } elseif ( substr( $href, 0, 4 ) == 'http' ) {
+ $type = 'external';
+ } else {
+ $type = 'other';
+ }
+
+ if ( $dom_object->attributes->getNamedItem( 'rel' ) ) {
+ $link_rel = $dom_object->attributes->getNamedItem( 'rel' )->textContent;
+ if ( stripos( $link_rel, 'nofollow' ) !== false ) {
+ $count[ $type ]['nofollow'] ++;
+ } else {
+ $count[ $type ]['dofollow'] ++;
+ }
+ } else {
+ $count[ $type ]['dofollow'] ++;
+ }
+ }
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Check whether the images alt texts contain the keyword.
+ *
+ * @param array $job The job array holding both the keyword versions.
+ * @param array $results The results array.
+ * @param array $imgs The array with images alt texts.
+ */
+ function score_images_alt_text( $job, &$results, $imgs ) {
+ $scoreImagesNoImages = __( 'No images appear in this page, consider adding some as appropriate.', 'wordpress-seo' );
+ $scoreImagesNoAlt = __( 'The images on this page are missing alt tags.', 'wordpress-seo' );
+ $scoreImagesAltKeywordIn = __( 'The images on this page contain alt tags with the target keyword / phrase.', 'wordpress-seo' );
+ $scoreImagesAltKeywordMissing = __( 'The images on this page do not have alt tags containing your keyword / phrase.', 'wordpress-seo' );
+
+ if ( $imgs['count'] == 0 ) {
+ $this->save_score_result( $results, 3, $scoreImagesNoImages, 'images_alt' );
+ } elseif ( count( $imgs['alts'] ) == 0 && $imgs['count'] != 0 ) {
+ $this->save_score_result( $results, 5, $scoreImagesNoAlt, 'images_alt' );
+ } else {
+ $found = false;
+ foreach ( $imgs['alts'] as $alt ) {
+ $haystack1 = $this->strip_separators_and_fold( $alt, true );
+ $haystack2 = $this->strip_separators_and_fold( $alt, false );
+ if ( strrpos( $haystack1, $job['keyword_folded'] ) !== false ) {
+ $found = true;
+ } elseif ( strrpos( $haystack2, $job['keyword_folded'] ) !== false ) {
+ $found = true;
+ }
+ }
+ if ( $found ) {
+ $this->save_score_result( $results, 9, $scoreImagesAltKeywordIn, 'images_alt' );
+ } else {
+ $this->save_score_result( $results, 5, $scoreImagesAltKeywordMissing, 'images_alt' );
+ }
+ }
+ }
+
+ /**
+ * Retrieve the alt texts from the images.
+ *
+ * @param int $post_id The post to find images in.
+ * @param string $body The post content to find images in.
+ * @param array $imgs The array holding the image information.
+ *
+ * @return array The updated images array.
+ */
+ function get_images_alt_text( $post_id, $body, $imgs ) {
+ preg_match_all( '`<img[^>]+>`im', $body, $matches );
+ $imgs['alts'] = array();
+ if ( isset( $matches[0] ) && is_array( $matches[0] ) && $matches[0] !== array() ) {
+ foreach ( $matches[0] as $img ) {
+ if ( preg_match( '`alt=(["\'])(.*?)\1`', $img, $alt ) && isset( $alt[2] ) ) {
+ $imgs['alts'][] = $this->strtolower_utf8( $alt[2] );
+ }
+ }
+ }
+ if ( strpos( $body, '[gallery' ) !== false ) {
+ $attachments = get_children( array(
+ 'post_parent' => $post_id,
+ 'post_status' => 'inherit',
+ 'post_type' => 'attachment',
+ 'post_mime_type' => 'image',
+ 'fields' => 'ids',
+ ) );
+ if ( is_array( $attachments ) && $attachments !== array() ) {
+ foreach ( $attachments as $att_id ) {
+ $alt = get_post_meta( $att_id, '_wp_attachment_image_alt', true );
+ if ( $alt && ! empty( $alt ) ) {
+ $imgs['alts'][] = $alt;
+ }
+ $imgs['count'] ++;
+ }
+ }
+ }
+
+ return $imgs;
+ }
+
+ /**
+ * Score the headings for keyword appearance.
+ *
+ * @param array $job The array holding the keywords.
+ * @param array $results The results array.
+ * @param array $headings The headings found in the document.
+ */
+ function score_headings( $job, &$results, $headings ) {
+ $scoreHeadingsNone = __( 'No subheading tags (like an H2) appear in the copy.', 'wordpress-seo' );
+ $scoreHeadingsKeywordIn = __( 'Keyword / keyphrase appears in %s (out of %s) subheadings in the copy. While not a major ranking factor, this is beneficial.', 'wordpress-seo' );
+ $scoreHeadingsKeywordMissing = __( 'You have not used your keyword / keyphrase in any subheading (such as an H2) in your copy.', 'wordpress-seo' );
+
+ $headingCount = count( $headings );
+ if ( $headingCount == 0 ) {
+ $this->save_score_result( $results, 7, $scoreHeadingsNone, 'headings' );
+ } else {
+ $found = 0;
+ foreach ( $headings as $heading ) {
+ $haystack1 = $this->strip_separators_and_fold( $heading, true );
+ $haystack2 = $this->strip_separators_and_fold( $heading, false );
+
+ if ( strrpos( $haystack1, $job['keyword_folded'] ) !== false ) {
+ $found ++;
+ } elseif ( strrpos( $haystack2, $job['keyword_folded'] ) !== false ) {
+ $found ++;
+ }
+ }
+ if ( $found ) {
+ $this->save_score_result( $results, 9, sprintf( $scoreHeadingsKeywordIn, $found, $headingCount ), 'headings' );
+ } else {
+ $this->save_score_result( $results, 3, $scoreHeadingsKeywordMissing, 'headings' );
+ }
+ }
+ }
+
+ /**
+ * Fetch all headings and return their content.
+ *
+ * @param string $postcontent Post content to find headings in.
+ *
+ * @return array Array of heading texts.
+ */
+ function get_headings( $postcontent ) {
+ $headings = array();
+
+ preg_match_all( '`<h([1-6])(?:[^>]+)?>(.*?)</h\\1>`si', $postcontent, $matches );
+
+ if ( isset( $matches[2] ) && is_array( $matches[2] ) && $matches[2] !== array() ) {
+ foreach ( $matches[2] as $heading ) {
+ $headings[] = $this->strtolower_utf8( $heading );
+ }
+ }
+
+ return $headings;
+ }
+
+ /**
+ * Score the meta description for length and keyword appearance.
+ *
+ * @param array $job The array holding the keywords.
+ * @param array $results The results array.
+ * @param string $description The meta description.
+ * @param int $maxlength The maximum length of the meta description.
+ */
+ function score_description( $job, &$results, $description, $maxlength = 155 ) {
+ $scoreDescriptionMinLength = 120;
+ $scoreDescriptionCorrectLength = __( 'In the specified meta description, consider: How does it compare to the competition? Could it be made more appealing?', 'wordpress-seo' );
+ $scoreDescriptionTooShort = __( 'The meta description is under 120 characters, however up to %s characters are available. %s', 'wordpress-seo' );
+ $scoreDescriptionTooLong = __( 'The specified meta description is over %s characters, reducing it will ensure the entire description is visible. %s', 'wordpress-seo' );
+ $scoreDescriptionMissing = __( 'No meta description has been specified, search engines will display copy from the page instead.', 'wordpress-seo' );
+ $scoreDescriptionKeywordIn = __( 'The meta description contains the primary keyword / phrase.', 'wordpress-seo' );
+ $scoreDescriptionKeywordMissing = __( 'A meta description has been specified, but it does not contain the target keyword / phrase.', 'wordpress-seo' );
+
+ $metaShorter = '';
+ if ( $maxlength != 155 ) {
+ $metaShorter = __( 'The available space is shorter than the usual 155 characters because Google will also include the publication date in the snippet.', 'wordpress-seo' );
+ }
+
+ if ( $description == '' ) {
+ $this->save_score_result( $results, 1, $scoreDescriptionMissing, 'description_length' );
+ } else {
+ $length = $this->statistics()->text_length( $description );
+
+ if ( $length < $scoreDescriptionMinLength ) {
+ $this->save_score_result( $results, 6, sprintf( $scoreDescriptionTooShort, $maxlength, $metaShorter ), 'description_length' );
+ } elseif ( $length <= $maxlength ) {
+ $this->save_score_result( $results, 9, $scoreDescriptionCorrectLength, 'description_length' );
+ } else {
+ $this->save_score_result( $results, 6, sprintf( $scoreDescriptionTooLong, $maxlength, $metaShorter ), 'description_length' );
+ }
+
+ // @todo MA Keyword/Title matching is exact match with separators removed, but should extend to distributed match
+ $haystack1 = $this->strip_separators_and_fold( $description, true );
+ $haystack2 = $this->strip_separators_and_fold( $description, false );
+ if ( strrpos( $haystack1, $job['keyword_folded'] ) === false && strrpos( $haystack2, $job['keyword_folded'] ) === false ) {
+ $this->save_score_result( $results, 3, $scoreDescriptionKeywordMissing, 'description_keyword' );
+ } else {
+ $this->save_score_result( $results, 9, $scoreDescriptionKeywordIn, 'description_keyword' );
+ }
+ }
+ }
+
+ /**
+ * Score the body for length and keyword appearance.
+ *
+ * @param array $job The array holding the keywords.
+ * @param array $results The results array.
+ * @param string $body The body.
+ * @param string $firstp The first paragraph.
+ */
+ function score_body( $job, &$results, $body, $firstp ) {
+ $lengthScore = array(
+ 'good' => 300,
+ 'ok' => 250,
+ 'poor' => 200,
+ 'bad' => 100,
+ );
+ $lengthScore = apply_filters( 'wpseo_body_length_score', $lengthScore, $job );
+
+ $scoreBodyGoodLength = __( 'There are %d words contained in the body copy, this is more than the %d word recommended minimum.', 'wordpress-seo' );
+ $scoreBodyPoorLength = __( 'There are %d words contained in the body copy, this is below the %d word recommended minimum. Add more useful content on this topic for readers.', 'wordpress-seo' );
+ $scoreBodyOKLength = __( 'There are %d words contained in the body copy, this is slightly below the %d word recommended minimum, add a bit more copy.', 'wordpress-seo' );
+ $scoreBodyBadLength = __( 'There are %d words contained in the body copy. This is far too low and should be increased.', 'wordpress-seo' );
+
+ $scoreKeywordDensityLow = __( 'The keyword density is %s%%, which is a bit low, the keyword was found %s times.', 'wordpress-seo' );
+ $scoreKeywordDensityHigh = __( 'The keyword density is %s%%, which is over the advised 4.5%% maximum, the keyword was found %s times.', 'wordpress-seo' );
+ $scoreKeywordDensityGood = __( 'The keyword density is %s%%, which is great, the keyword was found %s times.', 'wordpress-seo' );
+
+ $scoreFirstParagraphLow = __( 'The keyword doesn\'t appear in the first paragraph of the copy, make sure the topic is clear immediately.', 'wordpress-seo' );
+ $scoreFirstParagraphHigh = __( 'The keyword appears in the first paragraph of the copy.', 'wordpress-seo' );
+
+ $fleschurl = '<a href="http://en.wikipedia.org/wiki/Flesch-Kincaid_readability_test#Flesch_Reading_Ease">' . __( 'Flesch Reading Ease', 'wordpress-seo' ) . '</a>';
+ $scoreFlesch = __( 'The copy scores %s in the %s test, which is considered %s to read. %s', 'wordpress-seo' );
+
+ // Replace images with their alt tags, then strip all tags
+ $body = preg_replace( '`<img(?:[^>]+)?alt="([^"]+)"(?:[^>]+)>`', '$1', $body );
+ $body = strip_tags( $body );
+
+ // Copy length check
+ $wordCount = $this->statistics()->word_count( $body );
+
+ if ( $wordCount < $lengthScore['bad'] ) {
+ $this->save_score_result( $results, - 20, sprintf( $scoreBodyBadLength, $wordCount, $lengthScore['good'] ), 'body_length', $wordCount );
+ } elseif ( $wordCount < $lengthScore['poor'] ) {
+ $this->save_score_result( $results, - 10, sprintf( $scoreBodyPoorLength, $wordCount, $lengthScore['good'] ), 'body_length', $wordCount );
+ } elseif ( $wordCount < $lengthScore['ok'] ) {
+ $this->save_score_result( $results, 5, sprintf( $scoreBodyPoorLength, $wordCount, $lengthScore['good'] ), 'body_length', $wordCount );
+ } elseif ( $wordCount < $lengthScore['good'] ) {
+ $this->save_score_result( $results, 7, sprintf( $scoreBodyOKLength, $wordCount, $lengthScore['good'] ), 'body_length', $wordCount );
+ } else {
+ $this->save_score_result( $results, 9, sprintf( $scoreBodyGoodLength, $wordCount, $lengthScore['good'] ), 'body_length', $wordCount );
+ }
+
+ $body = $this->strtolower_utf8( $body );
+ $job['keyword'] = $this->strtolower_utf8( $job['keyword'] );
+
+ $keywordWordCount = $this->statistics()->word_count( $job['keyword'] );
+
+ if ( $keywordWordCount > 10 ) {
+ $this->save_score_result( $results, 0, __( 'Your keyphrase is over 10 words, a keyphrase should be shorter and there can be only one keyphrase.', 'wordpress-seo' ), 'focus_keyword_length' );
+ } else {
+ // Keyword Density check
+ $keywordDensity = 0;
+ if ( $wordCount > 100 ) {
+ $keywordCount = preg_match_all( '`\b' . preg_quote( $job['keyword'], '`' ) . '\b`miu', utf8_encode( $body ), $res );
+ if ( ( $keywordCount > 0 && $keywordWordCount > 0 ) && $wordCount > $keywordCount ) {
+ $keywordDensity = wpseo_calc( wpseo_calc( $keywordCount, '/', wpseo_calc( $wordCount, '-', ( wpseo_calc( wpseo_calc( $keywordWordCount, '-', 1 ), '*', $keywordCount ) ) ) ), '*', 100, true, 2 );
+ }
+ if ( $keywordDensity < 1 ) {
+ $this->save_score_result( $results, 4, sprintf( $scoreKeywordDensityLow, $keywordDensity, $keywordCount ), 'keyword_density' );
+ } elseif ( $keywordDensity > 4.5 ) {
+ $this->save_score_result( $results, - 50, sprintf( $scoreKeywordDensityHigh, $keywordDensity, $keywordCount ), 'keyword_density' );
+ } else {
+ $this->save_score_result( $results, 9, sprintf( $scoreKeywordDensityGood, $keywordDensity, $keywordCount ), 'keyword_density' );
+ }
+ }
+ }
+
+ $firstp = $this->strtolower_utf8( $firstp );
+
+ // First Paragraph Test
+ // check without /u modifier as well as /u might break with non UTF-8 chars.
+ if ( preg_match( '`\b' . preg_quote( $job['keyword'], '`' ) . '\b`miu', $firstp ) || preg_match( '`\b' . preg_quote( $job['keyword'], '`' ) . '\b`mi', $firstp ) || preg_match( '`\b' . preg_quote( $job['keyword_folded'], '`' ) . '\b`miu', $firstp )
+ ) {
+ $this->save_score_result( $results, 9, $scoreFirstParagraphHigh, 'keyword_first_paragraph' );
+ } else {
+ $this->save_score_result( $results, 3, $scoreFirstParagraphLow, 'keyword_first_paragraph' );
+ }
+
+ $lang = get_bloginfo( 'language' );
+ if ( substr( $lang, 0, 2 ) == 'en' && $wordCount > 100 ) {
+ // Flesch Reading Ease check
+ $flesch = $this->statistics()->flesch_kincaid_reading_ease( $body );
+
+ $note = '';
+ $level = '';
+ $score = 1;
+ if ( $flesch >= 90 ) {
+ $level = __( 'very easy', 'wordpress-seo' );
+ $score = 9;
+ } elseif ( $flesch >= 80 ) {
+ $level = __( 'easy', 'wordpress-seo' );
+ $score = 9;
+ } elseif ( $flesch >= 70 ) {
+ $level = __( 'fairly easy', 'wordpress-seo' );
+ $score = 8;
+ } elseif ( $flesch >= 60 ) {
+ $level = __( 'OK', 'wordpress-seo' );
+ $score = 7;
+ } elseif ( $flesch >= 50 ) {
+ $level = __( 'fairly difficult', 'wordpress-seo' );
+ $note = __( 'Try to make shorter sentences to improve readability.', 'wordpress-seo' );
+ $score = 6;
+ } elseif ( $flesch >= 30 ) {
+ $level = __( 'difficult', 'wordpress-seo' );
+ $note = __( 'Try to make shorter sentences, using less difficult words to improve readability.', 'wordpress-seo' );
+ $score = 5;
+ } elseif ( $flesch >= 0 ) {
+ $level = __( 'very difficult', 'wordpress-seo' );
+ $note = __( 'Try to make shorter sentences, using less difficult words to improve readability.', 'wordpress-seo' );
+ $score = 4;
+ }
+ $this->save_score_result( $results, $score, sprintf( $scoreFlesch, $flesch, $fleschurl, $level, $note ), 'flesch_kincaid' );
+ }
+ }
+
+ /**
+ * Retrieve the body from the post.
+ *
+ * @param object $post The post object.
+ *
+ * @return string The post content.
+ */
+ function get_body( $post ) {
+ // This filter allows plugins to add their content to the content to be analyzed.
+ $post_content = apply_filters( 'wpseo_pre_analysis_post_content', $post->post_content, $post );
+
+ // Strip shortcodes, for obvious reasons, if plugins think their content should be in the analysis, they should
+ // hook into the above filter.
+ $post_content = wpseo_strip_shortcode( $post_content );
+
+ if ( trim( $post_content ) == '' ) {
+ return '';
+ }
+
+ $htmdata3 = preg_replace( '`<(?:\x20*script|script).*?(?:/>|/script>)`', '', $post_content );
+ if ( $htmdata3 == null ) {
+ $htmdata3 = $post_content;
+ } else {
+ unset( $post_content );
+ }
+
+ $htmdata4 = preg_replace( '`<!--.*?-->`', '', $htmdata3 );
+ if ( $htmdata4 == null ) {
+ $htmdata4 = $htmdata3;
+ } else {
+ unset( $htmdata3 );
+ }
+
+ $htmdata5 = preg_replace( '`<(?:\x20*style|style).*?(?:/>|/style>)`', '', $htmdata4 );
+ if ( $htmdata5 == null ) {
+ $htmdata5 = $htmdata4;
+ } else {
+ unset( $htmdata4 );
+ }
+
+ return $htmdata5;
+ }
+
+ /**
+ * Retrieve the first paragraph from the post.
+ *
+ * @param string $body The post content to retrieve the first paragraph from.
+ *
+ * @return string
+ */
+ function get_first_paragraph( $body ) {
+ // To determine the first paragraph we first need to autop the content, then match the first paragraph and return.
+ $res = preg_match( '`<p[.]*?>(.*)</p>`s', wpautop( $body ), $matches );
+ if ( $res ) {
+ return $matches[1];
+ }
+
+ return false;
+ }
+
+ /********************** DEPRECATED METHODS **********************/
+
+ /**
+ * Adds the WordPress SEO box
+ *
+ * @deprecated 1.4.24
+ * @deprecated use WPSEO_Metabox::add_meta_box()
+ * @see WPSEO_Meta::add_meta_box()
+ */
+ public function add_custom_box() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.4.24', 'WPSEO_Metabox::add_meta_box()' );
+ $this->add_meta_box();
+ }
+
+ /**
+ * Retrieve the meta boxes for the given post type.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Meta::get_meta_field_defs()
+ * @see WPSEO_Meta::get_meta_field_defs()
+ *
+ * @param string $post_type
+ *
+ * @return array
+ */
+ public function get_meta_boxes( $post_type = 'post' ) {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Meta::get_meta_field_defs()' );
+
+ return $this->get_meta_field_defs( 'general', $post_type );
+ }
+
+ /**
+ * Pass some variables to js
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Meta::localize_script()
+ * @see WPSEO_Meta::localize_script()
+ */
+ public function script() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Meta::localize_script()' );
+
+ return $this->localize_script();
+ }
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Social_Admin' ) ) {
+ /**
+ * This class adds the Social tab to the WP SEO metabox and makes sure the settings are saved.
+ */
+ class WPSEO_Social_Admin extends WPSEO_Metabox {
+
+ /**
+ * Class constructor
+ */
+ public function __construct() {
+ add_action( 'wpseo_tab_translate', array( $this, 'translate_meta_boxes' ) );
+ add_action( 'wpseo_tab_header', array( $this, 'tab_header' ), 60 );
+ add_action( 'wpseo_tab_content', array( $this, 'tab_content' ) );
+ add_filter( 'wpseo_save_metaboxes', array( $this, 'save_meta_boxes' ), 10, 1 );
+ }
+
+ /**
+ * Translate text strings for use in the meta box
+ *
+ * IMPORTANT: if you want to add a new string (option) somewhere, make sure you add that array key to
+ * the main meta box definition array in the class WPSEO_Meta() as well!!!!
+ */
+ public static function translate_meta_boxes() {
+ self::$meta_fields['social']['opengraph-title']['title'] = __( 'Facebook Title', 'wordpress-seo' );
+ self::$meta_fields['social']['opengraph-title']['description'] = __( 'If you don\'t want to use the post title for sharing the post on Facebook but instead want another title there, write it here.', 'wordpress-seo' );
+
+ self::$meta_fields['social']['opengraph-description']['title'] = __( 'Facebook Description', 'wordpress-seo' );
+ self::$meta_fields['social']['opengraph-description']['description'] = __( 'If you don\'t want to use the meta description for sharing the post on Facebook but want another description there, write it here.', 'wordpress-seo' );
+
+ self::$meta_fields['social']['opengraph-image']['title'] = __( 'Facebook Image', 'wordpress-seo' );
+ self::$meta_fields['social']['opengraph-image']['description'] = __( 'If you want to override the Facebook image for this post, upload / choose an image or add the URL here.', 'wordpress-seo' );
+
+ self::$meta_fields['social']['google-plus-title']['title'] = __( 'Google+ Title', 'wordpress-seo' );
+ self::$meta_fields['social']['google-plus-title']['description'] = __( 'If you don\'t want to use the post title for sharing the post on Google+ but instead want another title there, write it here.', 'wordpress-seo' );
+
+ self::$meta_fields['social']['google-plus-description']['title'] = __( 'Google+ Description', 'wordpress-seo' );
+ self::$meta_fields['social']['google-plus-description']['description'] = __( 'If you don\'t want to use the meta description for sharing the post on Google+ but want another description there, write it here.', 'wordpress-seo' );
+
+ self::$meta_fields['social']['google-plus-image']['title'] = __( 'Google+ Image', 'wordpress-seo' );
+ self::$meta_fields['social']['google-plus-image']['description'] = __( 'If you want to override the image for this post that Google+ will use, upload / choose an image or add the URL here. Note that it will otherwise default to the Facebook one above.', 'wordpress-seo' );
+ }
+
+ /**
+ * Output the tab header for the Social tab
+ */
+ public function tab_header() {
+ echo '<li class="social"><a class="wpseo_tablink" href="#wpseo_social">' . __( 'Social', 'wordpress-seo' ) . '</a></li>';
+ }
+
+ /**
+ * Output the tab content
+ */
+ public function tab_content() {
+ $content = '';
+ foreach ( $this->get_meta_field_defs( 'social' ) as $meta_key => $meta_field ) {
+ $content .= $this->do_meta_box( $meta_field, $meta_key );
+ }
+ $this->do_tab( 'social', __( 'Social', 'wordpress-seo' ), $content );
+ }
+
+
+ /**
+ * Filter over the meta boxes to save, this function adds the Social meta boxes.
+ *
+ * @param array $field_defs Array of metaboxes to save.
+ * @return array
+ */
+ public function save_meta_boxes( $field_defs ) {
+ return array_merge( $field_defs, $this->get_meta_field_defs( 'social' ) );
+ }
+
+
+ /********************** DEPRECATED METHODS **********************/
+
+ /**
+ * Define the meta boxes for the Social tab
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Meta::get_meta_field_defs()
+ * @see WPSEO_Meta::get_meta_field_defs()
+ *
+ * @param string $post_type
+ * @return array Array containing the meta boxes
+ */
+ public function get_meta_boxes( $post_type = 'post' ) {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0', 'WPSEO_Meta::get_meta_field_defs()' );
+ return $this->get_meta_field_defs( 'social' );
+ }
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_Pointers' ) ) {
+ /**
+ * This class handles the pointers used in the introduction tour.
+ *
+ * @todo Add an introductory pointer on the edit post page too.
+ */
+ class WPSEO_Pointers {
+
+ /**
+ * @var object Instance of this class
+ */
+ public static $instance;
+
+ /**
+ * Class constructor.
+ */
+ private function __construct() {
+ if ( current_user_can( 'manage_options' ) ) {
+ $options = get_option( 'wpseo' );
+ if ( $options['tracking_popup_done'] === false || $options['ignore_tour'] === false ) {
+ wp_enqueue_style( 'wp-pointer' );
+ wp_enqueue_script( 'jquery-ui' );
+ wp_enqueue_script( 'wp-pointer' );
+ wp_enqueue_script( 'utils' );
+ }
+ if ( $options['tracking_popup_done'] === false && ! isset( $_GET['allow_tracking'] ) ) {
+ add_action( 'admin_print_footer_scripts', array( $this, 'tracking_request' ) );
+ } elseif ( $options['ignore_tour'] === false ) {
+ add_action( 'admin_print_footer_scripts', array( $this, 'intro_tour' ) );
+ }
+ }
+ }
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Shows a popup that asks for permission to allow tracking.
+ */
+ function tracking_request() {
+ $id = '#wpadminbar';
+ $nonce = wp_create_nonce( 'wpseo_activate_tracking' );
+
+ $content = '<h3>' . __( 'Help improve WordPress SEO', 'wordpress-seo' ) . '</h3>';
+ $content .= '<p>' . __( 'You\'ve just installed WordPress SEO by Yoast. Please helps us improve it by allowing us to gather anonymous usage stats so we know which configurations, plugins and themes to test with.', 'wordpress-seo' ) . '</p>';
+ $opt_arr = array(
+ 'content' => $content,
+ 'position' => array( 'edge' => 'top', 'align' => 'center' )
+ );
+ $button_array = array(
+ 'button1' => array(
+ 'text' => __( 'Do not allow tracking', 'wordpress-seo' ),
+ 'function' => 'wpseo_store_answer("no","' . $nonce . '")',
+ ),
+ 'button2' => array(
+ 'text' => __( 'Allow tracking', 'wordpress-seo' ),
+ 'function' => 'wpseo_store_answer("yes","' . $nonce . '")',
+ ),
+ );
+
+ $this->print_scripts( $id, $opt_arr, $button_array );
+ }
+
+ /**
+ * Load the introduction tour
+ */
+ function intro_tour() {
+ global $pagenow, $current_user;
+
+ // @FIXME: Links to tabs only work with target="_blank" and thus open in a new window
+ $adminpages = array(
+ 'wpseo_dashboard' => array(
+ 'content' => '<h3>' . __( 'Dashboard', 'wordpress-seo' ) . '</h3><p>' . __( 'This is the WordPress SEO Dashboard, here you can restart this tour or revert the WP SEO settings to default.', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'More WordPress SEO', 'wordpress-seo' ) . '</strong><br/>' . sprintf( __( 'There\'s more to learn about WordPress & SEO than just using this plugin. A great start is our article %1$sthe definitive guide to WordPress SEO%2$s.', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( 'https://yoast.com/articles/wordpress-seo/#utm_source=wpseo_dashboard&utm_medium=wpseo_tour&utm_campaign=tour' ) . '">', '</a>' ) . '</p>'
+ . '<p><strong>' . __( 'Tracking', 'wordpress-seo' ) . '</strong><br/>' . __( 'To provide you with the best experience possible, we need your help. Please enable tracking to help us gather anonymous usage data.', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'Webmaster Tools', 'wordpress-seo' ) . '</strong><br/>' . __( 'You can also add the verification codes for the different Webmaster Tools programs here, we highly encourage you to check out both Google and Bing\'s Webmaster Tools.', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'WordPress SEO Tour', 'wordpress-seo' ) . '</strong><br/>' . __( 'This tour will show you around in the plugin, to give you a general overview of the plugin.', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'Newsletter', 'wordpress-seo' ) . '</strong><br/>' .
+ __( 'If you would like to us to keep you up-to-date regarding WordPress SEO and other plugins by Yoast, subscribe to our newsletter:', 'wordpress-seo' ) . '</p>' .
+ '<form action="http://yoast.us1.list-manage1.com/subscribe/post?u=ffa93edfe21752c921f860358&id=972f1c9122" method="post" id="newsletter-form" accept-charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '">' .
+ '<p>' .
+ '<label for="newsletter-email">' . __( 'Email', 'wordpress-seo' ) . ':</label> <input style="margin: 5px; color:#666" name="EMAIL" value="' . esc_attr( $current_user->user_email ) . '" id="newsletter-email" placeholder="' . __( 'Email', 'wordpress-seo' ) . '"/><br/>' .
+ '<input type="hidden" name="group" value="2"/>' .
+ '<button type="submit" class="button-primary">' . __( 'Subscribe', 'wordpress-seo' ) . '</button>' .
+ '</p></form>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_titles' ) . '";',
+ 'position' => array( 'edge' => 'top', 'align' => 'center' ),
+ ),
+ 'wpseo_titles' => array(
+ 'content' => '<h3>' . __( 'Title & Metas settings', 'wordpress-seo' ) . '</h3>' . '<p>' . __( 'This is where you set the titles and meta-information for all your post types, taxonomies, archives, special pages and for your homepage. The page is divided into different tabs., make sure you check \'m all out!', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'Sitewide settings', 'wordpress-seo' ) . '</strong><br/>' . __( 'The first tab will show you site-wide settings. You can also set some settings for the entire site here to add specific meta tags or to remove some unneeded cruft.', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'Templates and settings', 'wordpress-seo' ) . '</strong><br/>' . sprintf( __( 'Now click on the \'%1$sPost Types%2$s\'-tab, as this will be our example.', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( admin_url( 'admin.php?page=wpseo_titles#top#post_types' ) ) . '">', '</a>' ) . '<br />' . __( 'The templates are built using variables. You can find all these variables in the help tab (in the top-right corner of the page). The settings allow you to set specific behavior for the post types.', 'wordpress-seo' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_social' ) . '";',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_dashboard' ) . '";',
+ ),
+ 'wpseo_social' => array(
+ 'content' => '<h3>' . __( 'Social settings', 'wordpress-seo' ) . '</h3>'
+ . '<p><strong>' . __( 'Facebook', 'wordpress-seo' ) . '</strong><br/>' . sprintf( __( 'On this tab you can enable the %1$sFacebook Open Graph%2$s functionality from this plugin, as well as assign a Facebook user or Application to be the admin of your site, so you can view the Facebook insights.', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( 'https://yoast.com/facebook-open-graph-protocol/#utm_source=wpseo_social&utm_medium=wpseo_tour&utm_campaign=tour' ) . '">', '</a>' ) . '</p><p>' . __( 'The frontpage settings allow you to set meta-data for your homepage, whereas the default settings allow you to set a fallback for all posts/pages without images. ', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'Twitter', 'wordpress-seo' ) . '</strong><br/>' . sprintf( __( 'With %1$sTwitter Cards%2$s, you can attach rich photos, videos and media experience to tweets that drive traffic to your website. Simply check the box, sign up for the service, and users who Tweet links to your content will have a "Card" added to the tweet that\'s visible to all of their followers.', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( 'https://yoast.com/twitter-cards/#utm_source=wpseo_social&utm_medium=wpseo_tour&utm_campaign=tour' ) . '">', '</a>' ) . '</p>'
+ . '<p><strong>' . __( 'Google+', 'wordpress-seo' ) . '</strong><br/>' . sprintf( __( 'This tab allows you to add specific post meta data for Google+. And if you have a Google+ page for your business, add that URL here and link it on your %1$sGoogle+%2$s page\'s about page.', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( 'https://plus.google.com/' ) . '">', '</a>' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_xml' ) . '";',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_titles' ) . '";',
+ ),
+ 'wpseo_xml' => array(
+ 'content' => '<h3>' . __( 'XML Sitemaps', 'wordpress-seo' ) . '</h3>'
+ . '<p><strong>' . __( 'What are XML sitemaps?', 'wordpress-seo' ) . '</strong><br/>' . __( 'A Sitemap is an XML file that lists the URLs for a site. It allows webmasters to include additional information about each URL: when it was last updated, how often it changes, and how important it is in relation to other URLs in the site. This allows search engines to crawl the site more intelligently.', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'What does the plugin do with XML Sitemaps?', 'wordpress-seo' ) . '</strong><br/>' . __( 'This plugin adds XML sitemaps to your site. The sitemaps are automatically updated when you publish a new post, page or custom post and Google and Bing will be automatically notified. You can also have the plugin automatically notify Yahoo! and Ask.com.', 'wordpress-seo' ) . '</p><p>' . __( 'If you want to exclude certain post types and/or taxonomies, you can also set that on this page.', 'wordpress-seo' ) . '</p><p>' . __( 'Is your webserver low on memory? Decrease the entries per sitemap (default: 1000) to reduce load.', 'wordpress-seo' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_permalinks' ) . '";',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_social' ) . '";',
+ ),
+ 'wpseo_permalinks' => array(
+ 'content' => '<h3>' . __( 'Permalink Settings', 'wordpress-seo' ) . '</h3><p>' . __( 'All of the options here are for advanced users only, if you don\'t know whether you should check any, don\'t touch them.', 'wordpress-seo' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_internal-links' ) . '";',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_xml' ) . '";',
+ ),
+ 'wpseo_internal-links' => array(
+ 'content' => '<h3>' . __( 'Breadcrumbs Settings', 'wordpress-seo' ) . '</h3><p>' . sprintf( __( 'If your theme supports my breadcrumbs, as all Genesis and WooThemes themes as well as a couple of other ones do, you can change the settings for those here. If you want to modify your theme to support them, %sfollow these instructions%s.', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( 'https://yoast.com/wordpress/plugins/breadcrumbs/#utm_source=wpseo_permalinks&utm_medium=wpseo_tour&utm_campaign=tour' ) . '">', '</a>' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_rss' ) . '";',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_permalinks' ) . '";',
+ ),
+ 'wpseo_rss' => array(
+ 'content' => '<h3>' . __( 'RSS Settings', 'wordpress-seo' ) . '</h3><p>' . __( 'This incredibly powerful function allows you to add content to the beginning and end of your posts in your RSS feed. This helps you gain links from people who steal your content!', 'wordpress-seo' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_import' ) . '";',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_internal-links' ) . '";',
+ ),
+ 'wpseo_import' => array(
+ 'content' => '<h3>' . __( 'Import & Export', 'wordpress-seo' ) . '</h3>'
+ . '<p><strong>' . __( 'Import from other (SEO) plugins', 'wordpress-seo' ) . '</strong><br/>' . __( 'We can imagine that you switch from another SEO plugin to WordPress SEO. If you just did, you can use these options to transfer your SEO-data. If you were using one of my older plugins like Robots Meta & RSS Footer, you can import the settings here too.', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'Other imports', 'wordpress-seo' ) . '</strong><br/>' . sprintf( __( 'If you\'re using one of our premium plugins, such as %1$sLocal SEO%2$s, you can also find specific import-options for that plugin here.', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( 'https://yoast.com/wordpress/plugins/local-seo/#utm_source=wpseo_import&utm_medium=wpseo_tour&utm_campaign=tour' ) . '">', '</a>' ) . '</p>'
+ . '<p><strong>' . __( 'Export', 'wordpress-seo' ) . '</strong><br/>' . __( 'If you have multiple blogs and you\'re happy with how you\'ve configured this blog, you can export the settings and import them on another blog so you don\'t have to go through this process twice!', 'wordpress-seo' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . network_admin_url( 'admin.php?page=wpseo_bulk-editor' ) . '";', // will auto-use admin_url if not on multi-site
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_rss' ) . '";',
+ ),
+ 'wpseo_bulk-editor' => array(
+ 'content' => '<h3>' . __( 'Bulk Editor', 'wordpress-seo' ) . '</h3><p>' . __( 'This page lets you view and edit the titles and meta descriptions of all posts and pages on your site. This allows you to edit the title or meta description of all your pages in one place, rather than having to edit each individual page.', 'wordpress-seo' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_files' ) . '";',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_import' ) . '";',
+ ),
+ 'wpseo_files' => array(
+ 'content' => '<h3>' . __( 'File Editor', 'wordpress-seo' ) . '</h3><p>' . __( 'Here you can edit the .htaccess and robots.txt files, two of the most powerful files in your WordPress install, if your WordPress installation has write-access to the files. But please, only touch these files if you know what you\'re doing!', 'wordpress-seo' ) . '</p>',
+ 'next' => __( 'Next', 'wordpress-seo' ),
+ 'next_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_licenses' ) . '";',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_bulk-editor' ) . '";',
+ ),
+ 'wpseo_licenses' => array(
+ 'content' => '<h3>' . __( 'Extensions and Licenses', 'wordpress-seo' ) . '</h3>'
+ . '<p><strong>' . __( 'Extensions', 'wordpress-seo' ) . '</strong><br/>' . sprintf( __( 'The powerful functions of WordPress SEO can be extended with %1$sYoast premium plugins%2$s. These premium plugins require the installation of WordPress SEO or WordPress SEO Premium and add specific functionality. You can read all about the Yoast Premium Plugins on %1$shttp://yoast.com/wordpress/plugins/%2$s.', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( 'https://yoast.com/wordpress/plugins/#utm_source=wpseo_licenses&utm_medium=wpseo_tour&utm_campaign=tour' ) . '">', '</a>' ) . '</p>'
+ . '<p><strong>' . __( 'Licenses', 'wordpress-seo' ) . '</strong><br/>' . __( 'Once you\'ve purchased WordPress SEO Premium or any other premium Yoast plugin, you\'ll have to enter a license key. You can do so on the Licenses-tab. Once you\'ve activated your premium plugin, you can use all its powerful features.', 'wordpress-seo' ) . '</p>'
+ . '<p><strong>' . __( 'Like this plugin?', 'wordpress-seo' ) . '</strong><br/>' . sprintf( __( 'So, we\'ve come to the end of the tour. If you like the plugin, please %srate it 5 stars on WordPress.org%s!', 'wordpress-seo' ), '<a target="_blank" href="https://wordpress.org/plugins/wordpress-seo/">', '</a>' ) . '</p>'
+ . '<p>' . sprintf( __( 'Thank you for using my plugin and good luck with your SEO!<br/><br/>Best,<br/>Team Yoast - %1$sYoast.com%2$s', 'wordpress-seo' ), '<a target="_blank" href="' . esc_url( 'https://yoast.com/#utm_source=wpseo_licenses&utm_medium=wpseo_tour&utm_campaign=tour' ) . '">', '</a>' ) . '</p>',
+ 'prev' => __( 'Previous', 'wordpress-seo' ),
+ 'prev_function' => 'window.location="' . admin_url( 'admin.php?page=wpseo_files' ) . '";',
+ ),
+ );
+
+ // Skip tour about wpseo_files page if file editing is disallowed or if the site is a multisite and the current user isn't a superadmin
+ if ( false === wpseo_allow_system_file_edit() ) {
+ unset( $adminpages['wpseo_files'] );
+ $adminpages['wpseo_bulk-editor']['function'] = 'window.location="' . admin_url( 'admin.php?page=wpseo_licenses' ) . '";';
+ }
+
+ $page = '';
+ if ( isset( $_GET['page'] ) ) {
+ $page = $_GET['page'];
+ }
+
+ $button_array = array(
+ 'button1' => array(
+ 'text' => __( 'Close', 'wordpress-seo' ),
+ 'function' => '',
+ )
+ );
+ $opt_arr = array();
+ $id = '#wpseo-title';
+ if ( 'admin.php' != $pagenow || ! array_key_exists( $page, $adminpages ) ) {
+ $id = 'li.toplevel_page_wpseo_dashboard';
+ $content = '<h3>' . __( 'Congratulations!', 'wordpress-seo' ) . '</h3>';
+ $content .= '<p>' . __( 'You\'ve just installed WordPress SEO by Yoast! Click "Start Tour" to view a quick introduction of this plugins core functionality.', 'wordpress-seo' ) . '</p>';
+ $opt_arr = array(
+ 'content' => $content,
+ 'position' => array( 'edge' => 'top', 'align' => 'center' )
+ );
+ $button_array['button2']['text'] = __( 'Start Tour', 'wordpress-seo' );
+ $button_array['button2']['function'] = 'document.location="' . admin_url( 'admin.php?page=wpseo_dashboard' ) . '";';
+ } else {
+ if ( '' != $page && in_array( $page, array_keys( $adminpages ) ) ) {
+ $align = ( is_rtl() ) ? 'left' : 'right';
+ $opt_arr = array(
+ 'content' => $adminpages[$page]['content'],
+ 'position' => ( isset ( $adminpages[$page]['position'] ) ) ? ( $adminpages[$page]['position'] ) : array( 'edge' => 'top', 'align' => $align ),
+ 'pointerWidth' => 450,
+ );
+ if ( isset( $adminpages[$page]['next'] ) && isset( $adminpages[$page]['next_function'] ) ) {
+ $button_array['button2'] = array(
+ 'text' => $adminpages[$page]['next'],
+ 'function' => $adminpages[$page]['next_function'],
+ );
+ }
+ if ( isset( $adminpages[$page]['prev'] ) && isset( $adminpages[$page]['prev_function'] ) ) {
+ $button_array['button3'] = array(
+ 'text' => $adminpages[$page]['prev'],
+ 'function' => $adminpages[$page]['prev_function'],
+ );
+ }
+ }
+ }
+
+ $this->print_scripts( $id, $opt_arr, $button_array );
+ }
+
+
+ /**
+ * Prints the pointer script
+ *
+ * @param string $selector The CSS selector the pointer is attached to.
+ * @param array $options The options for the pointer.
+ * @param array $button_array The options for the buttons.
+ */
+ function print_scripts( $selector, $options, $button_array ) {
+ $button_array_defaults = array(
+ 'button1' => array(
+ 'text' => false,
+ 'function' => '',
+ ),
+ 'button2' => array(
+ 'text' => false,
+ 'function' => '',
+ ),
+ 'button3' => array(
+ 'text' => false,
+ 'function' => '',
+ ),
+ );
+ $button_array = wp_parse_args( $button_array, $button_array_defaults );
+ ?>
+ <script type="text/javascript">
+ //<![CDATA[
+ (function ($) {
+ // Don't show the tour on screens with an effective width smaller than 1024px or an effective height smaller than 768px.
+ if (jQuery(window).width() < 1024 || jQuery(window).availWidth < 1024 || jQuery(window).height() < 768 || jQuery(window).availHeight < 768) {
+ return;
+ }
+
+ var wpseo_pointer_options = <?php echo json_encode( $options ); ?>, setup;
+
+ function wpseo_store_answer(input, nonce) {
+ var wpseo_tracking_data = {
+ action : 'wpseo_allow_tracking',
+ allow_tracking: input,
+ nonce : nonce
+ };
+ jQuery.post(ajaxurl, wpseo_tracking_data, function () {
+ jQuery('#wp-pointer-0').remove();
+ });
+ }
+
+ wpseo_pointer_options = $.extend(wpseo_pointer_options, {
+ buttons: function (event, t) {
+ var button = jQuery('<a id="pointer-close" style="margin-left:5px;" class="button-secondary">' + '<?php echo $button_array['button1']['text']; ?>' + '</a>');
+ button.bind('click.pointer', function () {
+ t.element.pointer('close');
+ });
+ return button;
+ },
+ close : function () {
+ }
+ });
+
+ setup = function () {
+ $('<?php echo $selector; ?>').pointer(wpseo_pointer_options).pointer('open');
+ <?php if ( $button_array['button2']['text'] ) { ?>
+ jQuery('#pointer-close').after('<a id="pointer-primary" class="button-primary">' + '<?php echo $button_array['button2']['text']; ?>' + '</a>');
+ jQuery('#pointer-primary').click(function () {
+ <?php echo $button_array['button2']['function']; ?>
+ });
+ <?php if ( $button_array['button3']['text'] ) { ?>
+ jQuery('#pointer-primary').after('<a id="pointer-ternary" style="float: left;" class="button-secondary">' + '<?php echo $button_array['button3']['text']; ?>' + '</a>');
+ jQuery('#pointer-ternary').click(function () {
+ <?php echo $button_array['button3']['function']; ?>
+ });
+ <?php } ?>
+ jQuery('#pointer-close').click(function () {
+ <?php if ( $button_array['button1']['function'] == '' ) { ?>
+ wpseo_setIgnore("tour", "wp-pointer-0", "<?php echo esc_js( wp_create_nonce( 'wpseo-ignore' ) ); ?>");
+ <?php } else { ?>
+ <?php echo $button_array['button1']['function']; ?>
+ <?php } ?>
+ });
+ <?php } else if ( $button_array['button3']['text'] ) { ?>
+ jQuery('#pointer-close').after('<a id="pointer-ternary" style="float: left;" class="button-secondary">' + '<?php echo $button_array['button3']['text']; ?>' + '</a>');
+ jQuery('#pointer-ternary').click(function () {
+ <?php echo $button_array['button3']['function']; ?>
+ });
+ <?php } ?>
+ };
+
+ if (wpseo_pointer_options.position && wpseo_pointer_options.position.defer_loading)
+ $(window).bind('load.wp-pointers', setup);
+ else
+ $(document).ready(setup);
+ })(jQuery);
+ //]]>
+ </script>
+ <?php
+ }
+
+
+ /**
+ * Load a tiny bit of CSS in the head
+ *
+ * @deprecated 1.5.0, now handled by css
+ */
+ function admin_head() {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.0' );
+
+ return;
+ }
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package XML_Sitemaps
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Sitemaps_Admin' ) ) {
+ /**
+ * Class that handles the Admin side of XML sitemaps
+ */
+ class WPSEO_Sitemaps_Admin {
+
+ /**
+ * Class constructor
+ */
+ function __construct() {
+ add_action( 'transition_post_status', array( $this, 'status_transition' ), 10, 3 );
+ add_action( 'admin_init', array( $this, 'delete_sitemaps' ) );
+ }
+
+ /**
+ * Find sitemaps residing on disk as they will block our rewrite.
+ *
+ * @todo issue #561 https://github.com/Yoast/wordpress-seo/issues/561
+ */
+ function delete_sitemaps() {
+ $options = WPSEO_Options::get_all();
+ if ( $options['enablexmlsitemap'] === true ) {
+
+ $file_to_check_for = array(
+ //ABSPATH . 'sitemap.xml',
+ //ABSPATH . 'sitemap.xslt',
+ //ABSPATH . 'sitemap.xsl',
+ ABSPATH . 'sitemap_index.xml',
+ );
+
+ $new_files_found = false;
+
+ foreach ( $file_to_check_for as $file ) {
+ if ( ( $options['blocking_files'] === array() || ( $options['blocking_files'] !== array() && in_array( $file, $options['blocking_files'] ) === false ) ) && file_exists( $file ) ) {
+ $options['blocking_files'][] = $file;
+ $new_files_found = true;
+ }
+ }
+ if ( $new_files_found === true ) {
+ update_option( 'wpseo', $options );
+ }
+ }
+ }
+
+ /**
+ * Hooked into transition_post_status. Will initiate search engine pings
+ * if the post is being published, is a post type that a sitemap is built for
+ * and is a post that is included in sitemaps.
+ */
+ function status_transition( $new_status, $old_status, $post ) {
+ if ( $new_status != 'publish' ) {
+ return;
+ }
+
+ wp_cache_delete( 'lastpostmodified:gmt:' . $post->post_type, 'timeinfo' ); // #17455
+
+ $options = WPSEO_Options::get_all();
+ if ( isset( $options[ 'post_types-' . $post->post_type . '-not_in_sitemap' ] ) && $options[ 'post_types-' . $post->post_type . '-not_in_sitemap' ] === true ) {
+ return;
+ }
+
+ if ( WP_CACHE ) {
+ wp_schedule_single_event( time() + 300, 'wpseo_hit_sitemap_index' );
+ }
+
+ // Allow the pinging to happen slightly after the hit sitemap index so the sitemap is fully regenerated when the ping happens.
+ if ( WPSEO_Meta::get_value( 'sitemap-include', $post->ID ) !== 'never' ) {
+ if ( defined( 'YOAST_SEO_PING_IMMEDIATELY' ) && YOAST_SEO_PING_IMMEDIATELY ) {
+ wpseo_ping_search_engines();
+ }
+ else {
+ wp_schedule_single_event( ( time() + 300 ), 'wpseo_ping_search_engines' );
+ }
+ }
+ }
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_Taxonomy' ) ) {
+ /**
+ * Class that handles the edit boxes on taxonomy edit pages.
+ */
+ class WPSEO_Taxonomy {
+
+ /**
+ * @var array Options array for the no-index options, including translated labels
+ */
+ public $no_index_options = array();
+
+ /**
+ * @var array Options array for the sitemap_include options, including translated labels
+ */
+ public $sitemap_include_options = array();
+
+ /**
+ * Class constructor
+ */
+ function __construct() {
+ $options = WPSEO_Options::get_all();
+
+ if ( is_admin() && ( isset( $_GET['taxonomy'] ) && $_GET['taxonomy'] !== '' ) &&
+ ( ! isset( $options[ 'hideeditbox-tax-' . $_GET['taxonomy'] ] ) || $options[ 'hideeditbox-tax-' . $_GET['taxonomy'] ] === false )
+ ) {
+ add_action( sanitize_text_field( $_GET['taxonomy'] ) . '_edit_form', array( $this, 'term_seo_form' ), 90, 1 );
+ }
+
+ add_action( 'edit_term', array( $this, 'update_term' ), 99, 3 );
+
+ add_action( 'init', array( $this, 'custom_category_descriptions_allow_html' ) );
+ add_filter( 'category_description', array( $this, 'custom_category_descriptions_add_shortcode_support' ) );
+ add_action( 'admin_init', array( $this, 'translate_meta_options' ) );
+
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
+ }
+
+
+ /**
+ * Translate options text strings for use in the select fields
+ *
+ * @internal IMPORTANT: if you want to add a new string (option) somewhere, make sure you add
+ * that array key to the main options definition array in the class WPSEO_Taxonomy_Meta() as well!!!!
+ */
+ public function translate_meta_options() {
+ $this->no_index_options = WPSEO_Taxonomy_Meta::$no_index_options;
+ $this->sitemap_include_options = WPSEO_Taxonomy_Meta::$sitemap_include_options;
+
+ $this->no_index_options['default'] = __( 'Use %s default (Currently: %s)', 'wordpress-seo' );
+ $this->no_index_options['index'] = __( 'Always index', 'wordpress-seo' );
+ $this->no_index_options['noindex'] = __( 'Always noindex', 'wordpress-seo' );
+
+ $this->sitemap_include_options['-'] = __( 'Auto detect', 'wordpress-seo' );
+ $this->sitemap_include_options['always'] = __( 'Always include', 'wordpress-seo' );
+ $this->sitemap_include_options['never'] = __( 'Never include', 'wordpress-seo' );
+ }
+
+
+
+
+
+ /**
+ * Test whether we are on a public taxonomy - no metabox actions needed if we are not
+ * Unfortunately we have to hook most everything in before the point where all taxonomies are registered and
+ * we know which taxonomy is being requested, so we need to use this check in nearly every hooked in function.
+ *
+ * @since 1.5.0
+ */
+ function tax_is_public() {
+ // Don't make static as taxonomies may still be added during the run
+ $taxonomies = get_taxonomies( array( 'public' => true ), 'names' );
+
+ return ( isset( $_GET['taxonomy'] ) && in_array( $_GET['taxonomy'], $taxonomies ) );
+ }
+
+
+ /**
+ * Add our admin css file
+ */
+ function admin_enqueue_scripts() {
+ global $pagenow;
+
+ if ( $pagenow === 'edit-tags.php' && ( isset( $_GET['action'] ) && $_GET['action'] === 'edit' ) ) {
+ wp_enqueue_style( 'yoast-taxonomy-css', plugins_url( 'css/taxonomy-meta' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
+ }
+ }
+
+
+ /**
+ * Create a row in the form table.
+ *
+ * @param string $var Variable the row controls.
+ * @param string $label Label for the variable.
+ * @param string $desc Description of the use of the variable.
+ * @param array $tax_meta Taxonomy meta value.
+ * @param string $type Type of form row to create.
+ * @param array $options Options to use when form row is a select box.
+ */
+ function form_row( $var, $label, $desc, $tax_meta, $type = 'text', $options = array() ) {
+ $val = '';
+ if ( isset( $tax_meta[ $var ] ) && $tax_meta[ $var ] !== '' ) {
+ $val = $tax_meta[ $var ];
+ }
+
+ $esc_var = esc_attr( $var );
+ $field = '';
+
+ if ( $type == 'text' ) {
+ $field .= '
+ <input name="' . $esc_var . '" id="' . $esc_var . '" type="text" value="' . esc_attr( $val ) . '" size="40"/>';
+ }
+ elseif ( $type == 'checkbox' ) {
+ $field .= '
+ <input name="' . $esc_var . '" id="' . $esc_var . '" type="checkbox" ' . checked( $val ) . '/>';
+ }
+ elseif ( $type == 'select' ) {
+ if ( is_array( $options ) && $options !== array() ) {
+ $field .= '
+ <select name="' . $esc_var . '" id="' . $esc_var . '">';
+
+ foreach ( $options as $option => $option_label ) {
+ $selected = selected( $option, $val, false );
+ $field .= '
+ <option ' . $selected . ' value="' . esc_attr( $option ) . '">' . esc_html( $option_label ) . '</option>';
+ }
+
+ $field .= '
+ </select>';
+ }
+ }
+ elseif ( $type == 'hidden' ) {
+ $field .= '
+ <input name="' . $esc_var . '" id="hidden_' . $esc_var . '" type="hidden" value="' . esc_attr( $val ) . '" />';
+ }
+
+ if ( $field !== '' && ( is_string( $desc ) && $desc !== '' ) ) {
+ $field .= '
+ <p class="description">' . $desc . '</p>';
+ }
+
+ echo '
+ <tr class="form-field">
+ <th scope="row">' . ( '' !== $label ? '<label for="' . $esc_var . '">' . esc_html( $label ) . ':</label>' : '' ) . '</th>
+ <td>' . $field . '</td>
+ </tr>';
+ }
+
+ /**
+ * Show the SEO inputs for term.
+ *
+ * @param object $term Term to show the edit boxes for.
+ */
+ function term_seo_form( $term ) {
+ if ( $this->tax_is_public() === false ) {
+ return;
+ }
+
+ $tax_meta = WPSEO_Taxonomy_Meta::get_term_meta( (int) $term->term_id, $term->taxonomy );
+ $options = WPSEO_Options::get_all();
+
+
+ echo '<h2>' . __( 'Yoast WordPress SEO Settings', 'wordpress-seo' ) . '</h2>';
+ echo '<table class="form-table wpseo-taxonomy-form">';
+
+ $this->form_row( 'wpseo_title', __( 'SEO Title', 'wordpress-seo' ), esc_html__( 'The SEO title is used on the archive page for this term.', 'wordpress-seo' ), $tax_meta );
+ $this->form_row( 'wpseo_desc', __( 'SEO Description', 'wordpress-seo' ), esc_html__( 'The SEO description is used for the meta description on the archive page for this term.', 'wordpress-seo' ), $tax_meta );
+
+ if ( $options['usemetakeywords'] === true ) {
+ $this->form_row( 'wpseo_metakey', __( 'Meta Keywords', 'wordpress-seo' ), esc_html__( 'Meta keywords used on the archive page for this term.', 'wordpress-seo' ), $tax_meta );
+ }
+
+ $this->form_row( 'wpseo_canonical', __( 'Canonical', 'wordpress-seo' ), esc_html__( 'The canonical link is shown on the archive page for this term.', 'wordpress-seo' ), $tax_meta );
+
+ if ( $options['breadcrumbs-enable'] === true ) {
+ $this->form_row( 'wpseo_bctitle', __( 'Breadcrumbs Title', 'wordpress-seo' ), sprintf( esc_html__( 'The Breadcrumbs title is used in the breadcrumbs where this %s appears.', 'wordpress-seo' ), $term->taxonomy ), $tax_meta );
+ }
+
+ $current = 'index';
+ if ( isset( $options[ 'noindex-tax-' . $term->taxonomy ] ) && $options[ 'noindex-tax-' . $term->taxonomy ] === true ) {
+ $current = 'noindex';
+ }
+
+ $noindex_options = $this->no_index_options;
+ $noindex_options['default'] = sprintf( $noindex_options['default'], $term->taxonomy, $current );
+
+ $desc = sprintf( esc_html__( 'This %s follows the indexation rules set under Metas and Titles, you can override it here.', 'wordpress-seo' ), $term->taxonomy );
+ if ( '0' == get_option( 'blog_public' ) ) {
+ $desc .= '<br /><span class="error-message">' . esc_html__( 'Warning: even though you can set the meta robots setting here, the entire site is set to noindex in the sitewide privacy settings, so these settings won\'t have an effect.', 'wordpress-seo' ) . '</span>';
+ }
+
+ $this->form_row( 'wpseo_noindex', sprintf( __( 'Noindex this %s', 'wordpress-seo' ), $term->taxonomy ), $desc, $tax_meta, 'select', $noindex_options );
+ unset( $current, $no_index_options, $desc );
+
+
+ $this->form_row( 'wpseo_sitemap_include', __( 'Include in sitemap?', 'wordpress-seo' ), '', $tax_meta, 'select', $this->sitemap_include_options );
+
+ echo '</table>';
+ }
+
+ /**
+ * Update the taxonomy meta data on save.
+ *
+ * @param int $term_id ID of the term to save data for
+ * @param int $tt_id The taxonomy_term_id for the term.
+ * @param string $taxonomy The taxonomy the term belongs to.
+ */
+ function update_term( $term_id, $tt_id, $taxonomy ) {
+ $tax_meta = get_option( 'wpseo_taxonomy_meta' );
+
+ /* Create post array with only our values */
+ $new_meta_data = array();
+ foreach ( WPSEO_Taxonomy_Meta::$defaults_per_term as $key => $default ) {
+ if ( isset( $_POST[ $key ] ) ) {
+ $new_meta_data[ $key ] = $_POST[ $key ];
+ }
+ }
+
+ /* Validate the post values */
+ $old = WPSEO_Taxonomy_Meta::get_term_meta( $term_id, $taxonomy );
+ $clean = WPSEO_Taxonomy_Meta::validate_term_meta_data( $new_meta_data, $old );
+
+ /* Add/remove the result to/from the original option value */
+ if ( $clean !== array() ) {
+ $tax_meta[ $taxonomy ][ $term_id ] = $clean;
+ }
+ else {
+ unset( $tax_meta[ $taxonomy ][ $term_id ] );
+ if ( isset( $tax_meta[ $taxonomy ] ) && $tax_meta[ $taxonomy ] === array() ) {
+ unset( $tax_meta[ $taxonomy ] );
+ }
+ }
+
+ // Prevent complete array validation
+ $tax_meta['wpseo_already_validated'] = true;
+
+ update_option( 'wpseo_taxonomy_meta', $tax_meta );
+ }
+
+
+ /**
+ * Allows HTML in descriptions
+ */
+ function custom_category_descriptions_allow_html() {
+ $filters = array(
+ 'pre_term_description',
+ 'pre_link_description',
+ 'pre_link_notes',
+ 'pre_user_description',
+ );
+
+ foreach ( $filters as $filter ) {
+ remove_filter( $filter, 'wp_filter_kses' );
+ }
+ remove_filter( 'term_description', 'wp_kses_data' );
+ }
+
+ /**
+ * Adds shortcode support to category descriptions.
+ *
+ * @param string $desc String to add shortcodes in.
+ * @return string
+ */
+ function custom_category_descriptions_add_shortcode_support( $desc ) {
+ // Wrap in output buffering to prevent shortcodes that echo stuff instead of return from breaking things.
+ ob_start();
+ $desc = do_shortcode( $desc );
+ ob_end_clean();
+
+ return $desc;
+ }
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'Yoast_Tracking' ) ) {
+ /**
+ * Class that creates the tracking functionality for WP SEO, as the core class might be used in more plugins,
+ * it's checked for existence first.
+ *
+ * NOTE: this functionality is opt-in. Disabling the tracking in the settings or saying no when asked will cause
+ * this file to not even be loaded.
+ *
+ * @todo [JRF => testers] check if tracking still works if an old version of the Yoast Tracking class was loaded
+ * (i.e. another plugin loaded their version first)
+ */
+ class Yoast_Tracking {
+
+ /**
+ * @var object Instance of this class
+ */
+ public static $instance;
+
+
+ /**
+ * Class constructor
+ */
+ function __construct() {
+ // Constructor is called from WP SEO
+ if ( current_filter( 'yoast_tracking' ) ) {
+ $this->tracking();
+ } // Backward compatibility - constructor is called from other Yoast plugin
+ elseif ( ! has_action( 'yoast_tracking', array( $this, 'tracking' ) ) ) {
+ add_action( 'yoast_tracking', array( $this, 'tracking' ) );
+ }
+ }
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Main tracking function.
+ */
+ function tracking() {
+
+ $transient_key = 'yoast_tracking_cache';
+ $data = get_transient( $transient_key );
+
+ // bail if transient is set and valid
+ if ( $data !== false ) {
+ return;
+ }
+
+ // Make sure to only send tracking data once a week
+ set_transient( $transient_key, 1, WEEK_IN_SECONDS );
+
+ // Start of Metrics
+ global $blog_id, $wpdb;
+
+ $hash = get_option( 'Yoast_Tracking_Hash', false );
+
+ if ( ! $hash || empty( $hash ) ) {
+ // create and store hash
+ $hash = md5( site_url() );
+ update_option( 'Yoast_Tracking_Hash', $hash );
+ }
+
+ $pts = array();
+ $post_types = get_post_types( array( 'public' => true ) );
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ foreach ( $post_types as $post_type ) {
+ $count = wp_count_posts( $post_type );
+ $pts[ $post_type ] = $count->publish;
+ }
+ }
+ unset( $post_types );
+
+ $comments_count = wp_count_comments();
+
+ $theme_data = wp_get_theme();
+ $theme = array(
+ 'name' => $theme_data->display( 'Name', false, false ),
+ 'theme_uri' => $theme_data->display( 'ThemeURI', false, false ),
+ 'version' => $theme_data->display( 'Version', false, false ),
+ 'author' => $theme_data->display( 'Author', false, false ),
+ 'author_uri' => $theme_data->display( 'AuthorURI', false, false ),
+ );
+ $theme_template = $theme_data->get_template();
+ if ( $theme_template !== '' && $theme_data->parent() ) {
+ $theme['template'] = array(
+ 'version' => $theme_data->parent()->display( 'Version', false, false ),
+ 'name' => $theme_data->parent()->display( 'Name', false, false ),
+ 'theme_uri' => $theme_data->parent()->display( 'ThemeURI', false, false ),
+ 'author' => $theme_data->parent()->display( 'Author', false, false ),
+ 'author_uri' => $theme_data->parent()->display( 'AuthorURI', false, false ),
+ );
+ } else {
+ $theme['template'] = '';
+ }
+ unset( $theme_template );
+
+
+ $plugins = array();
+ $active_plugin = get_option( 'active_plugins' );
+ foreach ( $active_plugin as $plugin_path ) {
+ if ( ! function_exists( 'get_plugin_data' ) ) {
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
+ }
+
+ $plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_path );
+
+ $slug = str_replace( '/' . basename( $plugin_path ), '', $plugin_path );
+ $plugins[ $slug ] = array(
+ 'version' => $plugin_info['Version'],
+ 'name' => $plugin_info['Name'],
+ 'plugin_uri' => $plugin_info['PluginURI'],
+ 'author' => $plugin_info['AuthorName'],
+ 'author_uri' => $plugin_info['AuthorURI'],
+ );
+ }
+ unset( $active_plugins, $plugin_path );
+
+
+ $data = array(
+ 'site' => array(
+ 'hash' => $hash,
+ 'version' => get_bloginfo( 'version' ),
+ 'multisite' => is_multisite(),
+ 'users' => $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->users INNER JOIN $wpdb->usermeta ON ({$wpdb->users}.ID = {$wpdb->usermeta}.user_id) WHERE 1 = 1 AND ( {$wpdb->usermeta}.meta_key = %s )", 'wp_' . $blog_id . '_capabilities' ) ),
+ 'lang' => get_locale(),
+ ),
+ 'pts' => $pts,
+ 'comments' => array(
+ 'total' => $comments_count->total_comments,
+ 'approved' => $comments_count->approved,
+ 'spam' => $comments_count->spam,
+ 'pings' => $wpdb->get_var( "SELECT COUNT(comment_ID) FROM $wpdb->comments WHERE comment_type = 'pingback'" ),
+ ),
+ 'options' => apply_filters( 'yoast_tracking_filters', array() ),
+ 'theme' => $theme,
+ 'plugins' => $plugins,
+ );
+
+ $args = array(
+ 'body' => $data,
+ 'blocking' => false,
+ 'sslverify' => false,
+ );
+
+ wp_remote_post( 'https://tracking.yoast.com/', $args );
+
+ }
+ } /* End of class */
+} /* End of class-exists wrapper */
+
+/**
+ * Adds tracking parameters for WP SEO settings. Outside of the main class as the class could also be in use in other plugins.
+ *
+ * @param array $options
+ *
+ * @return array
+ */
+function wpseo_tracking_additions( $options ) {
+ if ( function_exists( 'curl_version' ) ) {
+ $curl = curl_version();
+ } else {
+ $curl = null;
+ }
+
+
+ $opt = WPSEO_Options::get_all();
+
+ $options['wpseo'] = array(
+ 'xml_sitemaps' => ( $opt['enablexmlsitemap'] === true ) ? 1 : 0,
+ 'force_rewrite' => ( $opt['forcerewritetitle'] === true ) ? 1 : 0,
+ 'opengraph' => ( $opt['opengraph'] === true ) ? 1 : 0,
+ 'twitter' => ( $opt['twitter'] === true ) ? 1 : 0,
+ 'strip_category_base' => ( $opt['stripcategorybase'] === true ) ? 1 : 0,
+ 'on_front' => get_option( 'show_on_front' ),
+ 'wmt_alexa' => ( ! empty( $opt['alexaverify'] ) ) ? 1 : 0,
+ 'wmt_bing' => ( ! empty( $opt['msverify'] ) ) ? 1 : 0,
+ 'wmt_google' => ( ! empty( $opt['googleverify'] ) ) ? 1 : 0,
+ 'wmt_pinterest' => ( ! empty( $opt['pinterestverify'] ) ) ? 1 : 0,
+ 'wmt_yandex' => ( ! empty( $opt['yandexverify'] ) ) ? 1 : 0,
+ 'permalinks_clean' => ( $opt['cleanpermalinks'] == 1 ) ? 1 : 0,
+
+ 'site_db_charset' => DB_CHARSET,
+
+ 'webserver_apache' => wpseo_is_apache() ? 1 : 0,
+ 'webserver_apache_version' => function_exists( 'apache_get_version' ) ? apache_get_version() : 0,
+ 'webserver_nginx' => wpseo_is_nginx() ? 1 : 0,
+
+ 'webserver_server_software' => $_SERVER['SERVER_SOFTWARE'],
+ 'webserver_gateway_interface' => $_SERVER['GATEWAY_INTERFACE'],
+ 'webserver_server_protocol' => $_SERVER['SERVER_PROTOCOL'],
+
+ 'php_version' => phpversion(),
+
+ 'php_max_execution_time' => ini_get( 'max_execution_time' ),
+ 'php_memory_limit' => ini_get( 'memory_limit' ),
+ 'php_open_basedir' => ini_get( 'open_basedir' ),
+
+ 'php_bcmath_enabled' => extension_loaded( 'bcmath' ) ? 1 : 0,
+ 'php_ctype_enabled' => extension_loaded( 'ctype' ) ? 1 : 0,
+ 'php_curl_enabled' => extension_loaded( 'curl' ) ? 1 : 0,
+ 'php_curl_version_a' => phpversion( 'curl' ),
+ 'php_curl' => ( ! is_null( $curl ) ) ? $curl['version'] : 0,
+ 'php_dom_enabled' => extension_loaded( 'dom' ) ? 1 : 0,
+ 'php_dom_version' => phpversion( 'dom' ),
+ 'php_filter_enabled' => extension_loaded( 'filter' ) ? 1 : 0,
+ 'php_mbstring_enabled' => extension_loaded( 'mbstring' ) ? 1 : 0,
+ 'php_mbstring_version' => phpversion( 'mbstring' ),
+ 'php_pcre_enabled' => extension_loaded( 'pcre' ) ? 1 : 0,
+ 'php_pcre_version' => phpversion( 'pcre' ),
+ 'php_pcre_with_utf8_a' => @preg_match( '/^.{1}$/u', 'ñ', $UTF8_ar ),
+ 'php_pcre_with_utf8_b' => defined( 'PREG_BAD_UTF8_ERROR' ),
+ 'php_spl_enabled' => extension_loaded( 'spl' ) ? 1 : 0,
+ );
+
+ return $options;
+}
+
+add_filter( 'yoast_tracking_filters', 'wpseo_tracking_additions' );
--- /dev/null
+<?php
+
+class Yoast_Notification_Center {
+
+ const TRANSIENT_KEY = 'yoast_notifications';
+
+ private static $instance = null;
+
+ private $notifications = array();
+
+ /**
+ * Construct
+ */
+ private function __construct() {
+
+ // Load the notifications from cookie
+ $this->notifications = $this->get_notifications_from_transient();
+
+ // Clear the cookie
+ if ( count( $this->notifications ) > 0 ) {
+ $this->remove_transient();
+ }
+
+ // Display the notifications in all_admin_notices
+ add_action( 'all_admin_notices', array( $this, 'display_notifications' ) );
+
+ // Write the cookie on shutdown
+ add_action( 'shutdown', array( $this, 'set_transient' ) );
+
+ // AJAX
+ add_action( 'wp_ajax_yoast_get_notifications', array( $this, 'ajax_get_notifications' ) );
+ }
+
+ /**
+ * Singleton getter
+ *
+ * @return Yoast_Notification_Center
+ */
+ public static function get() {
+
+ if ( null == self::$instance ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Get the notifications from cookie
+ *
+ * @return array
+ */
+ private function get_notifications_from_transient() {
+
+ // The notifications array
+ $notifications = array();
+
+ $transient_notifications = get_transient( self::TRANSIENT_KEY );
+
+ // Check if cookie is set
+ if ( false !== $transient_notifications ) {
+
+ // Get json notifications from cookie
+ $json_notifications = json_decode( $transient_notifications, true );
+
+ // Create Yoast_Notification objects
+ if ( count( $json_notifications ) > 0 ) {
+ foreach ( $json_notifications as $json_notification ) {
+ $notifications[] = new Yoast_Notification( $json_notification['message'], $json_notification['type'] );
+ }
+ }
+ }
+
+ return $notifications;
+ }
+
+ /**
+ * Clear the cookie
+ */
+ private function remove_transient() {
+ delete_transient( self::TRANSIENT_KEY );
+ }
+
+ /**
+ * Clear local stored notifications
+ */
+ private function clear_notifications() {
+ $this->notifications = array();
+ }
+
+ /**
+ * Write the notifications to cookie
+ */
+ public function set_transient() {
+
+ // Count local stored notifications
+ if ( count( $this->notifications ) > 0 ) {
+
+ // Create array with all notifications
+ $arr_notifications = array();
+
+ // Add each notification as array to $arr_notifications
+ foreach ( $this->notifications as $notification ) {
+ $arr_notifications[] = $notification->to_array();
+ }
+
+ // Set the cookie with notifications
+ set_transient( self::TRANSIENT_KEY, json_encode( $arr_notifications ), MINUTE_IN_SECONDS * 10 );
+
+ }
+
+ }
+
+ /**
+ * Add notification to the cookie
+ *
+ * @param Yoast_Notification $notification
+ */
+ public function add_notification( Yoast_Notification $notification ) {
+ $this->notifications[] = $notification;
+ }
+
+ /**
+ * Display the notifications
+ */
+ public function display_notifications() {
+
+ // Display notifications
+ if ( count( $this->notifications ) > 0 ) {
+ foreach ( $this->notifications as $notification ) {
+ $notification->output();
+ }
+ }
+
+ // Clear the local stored notifications
+ $this->clear_notifications();
+
+ }
+
+ /**
+ * AJAX display notifications
+ */
+ public function ajax_get_notifications() {
+
+ // Display the notices
+ $this->display_notifications();
+
+ // AJAX die
+ exit;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+class Yoast_Notification {
+
+ private $message;
+ private $type;
+
+ /**
+ * The Constructor
+ *
+ * @param String $message
+ * @param String $type
+ */
+ public function __construct( $message, $type = 'updated' ) {
+ $this->message = $message;
+ $this->type = $type;
+ }
+
+ /**
+ * @return String
+ */
+ public function get_message() {
+ return $this->message;
+ }
+
+ /**
+ * @param String $message
+ */
+ public function set_message( $message ) {
+ $this->message = $message;
+ }
+
+ /**
+ * @return String
+ */
+ public function get_type() {
+ return $this->type;
+ }
+
+ /**
+ * @param String $type
+ */
+ public function set_type( $type ) {
+ $this->type = $type;
+ }
+
+ /**
+ * Return the object properties as an array
+ *
+ * @return array
+ */
+ public function to_array() {
+ return array(
+ 'message' => $this->get_message(),
+ 'type' => $this->get_type()
+ );
+ }
+
+ /**
+ * Output the message
+ */
+ public function output() {
+ echo '<div class="yoast-notice ' . $this->get_type() . '">' . wpautop( $this->get_message() ) . '</div>' . PHP_EOL;
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+Yoast License Manager
+=====================
+
+This library will take care of the following.
+
+- Managing license related options
+- Setting license key from a constant
+- Obfuscasting (valid) license keys
+- Remote license activation / deactivation
+- Checking for plugin or theme updates
+
+## Usage
+
+### Inside Plugins
+
+A sample plugin is included, [have a look at its source](https://github.com/Yoast/License-Manager/blob/master/samples/sample-plugin.php).
+
+### Inside Themes
+
+A sample theme `functions.php` file is included, [have a look here](https://github.com/Yoast/License-Manager/blob/master/samples/sample-theme-functions.php).
+
--- /dev/null
+<?php
+
+if( ! class_exists( "Yoast_API_Request", false ) ) {
+
+ /**
+ * Handles requests to the Yoast EDD API
+ */
+ class Yoast_API_Request {
+
+ /**
+ * @var string Request URL
+ */
+ private $url = '';
+
+ /**
+ * @var array Request parameters
+ */
+ private $args = array(
+ 'method' => 'GET',
+ 'timeout' => 10,
+ 'sslverify' => false,
+ 'headers' => array(
+ 'Accept-Encoding' => '*',
+ 'X-Yoast-EDD' => '1'
+ )
+ );
+
+ /**
+ * @var boolean
+ */
+ private $success = false;
+
+ /**
+ * @var mixed
+ */
+ private $response;
+
+ /**
+ * @var string
+ */
+ private $error_message = '';
+
+ /**
+ * Constructor
+ *
+ * @param string url
+ * @param array $args
+ */
+ public function __construct( $url, array $args = array() ) {
+
+ // set api url
+ $this->url = $url;
+
+ // set request args (merge with defaults)
+ $this->args = wp_parse_args( $args, $this->args );
+
+ // fire the request
+ $this->success = $this->fire();
+ }
+
+ /**
+ * Fires the request, automatically called from constructor
+ *
+ * @return boolean
+ */
+ private function fire() {
+
+ // fire request to shop
+ $response = wp_remote_request( $this->url, $this->args );
+
+ // validate raw response
+ if( $this->validate_raw_response( $response ) === false ) {
+ return false;
+ }
+
+ // decode the response
+ $this->response = json_decode( wp_remote_retrieve_body( $response ) );
+
+ // response should be an object
+ if( ! is_object( $this->response ) ) {
+ $this->error_message = 'No JSON object was returned.';
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param object $response
+ * @return boolean
+ */
+ private function validate_raw_response( $response ) {
+
+ // make sure response came back okay
+ if( is_wp_error( $response ) ) {
+ $this->error_message = $response->get_error_message();
+ return false;
+ }
+
+ // check response code, should be 200
+ $response_code = wp_remote_retrieve_response_code( $response );
+
+ if( false === strstr( $response_code, '200' ) ) {
+
+ $response_message = wp_remote_retrieve_response_message( $response );
+ $this->error_message = "{$response_code} {$response_message}";
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Was a valid response returned?
+ *
+ * @return boolean
+ */
+ public function is_valid() {
+ return ( $this->success === true );
+ }
+
+ /**
+ * @return string
+ */
+ public function get_error_message() {
+ return $this->error_message;
+ }
+
+ /**
+ * @return object
+ */
+ public function get_response() {
+ return $this->response;
+ }
+
+ }
+
+}
+
--- /dev/null
+<?php
+
+if( ! interface_exists( 'iYoast_License_Manager', false ) ) {
+
+ interface iYoast_License_Manager {
+
+ public function specific_hooks();
+ public function setup_auto_updater();
+
+ }
+
+}
+
+
+if( ! class_exists( 'Yoast_License_Manager', false ) ) {
+
+ /**
+ * Class Yoast_License_Manager
+ *
+ * @todo Maybe create a license class that contains key and option
+ * @todo Not sure if Yoast_License_Manager is a good name for this class, it's more managing the product (plugin or theme)
+ */
+ abstract class Yoast_License_Manager implements iYoast_License_Manager {
+
+ /**
+ * @const VERSION The version number of the License_Manager class
+ */
+ const VERSION = 1;
+
+ /**
+ * @var Yoast_License The license
+ */
+ protected $product;
+
+ /**
+ * @var string
+ */
+ private $license_constant_name = '';
+
+ /**
+ * @var boolean True if license is defined with a constant
+ */
+ private $license_constant_is_defined = false;
+
+ /**
+ * @var boolean True if remote license activation just failed
+ */
+ private $remote_license_activation_failed = false;
+
+ /**
+ * @var array Array of license related options
+ */
+ private $options = array();
+
+ /**
+ * @var string Used to prefix ID's, option names, etc..
+ */
+ protected $prefix;
+
+ /**
+ * @var bool Boolean indicating whether this plugin is network activated
+ */
+ protected $is_network_activated = false;
+
+ /**
+ * Constructor
+ *
+ * @param Yoast_Product $product
+ */
+ public function __construct( Yoast_Product $product ) {
+
+ // Set the license
+ $this->product = $product;
+
+ // set prefix
+ $this->prefix = sanitize_title_with_dashes( $this->product->get_item_name() . '_', null, 'save' );
+
+ // maybe set license key from constant
+ $this->maybe_set_license_key_from_constant();
+ }
+
+ /**
+ * Setup hooks
+ *
+ */
+ public function setup_hooks() {
+
+ // show admin notice if license is not active
+ add_action( 'admin_notices', array( $this, 'display_admin_notices' ) );
+
+ // catch POST requests from license form
+ add_action( 'admin_init', array( $this, 'catch_post_request') );
+
+ // setup item type (plugin|theme) specific hooks
+ $this->specific_hooks();
+
+ // setup the auto updater
+ $this->setup_auto_updater();
+
+ }
+
+ /**
+ * Display license specific admin notices, namely:
+ *
+ * - License for the product isn't activated
+ * - External requests are blocked through WP_HTTP_BLOCK_EXTERNAL
+ */
+ public function display_admin_notices() {
+
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return;
+ }
+
+ // show notice if license is invalid
+ if( ! $this->license_is_valid() ) {
+ if( $this->get_license_key() == '' ) {
+ $message = '<b>Warning!</b> You didn\'t set your %s license key yet, which means you\'re missing out on updates and support! <a href="%s">Enter your license key</a> or <a href="%s" target="_blank">get a license here</a>.';
+ } else {
+ $message = '<b>Warning!</b> Your %s license is inactive which means you\'re missing out on updates and support! <a href="%s">Activate your license</a> or <a href="%s" target="_blank">get a license here</a>.';
+ }
+ ?>
+ <div class="error">
+ <p><?php printf( __( $message, $this->product->get_text_domain() ), $this->product->get_item_name(), $this->product->get_license_page_url(), $this->product->get_tracking_url( 'activate-license-notice' ) ); ?></p>
+ </div>
+ <?php
+ }
+
+ // show notice if external requests are blocked through the WP_HTTP_BLOCK_EXTERNAL constant
+ if( defined( "WP_HTTP_BLOCK_EXTERNAL" ) && WP_HTTP_BLOCK_EXTERNAL === true ) {
+
+ // check if our API endpoint is in the allowed hosts
+ $host = parse_url( $this->product->get_api_url(), PHP_URL_HOST );
+
+ if( ! defined( "WP_ACCESSIBLE_HOSTS" ) || stristr( WP_ACCESSIBLE_HOSTS, $host ) === false ) {
+ ?>
+ <div class="error">
+ <p><?php printf( __( '<b>Warning!</b> You\'re blocking external requests which means you won\'t be able to get %s updates. Please add %s to %s.', $this->product->get_text_domain() ), $this->product->get_item_name(), '<strong>' . $host . '</strong>', '<code>WP_ACCESSIBLE_HOSTS</code>'); ?></p>
+ </div>
+ <?php
+ }
+
+ }
+ }
+
+ /**
+ * Set a notice to display in the admin area
+ *
+ * @param string $type error|updated
+ * @param string $message The message to display
+ */
+ protected function set_notice( $message, $success = true ) {
+ $css_class = ( $success ) ? 'updated' : 'error';
+ add_settings_error( $this->prefix . 'license', 'license-notice', $message, $css_class );
+ }
+
+ /**
+ * Remotely activate License
+ * @return boolean True if the license is now activated, false if not
+ */
+ public function activate_license() {
+
+ $result = $this->call_license_api( 'activate' );
+
+
+ if( $result ) {
+
+ // story expiry date
+ if( isset( $result->expires ) ) {
+ $this->set_license_expiry_date( $result->expires );
+ $expiry_date = strtotime( $result->expires );
+ } else {
+ $expiry_date = false;
+ }
+
+ // show success notice if license is valid
+ if($result->license === 'valid') {
+
+ // show a custom notice if users have an unlimited license
+ if( $result->license_limit == 0 ) {
+ $message = sprintf( __( "Your %s license has been activated. You have an unlimited license. ", $this->product->get_text_domain() ), $this->product->get_item_name() );
+ } else {
+ $message = sprintf( __( "Your %s license has been activated. You have used %d/%d activations. ", $this->product->get_text_domain() ), $this->product->get_item_name(), $result->site_count, $result->license_limit );
+ }
+
+ // add upgrade notice if user has less than 3 activations left
+ if( $result->license_limit > 0 && ( $result->license_limit - $result->site_count ) <= 3 ) {
+ $message .= sprintf( __( '<a href="%s">Did you know you can upgrade your license?</a>', $this->product->get_text_domain() ), $this->product->get_tracking_url( 'license-nearing-limit-notice' ) );
+ // add extend notice if license is expiring in less than 1 month
+ } elseif( $expiry_date !== false && $expiry_date < strtotime( "+1 month" ) ) {
+ $days_left = round( ( $expiry_date - strtotime( "now" ) ) / 86400 );
+ $message .= sprintf( __( '<a href="%s">Your license is expiring in %d days, would you like to extend it?</a>', $this->product->get_text_domain() ), $this->product->get_tracking_url( 'license-expiring-notice' ), $days_left );
+ }
+
+ $this->set_notice( $message, true );
+
+ } else {
+
+ if( isset($result->error) && $result->error === 'no_activations_left' ) {
+ // show notice if user is at their activation limit
+ $this->set_notice( sprintf( __('You\'ve reached your activation limit. You must <a href="%s">upgrade your license</a> to use it on this site.', $this->product->get_text_domain() ), $this->product->get_tracking_url( 'license-at-limit-notice' ) ), false );
+ } elseif( isset($result->error) && $result->error == "expired" ) {
+ // show notice if the license is expired
+ $this->set_notice( sprintf( __('Your license has expired. You must <a href="%s">extend your license</a> in order to use it again.', $this->product->get_text_domain() ), $this->product->get_tracking_url( 'license-expired-notice' ) ), false );
+ } else {
+ // show a general notice if it's any other error
+ $this->set_notice( __( "Failed to activate your license, your license key seems to be invalid.", $this->product->get_text_domain() ), false );
+ }
+
+ $this->remote_license_activation_failed = true;
+ }
+
+ $this->set_license_status( $result->license );
+ }
+
+ return ( $this->license_is_valid() );
+ }
+
+ /**
+ * Remotely deactivate License
+ * @return boolean True if the license is now deactivated, false if not
+ */
+ public function deactivate_license () {
+
+ $result = $this->call_license_api( 'deactivate' );
+
+ if( $result ) {
+
+ // show notice if license is deactivated
+ if( $result->license === 'deactivated' ) {
+ $this->set_notice( sprintf( __( "Your %s license has been deactivated.", $this->product->get_text_domain() ), $this->product->get_item_name() ) );
+ } else {
+ $this->set_notice( sprintf( __( "Failed to deactivate your %s license.", $this->product->get_text_domain() ), $this->product->get_item_name() ), false );
+ }
+
+ $this->set_license_status( $result->license );
+ }
+
+ return ( $this->get_license_status() === 'deactivated' );
+ }
+
+ /**
+ * @param string $action activate|deactivate
+ * @return mixed
+ */
+ protected function call_license_api( $action ) {
+
+ // don't make a request if license key is empty
+ if( $this->get_license_key() === '' ) {
+ return false;
+ }
+
+ // data to send in our API request
+ $api_params = array(
+ 'edd_action' => $action . '_license',
+ 'license' => $this->get_license_key(),
+ 'item_name' => urlencode( trim( $this->product->get_item_name() ) ),
+ 'url' => get_option( 'home' ) // grab the URL straight from the option to prevent filters from breaking it.
+ );
+
+ // create api request url
+ $url = add_query_arg( $api_params, $this->product->get_api_url() );
+
+ require_once dirname( __FILE__ ) . '/class-api-request.php';
+ $request = new Yoast_API_Request( $url );
+
+ if( $request->is_valid() !== true ) {
+ $this->set_notice( sprintf( __( "Request error: \"%s\" (%scommon license notices%s)", $this->product->get_text_domain() ), $request->get_error_message(), '<a href="http://kb.yoast.com/article/13-license-activation-notices">', '</a>' ), false );
+ }
+
+ // get response
+ $response = $request->get_response();
+
+ // update license status
+ $license_data = $response;
+
+ return $license_data;
+ }
+
+
+
+ /**
+ * Set the license status
+ *
+ * @param string $license_status
+ */
+ public function set_license_status( $license_status ) {
+ $this->set_option( 'status', $license_status );
+ }
+
+ /**
+ * Get the license status
+ *
+ * @return string $license_status;
+ */
+ public function get_license_status() {
+ $license_status = $this->get_option( 'status' );
+ return trim( $license_status );
+ }
+
+ /**
+ * Set the license key
+ *
+ * @param string $license_key
+ */
+ public function set_license_key( $license_key ) {
+ $this->set_option( 'key', $license_key );
+ }
+
+ /**
+ * Gets the license key from constant or option
+ *
+ * @return string $license_key
+ */
+ public function get_license_key() {
+ $license_key = $this->get_option( 'key' );
+ return trim( $license_key );
+ }
+
+ /**
+ * Gets the license expiry date
+ *
+ * @return string
+ */
+ public function get_license_expiry_date() {
+ return $this->get_option( 'expiry_date');
+ }
+
+ /**
+ * Stores the license expiry date
+ */
+ public function set_license_expiry_date( $expiry_date ) {
+ $this->set_option( 'expiry_date', $expiry_date );
+ }
+
+ /**
+ * Checks whether the license status is active
+ *
+ * @return boolean True if license is active
+ */
+ public function license_is_valid() {
+ return ( $this->get_license_status() === 'valid' );
+ }
+
+ /**
+ * Get all license related options
+ *
+ * @return array Array of license options
+ */
+ protected function get_options() {
+
+ // create option name
+ $option_name = $this->prefix . 'license';
+
+ // get array of options from db
+ if( $this->is_network_activated ) {
+ $options = get_site_option( $option_name, array( ) );
+ } else {
+ $options = get_option( $option_name, array( ) );
+ }
+
+ // setup array of defaults
+ $defaults = array(
+ 'key' => '',
+ 'status' => '',
+ 'expiry_date' => ''
+ );
+
+ // merge options with defaults
+ $this->options = wp_parse_args( $options, $defaults );
+
+ return $this->options;
+ }
+
+ /**
+ * Set license related options
+ *
+ * @param array $options Array of new license options
+ */
+ protected function set_options( array $options ) {
+ // create option name
+ $option_name = $this->prefix . 'license';
+
+ // update db
+ if( $this->is_network_activated ) {
+ update_site_option( $option_name, $options );
+ } else {
+ update_option( $option_name, $options );
+ }
+
+ }
+
+ /**
+ * Gets a license related option
+ *
+ * @param string $name The option name
+ * @return mixed The option value
+ */
+ protected function get_option( $name ) {
+ $options = $this->get_options();
+ return $options[ $name ];
+ }
+
+ /**
+ * Set a license related option
+ *
+ * @param string $name The option name
+ * @param mixed $value The option value
+ */
+ protected function set_option( $name, $value ) {
+ // get options
+ $options = $this->get_options();
+
+ // update option
+ $options[ $name ] = $value;
+
+ // save options
+ $this->set_options( $options );
+ }
+
+ public function show_license_form_heading() {
+ ?>
+ <h3>
+ <?php printf( __( "%s: License Settings", $this->product->get_text_domain() ), $this->product->get_item_name() ); ?>
+ </h3>
+ <?php
+ }
+
+ /**
+ * Show a form where users can enter their license key
+ *
+ * @param boolean $embedded Boolean indicating whether this form is embedded in another form?
+ */
+ public function show_license_form( $embedded = true ) {
+
+ $key_name = $this->prefix . 'license_key';
+ $nonce_name = $this->prefix . 'license_nonce';
+ $action_name = $this->prefix . 'license_action';
+
+
+ $visible_license_key = $this->get_license_key();
+
+ // obfuscate license key
+ $obfuscate = ( strlen( $this->get_license_key() ) > 5 && ( $this->license_is_valid() || ! $this->remote_license_activation_failed ) );
+
+ if($obfuscate) {
+ $visible_license_key = str_repeat('*', strlen( $this->get_license_key() ) - 4) . substr( $this->get_license_key(), -4 );
+ }
+
+ // make license key readonly when license key is valid or license is defined with a constant
+ $readonly = ( $this->license_is_valid() || $this->license_constant_is_defined );
+
+ require dirname( __FILE__ ) . '/views/form.php';
+
+ // enqueue script in the footer
+ add_action( 'admin_footer', array( $this, 'output_script'), 99 );
+ }
+
+ /**
+ * Check if the license form has been submitted
+ */
+ public function catch_post_request() {
+
+ $name = $this->prefix . 'license_key';
+
+ // check if license key was posted and not empty
+ if( ! isset( $_POST[$name] ) ) {
+ return;
+ }
+
+ // run a quick security check
+ $nonce_name = $this->prefix . 'license_nonce';
+
+ if ( ! check_admin_referer( $nonce_name, $nonce_name ) ) {
+ return;
+ }
+
+ // @TODO: check for user cap?
+
+ // get key from posted value
+ $license_key = $_POST[$name];
+
+ // check if license key doesn't accidentally contain asterisks
+ if( strstr($license_key, '*') === false ) {
+
+ // sanitize key
+ $license_key = trim( sanitize_key( $_POST[$name] ) );
+
+ // save license key
+ $this->set_license_key( $license_key );
+ }
+
+ // does user have an activated valid license
+ if( ! $this->license_is_valid() ) {
+
+ // try to auto-activate license
+ return $this->activate_license();
+
+ }
+
+ $action_name = $this->prefix . 'license_action';
+
+ // was one of the action buttons clicked?
+ if( isset( $_POST[ $action_name ] ) ) {
+
+ $action = trim( $_POST[ $action_name ] );
+
+ switch($action) {
+
+ case 'activate':
+ return $this->activate_license();
+ break;
+
+ case 'deactivate':
+ return $this->deactivate_license();
+ break;
+ }
+
+ }
+
+ }
+
+ /**
+ * Output the script containing the YoastLicenseManager JS Object
+ *
+ * This takes care of disabling the 'activate' and 'deactivate' buttons
+ */
+ public function output_script() {
+ require_once dirname( __FILE__ ) . '/views/script.php';
+ }
+
+ /**
+ * Set the constant used to define the license
+ *
+ * @param string $license_constant_name The license constant name
+ */
+ public function set_license_constant_name( $license_constant_name ) {
+ $this->license_constant_name = trim( $license_constant_name );
+ $this->maybe_set_license_key_from_constant();
+ }
+
+ /**
+ * Maybe set license key from a defined constant
+ */
+ private function maybe_set_license_key_from_constant( ) {
+
+ if( empty( $this->license_constant_name ) ) {
+ // generate license constant name
+ $this->set_license_constant_name( strtoupper( str_replace( array(' ', '-' ), '', sanitize_key( $this->product->get_item_name() ) ) ) . '_LICENSE');
+ }
+
+ // set license key from constant
+ if( defined( $this->license_constant_name ) ) {
+
+ $license_constant_value = constant( $this->license_constant_name );
+
+ // update license key value with value of constant
+ if( $this->get_license_key() !== $license_constant_value ) {
+ $this->set_license_key( $license_constant_value );
+ }
+
+ $this->license_constant_is_defined = true;
+ }
+ }
+
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+if ( class_exists( 'Yoast_License_Manager' ) && ! class_exists( "Yoast_Plugin_License_Manager", false ) ) {
+
+ class Yoast_Plugin_License_Manager extends Yoast_License_Manager {
+
+ /**
+ * Constructor
+ *
+ * @param Yoast_Product $product
+ */
+ public function __construct( Yoast_Product $product ) {
+
+ parent::__construct( $product );
+
+ // Check if plugin is network activated. We should use site(wide) options in that case.
+ if( is_admin() && is_multisite() ) {
+
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
+ require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
+ }
+
+ $this->is_network_activated = is_plugin_active_for_network( $product->get_slug() );
+ }
+ }
+
+ /**
+ * Setup auto updater for plugins
+ */
+ public function setup_auto_updater() {
+ if ( $this->license_is_valid() ) {
+ // setup auto updater
+ require_once( dirname( __FILE__ ) . '/class-update-manager.php' );
+ require_once( dirname( __FILE__ ) . '/class-plugin-update-manager.php' );
+ new Yoast_Plugin_Update_Manager( $this->product, $this );
+ }
+ }
+
+ /**
+ * Setup hooks
+ */
+ public function specific_hooks() {
+
+ // deactivate the license remotely on plugin deactivation
+ register_deactivation_hook( $this->product->get_slug(), array( $this, 'deactivate_license' ) );
+ }
+
+ /**
+ * Show a form where users can enter their license key
+ * Takes Multisites into account
+ *
+ * @param bool $embedded
+ * @return null
+ */
+ public function show_license_form( $embedded = true ) {
+
+ // For non-multisites, always show the license form
+ if( ! is_multisite() ) {
+ parent::show_license_form( $embedded );
+ return;
+ }
+
+ // Plugin is network activated
+ if( $this->is_network_activated ) {
+
+ // We're on the network admin
+ if( is_network_admin() ) {
+ parent::show_license_form( $embedded );
+ } else {
+ // We're not in the network admin area, show a notice
+ parent::show_license_form_heading();
+ if ( is_super_admin() ) {
+ echo "<p>" . sprintf( __( '%s is network activated, you can manage your license in the <a href="%s">network admin license page</a>.', $this->product->get_text_domain() ), $this->product->get_item_name(), $this->product->get_license_page_url() ) . "</p>";
+ } else {
+ echo "<p>" . sprintf( __( '%s is network activated, please contact your site administrator to manage the license.', $this->product->get_text_domain() ), $this->product->get_item_name() ) . "</p>";
+ }
+
+ }
+
+ } else {
+
+ if( false == is_network_admin() ) {
+ parent::show_license_form( $embedded );
+ }
+
+ }
+ }
+ }
+}
+
--- /dev/null
+<?php
+
+//set_site_transient( 'update_plugins', null );
+
+if( class_exists( 'Yoast_Update_Manager' ) && ! class_exists( "Yoast_Plugin_Update_Manager", false ) ) {
+
+ class Yoast_Plugin_Update_Manager extends Yoast_Update_Manager {
+
+ /**
+ * Constructor
+ *
+ * @param string $api_url
+ * @param string $item_name
+ * @param string $license_key
+ * @param string $slug The path to the main plugin file, relative to plugins dir
+ * @param string $version
+ * @param string $author (optional)
+ * @param string $text_domain
+ */
+ public function __construct( Yoast_Product $product, $license_key ) {
+ parent::__construct( $product, $license_key );
+
+ // setup hooks
+ $this->setup_hooks();
+
+ }
+
+ /**
+ * Setup hooks
+ */
+ private function setup_hooks() {
+
+ // check for updates
+ add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'set_updates_available_data' ) );
+
+ // get correct plugin information (when viewing details)
+ add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
+ }
+
+ /**
+ * Check for updates and if so, add to "updates available" data
+ *
+ * @param object $data
+ * @return object $data
+ */
+ public function set_updates_available_data( $data ) {
+
+ if ( empty( $data ) ) {
+ return $data;
+ }
+
+ // send of API request to check for updates
+ $remote_data = $this->get_remote_data();
+
+ // did we get a response?
+ if( $remote_data === false ) {
+ return $data;
+ }
+
+ // compare local version with remote version
+ if ( version_compare( $this->product->get_version(), $remote_data->new_version, '<' ) ) {
+
+ // remote version is newer, add to data
+ $data->response[ $this->product->get_slug() ] = $remote_data;
+
+ }
+
+ return $data;
+ }
+
+ /**
+ * Gets new plugin version details (view version x.x.x details)
+ *
+ * @uses api_request()
+ *
+ * @param object $data
+ * @param string $action
+ * @param object $args (optional)
+ *
+ * @return object $data
+ */
+ public function plugins_api_filter( $data, $action = '', $args = null ) {
+
+ // only do something if we're checking for our plugin
+ if ( $action !== 'plugin_information' || ! isset( $args->slug ) || $args->slug !== $this->product->get_slug() ) {
+ return $data;
+ }
+
+ $api_response = $this->get_remote_data();
+
+ // did we get a response?
+ if ( $api_response === false ) {
+ return $data;
+ }
+
+ // return api response
+ return $api_response;
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+if( ! class_exists( "Yoast_Product", false ) ) {
+
+ /**
+ * Class Yoast_Product
+ *
+ * @todo create a license class and store an object of it in this class
+ */
+ class Yoast_Product {
+
+ /**
+ * @var string The URL of the shop running the EDD API.
+ */
+ protected $api_url;
+
+ /**
+ * @var string The item name in the EDD shop.
+ */
+ protected $item_name;
+
+ /**
+ * @var string The theme slug or plugin file
+ */
+ protected $slug;
+
+ /**
+ * @var string The version number of the item
+ */
+ protected $version;
+
+ /**
+ * @var string The absolute url on which users can purchase a license
+ */
+ protected $item_url;
+
+ /**
+ * @var string Absolute admin URL on which users can enter their license key.
+ */
+ protected $license_page_url;
+
+ /**
+ * @var string The text domain used for translating strings
+ */
+ protected $text_domain;
+
+ /**
+ * @var string The item author
+ */
+ protected $author;
+
+ public function __construct( $api_url, $item_name, $slug, $version, $item_url = '', $license_page_url = '#', $text_domain = 'yoast', $author = 'Yoast' ) {
+ $this->api_url = $api_url;
+ $this->item_name = $item_name;
+ $this->slug = $slug;
+ $this->version = $version;
+ $this->item_url = $item_url;
+ $this->license_page_url = admin_url( $license_page_url );
+ $this->text_domain = $text_domain;
+ $this->author = $author;
+
+ // Fix possible empty item url
+ if ( $this->item_url === '' ) {
+ $this->item_url = $this->api_url;
+ }
+
+ if( is_admin() && is_multisite() ) {
+
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
+ require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
+ }
+
+ if( is_plugin_active_for_network( $slug ) ) {
+ $this->license_page_url = network_admin_url( $license_page_url );
+ }
+ }
+ }
+
+
+ /**
+ * @param string $api_url
+ */
+ public function set_api_url( $api_url ) {
+ $this->api_url = $api_url;
+ }
+
+ /**
+ * @return string
+ */
+ public function get_api_url() {
+ return $this->api_url;
+ }
+
+ /**
+ * @param string $author
+ */
+ public function set_author( $author ) {
+ $this->author = $author;
+ }
+
+ /**
+ * @return string
+ */
+ public function get_author() {
+ return $this->author;
+ }
+
+ /**
+ * @param string $item_name
+ */
+ public function set_item_name( $item_name ) {
+ $this->item_name = $item_name;
+ }
+
+ /**
+ * @return string
+ */
+ public function get_item_name() {
+ return $this->item_name;
+ }
+
+ /**
+ * @param string $item_url
+ */
+ public function set_item_url( $item_url ) {
+ $this->item_url = $item_url;
+ }
+
+ /**
+ * @return string
+ */
+ public function get_item_url() {
+ return $this->item_url;
+ }
+
+ /**
+ * @param string $license_page_url
+ */
+ public function set_license_page_url( $license_page_url ) {
+ $this->license_page_url = admin_page( $license_page_url );
+ }
+
+ /**
+ * @return string
+ */
+ public function get_license_page_url() {
+ return $this->license_page_url;
+ }
+
+ /**
+ * @param string $slug
+ */
+ public function set_slug( $slug ) {
+ $this->slug = $slug;
+ }
+
+ /**
+ * @return string
+ */
+ public function get_slug() {
+ return $this->slug;
+ }
+
+ /**
+ * Returns the dirname of the slug and limits it to 15 chars
+ *
+ * @return string
+ */
+ public function get_transient_prefix() {
+ return substr( dirname( $this->slug ), 0, 15 );
+ }
+
+ /**
+ * @param string $text_domain
+ */
+ public function set_text_domain( $text_domain ) {
+ $this->text_domain = $text_domain;
+ }
+
+ /**
+ * @return string
+ */
+ public function get_text_domain() {
+ return $this->text_domain;
+ }
+
+ /**
+ * @param string $version
+ */
+ public function set_version( $version ) {
+ $this->version = $version;
+ }
+
+ /**
+ * @return string
+ */
+ public function get_version() {
+ return $this->version;
+ }
+
+ /**
+ * Gets a Google Analytics Campaign url for this product
+ *
+ * @param string $link_identifier
+ * @return string The full URL
+ */
+ public function get_tracking_url( $link_identifier = '' ) {
+
+ $tracking_vars = array(
+ 'utm_campaign' => $this->get_item_name() . ' licensing',
+ 'utm_medium' => 'link',
+ 'utm_source' => $this->get_item_name(),
+ 'utm_content' => $link_identifier
+ );
+
+ // url encode tracking vars
+ $tracking_vars = urlencode_deep( $tracking_vars );
+
+ $query_string = build_query( $tracking_vars );
+
+
+ return $this->get_item_url() . '#' . $query_string;
+ }
+
+ }
+
+}
+
--- /dev/null
+<?php
+
+if( class_exists( 'Yoast_License_Manager' ) && ! class_exists( "Yoast_Theme_License_Manager", false ) ) {
+
+ class Yoast_Theme_License_Manager extends Yoast_License_Manager {
+
+ /**
+ * Setup auto updater for themes
+ */
+ public function setup_auto_updater() {
+ if ( $this->license_is_valid() ) {
+ // setup auto updater
+ require_once dirname( __FILE__ ) . '/class-update-manager.php';
+ require_once dirname( __FILE__ ) . '/class-theme-update-manager.php'; // @TODO: Autoload?
+ new Yoast_Theme_Update_Manager( $this->product, $this );
+ }
+ }
+
+ /**
+ * Setup hooks
+ */
+ public function specific_hooks() {
+ // remotely deactivate license upon switching away from this theme
+ add_action( 'switch_theme', array( $this, 'deactivate_license' ) );
+
+ // Add the license menu
+ add_action( 'admin_menu', array( $this, 'add_license_menu' ) );
+ }
+
+ /**
+ * Add license page and add it to Themes menu
+ */
+ public function add_license_menu() {
+ $theme_page = add_theme_page( sprintf( __( '%s License', $this->product->get_text_domain() ), $this->product->get_item_name() ), __( 'Theme License', $this->product->get_text_domain() ), 'manage_options', 'theme-license', array( $this, 'show_license_page' ) );
+ }
+
+ /**
+ * Shows license page
+ */
+ public function show_license_page() {
+ ?>
+ <div class="wrap">
+ <?php settings_errors(); ?>
+
+ <?php $this->show_license_form( false ); ?>
+ </div>
+ <?php
+ }
+
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+if( class_exists( 'Yoast_Update_Manager' ) && ! class_exists( "Yoast_Theme_Update_Manager", false ) ) {
+
+ class Yoast_Theme_Update_Manager extends Yoast_Update_Manager {
+
+ /**
+ * Constructor
+ *
+ * @param string $api_url
+ * @param string $item_name
+ * @param string $license_key
+ * @param string $slug
+ * @param string $theme_version
+ * @param string $author (optional)
+ */
+ public function __construct( Yoast_Product $product, $license_key ) {
+
+ parent::__construct( $product, $license_key );
+
+ // setup hooks
+ $this->setup_hooks();
+ }
+
+ /**
+ * Get the current theme version
+ *
+ * @return string The version number
+ */
+ private function get_theme_version() {
+
+ // if version was not set, get it from the Theme stylesheet
+ if( $this->product->get_version() === '' ) {
+ $theme = wp_get_theme( $this->product->get_slug() );
+ return $theme->get( 'Version' );
+ }
+
+ return $this->product->get_version();
+ }
+
+ /**
+ * Setup hooks
+ */
+ private function setup_hooks() {
+ add_filter( 'site_transient_update_themes', array( $this, 'set_theme_update_transient' ) );
+ add_action( 'load-themes.php', array( $this, 'load_themes_screen' ) );
+ }
+
+ /**
+ * Set "updates available" transient
+ */
+ public function set_theme_update_transient( $value ) {
+
+ $update_data = $this->get_update_data();
+
+ if( $update_data === false ) {
+ return $value;
+ }
+
+ // add update data to "updates available" array. convert object to array.
+ $value->response[ $this->product->get_slug() ] = (array) $update_data;
+
+ return $value;
+ }
+
+ /**
+ * Add hooks and scripts to the Appearance > Themes screen
+ */
+ public function load_themes_screen() {
+
+ $update_data = $this->get_update_data();
+
+ // only do if an update is available
+ if( $update_data === false ) {
+ return;
+ }
+
+ add_thickbox();
+ add_action( 'admin_notices', array( $this, 'show_update_details' ) );
+ }
+
+ /**
+ * Show update link.
+ * Opens Thickbox with Changelog.
+ */
+ public function show_update_details() {
+
+ $update_data = $this->get_update_data();
+
+ // only show if an update is available
+ if( $update_data === false ) {
+ return;
+ }
+
+ $update_url = wp_nonce_url( 'update.php?action=upgrade-theme&theme=' . urlencode( $this->product->get_slug() ), 'upgrade-theme_' . $this->product->get_slug() );
+ $update_onclick = ' onclick="if ( confirm(\'' . esc_js( __( "Updating this theme will lose any customizations you have made. 'Cancel' to stop, 'OK' to update." ) ) . '\') ) {return true;}return false;"';
+ ?>
+ <div id="update-nag">
+ <?php
+ printf(
+ __( '<strong>%s version %s</strong> is available. <a href="%s" class="thickbox" title="%s">Check out what\'s new</a> or <a href="%s" %s>update now</a>.' ),
+ $this->product->get_item_name(),
+ $update_data->new_version,
+ '#TB_inline?width=640&inlineId=' . $this->product->get_slug() . '_changelog',
+ $this->get_item_name(),
+ $update_url,
+ $update_onclick
+ );
+ ?>
+ </div>
+ <div id="<?php echo $this->product->get_slug(); ?>_changelog" style="display: none;">
+ <?php echo wpautop( $update_data->sections['changelog'] ); ?>
+ </div>
+ <?php
+ }
+
+
+ /**
+ * Get update data
+ *
+ * This gets the update data from a transient (12 hours), if set.
+ * If not, it will make a remote request and get the update data.
+ *
+ * @return object $update_data Object containing the update data
+ */
+ public function get_update_data() {
+
+ $api_response = $this->get_remote_data();
+
+ if( false === $api_response ) {
+ return false;
+ }
+
+ $update_data = $api_response;
+
+ // check if a new version is available.
+ if ( version_compare( $this->get_theme_version(), $update_data->new_version, '>=' ) ) {
+ return false;
+ }
+
+
+ // an update is available
+ return $update_data;
+ }
+
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+if( ! class_exists( "Yoast_Update_Manager", false ) ) {
+
+ class Yoast_Update_Manager {
+
+ /**
+ * @var Yoast_Product
+ */
+ protected $product;
+
+ /**
+ * @var Yoast_License_Manager
+ */
+ protected $license_manager;
+
+ /**
+ * @var string
+ */
+ protected $error_message = '';
+
+ /**
+ * @var object
+ */
+ protected $update_response = null;
+
+ /**
+ * @var string The transient name storing the API response
+ */
+ private $response_transient_key = '';
+
+ /**
+ * @var string The transient name that stores failed request tries
+ */
+ private $request_failed_transient_key = '';
+
+ /**
+ * Constructor
+ *
+ * @param string $api_url The url to the EDD shop
+ * @param string $item_name The item name in the EDD shop
+ * @param string $license_key The (valid) license key
+ * @param string $slug The slug. This is either the plugin main file path or the theme slug.
+ * @param string $version The current plugin or theme version
+ * @param string $author (optional) The item author.
+ */
+ public function __construct( Yoast_Product $product, $license_manager ) {
+ $this->product = $product;
+ $this->license_manager = $license_manager;
+
+ // generate transient names
+ $this->response_transient_key = $this->product->get_transient_prefix() . '-update-response';
+ $this->request_failed_transient_key = $this->product->get_transient_prefix() . '-update-request-failed';
+
+ // maybe delete transient
+ $this->maybe_delete_transients();
+ }
+
+ /**
+ * Deletes the various transients
+ * If we're on the update-core.php?force-check=1 page
+ */
+ private function maybe_delete_transients() {
+ global $pagenow;
+
+ if( $pagenow === 'update-core.php' && isset( $_GET['force-check'] ) ) {
+ delete_transient( $this->response_transient_key );
+ delete_transient( $this->request_failed_transient_key );
+ }
+ }
+
+ /**
+ * If the update check returned a WP_Error, show it to the user
+ */
+ public function show_update_error() {
+
+ if ( $this->error_message === '' ) {
+ return;
+ }
+
+ ?>
+ <div class="error">
+ <p><?php printf( __( '%s failed to check for updates because of the following error: <em>%s</em>', $this->product->get_text_domain() ), $this->product->get_item_name(), $this->error_message ); ?></p>
+ </div>
+ <?php
+ }
+
+ /**
+ * Calls the API and, if successfull, returns the object delivered by the API.
+ *
+ * @uses get_bloginfo()
+ * @uses wp_remote_post()
+ * @uses is_wp_error()
+ *
+ * @return false||object
+ */
+ private function call_remote_api() {
+
+ // only check if the failed transient is not set (or if it's expired)
+ if( get_transient( $this->request_failed_transient_key ) !== false ) {
+ return false;
+ }
+
+ // start request process
+ global $wp_version;
+
+ // set a transient to prevent failed update checks on every page load
+ // this transient will be removed if a request succeeds
+ set_transient( $this->request_failed_transient_key, 'failed', 10800 );
+
+ // setup api parameters
+ $api_params = array(
+ 'edd_action' => 'get_version',
+ 'license' => $this->license_manager->get_license_key(),
+ 'item_name' => $this->product->get_item_name(),
+ 'wp_version' => $wp_version,
+ 'item_version' => $this->product->get_version(),
+ 'url' => home_url(),
+ 'slug' => $this->product->get_slug()
+ );
+
+ // setup request parameters
+ $request_params = array(
+ 'method' => 'POST',
+ 'body' => $api_params
+ );
+
+ require_once dirname( __FILE__ ) . '/class-api-request.php';
+ $request = new Yoast_API_Request( $this->product->get_api_url(), $request_params );
+
+ if( $request->is_valid() !== true ) {
+
+ // show error message
+ $this->error_message = $request->get_error_message();
+ add_action( 'admin_notices', array( $this, 'show_update_error' ) );
+
+ return false;
+ }
+
+ // request succeeded, delete transient indicating a request failed
+ delete_transient( $this->request_failed_transient_key );
+
+ // decode response
+ $response = $request->get_response();
+
+ // check if response returned that a given site was inactive
+ if( isset( $response->license_check ) && ! empty( $response->license_check ) && $response->license_check != 'valid' ) {
+
+ // deactivate local license
+ $this->license_manager->set_license_status( 'invalid' );
+
+ // show notice to let the user know we deactivated his/her license
+ $this->error_message = __( "This site has not been activated properly on yoast.com and thus cannot check for future updates. Please activate your site with a valid license key.", $this->product->get_text_domain() );
+ add_action( 'admin_notices', array( $this, 'show_update_error' ) );
+ }
+
+ $response->sections = maybe_unserialize( $response->sections );
+
+ // store response
+ set_transient( $this->response_transient_key, $response, 10800 );
+
+ return $response;
+ }
+
+ /**
+ * Gets the remote product data (from the EDD API)
+ *
+ * - If it was previously fetched in the current requests, this gets it from the instance property
+ * - Next, it tries the 3-hour transient
+ * - Next, it calls the remote API and stores the result
+ *
+ * @return object
+ */
+ protected function get_remote_data() {
+
+ // always use property if it's set
+ if( null !== $this->update_response ) {
+ return $this->update_response;
+ }
+
+ // get cached remote data
+ $data = $this->get_cached_remote_data();
+
+ // if cache is empty or expired, call remote api
+ if( $data === false ) {
+ $data = $this->call_remote_api();
+ }
+
+ $this->update_response = $data;
+ return $data;
+ }
+
+ /**
+ * Gets the remote product data from a 3-hour transient
+ *
+ * @return bool|mixed
+ */
+ private function get_cached_remote_data() {
+
+ $data = get_transient( $this->response_transient_key );
+
+ if( $data ) {
+ return $data;
+ }
+
+ return false;
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+<?php
+
+/*
+Plugin Name: Sample Plugin
+Version: 1.0
+Plugin URI: https://yoast.com/
+Description: A sample plugin to test the License Manager
+Author: Yoast, DvanKooten
+Author URI: http://yoast.com/
+Text Domain: sample-plugin
+*/
+
+/**
+ * Class Sample_Plugin
+ *
+ */
+class Sample_Plugin {
+
+ public function __construct() {
+
+ // we only need license stuff inside the admin area
+ if ( is_admin() ) {
+
+ // add menu item
+ add_action( 'admin_menu', array( $this, 'add_license_menu' ) );
+
+ // load license class
+ $this->load_license_manager();
+ }
+
+
+ }
+
+ /**
+ * Loads the License_Plugin_Manager class
+ *
+ * The class will take care of the rest: notices, license (de)activations, updates, etc..
+ */
+ public function load_license_manager() {
+
+ // Instantiate license class
+ $license_manager = new Yoast_Plugin_License_Manager( new Sample_Product() );
+
+ // Setup the required hooks
+ $license_manager->setup_hooks();
+
+ }
+
+ /**
+ * Add license page and add it to Themes menu
+ */
+ public function add_license_menu() {
+ $theme_page = add_options_page( sprintf( __( '%s License', $this->text_domain ), $this->item_name ), sprintf( __( '%s License', $this->text_domain ), $this->item_name ), 'manage_options', $this->text_domain . '-license', array( $this, 'show_license_page' ) );
+ }
+
+ /**
+ * Shows license page
+ */
+ public function show_license_page() {
+
+ // Instantiate license class
+ $license_manager = new Yoast_Plugin_License_Manager( new Sample_Product() );
+
+ ?>
+ <div class="wrap">
+ <?php //settings_errors(); ?>
+
+ <?php $license_manager->show_license_form( false ); ?>
+ </div>
+ <?php
+ }
+}
+
+new Sample_Plugin();
--- /dev/null
+<?php
+
+/**
+ * Class Sample_Product
+ *
+ * Our sample product class
+ */
+class Sample_Product extends Yoast_Product {
+
+ public function __construct() {
+ parent::__construct(
+ 'https://yoast.com',
+ 'Sample Product',
+ 'sample-product',
+ '1.0',
+ 'https://yoast.com/wordpress/plugins/sample-product/',
+ 'admin.php?page=sample-product',
+ 'sample-product',
+ 'Yoast'
+ );
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+
+if( is_admin() ) {
+
+ // Instantiate license class
+ $license_manager = new Yoast_Theme_License_Manager( new Sample_Product() );
+
+ // Setup the required hooks
+ $license_manager->setup_hooks();
+
+}
\ No newline at end of file
--- /dev/null
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+ header( 'HTTP/1.0 403 Forbidden' );
+ die;
+}
+
+/**
+ * @var Yoast_Product $product
+ */
+$product = $this->product;
+
+$this->show_license_form_heading();
+
+// Output form tags if we're not embedded in another form
+if( ! $embedded ) {
+ echo '<form method="post" action="">';
+}
+
+wp_nonce_field( $nonce_name, $nonce_name ); ?>
+<table class="form-table yoast-license-form">
+ <tbody>
+ <tr valign="top">
+ <th scope="row" valign="top"><?php _e( 'License status', $product->get_text_domain() ); ?></th>
+ <td>
+ <?php if( $this->license_is_valid() ) { ?>
+ <span style="color: white; background: limegreen; padding:3px 6px;">ACTIVE</span> - you are receiving updates.
+ <?php } else { ?>
+ <span style="color:white; background: red; padding: 3px 6px;">INACTIVE</span> - you are <strong>not</strong> receiving updates.
+ <?php } ?>
+ </td>
+ </tr>
+ <tr valign="top">
+ <th scope="row" valign="top"><?php _e('Toggle license status', $product->get_text_domain() ); ?></th>
+ <td class="yoast-license-toggler">
+
+ <?php if( $this->license_is_valid() ) { ?>
+ <button name="<?php echo esc_attr( $action_name ); ?>" type="submit" class="button-secondary yoast-license-deactivate" value="deactivate"><?php echo esc_html_e( 'Deactivate License', $product->get_text_domain() ); ?></button>
+ <small><?php _e( '(deactivate your license so you can activate it on another WordPress site)', $product->get_text_domain() ); ?></small>
+ <?php } else {
+
+ if( $this->get_license_key() !== '') { ?>
+ <button name="<?php echo esc_attr( $action_name ); ?>" type="submit" class="button-secondary yoast-license-activate" value="activate" /><?php echo esc_html_e('Activate License', $product->get_text_domain() ); ?></button>
+ <?php } else {
+ _e( 'Please enter a license key in the field below first.', $product->get_text_domain() );
+ }
+
+ } ?>
+
+ </td>
+ </tr>
+ <tr valign="top">
+ <th scope="row" valign="top"><?php _e( 'License Key', $product->get_text_domain() ); ?></th>
+ <td>
+ <input name="<?php echo esc_attr( $key_name ); ?>" type="text" class="regular-text yoast-license-key-field <?php if( $obfuscate ) { ?>yoast-license-obfuscate<?php } ?>" value="<?php echo esc_attr( $visible_license_key ); ?>" placeholder="<?php echo esc_attr( sprintf( __( 'Paste your %s license key here..', $product->get_text_domain() ), $product->get_item_name() ) ); ?>" <?php if( $readonly ) { echo 'readonly="readonly"'; } ?> />
+ <?php if( $this->license_constant_is_defined ) { ?>
+ <p class="help"><?php printf( __( "You defined your license key using the %s PHP constant.", $product->get_text_domain() ), '<code>' . $this->license_constant_name . '</code>' ); ?></p>
+ <?php } ?>
+ </td>
+ </tr>
+
+ </tbody>
+</table>
+
+<?php
+
+if( $this->license_is_valid() ) {
+
+ $expiry_date = strtotime( $this->get_license_expiry_date() );
+
+ if( $expiry_date !== false ) {
+ echo '<p>';
+
+ printf( __( 'Your %s license will expire on %s.', $product->get_text_domain() ), $product->get_item_name(), date('F jS Y', $expiry_date ) );
+
+ if( strtotime( '+3 months' ) > $expiry_date ) {
+ printf( ' ' . __('%sRenew your license now%s.', $product->get_text_domain() ), '<a href="'. $this->product->get_tracking_url( 'renewal' ) .'">', '</a>' );
+ }
+
+ echo '</p>';
+ }
+}
+
+// Only show a "Save Changes" button and end form if we're not embedded in another form.
+if( ! $embedded ) {
+
+ // only show "Save Changes" button if license is not activated and not defined with a constant
+ if( $readonly === false ) {
+ submit_button();
+ }
+
+ echo '</form>';
+}
+
+$product = null;
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+<?php
+if ( ! defined( 'ABSPATH' ) ) {
+ header( 'HTTP/1.0 403 Forbidden' );
+ die;
+}
+?><script type="text/javascript">
+(function($) {
+
+ if( typeof YoastLicenseManager !== "undefined" ) {
+ return;
+ }
+
+ window.YoastLicenseManager = (function () {
+
+ function init() {
+ var $keyInputs = $(".yoast-license-key-field.yoast-license-obfuscate");
+ var $actionButtons = $('.yoast-license-toggler button');
+ var $submitButtons = $('input[type="submit"], button[type="submit"]');
+
+ $submitButtons.click( addDisableEvent );
+ $actionButtons.click( actOnLicense );
+ $keyInputs.click( setEmptyValue );
+ }
+
+ function setEmptyValue() {
+ if( ! $(this).is('[readonly]') ) {
+ $(this).val('');
+ }
+ }
+
+ function actOnLicense() {
+ var $formScope = $(this).closest('form');
+ var $actionButton = $formScope.find('.yoast-license-toggler button');
+
+ // fake input field with exact same name => value
+ $("<input />")
+ .attr('type', 'hidden')
+ .attr( 'name', $(this).attr('name') )
+ .val( $(this).val() )
+ .appendTo( $formScope );
+
+ // change button text to show we're working..
+ var text = ( $actionButton.hasClass('yoast-license-activate') ) ? "Activating..." : "Deactivating...";
+ $actionButton.text( text );
+ }
+
+ function addDisableEvent() {
+ var $formScope = $(this).closest('form');
+ $formScope.submit(disableButtons);
+ }
+
+ function disableButtons() {
+ var $formScope = $(this).closest('form');
+ var $submitButton = $formScope.find('input[type="submit"], button[type="submit"]');
+ $submitButton.prop( 'disabled', true );
+ }
+
+ return {
+ init: init
+ }
+
+ })();
+
+ YoastLicenseManager.init();
+
+})(jQuery);
+</script>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$options = get_option( 'wpseo' );
+
+$wpseo_bulk_titles_table = new WPSEO_Bulk_Title_Editor_List_Table();
+$wpseo_bulk_description_table = new WPSEO_Bulk_Description_List_Table();
+
+// If type is empty, fill it with value of first tab (title)
+$_GET['type'] = ( ! empty( $_GET['type'] ) ) ? $_GET['type'] : 'title';
+
+if ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
+ wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), stripslashes( $_SERVER['REQUEST_URI'] ) ) );
+ exit;
+}
+?>
+<script>
+ var wpseo_bulk_editor_nonce = '<?php echo wp_create_nonce( 'wpseo-bulk-editor' ); ?>';
+</script>
+
+<div class="wrap wpseo_table_page">
+
+ <h2 id="wpseo-title"><?php echo esc_html( get_admin_page_title() ); ?></h2>
+
+ <h2 class="nav-tab-wrapper" id="wpseo-tabs">
+ <a class="nav-tab" id="title-tab" href="#top#title"><?php _e( 'Title', 'wordpress-seo' ); ?></a>
+ <a class="nav-tab" id="description-tab" href="#top#description"><?php _e( 'Description', 'wordpress-seo' ); ?></a>
+ </h2>
+
+ <div class="tabwrapper">
+ <div id="title" class="wpseotab">
+ <?php
+ $wpseo_bulk_titles_table->prepare_page_navigation();
+ $wpseo_bulk_titles_table->prepare_items();
+ ?>
+
+ <?php $wpseo_bulk_titles_table->views(); ?>
+ <?php $wpseo_bulk_titles_table->display(); ?>
+
+ </div>
+ <div id="description" class="wpseotab">
+ <?php
+ $wpseo_bulk_description_table->prepare_page_navigation();
+ $wpseo_bulk_description_table->prepare_items();
+ ?>
+
+ <?php $wpseo_bulk_description_table->views(); ?>
+ <?php $wpseo_bulk_description_table->display(); ?>
+ </div>
+
+ </div>
+</div>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ *
+ *
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$options = get_option( 'wpseo' );
+
+if ( isset( $_GET['allow_tracking'] ) && check_admin_referer( 'wpseo_activate_tracking', 'nonce' ) ) {
+ $options['tracking_popup_done'] = true;
+ if ( $_GET['allow_tracking'] == 'yes' ) {
+ $options['yoast_tracking'] = true;
+ }
+ else {
+ $options['yoast_tracking'] = false;
+ }
+ update_option( 'wpseo', $options );
+
+ if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
+ wp_safe_redirect( $_SERVER['HTTP_REFERER'], 307 );
+ exit;
+ }
+}
+
+
+// Fix metadescription if so requested
+if ( isset( $_GET['fixmetadesc'] ) && check_admin_referer( 'wpseo-fix-metadesc', 'nonce' ) && $options['theme_description_found'] !== '' ) {
+ $path = false;
+ if ( file_exists( get_stylesheet_directory() . '/header.php' ) ) {
+ // theme or child theme
+ $path = get_stylesheet_directory();
+ }
+ elseif ( file_exists( get_template_directory() . '/header.php' ) ) {
+ // parent theme in case of a child theme
+ $path = get_template_directory();
+ }
+
+ if ( is_string( $path ) && $path !== '' ) {
+ $fcontent = file_get_contents( $path . '/header.php' );
+ $msg = '';
+ $backup_file = date( 'Ymd-H.i.s-' ) . 'header.php.wpseobak';
+ if ( ! file_exists( $path . '/' . $backup_file ) ) {
+ $backupfile = fopen( $path . '/' . $backup_file, 'w+' );
+ if ( $backupfile ) {
+ fwrite( $backupfile, $fcontent );
+ fclose( $backupfile );
+ $msg = __( 'Backed up the original file header.php to <strong><em>' . esc_html( $backup_file ) . '</em></strong>, ', 'wordpress-seo' );
+
+ $count = 0;
+ $fcontent = str_replace( $options['theme_description_found'], '', $fcontent, $count );
+ if ( $count > 0 ) {
+ $header_file = fopen( $path . '/header.php', 'w+' );
+ if ( $header_file ) {
+ if ( fwrite( $header_file, $fcontent ) !== false ) {
+ $msg .= __( 'Removed hardcoded meta description.', 'wordpress-seo' );
+ $options['theme_has_description'] = false;
+ $options['theme_description_found'] = '';
+ update_option( 'wpseo', $options );
+ }
+ else {
+ $msg .= '<span class="error">' . __( 'Failed to remove hardcoded meta description.', 'wordpress-seo' ) . '</span>';
+ }
+ fclose( $header_file );
+ }
+ }
+ else {
+ wpseo_description_test();
+ $msg .= '<span class="warning">' . __( 'Earlier found meta description was not found in file. Renewed the description test data.', 'wordpress-seo' ) . '</span>';
+ }
+ add_settings_error( 'yoast_wpseo_dashboard_options', 'error', $msg, 'updated' );
+ }
+ }
+ }
+
+ // Clean up the referrer url for later use
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'nonce', 'fixmetadesc' ), $_SERVER['REQUEST_URI'] );
+ }
+}
+
+if ( ( ! isset( $options['theme_has_description'] ) || ( ( isset( $options['theme_has_description'] ) && $options['theme_has_description'] === true ) || $options['theme_description_found'] !== '' ) ) || ( isset( $_GET['checkmetadesc'] ) && check_admin_referer( 'wpseo-check-metadesc', 'nonce' ) ) ) {
+ wpseo_description_test();
+ // Renew the options after the test
+ $options = get_option( 'wpseo' );
+}
+if ( isset( $_GET['checkmetadesc'] ) ) {
+ // Clean up the referrer url for later use
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'nonce', 'checkmetadesc' ), $_SERVER['REQUEST_URI'] );
+ }
+}
+
+
+
+$wpseo_admin_pages->admin_header( true, WPSEO_Options::get_group_name( 'wpseo' ), 'wpseo' );
+
+do_action( 'wpseo_all_admin_notices' );
+
+if ( is_array( $options['blocking_files'] ) && count( $options['blocking_files'] ) > 0 ) {
+ echo '<p id="blocking_files" class="wrong">'
+ . '<a href="javascript:wpseo_killBlockingFiles(\'' . esc_js( wp_create_nonce( 'wpseo-blocking-files' ) ) . '\')" class="button fixit">' . __( 'Fix it.', 'wordpress-seo' ) . '</a>'
+ . __( 'The following file(s) is/are blocking your XML sitemaps from working properly:', 'wordpress-seo' ) . '<br />';
+ foreach ( $options['blocking_files'] as $file ) {
+ echo esc_html( $file ) . '<br/>';
+ }
+ echo __( 'Either delete them (this can be done with the "Fix it" button) or disable WP SEO XML sitemaps.', 'wordpress-seo' );
+ echo '</p>';
+}
+
+
+if ( $options['theme_description_found'] !== '' ) {
+ echo '<p id="metadesc_found notice" class="wrong settings_error">'
+ . '<a href="' . esc_url( add_query_arg( array( 'nonce' => wp_create_nonce( 'wpseo-fix-metadesc' ) ), admin_url( 'admin.php?page=wpseo_dashboard&fixmetadesc' ) ) ) . '" class="button fixit">' . __( 'Fix it.', 'wordpress-seo' ) . '</a>'
+ . ' <a href="' . esc_url( add_query_arg( array( 'nonce' => wp_create_nonce( 'wpseo-check-metadesc' ) ), admin_url( 'admin.php?page=wpseo_dashboard&checkmetadesc' ) ) ) . '" class="button checkit">' . __( 'Re-check theme.', 'wordpress-seo' ) . '</a>'
+ . __( 'Your theme contains a meta description, which blocks WordPress SEO from working properly, please delete the following line, or press fix it:', 'wordpress-seo' ) . '<br />';
+ echo '<code>' . esc_html( $options['theme_description_found'] ) . '</code>';
+ echo '</p>';
+}
+
+
+if ( strpos( get_option( 'permalink_structure' ), '%postname%' ) === false && $options['ignore_permalink'] === false ) {
+ echo '<p id="wrong_permalink" class="wrong">'
+ . '<a href="' . esc_url( admin_url( 'options-permalink.php' ) ) . '" class="button fixit">' . __( 'Fix it.', 'wordpress-seo' ) . '</a>'
+ . '<a href="javascript:wpseo_setIgnore(\'permalink\',\'wrong_permalink\',\'' . esc_js( wp_create_nonce( 'wpseo-ignore' ) ) . '\');" class="button fixit">' . __( 'Ignore.', 'wordpress-seo' ) . '</a>'
+ . __( 'You do not have your postname in the URL of your posts and pages, it is highly recommended that you do. Consider setting your permalink structure to <strong>/%postname%/</strong>.', 'wordpress-seo' ) . '</p>';
+}
+
+if ( get_option( 'page_comments' ) && $options['ignore_page_comments'] === false ) {
+ echo '<p id="wrong_page_comments" class="wrong">'
+ . '<a href="javascript:setWPOption(\'page_comments\',\'0\',\'wrong_page_comments\',\'' . esc_js( wp_create_nonce( 'wpseo-setoption' ) ) . '\');" class="button fixit">' . __( 'Fix it.', 'wordpress-seo' ) . '</a>'
+ . '<a href="javascript:wpseo_setIgnore(\'page_comments\',\'wrong_page_comments\',\'' . esc_js( wp_create_nonce( 'wpseo-ignore' ) ) . '\');" class="button fixit">' . __( 'Ignore.', 'wordpress-seo' ) . '</a>'
+ . __( 'Paging comments is enabled, this is not needed in 999 out of 1000 cases, so the suggestion is to disable it, to do that, simply uncheck the box before "Break comments into pages..."', 'wordpress-seo' ) . '</p>';
+}
+
+echo '<h2>' . __( 'General', 'wordpress-seo' ) . '</h2>';
+
+if ( $options['ignore_tour'] === true ) {
+ echo '<label class="select">' . __( 'Introduction Tour:', 'wordpress-seo' ) . '</label><a class="button-secondary" href="' . esc_url( admin_url( 'admin.php?page=wpseo_dashboard&wpseo_restart_tour' ) ) . '">' . __( 'Start Tour', 'wordpress-seo' ) . '</a>';
+ echo '<p class="desc label">' . __( 'Take this tour to quickly learn about the use of this plugin.', 'wordpress-seo' ) . '</p>';
+}
+
+echo '<label class="select">' . __( 'Default Settings:', 'wordpress-seo' ) . '</label><a onclick="if( !confirm(\'' . __( 'Are you sure you want to reset your SEO settings?', 'wordpress-seo' ) . '\') ) return false;" class="button-secondary" href="' . esc_url( add_query_arg( array( 'nonce' => wp_create_nonce( 'wpseo_reset_defaults' ) ), admin_url( 'admin.php?page=wpseo_dashboard&wpseo_reset_defaults' ) ) ) . '">' . __( 'Reset Default Settings', 'wordpress-seo' ) . '</a>';
+echo '<p class="desc label">' . __( 'If you want to restore a site to the default WordPress SEO settings, press this button.', 'wordpress-seo' ) . '</p>';
+
+echo '<h2>' . __( 'Tracking', 'wordpress-seo' ) . '</h2>';
+echo $wpseo_admin_pages->checkbox( 'yoast_tracking', __( 'Allow tracking of this WordPress install\'s anonymous data.', 'wordpress-seo' ) );
+echo '<p class="desc">' . __( "To maintain a plugin as big as WordPress SEO, we need to know what we're dealing with: what kinds of other plugins our users are using, what themes, etc. Please allow us to track that data from your install. It will not track <em>any</em> user details, so your security and privacy are safe with us.", 'wordpress-seo' ) . '</p>';
+
+echo '<h2>' . __( 'Security', 'wordpress-seo' ) . '</h2>';
+echo $wpseo_admin_pages->checkbox( 'disableadvanced_meta', __( 'Disable the Advanced part of the WordPress SEO meta box', 'wordpress-seo' ) );
+echo '<p class="desc">' . __( 'Unchecking this box allows authors and editors to redirect posts, noindex them and do other things you might not want if you don\'t trust your authors.', 'wordpress-seo' ) . '</p>';
+
+echo '<h2>' . __( 'Webmaster Tools', 'wordpress-seo' ) . '</h2>';
+echo '<p>' . __( 'You can use the boxes below to verify with the different Webmaster Tools, if your site is already verified, you can just forget about these. Enter the verify meta values for:', 'wordpress-seo' ) . '</p>';
+echo $wpseo_admin_pages->textinput( 'alexaverify', '<a target="_blank" href="http://www.alexa.com/siteowners/claim">' . __( 'Alexa Verification ID', 'wordpress-seo' ) . '</a>' );
+echo $wpseo_admin_pages->textinput( 'msverify', '<a target="_blank" href="' . esc_url( 'http://www.bing.com/webmaster/?rfp=1#/Dashboard/?url=' . urlencode( str_replace( 'http://', '', get_bloginfo( 'url' ) ) ) ) . '">' . __( 'Bing Webmaster Tools', 'wordpress-seo' ) . '</a>' );
+echo $wpseo_admin_pages->textinput( 'googleverify', '<a target="_blank" href="' . esc_url( 'https://www.google.com/webmasters/verification/verification?hl=en&siteUrl=' . urlencode( get_bloginfo( 'url' ) ) . '/' ) . '">' . __( 'Google Webmaster Tools', 'wordpress-seo' ) . '</a>' );
+echo $wpseo_admin_pages->textinput( 'pinterestverify', '<a target="_blank" href="https://help.pinterest.com/entries/22488487-Verify-with-HTML-meta-tags">' . __( 'Pinterest', 'wordpress-seo' ) . '</a>' );
+echo $wpseo_admin_pages->textinput( 'yandexverify', '<a target="_blank" href="http://help.yandex.com/webmaster/service/rights.xml#how-to">' . __( 'Yandex Webmaster Tools', 'wordpress-seo' ) . '</a>' );
+
+do_action( 'wpseo_dashboard' );
+
+$wpseo_admin_pages->admin_footer();
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$robots_file = get_home_path() . 'robots.txt';
+$ht_access_file = get_home_path() . '.htaccess';
+
+if ( isset( $_POST['create_robots'] ) ) {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ die( __( 'You cannot create a robots.txt file.', 'wordpress-seo' ) );
+ }
+
+ check_admin_referer( 'wpseo_create_robots' );
+
+ ob_start();
+ error_reporting( 0 );
+ do_robots();
+ $robots_content = ob_get_clean();
+
+ $f = fopen( $robots_file, 'x' );
+ fwrite( $f, $robots_content );
+}
+
+if ( isset( $_POST['submitrobots'] ) ) {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ die( __( 'You cannot edit the robots.txt file.', 'wordpress-seo' ) );
+ }
+
+ check_admin_referer( 'wpseo-robotstxt' );
+
+ if ( file_exists( $robots_file ) ) {
+ $robotsnew = stripslashes( $_POST['robotsnew'] );
+ if ( is_writable( $robots_file ) ) {
+ $f = fopen( $robots_file, 'w+' );
+ fwrite( $f, $robotsnew );
+ fclose( $f );
+ $msg = __( 'Updated Robots.txt', 'wordpress-seo' );
+ }
+ }
+}
+
+if ( isset( $_POST['submithtaccess'] ) ) {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ die( __( 'You cannot edit the .htaccess file.', 'wordpress-seo' ) );
+ }
+
+ check_admin_referer( 'wpseo-htaccess' );
+
+ if ( file_exists( $ht_access_file ) ) {
+ $ht_access_new = stripslashes( $_POST['htaccessnew'] );
+ if ( is_writeable( $ht_access_file ) ) {
+ $f = fopen( $ht_access_file, 'w+' );
+ fwrite( $f, $ht_access_new );
+ fclose( $f );
+ }
+ }
+}
+
+$wpseo_admin_pages->admin_header( false );
+if ( isset( $msg ) && ! empty( $msg ) ) {
+ echo '<div id="message" style="width:94%;" class="updated fade"><p>' . esc_html( $msg ) . '</p></div>';
+}
+
+$action_url = network_admin_url( 'admin.php?page=wpseo_files' ); // auto-falls back on admin_url for non-multisite
+
+if ( ! file_exists( $robots_file ) ) {
+ if ( is_writable( get_home_path() ) ) {
+ $content = '<form action="' . esc_url( $action_url ) . '" method="post" id="robotstxtcreateform">';
+ $content .= wp_nonce_field( 'wpseo_create_robots', '_wpnonce', true, false );
+ $content .= '<p>' . __( 'You don\'t have a robots.txt file, create one here:', 'wordpress-seo' ) . '</p>';
+ $content .= '<input type="submit" class="button" name="create_robots" value="' . __( 'Create robots.txt file', 'wordpress-seo' ) . '">';
+ $content .= '</form>';
+ }
+ else {
+ $content = '<p>' . __( 'If you had a robots.txt file and it was editable, you could edit it from here.', 'wordpress-seo' );
+ }
+}
+else {
+ $f = fopen( $robots_file, 'r' );
+
+ $content = '';
+ if ( filesize( $robots_file ) > 0 ) {
+ $content = fread( $f, filesize( $robots_file ) );
+ }
+ $robots_txt_content = esc_textarea( $content );
+
+ if ( ! is_writable( $robots_file ) ) {
+ $content = '<p><em>' . __( 'If your robots.txt were writable, you could edit it from here.', 'wordpress-seo' ) . '</em></p>';
+ $content .= '<textarea class="large-text code" disabled="disabled" rows="15" name="robotsnew">' . $robots_txt_content . '</textarea><br/>';
+ } else {
+ $content = '<form action="' . esc_url( $action_url ) . '" method="post" id="robotstxtform">';
+ $content .= wp_nonce_field( 'wpseo-robotstxt', '_wpnonce', true, false );
+ $content .= '<p>' . __( 'Edit the content of your robots.txt:', 'wordpress-seo' ) . '</p>';
+ $content .= '<textarea class="large-text code" rows="15" name="robotsnew">' . $robots_txt_content . '</textarea><br/>';
+ $content .= '<div class="submit"><input class="button" type="submit" name="submitrobots" value="' . __( 'Save changes to Robots.txt', 'wordpress-seo' ) . '" /></div>';
+ $content .= '</form>';
+ }
+}
+
+$wpseo_admin_pages->postbox( 'robotstxt', __( 'Robots.txt', 'wordpress-seo' ), $content );
+
+if ( ( isset( $_SERVER['SERVER_SOFTWARE'] ) && stristr( $_SERVER['SERVER_SOFTWARE'], 'nginx' ) === false ) && file_exists( $ht_access_file ) ) {
+ $f = fopen( $ht_access_file, 'r' );
+
+ $contentht = '';
+ if ( filesize( $ht_access_file ) > 0 ) {
+ $contentht = fread( $f, filesize( $ht_access_file ) );
+ }
+ $contentht = esc_textarea( $contentht );
+
+ if ( ! is_writable( $ht_access_file ) ) {
+ $content = '<p><em>' . __( 'If your .htaccess were writable, you could edit it from here.', 'wordpress-seo' ) . '</em></p>';
+ $content .= '<textarea class="large-text code" disabled="disabled" rows="15" name="robotsnew">' . $contentht . '</textarea><br/>';
+ } else {
+ $content = '<form action="' . esc_url( $action_url ) . '" method="post" id="htaccessform">';
+ $content .= wp_nonce_field( 'wpseo-htaccess', '_wpnonce', true, false );
+ $content .= '<p>' . __( 'Edit the content of your .htaccess:', 'wordpress-seo' ) . '</p>';
+ $content .= '<textarea class="large-text code" rows="15" name="htaccessnew">' . $contentht . '</textarea><br/>';
+ $content .= '<div class="submit"><input class="button" type="submit" name="submithtaccess" value="' . __( 'Save changes to .htaccess', 'wordpress-seo' ) . '" /></div>';
+ $content .= '</form>';
+ }
+ $wpseo_admin_pages->postbox( 'htaccess', __( '.htaccess file', 'wordpress-seo' ), $content );
+} elseif ( ( isset( $_SERVER['SERVER_SOFTWARE'] ) && stristr( $_SERVER['SERVER_SOFTWARE'], 'nginx' ) === false ) && ! file_exists( $ht_access_file ) ) {
+ $content = '<p>' . __( 'If you had a .htaccess file and it was editable, you could edit it from here.', 'wordpress-seo' );
+ $wpseo_admin_pages->postbox( 'htaccess', __( '.htaccess file', 'wordpress-seo' ), $content );
+}
+
+$wpseo_admin_pages->admin_footer( false );
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+/**
+ * @todo [JRF => testers] Extensively test the export & import of the (new) settings!
+ * If that all works fine, getting testers to export before and after upgrade will make testing easier.
+ *
+ * @todo [Yoast] The import for the RSS Footer plugin checks for data already entered via WP SEO,
+ * the other import routines should do that too.
+*/
+
+global $wpseo_admin_pages;
+
+$msg = '';
+if ( isset( $_POST['import'] ) || isset( $_GET['import'] ) ) {
+
+ check_admin_referer( 'wpseo-import' );
+
+ global $wpdb;
+ $replace = false;
+ $deletekw = false;
+
+ if ( isset( $_POST['wpseo']['deleteolddata'] ) && $_POST['wpseo']['deleteolddata'] == 'on' ) {
+ $replace = true;
+ }
+
+ if ( isset( $_POST['wpseo']['importwoo'] ) ) {
+ WPSEO_Options::initialize();
+
+ $sep = get_option( 'seo_woo_seperator' );
+ $options = get_option( 'wpseo_titles' );
+
+ switch ( get_option( 'seo_woo_home_layout' ) ) {
+ case 'a':
+ $options['title-home-wpseo'] = '%%sitename%% ' . $sep . ' %%sitedesc%%';
+ break;
+ case 'b':
+ $options['title-home-wpseo'] = '%%sitename%% ' . get_option( 'seo_woo_paged_var' ) . ' %%pagenum%%';
+ break;
+ case 'c':
+ $options['title-home-wpseo'] = '%%sitedesc%%';
+ break;
+ }
+ if ( $replace ) {
+ delete_option( 'seo_woo_home_layout' );
+ }
+
+ switch ( get_option( 'seo_woo_single_layout' ) ) {
+ case 'a':
+ $options['title-post'] = '%%title%% ' . $sep . ' %%sitename%%';
+ break;
+ case 'b':
+ $options['title-post'] = '%%title%%';
+ break;
+ case 'c':
+ $options['title-post'] = '%%sitename%% ' . $sep . ' %%title%%';
+ break;
+ case 'd':
+ $options['title-post'] = '%%title%% ' . $sep . ' %%sitedesc%%';
+ break;
+ case 'e':
+ $options['title-post'] = '%%sitename%% ' . $sep . ' %%title%% ' . $sep . ' %%sitedesc%%';
+ break;
+ }
+ if ( $replace ) {
+ delete_option( 'seo_woo_single_layout' );
+ }
+
+ switch ( get_option( 'seo_woo_page_layout' ) ) {
+ case 'a':
+ $options['title-page'] = '%%title%% ' . $sep . ' %%sitename%%';
+ break;
+ case 'b':
+ $options['title-page'] = '%%title%%';
+ break;
+ case 'c':
+ $options['title-page'] = '%%sitename%% ' . $sep . ' %%title%%';
+ break;
+ case 'd':
+ $options['title-page'] = '%%title%% ' . $sep . ' %%sitedesc%%';
+ break;
+ case 'e':
+ $options['title-page'] = '%%sitename%% ' . $sep . ' %%title%% ' . $sep . ' %%sitedesc%%';
+ break;
+ }
+ if ( $replace ) {
+ delete_option( 'seo_woo_page_layout' );
+ }
+
+ $template = WPSEO_Options::get_default( 'wpseo_titles', 'title-tax-post' ); // the default is the same for all taxonomies, so post will do
+ switch ( get_option( 'seo_woo_archive_layout' ) ) {
+ case 'a':
+ $template = '%%term_title%% ' . $sep . ' %%page%% ' . $sep . ' %%sitename%%';
+ break;
+ case 'b':
+ $template = '%%term_title%%';
+ break;
+ case 'c':
+ $template = '%%sitename%% ' . $sep . ' %%term_title%% ' . $sep . ' %%page%%';
+ break;
+ case 'd':
+ $template = '%%term_title%% ' . $sep . ' %%page%%' . $sep . ' %%sitedesc%%';
+ break;
+ case 'e':
+ $template = '%%sitename%% ' . $sep . ' %%term_title%% ' . $sep . ' %%page%% ' . $sep . ' %%sitedesc%%';
+ break;
+ }
+ $taxonomies = get_taxonomies( array( 'public' => true ), 'names' );
+ if ( is_array( $taxonomies ) && $taxonomies !== array() ) {
+ foreach ( $taxonomies as $tax ) {
+ $options[ 'title-tax-'.$tax ] = $template;
+ }
+ }
+ unset( $taxonomies, $tax, $template );
+ if ( $replace ) {
+ delete_option( 'seo_woo_archive_layout' );
+ }
+
+ // Import the custom homepage description
+ if ( 'c' == get_option( 'seo_woo_meta_home_desc' ) ) {
+ $options['metadesc-home-wpseo'] = get_option( 'seo_woo_meta_home_desc_custom' );
+ }
+ if ( $replace ) {
+ delete_option( 'seo_woo_meta_home_desc' );
+ }
+
+ // Import the custom homepage keywords
+ if ( 'c' == get_option( 'seo_woo_meta_home_key' ) ) {
+ $options['metakey-home-wpseo'] = get_option( 'seo_woo_meta_home_key_custom' );
+ }
+ if ( $replace ) {
+ delete_option( 'seo_woo_meta_home_key' );
+ }
+
+ // If WooSEO is set to use the Woo titles, import those
+ if ( 'true' == get_option( 'seo_woo_wp_title' ) ) {
+ WPSEO_Meta::replace_meta( 'seo_title', WPSEO_Meta::$meta_prefix . 'title', $replace );
+ }
+
+ // If WooSEO is set to use the Woo meta descriptions, import those
+ if ( 'b' == get_option( 'seo_woo_meta_single_desc' ) ) {
+ WPSEO_Meta::replace_meta( 'seo_description', WPSEO_Meta::$meta_prefix . 'metadesc', $replace );
+ }
+
+ // If WooSEO is set to use the Woo meta keywords, import those
+ if ( 'b' == get_option( 'seo_woo_meta_single_key' ) ) {
+ WPSEO_Meta::replace_meta( 'seo_keywords', WPSEO_Meta::$meta_prefix . 'metakeywords', $replace );
+ }
+
+ /* @todo [JRF => whomever] verify how WooSEO sets these metas ( 'noindex', 'follow' )
+ and if the values saved are concurrent with the ones we use (i.e. 0/1/2) */
+ WPSEO_Meta::replace_meta( 'seo_follow', WPSEO_Meta::$meta_prefix . 'meta-robots-nofollow', $replace );
+ WPSEO_Meta::replace_meta( 'seo_noindex', WPSEO_Meta::$meta_prefix . 'meta-robots-noindex', $replace );
+
+ update_option( 'wpseo_titles', $options );
+ $msg .= __( 'WooThemes SEO framework settings & data successfully imported.', 'wordpress-seo' );
+ unset( $options, $sep );
+ }
+
+ if ( isset( $_POST['wpseo']['importheadspace'] ) ) {
+ WPSEO_Meta::replace_meta( '_headspace_description', WPSEO_Meta::$meta_prefix . 'metadesc', $replace );
+ WPSEO_Meta::replace_meta( '_headspace_keywords', WPSEO_Meta::$meta_prefix . 'metakeywords', $replace );
+ WPSEO_Meta::replace_meta( '_headspace_page_title', WPSEO_Meta::$meta_prefix . 'title', $replace );
+ /* @todo [JRF => whomever] verify how headspace sets these metas ( 'noindex', 'nofollow', 'noarchive', 'noodp', 'noydir' )
+ and if the values saved are concurrent with the ones we use (i.e. 0/1/2) */
+ WPSEO_Meta::replace_meta( '_headspace_noindex', WPSEO_Meta::$meta_prefix . 'meta-robots-noindex', $replace );
+ WPSEO_Meta::replace_meta( '_headspace_nofollow', WPSEO_Meta::$meta_prefix . 'meta-robots-nofollow', $replace );
+
+ /* @todo - [JRF => whomever] check if this can be done more efficiently by querying only the meta table
+ possibly directly changing it using concat on the existing values
+ */
+ $posts = $wpdb->get_results( "SELECT ID FROM $wpdb->posts" );
+ if ( is_array( $posts ) && $posts !== array() ) {
+ foreach ( $posts as $post ) {
+ $custom = get_post_custom( $post->ID );
+ $robotsmeta_adv = '';
+ if ( isset( $custom['_headspace_noarchive'] ) ) {
+ $robotsmeta_adv .= 'noarchive,';
+ }
+ if ( isset( $custom['_headspace_noodp'] ) ) {
+ $robotsmeta_adv .= 'noodp,';
+ }
+ if ( isset( $custom['_headspace_noydir'] ) ) {
+ $robotsmeta_adv .= 'noydir';
+ }
+ $robotsmeta_adv = preg_replace( '`,$`', '', $robotsmeta_adv );
+ WPSEO_Meta::set_value( 'meta-robots-adv', $robotsmeta_adv, $post->ID );
+ }
+ }
+ unset( $posts, $post, $custom, $robotsmeta_adv );
+
+ if ( $replace ) {
+ foreach ( array( 'noarchive', 'noodp', 'noydir' ) as $meta ) {
+ delete_post_meta_by_key( '_headspace_' . $meta );
+ }
+ unset( $meta );
+ }
+ $msg .= __( 'HeadSpace2 data successfully imported', 'wordpress-seo' );
+ }
+
+ // @todo [JRF => whomever] how does this correlate with the routine on the dashboard page ? isn't one superfluous ?
+ if ( isset( $_POST['wpseo']['importaioseo'] ) || isset( $_GET['importaioseo'] ) ) {
+ WPSEO_Meta::replace_meta( '_aioseop_description', WPSEO_Meta::$meta_prefix . 'metadesc', $replace );
+ WPSEO_Meta::replace_meta( '_aioseop_keywords', WPSEO_Meta::$meta_prefix . 'metakeywords', $replace );
+ WPSEO_Meta::replace_meta( '_aioseop_title', WPSEO_Meta::$meta_prefix . 'title', $replace );
+ $msg .= __( sprintf( 'All in One SEO data successfully imported. Would you like to %sdisable the All in One SEO plugin%s.', '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_import&deactivate_aioseo=1' ) ) . '">', '</a>' ), 'wordpress-seo' );
+ }
+
+ if ( isset( $_POST['wpseo']['importaioseoold'] ) ) {
+ WPSEO_Meta::replace_meta( 'description', WPSEO_Meta::$meta_prefix . 'metadesc', $replace );
+ WPSEO_Meta::replace_meta( 'keywords', WPSEO_Meta::$meta_prefix . 'metakeywords', $replace );
+ WPSEO_Meta::replace_meta( 'title', WPSEO_Meta::$meta_prefix . 'title', $replace );
+ $msg .= __( 'All in One SEO (Old version) data successfully imported.', 'wordpress-seo' );
+ }
+
+ if ( isset( $_POST['wpseo']['importrobotsmeta'] ) || isset( $_GET['importrobotsmeta'] ) ) {
+ $posts = $wpdb->get_results( "SELECT ID, robotsmeta FROM $wpdb->posts" );
+ if ( is_array( $posts ) && $posts !== array() ) {
+ foreach ( $posts as $post ) {
+ // sync all possible settings
+ if ( $post->robotsmeta ) {
+ $pieces = explode( ',', $post->robotsmeta );
+ foreach ( $pieces as $meta ) {
+ switch ( $meta ) {
+ case 'noindex':
+ WPSEO_Meta::set_value( 'meta-robots-noindex', '1', $post->ID );
+ break;
+
+ case 'index':
+ WPSEO_Meta::set_value( 'meta-robots-noindex', '2', $post->ID );
+ break;
+
+ case 'nofollow':
+ WPSEO_Meta::set_value( 'meta-robots-nofollow', '1', $post->ID );
+ break;
+ }
+ }
+ }
+ }
+ }
+ unset( $posts, $post, $pieces, $meta );
+ $msg .= __( sprintf( 'Robots Meta values imported. We recommend %sdisabling the Robots-Meta plugin%s to avoid any conflicts.', '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_import&deactivate_robots_meta=1' ) ) . '">', '</a>' ), 'wordpress-seo' );
+ }
+
+ if ( isset( $_POST['wpseo']['importrssfooter'] ) ) {
+ $optold = get_option( 'RSSFooterOptions' );
+ $optnew = get_option( 'wpseo_rss' );
+ if ( $optold['position'] == 'after' ) {
+ if ( $optnew['rssafter'] === '' || $optnew['rssafter'] === WPSEO_Options::get_default( 'wpseo_rss', 'rssafter' ) ) {
+ $optnew['rssafter'] = $optold['footerstring'];
+ }
+ }
+ else {
+ /* @internal Uncomment the second part if a default would be given to the rssbefore value */
+ if ( $optnew['rssbefore'] === '' /*|| $optnew['rssbefore'] === WPSEO_Options::get_default( 'wpseo_rss', 'rssbefore' )*/ ) {
+ $optnew['rssbefore'] = $optold['footerstring'];
+ }
+ }
+ update_option( 'wpseo_rss', $optnew );
+ unset( $optold, $optnew );
+ $msg .= __( 'RSS Footer options imported successfully.', 'wordpress-seo' );
+ }
+
+ if ( isset( $_POST['wpseo']['importbreadcrumbs'] ) ) {
+ $optold = get_option( 'yoast_breadcrumbs' );
+ $optnew = get_option( 'wpseo_internallinks' );
+
+ if ( is_array( $optold ) && $optold !== array() ) {
+ foreach ( $optold as $opt => $val ) {
+ if ( is_bool( $val ) && $val == true ) {
+ $optnew[ 'breadcrumbs-' . $opt ] = true;
+ }
+ else {
+ $optnew[ 'breadcrumbs-' . $opt ] = $val;
+ }
+ }
+ unset( $opt, $val );
+ update_option( 'wpseo_internallinks', $optnew );
+ $msg .= __( 'Yoast Breadcrumbs options imported successfully.', 'wordpress-seo' );
+ }
+ else {
+ $msg .= __( 'Yoast Breadcrumbs options could not be found', 'wordpress-seo' );
+ }
+ unset( $optold, $optnew );
+ }
+
+ // Allow custom import actions
+ do_action( 'wpseo_handle_import' );
+
+ /**
+ * Allow customization of import&export message
+ * @api string $msg The message.
+ */
+ $msg = apply_filters( 'wpseo_import_message', $msg );
+
+ // Check if we've deleted old data and adjust message to match it
+ if ( $replace ) {
+ $msg .= __( ', and old data deleted.', 'wordpress-seo' );
+ }
+ if ( $deletekw ) {
+ $msg .= __( ', and meta keywords data deleted.', 'wordpress-seo' );
+ }
+}
+
+
+$wpseo_admin_pages->admin_header( false );
+if ( $msg != '' ) {
+ echo '<div id="message" class="message updated" style="width:94%;"><p>' . $msg . '</p></div>';
+}
+
+$content = '<p>' . __( 'No doubt you\'ve used an SEO plugin before if this site isn\'t new. Let\'s make it easy on you, you can import the data below. If you want, you can import first, check if it was imported correctly, and then import & delete. No duplicate data will be imported.', 'wordpress-seo' ) . '</p>';
+$content .= '<p>' . sprintf( __( 'If you\'ve used another SEO plugin, try the %sSEO Data Transporter%s plugin to move your data into this plugin, it rocks!', 'wordpress-seo' ), '<a href="https://wordpress.org/plugins/seo-data-transporter/">', '</a>' ) . '</p>';
+// @todo [JRF => whomever] add action for form tag
+$content .= '<form action="" method="post" accept-charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '">';
+$content .= wp_nonce_field( 'wpseo-import', '_wpnonce', true, false );
+$content .= $wpseo_admin_pages->checkbox( 'importheadspace', __( 'Import from HeadSpace2?', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->checkbox( 'importaioseo', __( 'Import from All-in-One SEO?', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->checkbox( 'importaioseoold', __( 'Import from OLD All-in-One SEO?', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->checkbox( 'importwoo', __( 'Import from WooThemes SEO framework?', 'wordpress-seo' ) );
+$content .= '<br/>';
+$content .= $wpseo_admin_pages->checkbox( 'deleteolddata', __( 'Delete the old data after import? (recommended)', 'wordpress-seo' ) );
+$content .= '<br/>';
+$content .= '<input type="submit" class="button-primary" name="import" value="' . __( 'Import', 'wordpress-seo' ) . '" />';
+$content .= '<br/><br/>';
+
+$content .= '<h2>' . __( 'Import settings from other plugins', 'wordpress-seo' ) . '</h2>';
+$content .= $wpseo_admin_pages->checkbox( 'importrobotsmeta', __( 'Import from Robots Meta (by Yoast)?', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->checkbox( 'importrssfooter', __( 'Import from RSS Footer (by Yoast)?', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->checkbox( 'importbreadcrumbs', __( 'Import from Yoast Breadcrumbs?', 'wordpress-seo' ) );
+
+/**
+ * Allow option of importing from other 'other' plugins
+ * @api string $content The content containing all import and export methods
+ */
+$content = apply_filters( 'wpseo_import_other_plugins', $content );
+
+$content .= '<br/>';
+$content .= '<input type="submit" class="button-primary" name="import" value="' . __( 'Import', 'wordpress-seo' ) . '" />';
+$content .= '</form><br/>';
+
+$wpseo_admin_pages->postbox( 'import', __( 'Import', 'wordpress-seo' ), $content );
+
+/**
+ * Allow adding a custom import block
+ * @api WPSEO_Admin $this The WPSEO_Admin object
+ */
+do_action( 'wpseo_import', $this );
+
+// @todo [JRF => whomever] add action for form tag
+$content = '<h4>' . __( 'Export', 'wordpress-seo' ) . '</h4>';
+$content .= '<form action="" method="post" accept-charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '">';
+$content .= wp_nonce_field( 'wpseo-export', '_wpnonce', true, false );
+$content .= '<p>' . __( 'Export your WordPress SEO settings here, to import them again later or to import them on another site.', 'wordpress-seo' ) . '</p>';
+$content .= $wpseo_admin_pages->checkbox( 'include_taxonomy_meta', __( 'Include Taxonomy Metadata', 'wordpress-seo' ) );
+$content .= '<br/><input type="submit" class="button" name="wpseo_export" value="' . __( 'Export settings', 'wordpress-seo' ) . '"/>';
+$content .= '</form>';
+if ( isset( $_POST['wpseo_export'] ) ) {
+ check_admin_referer( 'wpseo-export' );
+ $include_taxonomy = false;
+ if ( isset( $_POST['wpseo']['include_taxonomy_meta'] ) ) {
+ $include_taxonomy = true;
+ }
+ $url = $wpseo_admin_pages->export_settings( $include_taxonomy );
+ if ( $url ) {
+ $GLOBALS['export_js'] = '
+ <script type="text/javascript">
+ document.location = \'' . $url . '\';
+ </script>';
+ add_action( 'admin_footer-' . $GLOBALS['hook_suffix'], 'wpseo_deliver_export_zip' );
+ }
+ else {
+ $content .= 'Error: ' . $url;
+ }
+}
+
+$content .= '<h4>' . __( 'Import', 'wordpress-seo' ) . '</h4>';
+if ( ! isset( $_FILES['settings_import_file'] ) || empty( $_FILES['settings_import_file'] ) ) {
+ $content .= '<p>' . __( 'Import settings by locating <em>settings.zip</em> and clicking', 'wordpress-seo' ) . ' "' . __( 'Import settings', 'wordpress-seo' ) . '":</p>';
+ // @todo [JRF => whomever] add action for form tag
+ $content .= '<form action="" method="post" enctype="multipart/form-data" accept-charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '">';
+ $content .= wp_nonce_field( 'wpseo-import-file', '_wpnonce', true, false );
+ $content .= '<input type="file" name="settings_import_file"/>';
+ $content .= '<input type="hidden" name="action" value="wp_handle_upload"/>';
+ $content .= '<input type="submit" class="button" value="' . __( 'Import settings', 'wordpress-seo' ) . '"/>';
+ $content .= '</form><br/>';
+}
+elseif ( isset( $_FILES['settings_import_file'] ) ) {
+ check_admin_referer( 'wpseo-import-file' );
+ $file = wp_handle_upload( $_FILES['settings_import_file'] );
+
+ if ( isset( $file['file'] ) && ! is_wp_error( $file ) ) {
+ $upload_dir = wp_upload_dir();
+
+ if ( ! defined( 'DIRECTORY_SEPARATOR' ) ) {
+ define( 'DIRECTORY_SEPARATOR', '/' );
+ }
+ $p_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'wpseo-import' . DIRECTORY_SEPARATOR;
+
+ if ( ! isset( $GLOBALS['wp_filesystem'] ) || ! is_object( $GLOBALS['wp_filesystem'] ) ) {
+ WP_Filesystem();
+ }
+
+ $unzipped = unzip_file( $file['file'], $p_path );
+ if ( ! is_wp_error( $unzipped ) ) {
+ $filename = $p_path . 'settings.ini';
+ if ( @is_file( $filename ) && is_readable( $filename ) ) {
+ $options = parse_ini_file( $filename, true );
+
+ if ( is_array( $options ) && $options !== array() ) {
+ $old_wpseo_version = null;
+ if ( isset( $options['wpseo']['version'] ) && $options['wpseo']['version'] !== '' ) {
+ $old_wpseo_version = $options['wpseo']['version'];
+ }
+ foreach ( $options as $name => $optgroup ) {
+ if ( $name === 'wpseo_taxonomy_meta' ) {
+ $optgroup = json_decode( urldecode( $optgroup['wpseo_taxonomy_meta'] ), true );
+ }
+
+ // Make sure that the imported options are cleaned/converted on import
+ $option_instance = WPSEO_Options::get_option_instance( $name );
+ if ( is_object( $option_instance ) && method_exists( $option_instance, 'import' ) ) {
+ $optgroup = $option_instance->import( $optgroup, $old_wpseo_version, $options );
+ }
+ elseif ( WP_DEBUG === true || ( defined( 'WPSEO_DEBUG' ) && WPSEO_DEBUG === true ) ) {
+ $content .= '<p><strong>' . sprintf( __( 'Setting "%s" is no longer used and has been discarded.', 'wordpress-seo' ), $name ) . '</strong></p>';
+
+ }
+ }
+ $content .= '<p><strong>' . __( 'Settings successfully imported.', 'wordpress-seo' ) . '</strong></p>';
+ }
+ else {
+ $content .= '<p><strong>' . __( 'Settings could not be imported:', 'wordpress-seo' ) . ' ' . __( 'No settings found in file.', 'wordpress-seo' ) . '</strong></p>';
+ }
+ unset( $options, $name, $optgroup );
+ }
+ else {
+ $content .= '<p><strong>' . __( 'Settings could not be imported:', 'wordpress-seo' ) . ' ' . __( 'Unzipping failed - file settings.ini not found.', 'wordpress-seo' ) . '</strong></p>';
+ }
+ @unlink( $filename );
+ @unlink( $p_path );
+ }
+ else {
+ $content .= '<p><strong>' . __( 'Settings could not be imported:', 'wordpress-seo' ) . ' ' . sprintf( __( 'Unzipping failed with error "%s".', 'wordpress-seo' ), $unzipped->get_error_message() ) . '</strong></p>';
+ }
+ unset( $zip, $unzipped );
+ @unlink( $file['file'] );
+ }
+ else {
+ if ( is_wp_error( $file ) ) {
+ $content .= '<p><strong>' . __( 'Settings could not be imported:', 'wordpress-seo' ) . ' ' . $file->get_error_message() . '</strong></p>';
+ }
+ else {
+ $content .= '<p><strong>' . __( 'Settings could not be imported:', 'wordpress-seo' ) . ' ' . __( 'Upload failed.', 'wordpress-seo' ) . '</strong></p>';
+ }
+ }
+}
+$wpseo_admin_pages->postbox( 'wpseo_export', __( 'Export & Import SEO Settings', 'wordpress-seo' ), $content );
+
+$wpseo_admin_pages->admin_footer( false );
+
+
+function wpseo_deliver_export_zip() {
+ if ( isset( $GLOBALS['export_js'] ) && $GLOBALS['export_js'] !== '' ) {
+ echo $GLOBALS['export_js'];
+ }
+}
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$wpseo_admin_pages->admin_header( true, WPSEO_Options::get_group_name( 'wpseo_internallinks' ), 'wpseo_internallinks' );
+
+$content = $wpseo_admin_pages->checkbox( 'breadcrumbs-enable', __( 'Enable Breadcrumbs', 'wordpress-seo' ) );
+$content .= '<br/>';
+$content .= $wpseo_admin_pages->textinput( 'breadcrumbs-sep', __( 'Separator between breadcrumbs', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->textinput( 'breadcrumbs-home', __( 'Anchor text for the Homepage', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->textinput( 'breadcrumbs-prefix', __( 'Prefix for the breadcrumb path', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->textinput( 'breadcrumbs-archiveprefix', __( 'Prefix for Archive breadcrumbs', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->textinput( 'breadcrumbs-searchprefix', __( 'Prefix for Search Page breadcrumbs', 'wordpress-seo' ) );
+$content .= $wpseo_admin_pages->textinput( 'breadcrumbs-404crumb', __( 'Breadcrumb for 404 Page', 'wordpress-seo' ) );
+if ( get_option( 'show_on_front' ) == 'page' && get_option( 'page_for_posts' ) > 0 ) {
+ $content .= $wpseo_admin_pages->checkbox( 'breadcrumbs-blog-remove', __( 'Remove Blog page from Breadcrumbs', 'wordpress-seo' ) );
+}
+$content .= $wpseo_admin_pages->checkbox( 'breadcrumbs-boldlast', __( 'Bold the last page in the breadcrumb', 'wordpress-seo' ) );
+$content .= '<br/><br/>';
+
+
+$post_types = get_post_types( array( 'public' => true ), 'objects' );
+if ( is_array( $post_types ) && $post_types !== array() ) {
+ $content .= '<strong>' . __( 'Taxonomy to show in breadcrumbs for:', 'wordpress-seo' ) . '</strong><br/>';
+ foreach ( $post_types as $pt ) {
+ $taxonomies = get_object_taxonomies( $pt->name, 'objects' );
+ if ( is_array( $taxonomies ) && $taxonomies !== array() ) {
+ $values = array( 0 => __( 'None', 'wordpress-seo' ) );
+ foreach ( $taxonomies as $tax ) {
+ $values[ $tax->name ] = $tax->labels->singular_name;
+ }
+ $content .= $wpseo_admin_pages->select( 'post_types-' . $pt->name . '-maintax', $pt->labels->name, $values );
+ unset( $values, $tax );
+ }
+ unset( $taxonomies );
+ }
+ unset( $pt );
+}
+$content .= '<br/>';
+
+
+$taxonomies = get_taxonomies( array( 'public' => true, '_builtin' => false ), 'objects' );
+if ( is_array( $taxonomies ) && $taxonomies !== array() ) {
+ $content .= '<strong>' . __( 'Post type archive to show in breadcrumbs for:', 'wordpress-seo' ) . '</strong><br/>';
+ foreach ( $taxonomies as $tax ) {
+ $values = array( 0 => __( 'None', 'wordpress-seo' ) );
+ if ( get_option( 'show_on_front' ) == 'page' && get_option( 'page_for_posts' ) > 0 ) {
+ $values['post'] = __( 'Blog', 'wordpress-seo' );
+ }
+
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ foreach ( $post_types as $pt ) {
+ if ( $pt->has_archive ) {
+ $values[ $pt->name ] = $pt->labels->name;
+ }
+ }
+ unset( $pt );
+ }
+ $content .= $wpseo_admin_pages->select( 'taxonomy-' . $tax->name . '-ptparent', $tax->labels->singular_name, $values );
+ unset( $values, $tax );
+ }
+}
+unset( $taxonomies, $post_types );
+
+
+$content .= '<br class="clear"/>';
+$content .= '<h4>' . __( 'How to insert breadcrumbs in your theme', 'wordpress-seo' ) . '</h4>';
+$content .= '<p>' . __( 'Usage of this breadcrumbs feature is explained <a href="https://yoast.com/wordpress/plugins/breadcrumbs/">here</a>. For the more code savvy, insert this in your theme:', 'wordpress-seo' ) . '</p>';
+$content .= '<pre><?php if ( function_exists('yoast_breadcrumb') ) {
+yoast_breadcrumb('<p id="breadcrumbs">','</p>');
+} ?></pre>';
+$wpseo_admin_pages->postbox( 'internallinks', __( 'Breadcrumbs Settings', 'wordpress-seo' ), $content );
+
+$wpseo_admin_pages->admin_footer();
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ * @todo Add default content (when no premium plugins are activated)
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+?>
+
+<div class="wrap wpseo_table_page">
+
+ <h2 id="wpseo-title"><?php echo esc_html( get_admin_page_title() ); ?></h2>
+
+ <h2 class="nav-tab-wrapper" id="wpseo-tabs">
+ <a class="nav-tab" id="extensions-tab" href="#top#extensions"><?php _e( 'Extensions', 'wordpress-seo' );?></a>
+ <a class="nav-tab" id="licenses-tab" href="#top#licenses"><?php _e( 'Licenses', 'wordpress-seo' );?></a>
+ </h2>
+
+ <div class="tabwrapper">
+ <div id="extensions" class="wpseotab">
+ <?php
+ $extensions = array();
+
+ if ( ! class_exists( 'WPSEO_Premium' ) ) {
+ $extensions['seo-premium'] = (object) array(
+ 'url' => 'https://yoast.com/wordpress/plugins/seo-premium/',
+ 'title' => __( 'WordPress SEO Premium', 'wordpress-seo' ),
+ 'desc' => __( 'The premium version of WordPress SEO with more features & support.', 'wordpress-seo' ),
+ );
+ }
+ if ( ! class_exists( 'wpseo_Video_Sitemap' ) ) {
+ $extensions['video-seo'] = (object) array(
+ 'url' => 'https://yoast.com/wordpress/plugins/video-seo/',
+ 'title' => __( 'Video SEO', 'wordpress-seo' ),
+ 'desc' => __( 'Optimize your videos to show them off in search results and get more clicks!', 'wordpress-seo' ),
+ );
+ }
+ if ( ! class_exists( 'WPSEO_News' ) ) {
+ $extensions['news-seo'] = (object) array(
+ 'url' => 'https://yoast.com/wordpress/plugins/news-seo/',
+ 'title' => __( 'News SEO', 'wordpress-seo' ),
+ 'desc' => __( 'Are you in Google News? Increase your traffic from Google News by optimizing for it!', 'wordpress-seo' ),
+ );
+ }
+ if ( ! defined( 'WPSEO_LOCAL_VERSION' ) ) {
+ $extensions['local-seo'] = (object) array(
+ 'url' => 'https://yoast.com/wordpress/plugins/local-seo/',
+ 'title' => __( 'Local SEO', 'wordpress-seo' ),
+ 'desc' => __( 'Rank better locally and in Google Maps, without breaking a sweat!', 'wordpress-seo' ),
+ );
+ }
+ if ( class_exists( 'Woocommerce' ) && ! class_exists( 'Yoast_WooCommerce_SEO' ) ) {
+ $extensions['woocommerce-seo'] = (object) array(
+ 'url' => 'https://yoast.com/wordpress/plugins/yoast-woocommerce-seo/',
+ 'title' => __( 'Yoast WooCommerce SEO', 'wordpress-seo' ),
+ 'desc' => __( 'Seamlessly integrate WooCommerce with WordPress SEO and get extra features!', 'wordpress-seo' )
+ );
+ }
+
+ foreach ( $extensions as $id => $extension ) {
+ $utm = '#utm_source=wordpress-seo-config&utm_medium=banner&utm_campaign=extension-page-banners';
+ ?>
+ <div class="extension <?php echo esc_attr( $id ); ?>">
+ <a target="_blank" href="<?php echo esc_url( $extension->url . $utm ); ?>">
+ <h3><?php echo esc_html( $extension->title ); ?></h3>
+ </a>
+ <p><?php echo esc_html( $extension->desc ); ?></p>
+ <p><a target="_blank" href="<?php echo esc_url( $extension->url . $utm ); ?>" class="button-primary">
+ <?php esc_html_e( 'Get this extension', 'wordpress-seo' ); ?>
+ </a></p>
+ </div>
+ <?php
+ }
+ ?>
+ </div>
+ <div id="licenses" class="wpseotab">
+ <?php
+
+ /**
+ * Display license page
+ */
+ settings_errors();
+ do_action( 'wpseo_licenses_forms' );
+ ?>
+ </div>
+ </div>
+
+</div>
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$options = WPSEO_Options::get_all();
+
+$wpseo_admin_pages->admin_header( true, WPSEO_Options::get_group_name( 'wpseo_titles' ), 'wpseo_titles' );
+?>
+
+<h2 class="nav-tab-wrapper" id="wpseo-tabs">
+ <a class="nav-tab" id="general-tab" href="#top#general"><?php _e( 'General', 'wordpress-seo' );?></a>
+ <a class="nav-tab" id="home-tab" href="#top#home"><?php _e( 'Home', 'wordpress-seo' );?></a>
+ <a class="nav-tab" id="post_types-tab" href="#top#post_types"><?php _e( 'Post Types', 'wordpress-seo' );?></a>
+ <a class="nav-tab" id="taxonomies-tab" href="#top#taxonomies"><?php _e( 'Taxonomies', 'wordpress-seo' );?></a>
+ <a class="nav-tab" id="archives-tab" href="#top#archives"><?php _e( 'Other', 'wordpress-seo' );?></a>
+</h2>
+
+<div class="tabwrapper">
+<div id="general" class="wpseotab">
+ <?php
+ echo '<h2>' . __( 'Title settings', 'wordpress-seo' ) . '</h2>';
+ echo $wpseo_admin_pages->checkbox( 'forcerewritetitle', __( 'Force rewrite titles', 'wordpress-seo' ) );
+ echo '<p class="desc">' . __( 'WordPress SEO has auto-detected whether it needs to force rewrite the titles for your pages, if you think it\'s wrong and you know what you\'re doing, you can change the setting here.', 'wordpress-seo' ) . '</p>';
+
+ echo '<h2>' . __( 'Title Separator', 'wordpress-seo' ) . '</h2>';
+ echo $wpseo_admin_pages->radio( 'separator', WPSEO_Option_Titles::get_instance()->get_separator_options(), '' );
+ echo '<p class="desc">' . __( 'Choose the symbol to use as your title separator. This will display, for instance, between your post title and site name.', 'wordpress-seo' ) . ' ' . __( 'Symbols are shown in the size they\'ll appear in in search results.', 'wordpress-seo' ) . '</p>';
+
+ echo '<h2>' . __( 'Sitewide <code>meta</code> settings', 'wordpress-seo' ) . '</h2>';
+ echo $wpseo_admin_pages->checkbox( 'noindex-subpages-wpseo', __( 'Noindex subpages of archives', 'wordpress-seo' ) );
+ echo '<p class="desc">' . __( 'If you want to prevent /page/2/ and further of any archive to show up in the search results, enable this.', 'wordpress-seo' ) . '</p>';
+
+ echo $wpseo_admin_pages->checkbox( 'usemetakeywords', __( 'Use <code>meta</code> keywords tag?', 'wordpress-seo' ) );
+ echo '<p class="desc">' . __( 'I don\'t know why you\'d want to use meta keywords, but if you want to, check this box.', 'wordpress-seo' ) . '</p>';
+
+ echo $wpseo_admin_pages->checkbox( 'noodp', __( 'Add <code>noodp</code> meta robots tag sitewide', 'wordpress-seo' ) );
+ echo '<p class="desc">' . __( 'Prevents search engines from using the DMOZ description for pages from this site in the search results.', 'wordpress-seo' ) . '</p>';
+
+ echo $wpseo_admin_pages->checkbox( 'noydir', __( 'Add <code>noydir</code> meta robots tag sitewide', 'wordpress-seo' ) );
+ echo '<p class="desc">' . __( 'Prevents search engines from using the Yahoo! directory description for pages from this site in the search results.', 'wordpress-seo' ) . '</p>';
+
+ echo '<h2>' . __( 'Clean up the <code><head></code>', 'wordpress-seo' ) . '</h2>';
+ echo $wpseo_admin_pages->checkbox( 'hide-rsdlink', __( 'Hide RSD Links', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->checkbox( 'hide-wlwmanifest', __( 'Hide WLW Manifest Links', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->checkbox( 'hide-shortlink', __( 'Hide Shortlink for posts', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->checkbox( 'hide-feedlinks', __( 'Hide RSS Links', 'wordpress-seo' ) );
+ ?>
+</div>
+<div id="home" class="wpseotab">
+ <?php
+ if ( 'posts' == get_option( 'show_on_front' ) ) {
+ echo '<h2>' . __( 'Homepage', 'wordpress-seo' ) . '</h2>';
+ echo $wpseo_admin_pages->textinput( 'title-home-wpseo', __( 'Title template', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->textarea( 'metadesc-home-wpseo', __( 'Meta description template', 'wordpress-seo' ), '', 'metadesc' );
+ if ( $options['usemetakeywords'] === true ) {
+ echo $wpseo_admin_pages->textinput( 'metakey-home-wpseo', __( 'Meta keywords template', 'wordpress-seo' ) );
+ }
+ }
+ else {
+ echo '<h2>' . __( 'Homepage & Front page', 'wordpress-seo' ) . '</h2>';
+ echo '<p>' . sprintf( __( 'You can determine the title and description for the front page by %sediting the front page itself »%s', 'wordpress-seo' ), '<a href="' . esc_url( get_edit_post_link( get_option( 'page_on_front' ) ) ) . '">', '</a>' ) . '</p>';
+ if ( get_option( 'page_for_posts' ) > 0 ) {
+ echo '<p>' . sprintf( __( 'You can determine the title and description for the blog page by %sediting the blog page itself »%s', 'wordpress-seo' ), '<a href="' . esc_url( get_edit_post_link( get_option( 'page_for_posts' ) ) ) . '">', '</a>' ) . '</p>';
+ }
+ }
+ ?>
+</div>
+<div id="post_types" class="wpseotab">
+ <?php
+ $post_types = get_post_types( array( 'public' => true ), 'objects' );
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ foreach ( $post_types as $pt ) {
+ $warn = false;
+ if ( $options['redirectattachment'] === true && $pt->name == 'attachment' ) {
+ echo '<div class="wpseo-warning">';
+ $warn = true;
+ }
+
+ $name = $pt->name;
+ echo '<h4 id="' . esc_attr( $name ) . '">' . esc_html( ucfirst( $pt->labels->name ) ) . '</h4>';
+ if ( $warn === true ) {
+ echo '<h4 class="error-message">' . __( 'Take note:', 'wordpress-seo' ) . '</h4>';
+
+ echo '<p class="error-message">' . __( 'As you are redirecting attachment URLs to parent post URLs, these settings will currently only have an effect on <strong>unattached</strong> media items!', 'wordpress-seo' ) . '</p>';
+ echo '<p class="error-message">' . sprintf( __( 'So remember: If you change the %sattachment redirection setting%s in the future, the below settings will take effect for *all* media items.', 'wordpress-seo' ), '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_permalinks' ) ) . '">', '</a>' ) . '</p>';
+ }
+
+ echo $wpseo_admin_pages->textinput( 'title-' . $name, __( 'Title template', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->textarea( 'metadesc-' . $name, __( 'Meta description template', 'wordpress-seo' ), '', 'metadesc' );
+ if ( $options['usemetakeywords'] === true ) {
+ echo $wpseo_admin_pages->textinput( 'metakey-' . $name, __( 'Meta keywords template', 'wordpress-seo' ) );
+ }
+ echo $wpseo_admin_pages->checkbox( 'noindex-' . $name, '<code>noindex, follow</code>', __( 'Meta Robots', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->checkbox( 'showdate-' . $name, __( 'Show date in snippet preview?', 'wordpress-seo' ), __( 'Date in Snippet Preview', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->checkbox( 'hideeditbox-' . $name, __( 'Hide', 'wordpress-seo' ), __( 'WordPress SEO Meta Box', 'wordpress-seo' ) );
+
+ /**
+ * Allow adding a custom checkboxes to the admin meta page - Post Types tab
+ * @api WPSEO_Admin_Pages $wpseo_admin_pages The WPSEO_Admin_Pages object
+ * @api String $name The post type name
+ */
+ do_action( 'wpseo_admin_page_meta_post_types', $wpseo_admin_pages, $name );
+
+ echo '<br/>';
+ if ( $warn === true ) {
+ echo '</div>';
+ }
+ unset( $warn );
+ }
+ unset( $pt );
+ }
+ unset( $post_types );
+
+
+ $post_types = get_post_types( array( 'public' => true, '_builtin' => false ), 'objects' );
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ echo '<h2>' . __( 'Custom Post Type Archives', 'wordpress-seo' ) . '</h2>';
+ echo '<p>' . __( 'Note: instead of templates these are the actual titles and meta descriptions for these custom post type archive pages.', 'wordpress-seo' ) . '</p>';
+
+ foreach ( $post_types as $pt ) {
+ if ( ! $pt->has_archive ) {
+ continue;
+ }
+
+ $name = $pt->name;
+
+ echo '<h4>' . esc_html( ucfirst( $pt->labels->name ) ) . '</h4>';
+ echo $wpseo_admin_pages->textinput( 'title-ptarchive-' . $name, __( 'Title', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->textarea( 'metadesc-ptarchive-' . $name, __( 'Meta description', 'wordpress-seo' ), '', 'metadesc' );
+ if ( $options['usemetakeywords'] === true ) {
+ echo $wpseo_admin_pages->textinput( 'metakey-ptarchive-' . $name, __( 'Meta keywords', 'wordpress-seo' ) );
+ }
+ if ( $options['breadcrumbs-enable'] === true ) {
+ echo $wpseo_admin_pages->textinput( 'bctitle-ptarchive-' . $name, __( 'Breadcrumbs Title', 'wordpress-seo' ) );
+ }
+ echo $wpseo_admin_pages->checkbox( 'noindex-ptarchive-' . $name, '<code>noindex, follow</code>', __( 'Meta Robots', 'wordpress-seo' ) );
+ }
+ unset( $pt );
+ }
+ unset( $post_types );
+
+ ?>
+</div>
+<div id="taxonomies" class="wpseotab">
+ <?php
+ $taxonomies = get_taxonomies( array( 'public' => true ), 'objects' );
+ if ( is_array( $taxonomies ) && $taxonomies !== array() ) {
+ foreach ( $taxonomies as $tax ) {
+ echo '<h4>' . esc_html( ucfirst( $tax->labels->name ) ). '</h4>';
+ echo $wpseo_admin_pages->textinput( 'title-tax-' . $tax->name, __( 'Title template', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->textarea( 'metadesc-tax-' . $tax->name, __( 'Meta description template', 'wordpress-seo' ), '', 'metadesc' );
+ if ( $options['usemetakeywords'] === true ) {
+ echo $wpseo_admin_pages->textinput( 'metakey-tax-' . $tax->name, __( 'Meta keywords template', 'wordpress-seo' ) );
+ }
+ echo $wpseo_admin_pages->checkbox( 'noindex-tax-' . $tax->name, '<code>noindex, follow</code>', __( 'Meta Robots', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->checkbox( 'hideeditbox-tax-' . $tax->name, __( 'Hide', 'wordpress-seo' ), __( 'WordPress SEO Meta Box', 'wordpress-seo' ) );
+ echo '<br/>';
+ }
+ unset( $tax );
+ }
+ unset( $taxonomies );
+
+ ?>
+</div>
+<div id="archives" class="wpseotab">
+ <?php
+ echo '<h4>' . __( 'Author Archives', 'wordpress-seo' ) . '</h4>';
+ echo $wpseo_admin_pages->textinput( 'title-author-wpseo', __( 'Title template', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->textarea( 'metadesc-author-wpseo', __( 'Meta description template', 'wordpress-seo' ), '', 'metadesc' );
+ if ( $options['usemetakeywords'] === true ) {
+ echo $wpseo_admin_pages->textinput( 'metakey-author-wpseo', __( 'Meta keywords template', 'wordpress-seo' ) );
+ }
+ echo $wpseo_admin_pages->checkbox( 'noindex-author-wpseo', '<code>noindex, follow</code>', __( 'Meta Robots', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->checkbox( 'disable-author', __( 'Disable the author archives', 'wordpress-seo' ), '' );
+ echo '<p class="desc label">' . __( 'If you\'re running a one author blog, the author archive will always look exactly the same as your homepage. And even though you may not link to it, others might, to do you harm. Disabling them here will make sure any link to those archives will be 301 redirected to the homepage.', 'wordpress-seo' ) . '</p>';
+ echo '<br/>';
+ echo '<h4>' . __( 'Date Archives', 'wordpress-seo' ) . '</h4>';
+ echo $wpseo_admin_pages->textinput( 'title-archive-wpseo', __( 'Title template', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->textarea( 'metadesc-archive-wpseo', __( 'Meta description template', 'wordpress-seo' ), '', 'metadesc' );
+ echo '<br/>';
+ echo $wpseo_admin_pages->checkbox( 'noindex-archive-wpseo', '<code>noindex, follow</code>', __( 'Meta Robots', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->checkbox( 'disable-date', __( 'Disable the date-based archives', 'wordpress-seo' ), '' );
+ echo '<p class="desc label">' . __( 'For the date based archives, the same applies: they probably look a lot like your homepage, and could thus be seen as duplicate content.', 'wordpress-seo' ) . '</p>';
+
+ echo '<h2>' . __( 'Special Pages', 'wordpress-seo' ) . '</h2>';
+ echo '<p>' . __( 'These pages will be noindex, followed by default, so they will never show up in search results.', 'wordpress-seo' ) . '</p>';
+ echo '<h4>' . __( 'Search pages', 'wordpress-seo' ) . '</h4>';
+ echo $wpseo_admin_pages->textinput( 'title-search-wpseo', __( 'Title template', 'wordpress-seo' ) );
+ echo '<h4>' . __( '404 pages', 'wordpress-seo' ) . '</h4>';
+ echo $wpseo_admin_pages->textinput( 'title-404-wpseo', __( 'Title template', 'wordpress-seo' ) );
+ echo '<br class="clear"/>';
+ ?>
+</div>
+<div id="template_help" class="wpseotab">
+ <?php
+
+ echo '<h2>' . __( 'Variables', 'wordpress-seo' ) . '</h2>';
+ echo '</div>';
+ echo '</div>';
+ $wpseo_admin_pages->admin_footer();
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$options = get_site_option( 'wpseo_ms' );
+
+if ( isset( $_POST['wpseo_submit'] ) ) {
+ check_admin_referer( 'wpseo-network-settings' );
+
+ foreach ( array( 'access', 'defaultblog' ) as $opt ) {
+ $options[ $opt ] = $_POST['wpseo_ms'][ $opt ];
+ }
+ WPSEO_Options::update_site_option( 'wpseo_ms', $options );
+ add_settings_error( 'wpseo_ms', 'settings_updated', __( 'Settings Updated.', 'wordpress-seo' ), 'updated' );
+}
+
+if ( isset( $_POST['wpseo_restore_blog'] ) ) {
+ check_admin_referer( 'wpseo-network-restore' );
+ if ( isset( $_POST['wpseo_ms']['restoreblog'] ) && is_numeric( $_POST['wpseo_ms']['restoreblog'] ) ) {
+ $restoreblog = (int) WPSEO_Option::validate_int( $_POST['wpseo_ms']['restoreblog'] );
+ $blog = get_blog_details( $restoreblog );
+
+ if ( $blog ) {
+ WPSEO_Options::reset_ms_blog( $restoreblog );
+ add_settings_error( 'wpseo_ms', 'settings_updated', sprintf( __( '%s restored to default SEO settings.', 'wordpress-seo' ), esc_html( $blog->blogname ) ), 'updated' );
+ }
+ else {
+ add_settings_error( 'wpseo_ms', 'settings_updated', sprintf( __( 'Blog %s not found.', 'wordpress-seo' ), esc_html( $restoreblog ) ), 'error' );
+ }
+ unset( $restoreblog );
+ }
+}
+
+/* Set up selectbox dropdowns for smaller networks (usability) */
+$use_dropdown = true;
+if ( get_blog_count() > 100 ) {
+ $use_dropdown = false;
+}
+else {
+ $sites = wp_get_sites( array( 'deleted' => 0 ) );
+ if ( is_array( $sites ) && $sites !== array() ) {
+ $dropdown_input = array(
+ '-' => __( 'None', 'wordpress-seo' ),
+ );
+
+ foreach ( $sites as $site ) {
+ $dropdown_input[ $site['blog_id'] ] = $site['blog_id'] . ': ' . $site['domain'];
+
+ $blog_states = array();
+ if ( $site['public'] === '1' ) {
+ $blog_states[] = __( 'public', 'wordpress-seo' );
+ }
+ if ( $site['archived'] === '1' ) {
+ $blog_states[] = __( 'archived', 'wordpress-seo' );
+ }
+ if ( $site['mature'] === '1' ) {
+ $blog_states[] = __( 'mature', 'wordpress-seo' );
+ }
+ if ( $site['spam'] === '1' ) {
+ $blog_states[] = __( 'spam', 'wordpress-seo' );
+ }
+ if ( $blog_states !== array() ) {
+ $dropdown_input[ $site['blog_id'] ] .= ' [' . implode( ', ', $blog_states ) . ']';
+ }
+ }
+ unset( $site, $blog_states );
+ }
+ else {
+ $use_dropdown = false;
+ }
+ unset( $sites );
+}
+
+
+
+$wpseo_admin_pages->admin_header( false, 'wpseo-network-settings', 'wpseo_ms' );
+
+$content = '<form method="post" accept-charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '">';
+$content .= wp_nonce_field( 'wpseo-network-settings', '_wpnonce', true, false );
+
+/* @internal Important: Make sure the options added to the array here are in line with the options set in the WPSEO_Option_MS::$allowed_access_options property */
+$content .= $wpseo_admin_pages->select(
+ 'access',
+ __( 'Who should have access to the WordPress SEO settings', 'wordpress-seo' ),
+ array(
+ 'admin' => __( 'Site Admins (default)', 'wordpress-seo' ),
+ 'superadmin' => __( 'Super Admins only', 'wordpress-seo' )
+ ),
+ 'wpseo_ms'
+);
+
+if ( $use_dropdown === true ) {
+ $content .= $wpseo_admin_pages->select(
+ 'defaultblog',
+ __( 'New sites in the network inherit their SEO settings from this site', 'wordpress-seo' ),
+ $dropdown_input,
+ 'wpseo_ms'
+ );
+ $content .= '<p>' . __( 'Choose the site whose settings you want to use as default for all sites that are added to your network. If you choose \'None\', the normal plugin defaults will be used.', 'wordpress-seo' ) . '</p>';
+}
+else {
+ $content .= $wpseo_admin_pages->textinput( 'defaultblog', __( 'New sites in the network get the SEO settings from this site', 'wordpress-seo' ), 'wpseo_ms' );
+ $content .= '<p>' . sprintf( __( 'Enter the %sSite ID%s for the site whose settings you want to use as default for all sites that are added to your network. Leave empty for none (i.e. the normal plugin defaults will be used).', 'wordpress-seo' ), '<a href="' . esc_url( network_admin_url( 'sites.php' ) ) . '">', '</a>' ) . '</p>';
+}
+ $content .= '<p><strong>' . __( 'Take note :', 'wordpress-seo' ) . '</strong> ' . __( 'Privacy sensitive (FB admins and such), theme specific (title rewrite) and a few very site specific settings will not be imported to new blogs.', 'wordpress-seo' ) . '</p>';
+
+
+$content .= '<input type="submit" name="wpseo_submit" class="button-primary" value="' . __( 'Save MultiSite Settings', 'wordpress-seo' ) . '"/>';
+$content .= '</form>';
+
+$wpseo_admin_pages->postbox( 'wpseo_network_settings', __( 'MultiSite Settings', 'wordpress-seo' ), $content );
+
+
+$content = '<form method="post" accept-charset="' . esc_attr( get_bloginfo( 'charset' ) ) . '">';
+$content .= wp_nonce_field( 'wpseo-network-restore', '_wpnonce', true, false );
+$content .= '<p>' . __( 'Using this form you can reset a site to the default SEO settings.', 'wordpress-seo' ) . '</p>';
+
+if ( $use_dropdown === true ) {
+ unset( $dropdown_input['-'] );
+ $content .= $wpseo_admin_pages->select(
+ 'restoreblog',
+ __( 'Site ID', 'wordpress-seo' ),
+ $dropdown_input,
+ 'wpseo_ms'
+ );
+}
+else {
+ $content .= $wpseo_admin_pages->textinput( 'restoreblog', __( 'Blog ID', 'wordpress-seo' ), 'wpseo_ms' );
+}
+
+$content .= '<input type="submit" name="wpseo_restore_blog" value="' . __( 'Restore site to defaults', 'wordpress-seo' ) . '" class="button"/>';
+$content .= '</form>';
+
+$wpseo_admin_pages->postbox( 'wpseo-network-restore', __( 'Restore site to default settings', 'wordpress-seo' ), $content );
+
+$wpseo_admin_pages->admin_footer( false );
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$wpseo_admin_pages->admin_header( true, WPSEO_Options::get_group_name( 'wpseo_permalinks' ), 'wpseo_permalinks' );
+
+$options = get_option( 'wpseo_permalinks' );
+
+$content = '';
+
+$content .= $wpseo_admin_pages->checkbox( 'stripcategorybase', __( 'Strip the category base (usually <code>/category/</code>) from the category URL.', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . sprintf( __( 'We suggest using %1$sFV Top Level Categories%2$s, if you insist on keeping this but do know that the feature is very error prone and not <em>that</em> important for your SEO.', 'wordpress-seo' ), '<a href="https://wordpress.org/plugins/fv-top-level-cats/">', '</a>' ) . '</p>';
+
+$content .= $wpseo_admin_pages->checkbox( 'trailingslash', __( 'Enforce a trailing slash on all category and tag URL\'s', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . __( 'If you choose a permalink for your posts with <code>.html</code>, or anything else but a / on the end, this will force WordPress to add a trailing slash to non-post pages nonetheless.', 'wordpress-seo' ) . '</p>';
+
+$content .= $wpseo_admin_pages->checkbox( 'cleanslugs', __( 'Remove stop words from slugs.', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . __( 'This helps you to create cleaner URLs by automatically removing the stopwords from them.', 'wordpress-seo' ) . '</p>';
+
+$content .= $wpseo_admin_pages->checkbox( 'redirectattachment', __( 'Redirect attachment URL\'s to parent post URL.', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . __( 'Attachments to posts are stored in the database as posts, this means they\'re accessible under their own URL\'s if you do not redirect them, enabling this will redirect them to the post they were attached to.', 'wordpress-seo' ) . '</p>';
+
+$content .= $wpseo_admin_pages->checkbox( 'cleanreplytocom', __( 'Remove the <code>?replytocom</code> variables.', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . __( 'This prevents threaded replies from working when the user has JavaScript disabled, but on a large site can mean a <em>huge</em> improvement in crawl efficiency for search engines when you have a lot of comments.', 'wordpress-seo' ) . '</p>';
+
+$content .= $wpseo_admin_pages->checkbox( 'cleanpermalinks', __( 'Redirect ugly URL\'s to clean permalinks. (Not recommended in many cases!)', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . __( 'People make mistakes in their links towards you sometimes, or unwanted parameters are added to the end of your URLs, this allows you to redirect them all away. Please note that while this is a feature that is actively maintained, it is known to break several plugins, and should for that reason be the first feature you disable when you encounter issues after installing this plugin.', 'wordpress-seo' ) . '</p>';
+
+$wpseo_admin_pages->postbox( 'permalinks', __( 'Permalink Settings', 'wordpress-seo' ), $content );
+
+/* @internal Important: Make sure the options added to the array here are in line with the options set in the WPSEO_Option_Permalinks::$force_transport_options property */
+$content = $wpseo_admin_pages->select( 'force_transport', __( 'Force Transport', 'wordpress-seo' ), array( 'default' => __( 'Leave default', 'wordpress-seo' ), 'http' => __( 'Force http', 'wordpress-seo' ), 'https' => __( 'Force https', 'wordpress-seo' ) ) );
+$content .= '<p class="desc label">' . __( 'Force the canonical to either http or https, when your blog runs under both.', 'wordpress-seo' ) . '</p>';
+
+$wpseo_admin_pages->postbox( 'canonical', __( 'Canonical Settings', 'wordpress-seo' ), $content );
+
+
+$content = $wpseo_admin_pages->checkbox( 'cleanpermalink-googlesitesearch', __( 'Prevent cleaning out Google Site Search URL\'s.', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . __( 'Google Site Search URL\'s look weird, and ugly, but if you\'re using Google Site Search, you probably do not want them cleaned out.', 'wordpress-seo' ) . '</p>';
+
+$content .= $wpseo_admin_pages->checkbox( 'cleanpermalink-googlecampaign', __( 'Prevent cleaning out Google Analytics Campaign & Google AdWords Parameters.', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . __( 'If you use Google Analytics campaign parameters starting with <code>?utm_</code>, check this box. You shouldn\'t use these btw, you should instead use the hash tagged version instead.', 'wordpress-seo' ) . '</p>';
+
+$content .= $wpseo_admin_pages->textinput( 'cleanpermalink-extravars', __( 'Other variables not to clean', 'wordpress-seo' ) );
+$content .= '<p class="desc">' . __( 'You might have extra variables you want to prevent from cleaning out, add them here, comma separated.', 'wordpress-seo' ) . '</p>';
+
+$wpseo_admin_pages->postbox( 'cleanpermalinksdiv', __( 'Clean Permalink Settings', 'wordpress-seo' ), $content );
+
+
+$wpseo_admin_pages->admin_footer();
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$options = WPSEO_Options::get_all();
+$wpseo_admin_pages->admin_header( true, WPSEO_Options::get_group_name( 'wpseo_rss' ), 'wpseo_rss' );
+
+$content = '<p>' . __( "This feature is used to automatically add content to your RSS, more specifically, it's meant to add links back to your blog and your blog posts, so dumb scrapers will automatically add these links too, helping search engines identify you as the original source of the content.", 'wordpress-seo' ) . '</p>';
+$rows = array();
+
+$rows[] = array(
+ 'id' => 'rssbefore',
+ 'label' => __( 'Content to put before each post in the feed', 'wordpress-seo' ),
+ 'desc' => __( '(HTML allowed)', 'wordpress-seo' ),
+ 'content' => '<textarea cols="50" rows="5" id="rssbefore" name="wpseo_rss[rssbefore]">' . esc_textarea( $options['rssbefore'] ) . '</textarea>',
+);
+$rows[] = array(
+ 'id' => 'rssafter',
+ 'label' => __( 'Content to put after each post', 'wordpress-seo' ),
+ 'desc' => __( '(HTML allowed)', 'wordpress-seo' ),
+ 'content' => '<textarea cols="50" rows="5" id="rssafter" name="wpseo_rss[rssafter]">' . esc_textarea( $options['rssafter'] ) . '</textarea>',
+);
+$rows[] = array(
+ 'label' => __( 'Explanation', 'wordpress-seo' ),
+ 'content' => '<p>' . __( 'You can use the following variables within the content, they will be replaced by the value on the right.', 'wordpress-seo' ) . '</p>' .
+ '<table>' .
+ '<tr><th><strong>%%AUTHORLINK%%</strong></th><td>' . __( 'A link to the archive for the post author, with the authors name as anchor text.', 'wordpress-seo' ) . '</td></tr>' .
+ '<tr><th><strong>%%POSTLINK%%</strong></th><td>' . __( 'A link to the post, with the title as anchor text.', 'wordpress-seo' ) . '</td></tr>' .
+ '<tr><th><strong>%%BLOGLINK%%</strong></th><td>' . __( "A link to your site, with your site's name as anchor text.", 'wordpress-seo' ) . '</td></tr>' .
+ '<tr><th><strong>%%BLOGDESCLINK%%</strong></th><td>' . __( "A link to your site, with your site's name and description as anchor text.", 'wordpress-seo' ) . '</td></tr>' .
+ '</table>'
+);
+$wpseo_admin_pages->postbox( 'rssfootercontent', __( 'Content of your RSS Feed', 'wordpress-seo' ), $content . $wpseo_admin_pages->form_table( $rows ) );
+
+$wpseo_admin_pages->admin_footer();
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$fbconnect = '
+ <p><strong>' . esc_html__( 'Facebook Insights and Admins', 'wordpress-seo' ) . '</strong><br>
+ ' . sprintf( esc_html__( 'To be able to access your %sFacebook Insights%s for your site, you need to specify a Facebook Admin. This can be a user, but if you have an app for your site, you could use that. For most people a user will be "good enough" though.', 'wordpress-seo' ), '<a href="https://www.facebook.com/insights">', '</a>' ) . '</p>';
+$fbbuttons = array();
+
+$clearall = false;
+
+$options = get_option( 'wpseo_social' );
+
+if ( isset( $_GET['delfbadmin'] ) ) {
+ if ( wp_verify_nonce( $_GET['nonce'], 'delfbadmin' ) != 1 ) {
+ die( 'I don\'t think that\'s really nice of you!.' );
+ }
+
+ $id = sanitize_text_field( $_GET['delfbadmin'] );
+ if ( isset( $options['fb_admins'][ $id ] ) ) {
+ $fbadmin = $options['fb_admins'][ $id ]['name'];
+ unset( $options['fb_admins'][ $id ] );
+ update_option( 'wpseo_social', $options );
+ add_settings_error( 'yoast_wpseo_social_options', 'success', sprintf( __( 'Successfully removed admin %s', 'wordpress-seo' ), $fbadmin ), 'updated' );
+ unset( $fbadmin );
+ }
+ unset( $id );
+
+ // Clean up the referrer url for later use
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'nonce', 'delfbadmin' ), sanitize_text_field( $_SERVER['REQUEST_URI'] ) );
+ }
+}
+
+elseif ( isset( $_GET['fbclearall'] ) ) {
+ if ( wp_verify_nonce( $_GET['nonce'], 'fbclearall' ) != 1 ) {
+ die( "I don't think that's really nice of you!." );
+ }
+ // Reset to defaults, don't unset as otherwise the old values will be retained
+ $options['fb_admins'] = WPSEO_Options::get_default( 'wpseo_social', 'fb_admins' );
+ $options['fbapps'] = WPSEO_Options::get_default( 'wpseo_social', 'fbapps' );
+ $options['fbadminapp'] = WPSEO_Options::get_default( 'wpseo_social', 'fbadminapp' );
+ update_option( 'wpseo_social', $options );
+ add_settings_error( 'yoast_wpseo_social_options', 'success', __( 'Successfully cleared all Facebook Data', 'wordpress-seo' ), 'updated' );
+
+ // Clean up the referrer url for later use
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'nonce', 'fbclearall' ), sanitize_text_field( $_SERVER['REQUEST_URI'] ) );
+ }
+}
+
+elseif ( isset( $_GET['key'] ) ) {
+ if ( $_GET['key'] === $options['fbconnectkey'] ) {
+ if ( isset( $_GET['userid'] ) ) {
+ $user_id = sanitize_text_field( $_GET['userid'] );
+ if ( ! isset( $options['fb_admins'][ $user_id ] ) ) {
+ $options['fb_admins'][ $user_id ]['name'] = sanitize_text_field( urldecode( $_GET['userrealname'] ) );
+ $options['fb_admins'][ $user_id ]['link'] = sanitize_text_field( urldecode( $_GET['link'] ) );
+ update_option( 'wpseo_social', $options );
+ add_settings_error( 'yoast_wpseo_social_options', 'success', sprintf( __( 'Successfully added %s as a Facebook Admin!', 'wordpress-seo' ), '<a href="' . esc_url( $options['fb_admins'][ $user_id ]['link'] ) . '">' . esc_html( $options['fb_admins'][ $user_id ]['name'] ) . '</a>' ), 'updated' );
+ }
+ else {
+ add_settings_error( 'yoast_wpseo_social_options', 'error', sprintf( __( '%s already exists as a Facebook Admin.', 'wordpress-seo' ), '<a href="' . esc_url( $options['fb_admins'][ $user_id ]['link'] ) . '">' . esc_html( $options['fb_admins'][ $user_id ]['name'] ) . '</a>' ), 'error' );
+ }
+ unset( $user_id );
+ }
+ elseif ( isset( $_GET['apps'] ) ) {
+ $apps = json_decode( stripslashes( $_GET['apps'] ), true );
+ if ( is_array( $apps ) && $apps !== array() ) {
+ $options['fbapps'] = array( '0' => __( 'Do not use a Facebook App as Admin', 'wordpress-seo' ) );
+ foreach ( $apps as $app ) {
+ $options['fbapps'][ $app['app_id'] ] = $app['display_name'];
+ }
+ update_option( 'wpseo_social', $options );
+ add_settings_error( 'yoast_wpseo_social_options', 'success', __( 'Successfully retrieved your apps from Facebook, now select an app to use as admin.', 'wordpress-seo' ), 'updated' );
+ }
+ else {
+ add_settings_error( 'yoast_wpseo_social_options', 'error', __( 'Failed to retrieve your apps from Facebook.', 'wordpress-seo' ), 'error' );
+ }
+ unset( $apps, $app );
+ }
+ }
+
+ // Clean up the referrer url for later use
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'key', 'userid', 'userrealname', 'link', 'apps' ), sanitize_text_field( $_SERVER['REQUEST_URI'] ) );
+ }
+}
+
+// Refresh option after updates
+$options = get_option( 'wpseo_social' );
+
+if ( is_array( $options['fb_admins'] ) && $options['fb_admins'] !== array() ) {
+ $clearall = true;
+}
+
+if ( is_array( $options['fbapps'] ) && $options['fbapps'] !== array() ) {
+ $clearall = true;
+}
+
+$app_button_text = __( 'Use a Facebook App as Admin', 'wordpress-seo' );
+if ( is_array( $options['fbapps'] ) && $options['fbapps'] !== array() ) {
+ // @todo [JRF => whomever] use WPSEO_Admin_Pages->select() method ?
+ $fbconnect .= '
+ <p>' . __( 'Select an app to use as Facebook admin:', 'wordpress-seo' ) . '</p>
+ <select name="wpseo_social[fbadminapp]" id="fbadminapp">';
+
+ foreach ( $options['fbapps'] as $id => $app ) {
+ $fbconnect .= '
+ <option value="' . esc_attr( $id ) . '" ' . selected( $id, $options['fbadminapp'], false ) . '>' . esc_attr( $app ) . '</option>';
+ }
+ $fbconnect .= '
+ </select>
+ <div class="clear"></div><br/>';
+
+ $app_button_text = __( 'Update Facebook Apps', 'wordpress-seo' );
+}
+
+if ( $options['fbadminapp'] == 0 ) {
+ $button_text = __( 'Add Facebook Admin', 'wordpress-seo' );
+ $primary = true;
+ if ( is_array( $options['fb_admins'] ) && $options['fb_admins'] !== array() ) {
+ $fbconnect .= '
+ <p>' . __( 'Currently connected Facebook admins:', 'wordpress-seo' ) . '</p>
+ <ul>';
+ $nonce = wp_create_nonce( 'delfbadmin' );
+
+ foreach ( $options['fb_admins'] as $admin_id => $admin ) {
+ $admin_id = esc_attr( $admin_id );
+ $fbconnect .= '
+ <li><a href="' . esc_url( $admin['link'] ) . '">' . esc_html( $admin['name'] ) . '</a> - <strong><a href="' . esc_url( add_query_arg( array( 'delfbadmin' => $admin_id, 'nonce' => $nonce ), admin_url( 'admin.php?page=wpseo_social' ) ) ) . '">X</a></strong></li>';
+ }
+ $fbconnect .= '
+ </ul>';
+ $button_text = __( 'Add Another Facebook Admin', 'wordpress-seo' );
+ $primary = false;
+ }
+ $but_primary = '';
+ if ( $primary ) {
+ $but_primary = '-primary';
+ }
+ $fbbuttons[] = '
+ <a class="button' . esc_attr( $but_primary ) . '" href="' . esc_url( 'https://yoast.com/fb-connect/?key=' . urlencode( $options['fbconnectkey'] ) . '&redirect=' . urlencode( admin_url( 'admin.php?page=wpseo_social' ) ) ) . '">' . $button_text . '</a>';
+}
+
+$fbbuttons[] = '
+ <a class="button" href="' . esc_url( 'https://yoast.com/fb-connect/?key=' . urlencode( $options['fbconnectkey'] ) . '&type=app&redirect=' . urlencode( admin_url( 'admin.php?page=wpseo_social' ) ) ) . '">' . esc_html( $app_button_text ) . '</a>';
+
+if ( $clearall ) {
+ $fbbuttons[] = '
+ <a class="button" href="' . esc_url( add_query_arg( array( 'nonce' => wp_create_nonce( 'fbclearall' ), 'fbclearall' => 'true' ), admin_url( 'admin.php?page=wpseo_social' ) ) ) . '">' . __( 'Clear all Facebook Data', 'wordpress-seo' ) . '</a> ';
+}
+
+if ( is_array( $fbbuttons ) && $fbbuttons !== array() ) {
+ $fbconnect .= '
+ <p class="fb-buttons">' . implode( '', $fbbuttons ) . '</p>';
+}
+
+$wpseo_admin_pages->admin_header( true, WPSEO_Options::get_group_name( 'wpseo_social' ), 'wpseo_social' );
+?>
+
+<h2 class="nav-tab-wrapper" id="wpseo-tabs">
+ <a class="nav-tab nav-tab-active" id="facebook-tab" href="#top#facebook"><?php _e( 'Facebook', 'wordpress-seo' );?></a>
+ <a class="nav-tab" id="twitterbox-tab" href="#top#twitterbox"><?php _e( 'Twitter', 'wordpress-seo' );?></a>
+ <a class="nav-tab" id="google-tab" href="#top#google"><?php _e( 'Google+', 'wordpress-seo' );?></a>
+</h2>
+
+<div id="facebook" class="wpseotab">
+ <?php
+ echo '<p>';
+ echo $wpseo_admin_pages->checkbox( 'opengraph', __( 'Add Open Graph meta data', 'wordpress-seo' ) );
+ echo '</p>';
+ echo'<p class="desc">' . __( 'Add Open Graph meta data to your site\'s <code><head></code> section. You can specify some of the ID\'s that are sometimes needed below:', 'wordpress-seo' ) . '</p>';
+ echo $fbconnect;
+ echo $wpseo_admin_pages->textinput( 'facebook_site', __( 'Facebook Page URL', 'wordpress-seo' ) );
+ if ( 'posts' == get_option( 'show_on_front' ) ) {
+ echo '<h4>' . esc_html__( 'Frontpage settings', 'wordpress-seo' ) . '</h4>';
+ echo $wpseo_admin_pages->media_input( 'og_frontpage_image', __( 'Image URL', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->textinput( 'og_frontpage_title', __( 'Title', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->textinput( 'og_frontpage_desc', __( 'Description', 'wordpress-seo' ) );
+
+ // Offer copying of meta description
+ $meta_options = get_option( 'wpseo_titles' );
+ echo '<input type="hidden" id="meta_description" value="' . $meta_options['metadesc-home-wpseo'] . '" />';
+ echo '<p class="label desc" style="border:0;"><a href="javascript:;" onclick="copy_home_meta();" class="button">' . __( 'Copy home meta description', 'wordpress-seo' ) . '</a></p>';
+
+ echo '<p class="desc label">' . esc_html__( 'These are the title, description and image used in the Open Graph meta tags on the front page of your site.', 'wordpress-seo' ) . '</p>';
+ }
+ echo '<h4>' . esc_html__( 'Default settings', 'wordpress-seo' ) . '</h4>';
+ echo $wpseo_admin_pages->media_input( 'og_default_image', __( 'Image URL', 'wordpress-seo' ) );
+ echo '<p class="desc label">' . esc_html__( 'This image is used if the post/page being shared does not contain any images.', 'wordpress-seo' ) . '</p>';
+ do_action( 'wpseo_admin_opengraph_section' );
+ ?>
+</div>
+
+<div id="twitterbox" class="wpseotab">
+ <?php
+ echo '<p><strong>';
+ printf( esc_html__( 'Note that for the Twitter Cards to work, you have to check the box below and then validate your Twitter Cards through the %1$sTwitter Card Validator%2$s.', 'wordpress-seo' ), '<a target="_blank" href="https://dev.twitter.com/docs/cards/validation/validator">', '</a>' );
+ echo '</p></strong>';
+ echo '<p>';
+ echo $wpseo_admin_pages->checkbox( 'twitter', __( 'Add Twitter card meta data', 'wordpress-seo' ) );
+ echo '</p>';
+ echo'<p class="desc">' . __( 'Add Twitter card meta data to your site\'s <code><head></code> section.', 'wordpress-seo' ) . '</p>';
+ echo $wpseo_admin_pages->textinput( 'twitter_site', __( 'Site Twitter Username', 'wordpress-seo' ) );
+ echo $wpseo_admin_pages->select( 'twitter_card_type', __( 'The default card type to use', 'wordpress-seo' ), WPSEO_Option_Social::$twitter_card_types );
+ do_action( 'wpseo_admin_twitter_section' );
+ ?>
+</div>
+
+<div id="google" class="wpseotab">
+ <?php
+ echo '<p>';
+ echo $wpseo_admin_pages->checkbox( 'googleplus', __( 'Add Google+ specific post meta data', 'wordpress-seo' ) );
+ echo '</p>';
+
+ echo $wpseo_admin_pages->textinput( 'plus-publisher', __( 'Google Publisher Page', 'wordpress-seo' ) );
+ echo '<p class="desc label">' . esc_html__( 'If you have a Google+ page for your business, add that URL here and link it on your Google+ page\'s about page.', 'wordpress-seo' ) . '</p>';
+ do_action( 'wpseo_admin_googleplus_section' );
+ ?>
+</div>
+
+<?php
+$wpseo_admin_pages->admin_footer();
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Admin
+ */
+
+/**
+ * @todo - [JRF => whomever] check for other sitemap plugins which may conflict ?
+ * @todo - [JRF => whomever] check for existance of .xls rewrite rule in .htaccess from
+ * google-sitemaps-plugin/generator and remove as it will cause errors for our sitemaps
+ * (or inform the user and disallow enabling of sitemaps )
+ * @todo - [JRF => whomever] check if anything along these lines is already being done
+ */
+
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+global $wpseo_admin_pages;
+
+$wpseo_admin_pages->admin_header( true, WPSEO_Options::get_group_name( 'wpseo_xml' ), 'wpseo_xml' );
+
+$options = get_option( 'wpseo_xml' );
+
+$content = $wpseo_admin_pages->checkbox( 'enablexmlsitemap', __( 'Check this box to enable XML sitemap functionality.', 'wordpress-seo' ), false );
+$content .= '<div id="sitemapinfo">';
+if ( wpseo_is_nginx() ) {
+ $content .= '<div style="margin: 5px 0; padding: 3px 10px; background-color: #ffffe0; border: 1px solid #E6DB55; border-radius: 3px;">';
+ $content .= '<p>' . __( 'As you\'re on NGINX, you\'ll need the following rewrites:', 'wordpress-seo' ) . '</p>';
+ $content .= '<pre>rewrite ^/sitemap_index\.xml$ /index.php?sitemap=1 last;
+rewrite ^/([^/]+?)-sitemap([0-9]+)?\.xml$ /index.php?sitemap=$1&sitemap_n=$2 last;</pre>';
+ $content .= '</div>';
+}
+
+if ( $options['enablexmlsitemap'] === true ) {
+ $content .= '<p>' . sprintf( esc_html__( 'You can find your XML Sitemap here: %sXML Sitemap%s', 'wordpress-seo' ), '<a target="_blank" class="button-secondary" href="' . esc_url( wpseo_xml_sitemaps_base_url( 'sitemap_index.xml' ) ) . '">', '</a>' ) . '<br/><br/>' . __( 'You do <strong>not</strong> need to generate the XML sitemap, nor will it take up time to generate after publishing a post.', 'wordpress-seo' ) . '</p>';
+} else {
+ $content .= '<p>' . __( 'Save your settings to activate XML Sitemaps.', 'wordpress-seo' ) . '</p>';
+}
+
+// When we write the help tab for this we should definitely reference this plugin :https://wordpress.org/plugins/edit-author-slug/
+$content .= '<h2>' . __( 'User sitemap', 'wordpress-seo' ) . '</h2>';
+$content .= $wpseo_admin_pages->checkbox( 'disable_author_sitemap', __( 'Disable author/user sitemap', 'wordpress-seo' ), false );
+
+$content .= '<div id="xml_user_block">';
+$content .= '<p><strong>' . __( 'Exclude users without posts', 'wordpress-seo' ) . '</strong><br/>';
+$content .= $wpseo_admin_pages->checkbox( 'disable_author_noposts', __( 'Disable all users with zero posts', 'wordpress-seo' ), false );
+
+$roles = wpseo_get_roles();
+if ( is_array( $roles ) && $roles !== array() ) {
+ $content .= '<p><strong>' . __( 'Exclude userroles', 'wordpress-seo' ) . '</strong><br/>';
+ $content .= __( 'Please check the appropriate box below if there\'s a user role that you do <strong>NOT</strong> want to include in your sitemap:', 'wordpress-seo' ) . '</p>';
+ foreach ( $roles as $role_key => $role_name ) {
+ $content .= $wpseo_admin_pages->checkbox( 'user_role-' . $role_key . '-not_in_sitemap', $role_name );
+ }
+}
+$content .= '</div>';
+
+$content .= '<br/>';
+$content .= '<h2>' . __( 'General settings', 'wordpress-seo' ) . '</h2>';
+$content .= '<p>' . __( 'After content publication, the plugin automatically pings Google and Bing, do you need it to ping other search engines too? If so, check the box:', 'wordpress-seo' ) . '</p>';
+$content .= $wpseo_admin_pages->checkbox( 'xml_ping_yahoo', __( 'Ping Yahoo!', 'wordpress-seo' ), false );
+$content .= $wpseo_admin_pages->checkbox( 'xml_ping_ask', __( 'Ping Ask.com', 'wordpress-seo' ), false );
+
+
+$post_types = apply_filters( 'wpseo_sitemaps_supported_post_types', get_post_types( array( 'public' => true ), 'objects' ) );
+if ( is_array( $post_types ) && $post_types !== array() ) {
+ $content .= '<h2>' . __( 'Exclude post types', 'wordpress-seo' ) . '</h2>';
+ $content .= '<p>' . __( 'Please check the appropriate box below if there\'s a post type that you do <strong>NOT</strong> want to include in your sitemap:', 'wordpress-seo' ) . '</p>';
+ foreach ( $post_types as $pt ) {
+ $content .= $wpseo_admin_pages->checkbox( 'post_types-' . $pt->name . '-not_in_sitemap', $pt->labels->name . ' (<code>' . $pt->name . '</code>)' );
+ }
+}
+
+
+$taxonomies = apply_filters( 'wpseo_sitemaps_supported_taxonomies', get_taxonomies( array( 'public' => true ), 'objects' ) );
+if ( is_array( $taxonomies ) && $taxonomies !== array() ) {
+ $content .= '<h2>' . __( 'Exclude taxonomies', 'wordpress-seo' ) . '</h2>';
+ $content .= '<p>' . __( 'Please check the appropriate box below if there\'s a taxonomy that you do <strong>NOT</strong> want to include in your sitemap:', 'wordpress-seo' ) . '</p>';
+ foreach ( $taxonomies as $tax ) {
+ if ( isset( $tax->labels->name ) && trim( $tax->labels->name ) != '' ) {
+ $content .= $wpseo_admin_pages->checkbox( 'taxonomies-' . $tax->name . '-not_in_sitemap', $tax->labels->name . ' (<code>' . $tax->name . '</code>)' );
+ }
+ }
+}
+
+
+$content .= '<br/>';
+$content .= '<h2>' . __( 'Entries per page', 'wordpress-seo' ) . '</h2>';
+$content .= '<p>' . sprintf( __( 'Please enter the maximum number of entries per sitemap page (defaults to %s, you might want to lower this to prevent memory issues on some installs):', 'wordpress-seo' ), WPSEO_Options::get_default( 'wpseo_xml', 'entries-per-page' ) ) . '</p>';
+$content .= $wpseo_admin_pages->textinput( 'entries-per-page', __( 'Max entries per sitemap page', 'wordpress-seo' ) );
+
+$content .= '<br class="clear"/>';
+$content .= '</div>';
+
+$wpseo_admin_pages->postbox( 'xmlsitemaps', __( 'XML Sitemap', 'wordpress-seo' ), $content );
+
+do_action( 'wpseo_xmlsitemaps_config' );
+
+$wpseo_admin_pages->admin_footer();
\ No newline at end of file
--- /dev/null
+= 1.4.17 =
+
+* Missed a line in the commit of the option to stop stop words cleaning.
+
+= 1.4.16 =
+
+* Fix for compatibility with NextGen Gallery.
+
+* Enhancements
+ * Add option to enable slug stop word cleaning, find it under SEO -> Permalinks. It's on by default.
+ * Remove tracking variables from the Yoast Tracking that weren't used.
+
+* i18n
+ * Updated de_DE, fa_IR, fi, hu_HU, it_IT, pl_PL, sv_SE and tr_TK
+
+= 1.4.15 =
+
+* Bugfixes
+ * Fix the white XML sitemap errors caused by non-working XSL.
+ * Fixed the errors in content analysis reporting an H2 was not found when it was really there.
+ * Fix slug stopwords removal, props [amm350](https://github.com/amm350).
+ * Fix PHP Notice logged when site has capabilities created without 3rd value in args array, props [mbijon](https://github.com/mbijon).
+ * Fix the fact that meta description template for archive pages didn't work, props [MarcQueralt](https://github.com/MarcQueralt).
+ * Prevent wrong shortcodes (that echo instead of return) from causing erroneous output.
+ * Fix edge cases issue for keyword in first paragraph test not working.
+ * Revert change in 1.4.14 that did a `do_shortcode` while in the `head` to retrieve images from posts, as too many plugins crash then, instead added `wpseo_pre_analysis_post_content` filter there as well.
+
+= 1.4.14 =
+
+This release contains tons and tons of bugfixes, thanks in *large* part to [Jrf](http://profiles.wordpress.org/jrf), who now has commit rights to the code on Github directly. Please join me in thanking her for her efforts!
+
+* Notes:
+ * Our GitHub repository moved to [https://github.com/Yoast/wordpress-seo](https://github.com/Yoast/wordpress-seo), old links should redirect but please check.
+
+* Bugfixes
+ * Switch to stock autocomplete file and fix clash with color picker, props [Heinrich Luehrsen](http://www.luehrsen-heinrich.de/).
+ * Prevent strip category base code from breaking Custom Post Type rewrites, props [Steve Hulet](http://about.me/stevehulet).
+ * Fixed [issue with canonical links](http://wordpress.org/support/topic/serious-canonical-issue-with-paginated-posts) on last page of paginated posts - props [maxbugfiy](http://wordpress.org/support/profile/maxbuxfiy)
+ * Fixed bug in shortcode removal from meta description as reported by [professor44](http://profiles.wordpress.org/professor44/) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed bug preventing saving of taxonomy meta data on first try - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed small (potential) issue in wpseo_title_test() - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed bug where RSS excerpt would be double wrapped in `<p>` tags as reported by [mikeprince](http://profiles.wordpress.org/mikeprince) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed HTML validation error: Duplicate id Twitter on Social tab - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed undefined index notice as reported by [szepeviktor](http://profiles.wordpress.org/szepeviktor).
+ * Fixed error in a database query as reported by [Watch Teller](http://wordpress.org/support/profile/watchteller) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed small issue with how styles where enqueued/registered - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed bug in alt text of score dots as [reported by Rocket Pixels](http://wordpress.org/support/topic/dots-on-hover-over-show-na-tooltip) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Applied best practices to all uses of preg_ functions fixing some bugs in the process - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed bug in processing of `%%ct_<custom-tax-name>%%` as [reported by Joy](http://wordpress.org/support/topic/plugin-dies-when-processing-ct_desc_) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: no more empty og: or twitter: tags. Also added additional escaping where needed - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: Meta description tag discovery looked in parent theme header file even when a child theme is the current theme - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: Using the 'Fix it' button would remove the meta description tag from the parent theme header file, even when a child theme is the current theme - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: Using the 'Fix it' button would fail if it had already been used once (i.e. if a wpseo backup file already existed) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed repeated unnecessary meta description tag checks on each visit to dashboard page - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: Meta description 'Fix it' feedback message was not shown - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Mini-fix for plugin_dir_url - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed Author Highlighting to only show authors as possible choice for Google+ Plus author as reported by [Sanoma](https://github.com/jdevalk/wordpress-seo/issues/131) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed `adjacent_rel_links()` for Genesis users - props [benjamin74](https://github.com/benjamin74) for reporting.
+ * Replace jQuery .live function with .on(), as .live() has been deprecated and deleted. Props [Viktor Kostadinov](http://www.2buy1click.com/) & [Taco Verdonschot](https://yoast.com/about-us/taco-verdonschot/).
+ * Fix how breadcrumbs deal with taxonomy orders. Props [Gaya Kessler](http://www.gayadesign.com/).
+ * Fixed some PHP warnings
+
+* Enhancements
+ * Added `wpseo_pre_analysis_post_content` filter. This allows plugins to add content to the content that is analyzed by the page analysis functionality.
+ * Added `wpseo_genesis_force_adjacent_rel_home` filter to allow forcing of rel=next / rel=prev links on the homepage pagination for Genesis users, they're off by default.
+ * Make `$wpseo_metabox` a global, props [Peter Chester](http://tri.be/).
+ * No need to show Twitter image when OpenGraph is showing, props [Gary Jones](http://garyjones.co.uk/).
+ * Make sure WPML works again, props [dominykasgel](https://github.com/dominykasgel).
+ * Added checks for the meta description tag on theme switch, on theme update and on (re-)activation of the WP SEO plugin including a visual warning if the check would warrant it - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Added the ability to request re-checking a theme for the meta description tag. Useful when you've manually removed it (to get rid of the warning), inspired by [tzeldin88](http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-your-theme-contains-a-meta-description-which-blocks-wordpress-seo) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * OpenGraph image tags will now also be added for images added to the post via shortcodes, as suggested by [msebald](http://wordpress.org/support/topic/ogimage-set-to-default-image-but-articlepage-has-own-images?replies=3#post-4436317) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Added 'wpseo_breadcrumb_single_link_with_sep' filter which allows users to filter a complete breadcrumb element including the separator - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Added 'wpseo_stopwords' filter which allows users to filter the stopwords list - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Added 'wpseo_terms' filter which allows users to filter the terms string - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Hide advanced tab for users for which it has been disabled, as [suggested by jrgmartin](https://github.com/jdevalk/wordpress-seo/issues/93) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Updated Facebook supported locales list for og:locale
+
+* i18n
+ * Updated languages tr_TK, fi, ru_RU & da_DK
+ * Added language hi_IN
+ * Updated wordpress-seo.pot file
+
+= 1.4.13 =
+
+* Bugfixes
+ * Fixed ampersand (&) in site title in Title Templates loading as &
+ * Fixed error when focus keyword contains a / - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed issue with utf8 characters in meta description - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed undefined property error - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed undefined index error for the last page of the tour - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed undefined index error for images without alt - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix output of author for Google+ when using a static front page - props [petervanderdoes](https://github.com/petervanderdoes).
+ * Keyword density calculation not working when special character in focus keyword - props [siriuzwhite](https://github.com/siriuzwhite).
+ * Reverse output buffer cleaning for XML sitemaps, as that collides with WP Super Cache, thanks to [Rarst](https://github.com/Rarst) for finding this.
+ * Fix canonical and rel=prev / rel=next links for paginated home pages using index.php links.
+ * Fixed og:title not following title settings.
+* Enhancements
+ * Improved breadcrumbs and titles for 404 pages - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Moved XSL stylesheet from a static file in wp-content folder to a dynamic one, allowing it to work for sites that prevented the wp-content dir from being opened directly, f.i. through Sucuri's hardening.
+ * Added a link in the XSL pointing back to the sitemap index on individual sitemaps.
+ * When remove replytocom is checked in the permalink settings, these are now also redirected out.
+ * Added filters to OpenGraph output functions that didn't have them yet.
+
+= 1.4.12 =
+
+* Bugfixes
+ * Submit button displays again on Titles & Metas page.
+ * SEO Title now calculates length correctly.
+ * Force rewrite titles should no longer reset wrongly on update.
+
+= 1.4.11 =
+
+* i18n
+ * Updated de_DE, ru_RU, zh_CN.
+* Bugfixes
+ * Make rel="publisher" markup appear on every page.
+ * Prevent empty property='article:publisher' markup from being output .
+ * Fixed twitter:description tag should only appears if OpenGraph is inactive.
+ * og:description will default to get_the_excerpt when meta description is blank (similar to how twitter:description works).
+ * Fixes only 25 tags (and other taxonomy) are being indexed in taxonomy sitemaps.
+ * Fix lastmod dates for taxonomies in XML sitemap index file.
+* Enhancements
+ * Changed Social Admin section to have a tab-layout.
+ * Moved Google+ section from Homepage tab of Titles & Metas to Social tab.
+ * Make twitter:domain use WordPress site name instead of domain name.
+ * Added more output filters in the Twitter class.
+
+= 1.4.10 =
+
+* Fixes
+ * Caching was disabled in certain cases, this update fixes that.
+* Enhancements
+ * Added option to disable author sitemap.
+ * If author pages are disabled, author sitemaps are now automatically disabled.
+
+= 1.4.9 =
+
+* i18n
+ * Updated .pot file
+ * Updated ar, da_DK, de_DE, el_GR, es_ES, fa_IR, fr_FR, he_IL, id_ID, nl_NL, ro_RO, sv_SE & tr_TK
+ * Added hr & sl_SI
+ * Many localization fixes
+* Bugfixes
+ * Fixed sitemap "loc" element to have encoded entities.
+ * Honor the language setting if other plugins set the language.
+ * sitemap.xml will now redirect to sitemap_index.xml if it doesn't exist statically.
+ * Added filters 'wpseo_sitemap_exclude_post_type' and 'wpseo_sitemap_exclude_taxonomy' to allow themes/plugins to exclude entries in the XML sitemap.
+ * Added RTL support, some CSS fixes.
+ * Focus word gets counted in meta description when defined by a template.
+ * Fixed some bugs with the focus keyword in the first paragraph test.
+ * Fixed display bug in SEO Title column when defined by a template ('Page # of #').
+ * Fixed a few strict notices that would pop up in WP 3.6.
+ * Prevent other plugins from overriding the WP SEO menu position.
+ * Enabled the advanced tab for site-admins on a multi-site install.
+ * Fixed post save error when page analysis is disabled.
+ * OpenGraph frontpage og:description and og:image tags now properly added to the frontpage.
+* Enhancements
+ * Added an HTML sitemap shortcode [wpseo_sitemap].
+ * Added an XML sitemap listing the author profile URLs.
+ * Added detection of Yoast's robots meta plugin and All In One SEO plugins, plugin now gives a notice to import settings and disable those plugins.
+ * Prevent empty image tags in Twitter Cards - props [Mike Bijon](https://github.com/mbijon).
+ * Add new `twitter:domain` tag - props [Mike Bijon](https://github.com/mbijon).
+ * Add support for Facebooks new OG tags for media publishers.
+ * Allow authorship to be removed per post type.
+
+= 1.4.7 =
+
+* Properly fix security bug that should've been fixed in 1.4.5.
+* Move from using several $options arrays in the frontend to 1 class wide option.
+* Instead of firing all plugin options as function within head function, attach them to `wpseo_head` action, allowing easier filtering and changing.
+* Where possible, use larger images for Facebook Opengraph.
+* Add several filters and actions around social settings.
+
+= 1.4.6 =
+
+* Fix a possible fatal error in tracking.
+
+= 1.4.5 =
+
+* Bug fixes:
+ * Fix security issue which allowed any user to reset settings.
+ * Allow saving of SEO metadata for attachments.
+ * Set the max-width of the snippet preview to 520px to look more like Google search results, while still allowing it to work on lower resolutions.
+* Enhancements:
+ * Remove the shortlink http header when the hide shortlink checkbox is checked.
+ * Added a check on focus keyword in the page analysis functionality, checking whether a focus keyword has already been used before.
+ * Update how the tracking class calculates users to improve speed.
+
+= 1.4.4 =
+
+* Fix changelog for 1.4.3
+* Bugfixes
+ * Fix activation bug.
+* i18n
+ * Updated es_ES, id_ID, he_IL.
+
+= 1.4.3 =
+
+* Bugfixes
+ * Register core SEO menu at a lower than default prio so other plugins can tie in more easily.
+ * Remove alt= from page analysis score divs.
+ * Make site tracking use the site hash consistently between plugins.
+ * Improve popup pointer removal.
+
+= 1.4.2 =
+
+* Bugfixes
+ * Made the sitemaps class load in backend too so it always generates rewrites correctly.
+ * Changed > to /> in class-twitter.php for validation as XHTML.
+ * Small fix in metabox CSS for small screens (thx [Ryan Hellyer](http://ryanhellyer.net)).
+ * Load classes on plugins_loaded instead of immediately on load to allow WPML to filter options.
+* i18n
+ * Updated bs_BA, cs_CZ, da_DK, de_DE, fa_IR, fr_FR, he_IL, hu_HU, id_ID, it_IT, nl_NL, pl_PL, pt_BR, ru_RU and tr_TR
+
+= 1.4.1 =
+
+* i18n:
+ * Updated .pot file
+ * Updated bg_BG, bs_BA, cs_CZ, fa_IR, hu_HU, pl_PL & ru_RU
+* Bugfixes:
+ * Focus keyword check now works again in all cases.
+ * Fix typo in Video SEO banner.
+* Enhancements:
+ * Don't show banners for plugins you already have.
+
+= 1.4 =
+
+* i18n & documentation:
+ * Updated Hebrew (he_IL)
+ * Updated Italian (it_IT)
+ * Updated Dutch (nl_NL)
+ * Updated Swedish (sv_SE)
+ * Updated some strings to fix typos.
+ * Removed affiliate links from readme.txt.
+* Bugfixes:
+ * Fixed a bug in saving post meta details for revisions.
+ * Prevent an error when there are no posts for post type.
+ * Fix the privacy warning to point to the right place.
+* Enhancements:
+ * Slight performance improvement in <head> functionality by not resetting query when its not needed (kudos to @Rarst).
+ * Slight performance improvement in options call by adding some caching (kudos to @Rarst as well).
+ * Changed inner workings of search engine ping, adding YOAST_SEO_PING_IMMEDIATELY constant to allow immediate ping on publish.
+ * Changed design of meta box, moving much of the help text out in favor of clicking on a help icon.
+ * Removed Linkdex branding from page analysis functionality.
+
+= 1.3.4.4 =
+
+* Bug with revisions in XML sitemap for some weird combinations.
+* Improved logic for rel=publisher on frontpage.
+* Allow variables in meta description for post type archive.
+* Improved counting of images for page analysis.
+* updated Turkish (tr_TR)
+* updated Russian (ru_RU)
+* updated Indonesian (id_ID)
+* updated French (fr_FR)
+* updated Czech (cs_CZ)
+* added Japanese (ja)
+
+= 1.3.4.3 =
+
+* Regex annoyances anyone? Sigh. Bug fixed.
+
+= 1.3.4.2 =
+
+* Added missing filter for meta box priority.
+* Fixed bug in JS encoding.
+
+= 1.3.4.1 =
+
+* Bug in page analysis regex.
+
+= 1.3.4 =
+
+* Fix bug in custom field value retrieval for new drafts.
+* Fix bug in meta box value for checkboxes (only used currently in News extension).
+* Remove redirect added in 1.3.3 as it seems to cause loops on some servers, will investigate later.
+* Add option to filter `wpseo_admin_pages` so more pages can use WP SEO admin stylesheets.
+* Prevent notice for images without alt tags.
+* Use mb_string when possible.
+
+= 1.3.3 =
+
+* Properly `$wpdb->prepare` all queries that need preparing.
+* Fix wrong escaping in admin pointers.
+* Make %%currentdate%% and %%currenttime%% variables respect WP date format settings.
+* Add %%currentday%% format.
+* Force remove Jetpack OpenGraph.
+* Fix the weird addition of `noindex, nofollow` on URLs with ?replytocom that was added in 3.5.
+* Force XML sitemap to be displayed on the proper domain URL, so XSLT works.
+
+= 1.3.2 =
+
+* Updated wordpress-seo.pot
+* Updated Turkish (tr_TR) filename.
+* Updated Spanish (es_ES) translation.
+* Fixed bug where non-admin users couldn't save their profile updates.
+* Fixed bug with the same OpenGraph image appearing multiple times.
+* Fixed bug that would prevent import and export of plugin settings.
+* Try to do a redirect back after saving settings.
+* Properly allow for attachment pages in XML sitemaps, default them to off.
+* Fixed annoying bug where checkboxes wouldn't display as "checked" even when the value was set to true.
+* Show post type name and taxonomy name (as opposed to label) next to labels in XML sitemap settings to more easily identify post types and taxonomies.
+* Switch tracking to a daily cronjob instead of an admin process to prevent tracking from slowing down admin interface.
+* Focus keyword detection now properly works for diacritical focus keywords as well.
+* Properly apply filters to meta desc and titles in admin grid.
+* Properly detect new versions of Facebook plugin too.
+* Allow changing of the number of posts per XML sitemap, to prevent memory issues on certain installs.
+
+= 1.3.1.1 =
+
+* Some of that escaping was too aggressive.
+
+= 1.3.1 =
+
+* Fix somewhat too aggressive escaping of content.
+* Added notice text for non-existing .htaccess file / robots.txt file.
+
+= 1.3 =
+
+* Long list of small fixes and improvements to code best practices after Sucuri review. Fixes 3 small security issues.
+* Updated .pot file
+* Updated Danish (da_DK), Indonesian (id_ID), Chinese (zh_CN), Russian (ru_RU), Norwegian (nb_NO), Turkish (tr_TK), Hebrew (he_IL) and Persian (fa_IR).
+* Added Arabic (ar), Catalan (ca) and Romanian (ro_RO).
+
+= 1.2.8.7 =
+
+* Fixed %%category%% and %%tag%% and some other variables that weren't working since 1.2.8.6.
+
+= 1.2.8.6 =
+
+* Revert gplus changes in 1.2.8.5 that were causing issues.
+* Fix a tracking timeout.
+* Fix a bunch of notices throughout variables functions.
+
+= 1.2.8.5 =
+
+* Fixed a bug for MultiSite due to a missing attribute in calling `get_admin_url`.
+* Updated Hebrew (he_IL), Dutch (nl_NL) French (fr_FR), Czech (cs_CZ), Italian (it_IT), Brazilian Portuguese (pt_BR).
+* Added Norwegian (nb_NO) and Portuguese (pt_PT).
+* Added a `wpseo_robots` filter for the robots meta tag.
+* Fixed integration with the [Facebook plugin](https://wordpress.org/plugins/facebook).
+
+= 1.2.8.4 =
+
+* Fix for double title issues with themes that filter `wp_title`, by having WP SEO filter a bit later in the process
+ (but no too late because the genesis <title> wrap filter is on 30).
+* Improved Twitter Card functionality: twitter meta tags now appear above OpenGraph meta tags.
+* Updated a bunch of languages: da_DK, de_DE, es_ES, fa_IR, fr_FR, he_IL, hu, hu_HU, it_IT, nl_NL, pt_BR, ru_RU, tr_TK, zh_CN.
+* Added Czech (cs_CZ) and Spanish - Venezuela (es_VE).
+
+= 1.2.8.3 =
+
+* Updated all the languages in hopes to fix the empty text strings.
+* Added basic translations for Danish (da_DK) and Finnish (fi).
+
+= 1.2.8.2 =
+
+* "Fix" for tracking popup with empty buttons.
+
+* Updated languages
+ * French fr_FR
+ * Dutch nl_NL
+ * Turkish tr_TK
+
+= 1.2.8.1 =
+
+* Fixed one s, that caused a fatal error. Sigh.
+
+= 1.2.8 =
+
+* Bug fixes:
+ * Fix for images not showing up in XML sitemap.
+ * Fix to allow breadcrumb titles to once again be set for CPT archive pages.
+ * Prevent empty rel=publisher link from being put out.
+ * Several fixes to the strip category base settings.
+ * Several fixes to the hardcoded meta description recognition code.
+ * Prevent title testing from priming the cache.
+ * Prevent upgrading from a recent version to force a title test, no longer overrides manual force rewrite settings.
+ * Fix paginated singular post / page issue when single isn't paginated.
+* Enhancements:
+ * No longer show .htaccess editor when on NGINX.
+ * Move tracking to its own file, switching to my own tracking instead of PressTrends so more specific options can be tracked.
+ * Tracking can now be enabled and disabled from the SEO Dashboard screen.
+* Documentation:
+ * Added rewrite rules for NGINX to FAQ.
+ * Now showing rewrite rules for NGINX on XML sitemaps settings page.
+
+= 1.2.7 =
+
+* Fixed compatibility with 3.3 and lower.
+
+= 1.2.6 =
+
+* Enhancements:
+ * Added (optional) PressTrends tracking to get some more info on common configurations of WP to test for.
+ * Made config page loading even faster.
+ * Added a link to my [Amazon wishlist](http://amzn.com/w/CBV7CEOJJH98) to the plugin sidebar in the admin ;)
+ * Added a check to see whether your theme contains a hardcoded meta description, and options to remove it.
+ * Added a Google+ Publisher input option for the homepage.
+ * Added the option to allow showing the date in the snippet preview per post type.
+ * Removed aggressive cache deleting in the XML sitemaps. Could cause issues with some plugins but should increase speed.
+ * Force the XML Sitemap to return a 200 OK Header.
+* Deprecated functionality:
+ * The breadcrumbs functionality no longer automatically hooks as this was giving too many issues.
+* Bug fixes:
+ * Prevent adding trailing slash on paged posts when force trailing slash on categories and tags is on.
+ * Breadcrumbs now properly use `home_url` instead of `site_url`.
+ * Simplify shortcode stripping, to make it actually work.
+ * Prevent several notices in XML Sitemaps class.
+* Textual / documentation changes:
+ * Add a "without @" notice to the Twitter username input field.
+
+= 1.2.5 =
+
+* Bug fixes:
+ * Make sure html entities are decoded and if needed re-encoded to XML entities for the XML sitemap.
+ * Fix infinite loop in sub-category or other sub-taxonomy archive page breadcrumbs.
+ * Fix breaking rewrite for categories when strip category is enabled.
+ * Fix non-global classes that should be global.
+* i18n:
+ * Updated French translation.
+ * Updated Bosnian language code and deleted unused Bosnian version.
+ * Updated Bulgarian and deleted unused Bulgarian `bul` version.
+ * Updated and completed it_IT and pt_BR translations.
+
+= 1.2.4 =
+
+* Bug fixes:
+ * Properly restore $wp_query after running header functionality, so we're not breaking badly built themes.
+ * Make the title test helper function only return the test title to the right WordPress user-agent.
+ * Fix for slug saving that should help interoperability with other slug-enhancing / changing functions.
+ * Fix wrong homepage titles with 12345 in them because of bug in 1.2.2.
+ * Added text domains on some strings that were missing it.
+ * Replace `split` with `explode` as `split` is deprecated in newer PHP versions.
+ * Properly deal with shortcodes with text inbetween.
+ * Remove several functions that are no longer used.
+ * `%%page%%` only outputs '`%%sep%%` Page 2 of X' when not on page 1. If you want it to show up on Page 1 you can use `%%pagetotal%%` and `%%pagenumber%%`. This both works for paginated posts & pages too.
+ * Allow for rel=author on sites with static frontpage too.
+* Enhancements:
+ * Massive updates to how parts of the plugin are loaded, leading to a reduction of memory usage in WordPress admin of 1~2 MB.
+ * Respect `DISALLOW_FILE_MODS` and `DISALLOW_FILE_EDIT` constants. When set to true, edit files menu option won't show.
+ * Added support for image galleries in the page analysis functionality, alt tags from images in galleries are now parsed too.
+ * Add an option to remove the `replytocom` variables from comment links (they're bloody stupid).
+ * Added variables `%%pt_single%%` and `%%pt_plural%%` which output the single and plural label of the current post type (useful for post type archives).
+ * Made the default settings smarter, they'll now use better titles _and_ will set titles for each public post type, post type archive and taxonomy.
+ * Updates to introductory tour.
+ * Added PHPdoc to the entire plugins codebase.
+ * Refactored all of the code not using WordPress code style.
+ * Breadcrumbs now use [RDFA](http://support.google.com/webmasters/bin/answer.py?hl=en&answer=185417) and have been completely rewritten for speed and more filter options.
+* i18n:
+ * Updated Russian translation.
+ * Better language codes for Hungarian and Bulgarian added.
+ * Updated .pot file.
+
+= 1.2.3 =
+
+* Bug fixes:
+ * Fixed possible bug on multi site.
+ * Fixed a bug in alt & title elements for XML sitemaps.
+ * Fix to force title rewrites in two places: call `wp_reset_query` for stupid themes and plugins.
+ * Fixed bug in saving some options.
+ * Fixed OpenGraph bug where default image wouldn't be used for post without images.
+ * Prevent error on division by zero when swapping around text.
+ * Prevent notice in title_test and also fix possible bugs.
+ * Properly escape the delimiter too in all `preg_quote` calls.
+ * Don't show SEO filter on upload.php.
+ * Only count alt tags in keyword density and word count calculations, leave out title attributes.
+ * Rewrote the force rewrite test to be simpler and better.
+* Enhancements:
+ * New icons for the analysis functionality.
+ * Twitter card functionality.
+ * Removed 200 lines of code from OpenGraph class because I could just inherit it from the parent class. d0h!
+ * Added a *bunch* of translations; bg, bos, bs, bul, es_ES, hu, hun, id_ID, pl_PL, pt_BR.
+ * Updated another bunch: de_DE, fr_FR, he_IL, it_IT, nl_NL, ru_RU, sv_SE.
+
+= 1.2.2 =
+
+* Some small bug fixes.
+* Made loading the TextStatistics class conditional on the existence of said class.
+* Added a posts filter option for SEO quality in the edit posts / pages overview.
+* Added a filter `wpseo_use_page_analysis` that disables the page analysis functionality when false is returned.
+* Added a filter `wpseo_show_date_in_snippet` that disables the date in the preview snippet when false is returned.
+
+= 1.2.1 =
+
+* Bugs fixed:
+ * Trim the focus keyword before running page analysis tests.
+ * Title's should be (and now is) Titles.
+ * Fixes to Theme integrations for Woo, Genesis and Thematic.
+ * Enhancement to force rewrite title test.
+
+= 1.2 =
+
+* Bugs fixed:
+ * ereg_replace != preg_replace ; in other words: alt and title tags for images in xml sitemap fixed.
+ * Image size for OpenGraph now defaults to medium for thumbnail image.
+ * Selecting a Facebook App as the admin of your site now actually works.
+ * Saving the SEO -> Dashboard settings no longer makes you loose the SEO -> Social settings.
+ * Tweaks to clean slug functionality.
+ * Fix for UTF-8 terms in titles and descriptions.
+ * Fixed bug where empty but saved title template settings could lead to empty homepage title on blogs with a static front page.
+ * Fixed several bugs around page numbers in titles and descriptions.
+ * Prevented an error in the opendir functionality for WP SEO modules.
+ * Allow ';' in focus keyword.
+ * Don't double encode characters in suggest functionality.
+ * Don't remove non-alphanumeric values for keyword checks.
+ * Fixed a bug in snippet preview occurring when content was shorter than max snippet length.
+ * Fixed keyword count in content for cases where keyword was surrounded by parentheses and some other characters.
+ * Loads of Regex Fu to improve keyword bolding.
+ * Activation and deactivation handlers properly specified.
+ * WP Super Cache now properly emptied on update of settings.
+ * Fixes to OpenGraph images for homepage.
+ * Fixed a notice in OpenGraph class on 404 pages.
+ * Fixed notices in OpenGraph admin when selecting Facebook app as admin.
+ * Fixed a bug where half the Page analyses wouldn't work when the visual editor is disabled.
+ * Changed the mime type of the XSL file for XML sitemaps to text/xml, so Firefox will display them properly.
+ * Made sure the default OpenGraph image will always show up when there's no other image.
+ * Updated tablesorting JS used in XML Sitemaps.
+* Enhancements:
+ * Added a page level "score" for the site analysis functionality.
+ * Allow sitewide noindexing of post types and taxonomies with post- and term-level overrides.
+ * Automatically check whether force rewrite needs to be enabled.
+ * Upon activation, XML sitemaps are automatically enabled.
+ * Upon activation, title templates are pre-filled with sensible defaults.
+ * Plugin now auto-detects whether titles need to be force-rewritten (using output buffering) or not.
+ * Redesign of the admin, removal of the indexations page and renaming the Titles page into Metas & Titles page.
+ * Allow noindex-following per custom taxonomy and custom post type.
+ * No longer show non-public post types on the Titles & Metas page.
+ * On activation, W3 Total Cache or WP Super Cache cache gets cleared automatically.
+ * Added an uninstall handler, deleting the plugin through the WP backend will now delete options from the DB too.
+ * Added the option to display custom taxonomy in titles and descriptions, use `%%ct_<custom-taxname>%%` for a comma separated list or `%%ct_<custom-taxname>%%single%%` for only one taxonomy term.
+ * Added the option to display custom taxonomy descriptions in post description fields, use `%%ct_desc_<custom-taxname>%%`.
+ * Allow for 'Page x of x' to be localized too.
+ * Force the query for the current page to be used instead of the query that a bad plugin or even theme was using by calling `wp_reset_query` before the header functionality.
+ * If you're a Woothemes user and you activate WordPress SEO, the "use 3rd party plugins data" checkbox will be checked on upon activation.
+ * Non front page blog pages now have a title template: `%%title%% %%page%% - %%sitename%%` if they don't have a specific SEO title and there is no page template.
+ * Pinging search engines on post of new content now moved to cron to prevent lag.
+ * Only embed images in the XML sitemap that match the main domain, subdomains should not matter but images from other domains are now ignored.
+ * Fixed a bug where homepage wouldn't be in the XML sitemap when there are no posts, yet the homepage is set to display recent posts.
+* API Improvements:
+ * Added a filter to allow adding URLs to specific XML sitemaps, see [this thread](http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-how-to-add-a-non-wordpress-page-to-the-sitemap). The filter is `wpseo_sitemap_<$post_type>_content`.
+ * Added a filter for the meta keywords, `wpseo_metakey`.
+ * Added a filter to allow disabling `rel="next"` and `rel="prev"` links, use `wpseo_<prev|next>_rel_link`.
+ * Added a filter `wpseo_xml_sitemap_img_src` to allow changing the hostname of images, most common use case is to force them to the CDN.
+ * See the new [WordPress SEO API docs](http://yoast.com/wordpress/seo/api-docs/).
+
+= 1.1.9 =
+
+* Sigh... Sorry about that.
+
+= 1.1.8 =
+
+* Bugs fixed / Changes:
+ * Fix the clean slug function to not freak people out and remove chars.
+ * Fixed a couple of notices.
+ * Allow '+' in focus keyword.
+ * A *load* of i18n fixes (including a new POT file).
+
+= 1.1.7 =
+
+* Bugs fixed / Changes:
+ * Fixes issue with un-paginated canonicals for paged single posts / pages.
+ * Fixes %%page%% variable to work in title & description template on paginated singular post(type)s.
+ * Allow - in focus keyword.
+ * Removed the option to use a Facebook page as an admin in the Facebook OpenGraph, as Facebook deprecated that.
+ * Force OpenGraph locale to lowercase.
+ * Catch some weird locales and convert them to proper Facebook supported iso_country versions.
+ * Now adding _all_ the images in a post with an og:image tag, so people can more easily share the right image.
+ * Allow regex specific characters in the focus keyword for the Page Analysis checks.
+ * Add proper (and i18n compatible) [stop word](http://en.wikipedia.org/wiki/Stop_words) removal.
+ * Removed code to add noindex to login page as that's now in core for long enough.
+ * Fixed several notices.
+ * When a static homepage has no SEO title, default to the site's name + description.
+ * Only show images once in OpenGraph tags.
+ * Prevent a timeout on retrieving term meta.
+ * Don't do next / prev links on the homepage for Genesis based themes as that leads to trouble.
+ * XML Sitemaps & feeds:
+ * Properly fix featured image showing up in XML Sitemap.
+ * Optimized the main query for XML sitemaps per [this thread](http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-performance-suggestion).
+ * Switch feed noindexing from `xhtml:meta` tags to X-Robots-Tag HTTP headers to prevent feed display issues.
+ * Force XML Sitemap descriptions for images to be clean to prevent XML parse errors.
+ * Tiny change in CSS for explanatory text in XSL.
+* New filters:
+ * Add filter `wpseo_locale` for the locale in the opengraph settings.
+ * Add filter `wpseo_metabox_prio` to allow WP SEO metabox priority to be changed.
+* Documentation:
+ * Removed the "Other Notes" tab from the plugin page, enough tabs there already.
+ * Added %%cf_ options to config page for titles.
+ * Fixed the Yoast Facebook URL.
+ * Changed plugin support link to [the new support URL format](http://wordpress.org/support/plugin/wordpress-seo).
+
+= 1.1.6 =
+
+* Tiny fix to showing meta description on posts page.
+* Fix for showing proper link to bug tracker.
+* Fix for redirecting attachment pages when they don't have a parent.
+* Fix for titles of custom post type pages.
+* Dozens more tiny bugfixes.
+
+= 1.1.5 =
+
+* Removing the Dashboard widget.
+
+= 1.1.4 =
+
+* Removed the canonical redirect as that was screwing with people's setups (and minds).
+
+= 1.1.3 =
+
+* Fix for the blog homepage title, description etc.
+* Added several filters for use in the soon to be released [Video SEO module](http://yoast.com/wordpress/seo/video-seo/).
+* Change to XSL for XML Sitemaps.
+* Non-canonical hostnames (like example.com when site setting is www.example.com) now 301 redirect to canonical hostname.
+* Static frontpages now added to XML sitemap *with* images if it has them, same for interior blog pages.
+
+= 1.1.2 =
+
+* No longer add redirected URLs to the XML Sitemap.
+* Plugin now properly adds images in galleries to the XML Sitemap too.
+* Fixed a bug in home page title logic, affecting blogs with a static front page and a separate posts page, who were unable to set the posts page's title.
+* Fixed a bug in the OpenGraph admin implementation.
+* Google Suggest works again, and properly this time.
+* Using entities in the snippet preview title & description field now still renders a correct "chars left" count.
+* Replaced the last single quotes with double quotes for meta fields, apparently Bing wouldn't verify because of the single quotes in the verification line.
+* Added option to verify your site with Alexa, as a lot of people requested this.
+
+= 1.1.1 =
+
+* Fixed `sprintf` bug in page analysis.
+* Fixed bug that caused inability to edit / save the search page title.
+* Fixed bug that caused inability to edit / save the "parent" blog in multisite settings.
+* Removed ability to edit WP Super Cache .htaccess file as it doesn't seem to be there anymore.
+* Removed Yahoo! Site Explorer.
+
+= 1.1 =
+
+* Biggest change: entire plugin now supports localization, testing can be done by dropping your properly named .mo file (wordpress-seo-nl_NL.mo for instance) into the languages dir.
+* Added Dutch, German, French, Hebrew, Italian, Russian and Swedish translations.
+* Show proper post type names in admin menu for disabling editor box and hide non-public post types (props Nacin).
+* Disabling the Advanced Editor now only disables it for non admins.
+* Replaced single quotes with double quotes in meta description and canonical, to please [Danny Sullivan](http://searchengineland.com).
+* Fixed issue with images in XML sitemaps.
+* Added Social menu item.
+* Lots of changes to the Facebook integration:
+ * Added the locale.
+ * Added default image.
+ * Added homepage image & description.
+ * New method of adding Facebook Admins.
+* Removed all nofollow settings, login register links are now nofollow by default, all other functionality removed as it doesn't make sense anymore.
+* Cosmetic changes: added some Yoast links to backend widget in the right sidebar.
+* Switched text domain from a constant to a proper string 'wordpress-seo'.
+* Removed the now unneeded extra styling for admin pointers.
+* Fixed a notice in 3.3beta with caused by `wp_reset_query` being called too early.
+* Added several filters to allow WPML (and other plugins) to create XML Sitemaps for other languages.
+* Minor updates to the plugin tour, removing the "Stop tour" button, "Close" now closes until you manually restart.
+
+= 1.0.3 =
+
+* This release is dedicated to removing options and setting defaults instead. Specifically, the following settings are now defaults without options to change them:
+ * Images are now always included in the XML sitemap as Bing no longer breaks on them.
+ * Pinging Google and Bing is now a default action as that's the whole point of having XML sitemaps.
+ * RSS feeds are now always noindex, followed. No search engine should ever list an RSS feed as a result in the resultpages.
+ * Admin, login and registration pages are always noindexed now for the same reason.
+ * Search result pages are now always noindex, follow.
+ * Subpages of the homepage are now also noindex, follow. It just doesn't make sense to index,follow those especially with the new rel="prev" and rel="next" changes.
+* The option to add `nosnippet` and `noarchive` meta tags sitewide has been removed. No one in his right mind would want to do that, and if you do, then adding the robots meta to your template yourself should be doable.
+* Pinging search engines with your RSS feed is no longer part of this plugin (as its unneeded because the XML sitemaps do that).
+* UTF-8 characters in RSS footer no longer break.
+* Added the option to use the author name and link in the RSS header and footer using %%AUTHORLINK%%.
+* No longer show the admin bar SEO menu for subscribers.
+* Some style changes to make the backend look nicer in WP 3.2 and 3.3.
+* Bug with importing settings fixed.
+* No longer redirect attachments that have no parent.
+* Correctly grab thumbnail for OpenGraph settings.
+* Pages now get the correct priority (0.8).
+* Added the option to noindex or completely disable post format archives.
+
+= 1.0.2.3 =
+
+* Fixed some edge cases with rel=next and rel=prev, particularly relating to a paginated page as a frontpage.
+* Updated the snippet preview so the date part of the snippet has the right color.
+* Fixed a bug in the update routine that could cause errors in the backend.
+* Enabled OpenGraph meta box on edit screens, first "stab".
+
+= 1.0.2.2 =
+
+* Removed all rel=index, rel=prev etc references that WP core currently uses as they're wrongly implemented.
+* Added rel=prev and rel=next for paginated posts and pages.
+* Removed the interface options for all rel= links, they're off by default now.
+* Removed the option to hide the version number. It's very easy to detect the version number anyway so let's not clutter the interface.
+
+= 1.0.2.1 =
+
+* I missed a case where the next link would point to the current page, fixed it though :)
+
+= 1.0.2 =
+
+* Fixed keyword in slug detection on non-post post types.
+* Optimizations in canonical functionality, adding canonicals to author archives and more.
+* Added prev and next links as [suggested by Google](http://googlewebmastercentral.blogspot.com/2011/09/pagination-with-relnext-and-relprev.html).
+* Fixed issue with unescaped characters in title and description when updating edit screen.
+* Posts with a canonical set to another URL are no longer included in the XML sitemap.
+
+= 1.0.1 =
+
+* Fixed a bug where canonical would be set to /page/1/ on archives.
+* Fixed an error for blocking files.
+* Fixed a JS error that would cause the link editor in the rich editor not to work.
+* Fixed possible error when reading settings have gone haywire (ie not 'posts' or 'page' on front but something else).
+
+= 1.0 =
+
+* I now dare call this plugin STABLE. This doesn't mean there are no more bugs, it does mean that I think it won't break sites anymore.
+* Fixed the import/export settings functionality.
+* Updated the snippet preview to Google's new snippet design.
+
+= 0.4.3 =
+
+* Heavily, very heavily, reduced memory usage in XML Sitemap generation.
+
+= 0.4.2 =
+
+* Fixes:
+ * XML Sitemaps:
+ * Bug in redirection of www to non-www
+ * Removed no longer needed robots_txt code.
+ * Proper flushing of rewrites.
+ * Fix for sites using index.php in permalinks.
+ * Moved XML settings to its own options array, cleaning up the settings. This also allows you to save XML Sitemap settings again.
+ * Sitemaps now contains all the last updated posts (under 1,000) of one post type in one new XML sitemap, so SE only has to crawl one sitemap per post type.
+ * XML Sitemap Index file is now being pushed into cache if a caching plugin is active (by loading it through a cron job).
+ * No longer breaks when `get_post_type_archive` doesn't exist.
+ * Metaboxes / Edit Post:
+ * Proper escaping of attribute values / already filled boxes.
+ * Bug in bolding / keyword recognition JS code.
+ * Background of tab content is now white again.
+ * Taxonomies:
+ * Fixed bug that could prevent noindex from showing up.
+ * Other:
+ * Admin menu works properly again and has XML Sitemaps menu added.
+
+= 0.4.1 =
+
+* Fixes bug in saving XML Sitemap and Dashboard settings.
+* Forces flush of rewrite rules so XML sitemaps start working immediately after enabling.
+* Adds a line of copy to the XML Sitemaps page to point the user at the index sitemap file.
+
+= 0.4 =
+
+* Fixes:
+ * XML Sitemaps:
+ * Complete rewrite of the XML Sitemap system, now using a sitemap index file and sitemap files per taxonomy and post type. Way more scaleable and awesome.
+ * Updated the XSL to work with Sitemap Index file too.
+ * Added functionality to remove old style & potential blocking XML sitemaps.
+ * Removed all code that wrote files, as it's no longer needed.
+ * Removed all functionality for updating sitemaps after publish, as it's no longer needed (search engines will still be pinged though).
+ * Breadcrumbs:
+ * Support for bbPress (the plugin), breadcrumbs.
+ * Fixed bug with blog URL appearing for non-post post_types.
+ * Fixed bug with post ancestors being in wrong order.
+ * Removed erroneous var_dump.
+ * Bug with title for homepage when using page as homepage.
+ * OpenGraph:
+ * Moved all OpenGraph code to specific OpenGraph class.
+ * Added option to specify and add FB Page and App ID and FB admin ID or ID's.
+ * Page Analaysis:
+ * Fixed bug in detection of headings with an ID or other attribute.
+ * Several performance optimizations to class includes.
+ * Some fixes in JavaScript keyword detection and keyword bolding in snippet when using colon and semicolons etc in title or meta description.
+ * Tiny CSS fixes so it all looks nice in WordPress 3.2.
+
+
+* Also:
+ * XML Sitemaps now have their own settings page.
+ * Plugin version is now stored in the options for the plugin to allow easy upgrade.
+ * Added the option to use custom fields in title and description templates. Use `%%cf_<custom-field-name>%%` and it'll be replaced with your custom field. So for instance %%cf_city%% when your custom field is named "city".
+ * Removed some of the tabs and empty lines the plugin was outputting.
+ * Added some filters:
+ * `wpseo_sitemap_urlimages` so you can add images to the sitemap, found in inc/class-sitemaps.php
+ * `wpseo_title` and `wpseo_metadesc` in frontend/class-frontend.php
+
+
+= 0.3.5 =
+
+* Fixes:
+ * Issue in post / page editor with link dialog and other plugins using jQuery UI.
+ * No longer uses Google JS API for jQuery UI, but just include jQuery UI autocomplete library and uses the rest from WP core.
+ * No longer uses Google JS API for jQuery UI CSS, but included the needed classes in the plugins CSS files.
+ * Properly update sitemap for custom post types on publication.
+ * Fixed a notice in heading detection when no headings were found.
+ * Fixed a typo in Page Analysis messages.
+
+= 0.3.4 =
+
+* Fixes:
+ * CSS issue caused by 0.3.3.
+ * Some security issues, thanks Jon Cave and Andrew Nacin for pointing them out and helping to fix!
+
+= 0.3.3 =
+
+* Fixes:
+ * CSS collisions due to too generic class names.
+ * Issue with outbound link recognition caused in 0.3.2.
+ * Improved first paragraph detection.
+ * Word count is now correct for non western european languages too.
+ * Keyword detection in content and first paragraph in Cyrillic, Russian and other languages.
+ * Handling of UTF-8 slugs.
+ * Proper detection of keywords underneath the focus keyword input field by using word boundaries.
+ * Proper keyword bolding and counting in snippet preview for Cyrillic, Russian and other languages.
+ * Proper counting of length of description and title for strings with accents or non-ASCII characters.
+ * No longer calculating Flesch score for non-English, as it's not correct.
+ * Related keywords button works again.
+ * Several performance improvements to edit post page JavaScript.
+
+* Also:
+ * Notice now properly tells you to save as draft or update a post to see new Linkdex analysis. No, it's not going to be AJAX, ever, for performance reasons.
+
+= 0.3.2 =
+
+* Fixes:
+ * Instant update of snippet preview now also works when editing post excerpt.
+ * Bugs with non-ASCII characters in the focus keyword and keyword recognition.
+ * Issues with themes using `cat` in search, most specifically the ClassiPress themes.
+ * The snippet preview no longer crashes when you use "on", "strong" or any other term contained in "strong" in your focus keyword.
+ * Made the Linkdex check for keyword in URL work in all occasions.
+ * Notices on XML import.
+ * Issue with title of blog page on paginated pages.
+
+* Also:
+ * Removed 3 no longer needed JavaScript files.
+ * Added the new bug tracker to the plugins sidebar notice under support.
+ * Canonical now has a filter (`wpseo_canonical`) so you can remove it or change it.
+
+= 0.3.1 =
+
+* Fixes:
+ * Bug in meta box JS code causing annoying issues in Firefox and Internet Explorer.
+ * Issue with outbound anchor text detection in Linkdex Page Analysis.
+ * Small bug in detection of keyword in Title when keyword contained dashes or other non alphanumeric chars.
+
+= 0.3 =
+
+* Major new feature: Linkdex Page Analysis has been integrated into WordPress SEO.
+
+* Other new features:
+ * You can now noindex, follow all sub pages of archives, taxonomies and categories.
+
+* Fixes / Updates:
+ * Snippet will now use ellipsis when post title or meta description is too long.
+ * Various JS optimizations in snippet preview generation.
+ * Snippet length will once again correctly correct for date inclusion.
+ * Date in snippet will be the current date when post is an unpublished draft.
+ * Preventing some errors when there's no focus keyword.
+ * Keyword detection in the URL now works correctly.
+ * URLs are no longer (incorrectly) "shortened".
+ * Fixed possible infinite loop in editor when adding focus keyword, which was causing FF crashes.
+ * Fixed count of keyword in content by removing tags properly.
+ * Fixed issues with HTML tags appearing in snippet inappropriately.
+ * Switched Google Suggest autocompletion for focus keyword to use [Google Suggest jQuery](http://code.google.com/p/googlesuggest-jquery/).
+ * No longer relies on `is_post_type_archive` to work, so compatible with WP 3.0 again.
+ * On themes that badly include $post in the head a redirect no longer occurs if the last post was redirected.
+ * SEO Settings menu in Admin Bar will no longer show for people that don't have the rights needed to use it.
+ * No longer wrongly showing image for last post in OpenGraph when on front page.
+ * No longer redirecting taxonomy feeds wrongly under some conditions with clean permalinks enabled.
+ * Fixed wrong titles for feeds.
+
+= 0.2.5.4 =
+
+* Custom post type archives update:
+ * You can now set a title, meta description and breadcrumbs title for custom post type archives on the Titles page
+ * Custom post type archives now have a correct canonical
+* Other fixes:
+ * Fixed a notice for an uninitialized setting
+ * Slightly changed the CSS for the admin area
+ * Changed input fields for meta descriptions on titles page from text input to textarea
+ * More preparations for allowing the plugin to be fully translated
+
+= 0.2.5.3 =
+
+* Bugs fixed:
+ * `edit_posts` is not the same as `edit_post`, what a difference an s makes, thanks to nacin.
+ * preview URLs now work again with clean permalinks on, but only for those with the actual rights to view them.
+ * Fix for catchable fatal error in canonical function.
+ * First stab at fix for errors with focus keyword check and keywords filled with HTML tags.
+ * canonical links for paginated posts are now set correctly.
+ * Issue where parent taxonomy items would display in the reverse order in breadcrumbs.
+ * Improved error handling for non PHP 5.2+ installs and also made automatic deactivation work properly.
+
+* Documentation fixes:
+ * Added screenshot and a FAQ with several items to this plugin's page.
+
+= 0.2.5.2 =
+
+* This WordPress SEO plugin now officially requires PHP5. WordPress version 3.2 will also require it, so you'd better upgrade now. If you're on any version lower than PHP 5.2, this plugin will deactivate itself. You can then use SEO Data Transporter to migrate your data to another plugin, or you could do the more sensible thing and get your hosting upgraded to PHP 5.2 ASAP.
+
+* Bugs fixed:
+ * Attempted compatibility fix with other plugins that hook into robots.txt functionality.
+ * Issue with not loading meta box for some custom post types.
+ * Issue where SEO data for custom post types would not save.
+ * Issue where post title in snippet preview would show %%sitename% instead of your site's name when no title template for post type was set.
+ * Issue with removing tags when string was empty in JavaScript.
+ * Hiding the post_format taxonomy on the right places.
+ * Should now work better with crappy themes that do weird things in header.php.
+
+* Design fixes:
+ * Updated tabs in meta boxes to reflect Core UI, for more info see [this post](http://developersmind.com/tabbed-meta-box-in-wordpress/) by Pete Mall.
+
+* Enhancements:
+ * Now using plugin version number to enqueue files so browser cache isn't in the way when upgrading.
+ * Loading scripts in footer now when possible.
+
+= 0.2.5.1 =
+
+* Security fixes:
+ * Added nonces for security to htaccess and robots.txt file editing to prevent possible CSRF.
+
+* Bugs fixed:
+ * Prevent JS error when WordPress SEO Meta box was hidden on edit pages.
+ * Fix for title in snippet preview not showing when no title template was set for the post type.
+ * Fix for focus keyword count, would give wrong return on slugs that were too long.
+ * Removed post_format from list of taxonomies you can edit title and meta desc template for as it's of no use.
+ * Removed post_format from list of taxonomies to exclude for XML Sitemap, it was already excluded by default.
+
+* New features:
+ * Option to disable WordPress SEO meta data box on (custom) taxonomies.
+
+= 0.2.5 =
+
+* Bugs fixed:
+ * Snippet preview quicker than ever and it no longer blows up some browsers: all AJAX calls have been removed and the entire process is done with JavaScript within the browser.
+
+* Feature enhancements:
+ * The meta box on the edit posts page now features tabs, thanks to Pete Mall (even works nicely in the blue theme).
+ * Advanced features moved to their own tab instead of button.
+ * Focus keyword now shown straight below snippet preview.
+
+* Other news: The Google News module almost ready for mass-deployment. Stay tuned.
+
+= 0.2.3.4 =
+
+* Added WPML config file, so you can SEO in multiple languages.
+
+= 0.2.3.3 =
+
+* Bugs fixed:
+ * Comma in priority in sitemap which should be dot. I hate European servers.
+ * Fixed a notice in generating a path to the wpseo directory in the uploads dir.
+ * Fixed a rather annoying XML Sitemap date issue, props to [Staze in wp.org forums](http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-sitemapxml-wrong-date-for-blog?replies=8).
+
+* Feature enhancement:
+ * Breadcrumbs now output links to post type archives too.
+
+= 0.2.3.2 =
+
+* Bugs fixed:
+ * Fixed race condition where sitemap wouldn't load sometimes.
+ * ... in snippet preview are now bold as they should be.
+ * Desc and Title in snippet preview should now update less frequently to prevent crashing low mem browsers.
+
+= 0.2.3.1 =
+
+* Bugs fixed:
+ * Error in saving certain data when it was a checkbox.
+ * Fixed notice for non-existing title and for empty metakey.
+ * Fix for an error that could occur when the post thumbnail functionality is not active.
+* Changes:
+ * Added page numbers to default titles for taxonomies and archives.
+
+= 0.2.3 =
+
+* New features:
+ * First stab at (Facebook) OpenGraph implementation.
+ * Meta Description can now be returned, using `$wpseo_front->metadesc( false );` for use elsewhere.
+ * Plugins can now register their own variables to not be cleaned out when permalink redirect is enabled.
+
+* Bugs fixed:
+ * Deleting the dashboard widget will now really delete it.
+ * Some fixes for notices.
+ * Strip tags out of titles.
+ * Use blog charset for XML Sitemap instead of UTF-8.
+ * Import of Meta Keywords fixed.
+ * Small fix for possible error in AJAX routines.
+ * Breadcrumb now actually returns when you ask it to.
+ * Fixed some errors in JavaScript of title generation within snippet preview.
+ * Removed SEO title from post edit overview as you couldn't edit it there anyway.
+
+* Documentation fixes:
+ * Added an extra notice to clean permalink to let people know they're playing with fire.
+ * Small improvement to error handling for upload path.
+
+= 0.2.2 =
+
+* Bugs fixed:
+ * Disabling sitemaps now properly does what it says on the tin: disable sitemaps.
+ * Properly return title for homepage in rare instances where `is_home` returns true for front page even when front page is set to static page (yes, that's a WordPress bug I had to work around).
+ * An empty title separator will now be changed to ' - ' so titles don't get all borked.
+ * Several fixes in rewrites for MultiSite instances.
+ * Option to force http or https on canonical URLs.
+ * Several other bugfixes.
+
+= 0.2.1 =
+
+* Bugs fixed:
+ * Plugin frontend URL should now be properly defined for sites with https admin.
+ * Manually entered category title now actually works.
+ * Import now works properly again for HeadSpace and AIOSEO, even for meta keywords.
+ * Fixed typo in *wpseo-functions.php*, apparently `udpate_option` is not the same as `update_option`.
+ * Fixed a notice about date snippet.
+ * Fixed a notice about empty canonical.
+ * Prevent cleaning out the WP Subscription managers interface for everyone.
+ * Meta keywords are now properly comma separated.
+ * Year archives now give proper breadcrumb.
+ * Nofollowed meta widget actually works now.
+ * %%date%% replacement in templates improved significantly.
+ * Shortcodes stripped out in generation of title & description templates.
+
+* Changes:
+ * Moved all rewrites to their own class, *inc/class-rewrite.php*.
+ * Further improved error handling when *uploads/wpseo* dir creation fails.
+
+* New features:
+ * Remove category base, removes `/category/` from category URL's. Find it under Permalinks. Props to [WP No Category Base](http://wordpresssupplies.com/wordpress-plugins/no-category-base/) for having the cleanest code I could find in this area, which I reused and modified.
+ * Admin bar goodness: an SEO menu! Try it if you're on 3.1 already, it allows you to perform several SEO actions!
+
+= 0.2 =
+
+* Bugs fixed:
+ * Chars left counter works again as you type in title and SEO title.
+ * No longer error out when unable to delete sitemap files in site root.
+ * Fixed error when `memory_get_peak_usage` doesn't exist (below PHP 5.2).
+ * Fixed error when Yoast News feed couldn't be loaded.
+ * Fix for people who agressively empty their dashboards.
+ * Permalink redirect fix for paginated searches.
+
+* Changes:
+ * Plugin now properly reports which sitemap files are blocking it from working properly and asks you to delete them if it can't delete them itself.
+ * Some cosmetic fixes to dashboard widget.
+ * Removed some old links to Yoast CDN and replaced with images shipped with plugin, for SSL backends.
+ * New general settings panel on WPSEO Dashboard which allows you to disable WordPress SEO box on certain post types.
+ * Option to use focus keyword in title, meta description and keyword templates.
+ * Changed the hook for the permalink cleaning from `get_header` to `template_redirect`, which means it redirects faster and is less error prone.
+
+* New Features:
+ * Added option to export taxonomy metadata (PHP 5.2+ only for now).
+ * Meta keywords are now an option... I don't like them but there's sufficient demand apparently. Works for homepage, post types, author pages and taxonomies.
+ * Added an option to disable the advanced part of the edit post / page metabox.
+ * Added option to disable date display in snippet preview for posts.
+ * Multisite Network Admin page added, with three features:
+ * The option to make WordPress SEO only accessible to Super admins instead of site admins.
+ * The option to set a "default" site, from which new sites will henceforth acquire their settings on creation.
+ * The option to revert a site to the "default" site's settings.
+
+= 0.1.8 =
+
+* Notice: The functionality in the post / page editor has changed quite a bit. Meta descriptions are now generated using the meta descriptions template if no meta description is entered, so it will for instance use the post excerpt, the SEO title is no longer filled automatically BUT it is properly shown in the snippet preview based on your title template. It should work faster, more intuitive and just better in general, but I do need your feedback, let me know if it's an improvement.
+
+* Bugs fixed:
+ * Fixed a notice for non existing metadesc.
+ * Fixed several notices in title generation.
+ * Directory paths in backend now properly recognized even when erroneously set to 1.
+ * Fixed bug where frontpage title wouldn't be generated properly.
+ * Made sure unzip of settings.zip (for settings import) works properly everywhere (by getting rid of `WP_Filesystem` and `unzip_file()`, as they do not work reliably).
+ * Made sure meta descriptions are not shown on paged archives or homepages.
+
+* Changes:
+ * Admin:
+ * Moved image used in news widget into images directory instead of loading from CDN to prevent https issues.
+ * Breadcrumbs:
+ * Creating proper breadcrumbs for daily archives now (linking back to month archives).
+ * Post / Page edit box:
+ * Meta description now properly generated using template for that particular post_type.
+ * SEO Title is no longer auto filled, if you leave it empty "normal" title template generation is used.
+ * Several improvements to javascripts.
+ * Titles, Meta descriptions & Canonicals:
+ * Speed up of variable replacement for titles and meta descriptions.
+ * In fallback titles (when there's no template), plugin now sticks to `$sep` defined in `wp_title`.
+ * Now properly generating canonical links for date archives.
+ * The %%date%% variable now works properly on date archives too.
+ * Added new filter to make title work properly on HeadWay 2.0.5 and up.
+ * Fixed canonical and permalink redirection for paginated pages and posts (props to @rrolfe for finding the bug and coming up with first patch).
+ * XML Sitemaps:
+ * During sitemap generation, plugin now checks whether old sitemap.xml or sitemap.xml.gz files exist in root and deletes those if so.
+ * Made including images optional.
+ * Made it possible to pick which search engines to ping.
+ * Fix in XSL path generation on HTTPS admin backends when frontend is normal HTTP.
+ * XML Sitemap update on post publish now actually works properly.
+ * No longer are XML Sitemaps enabled automatically when publishing a post (sorry about that).
+
+= 0.1.7.1 =
+
+* Apparently `is_network_admin()` didn't exist before WP 3.1. D0h!!!
+
+= 0.1.7 =
+
+* Bugs fixed:
+ * Empty Home link when blog page is used and no settings have been set.
+ * Fixed couple more notices (well, like, 10).
+ * Bug in directory creation that would create the directory correctly but still throw an error and save the path wrongly to options.
+ * Dismissing Blog public warning was only possible on SEO pages, now it's possible everywhere.
+ * Excerpts, when used in description, are now properly sanitized from tags and shortcodes.
+ * Properly fallback to `$wp_query->get_queried_object()` instead of `get_queried_object()` for < 3.1 installs.
+ * Fixed several bugs in title generation, making it more stable and faster in the process.
+ * Properly escape entities in page titles, both in front end and in posts overview.
+
+* Changes:
+ * Latest news from Yoast now appears on Network Admin too, and you can disable it there and on normal admin pages individually. First step towards getting a Multi Site Network Admin SEO page.
+ * Added a "Re-test focus keyword" button for people using the Rich Text editor, which wasn't sending update events properly.
+
+= 0.1.6 =
+
+* New features:
+ * Export & Import your WordPress Settings easily.
+ * You can now supply extra variables to prevent from being cleaned out when clean permalinks is on.
+
+* Bugs fixed:
+ * No longer throw errors when wpseo dir cannot be created.
+ * Your blog is not public warning can now be properly dismissed.
+ * Fixed rewrite issues: apparently if you only load rewrite rules on the front-end, they don't get added when changing rewrites in the backend. D0h.
+ * Rewrite rule for sitemap is now forced even harder when regenerating sitemap by hand.
+ * Search permalinks now work properly, though in "old" ?s=query style, because of a bug in core.
+ * Breadcrumbs no longer errors when term that is supposed to show is empty.
+ * Enabling breadcrumbs without setting any of the text fields no longer gives notices but proper defaults.
+ * Proper fallback for get_term_title for pre WP 3.1 sites with custom taxonomies.
+
+* Changes:
+ * You can now dismiss settings advice.
+ * You can now fix some of the settings advice just by clicking the button.
+ * You can now make posts, pages and taxonomy terms of any kind always appear in sitemap even if they're noindex, or never, set on a piece by piece basis.
+ * Permalink changes now invoke immediate XML sitemap update.
+ * Added canonical url to the blog page if using a static page for front page (props [@rrolfe](http://twitter.com/rrolfe)).
+ * Removing RSS feeds now actually works (props @rrolfe).
+ * Added breadcrumb for 404 pages (props @rrolfe).
+ * Drastically reduced memory usage during XML sitemap generation.
+
+= 0.1.5 =
+
+* Bugs fixed:
+ * Duplicate noodp,noydir showing up in some occasions. Reworked most of robots meta output function.
+ * Fixed couple more notices.
+ * Trailing slash (when option set) now applied correctly in XML sitemap too.
+ * Made sure regenerating sitemap worked again on post publish.
+ * Force flush rewrite rules on activation / upgrade of plugin to make rewrite work.
+ * Fixed empty RSS content bug caused in 0.1.4.
+
+* Changes:
+ * Removed part done quick edit functionality, will need to revisit once API improves.
+ * Implemented a hook that would make the title work with Thematic based themes properly.
+ * Added option to remove "start" rel link from head section.
+ * Several style sheet changes to make backend styling easier and more robust.
+ * Added option to force rewrite titles for people that can't adapt their theme, it's ugly but not as ugly as how All In One SEO handles it.
+ * If title templates aren't set, the plugin now generates proper default titles.
+ * The News module has moved to a separate directory, where all other modules will reside too, so they can be updated individually later. Download link for the news module will appear on yoast.com shortly.
+
+* Documentation:
+ * Added Admin Only notice in HTML code when no meta description could be generated.
+ * Added a donation box, I'll gladly take your money ;)
+
+= 0.1.4 =
+
+* Bugs fixed:
+ * Fixed canonical for paginated archives of any kind when permalink structure doesn't end with /
+ * Fixed permalink redirect for paginated archives of any kind when permalink structure doesn't end with /
+ * Made sure blog shows up in breadcrumbs when you want it too.
+ * Fixed small javascript notice for js/wp-seo-metabox.js
+ * Rewrote parts of XML Sitemap generation so it's now fully compliant with MultiSite. You no longer have to choose paths for sitemaps, they'll all have fixed locations and using WP Rewrite will be placed in the correct positions, f.i. example.com/sitemap.xml.
+ * Heavily reduced memory usage on admin pages.
+ * Rewrote module structure and added some API's to be used in the modules.
+ * Plugin now creates uploads/wpseo dir to store all files it creates and takes in.
+ * Fixed several notices throughout the code.
+ * Made sure SEO title in edit posts screen shows correct SEO Title.
+ * Changed table sorting javascript for XSL's to Yoast CDN.
+
+= 0.1.3 =
+
+* Bugs fixed:
+ * SEO Title no longer being overwritten when it's already set.
+ * Titles for date archives work too now.
+ * On initial page load or SEO title regeneration number of chars remaining updates properly.
+ * Entities in titles and meta descriptions should now work correctly.
+ * When editing SEO title snippet preview now correctly updates with focus keyword bolded.
+ * Entities in XML sitemap should now show correctly.
+ * When using %%excerpt%% in descriptions it now correctly is shortened to 155 chars.
+ * Regenerating XML News sitemaps should no longer give a Fatal error but just work.
+ * Focus keyword should now properly be recognized in slug even when slug is too long to display.
+ * Breadcrumbs now show proper home link when showing blog link is disabled.
+ * Non post singular pages (pages and custom post types) no longer show blog link in breadcrumb path.
+
+* New features:
+ * Added option to regenerate SEO title (just click the button).
+ * Advanced button now looks cooler (hey even little changes deserve a changelog line!).
+ * Now pinging Ask.com too for updated sitemaps, for those of you caring about SEO for Ask.
+ * Added plugin version number to "branding" comment to help in bug fixing.
+
+= 0.1.2.1 =
+
+* Added a missing ) to prevent death on install / going into wp-admin.
+
+= 0.1.2 =
+
+* Bugs fixed:
+ * Non ASCII characaters should now display properly.
+ * Google News Module: added input field for Google News publication name, as this has to match how Google has you on file.
+ * Stripped tags out of title and meta description output when using, f.i., excerpts in template.
+ * Meta description now updates in snippet preview as well when post content changes and no meta description has been set yet.
+ * Meta description generated from post content now searches ahead to focus keyword and bolds it.
+ * Meta description should now show properly on blog pages when blog page is not site homepage.
+ * Alt or title for previous image could show up in image sitemap when one image didn't have that attribute.
+ * Prevented fatal error on remote_get of XML sitemap in admin/ajax.php.
+ * When there's a blog in / and in /example/ file editor should now properly get robots.txt and .htaccess from /example/ and not /.
+ * Reference to wrongly named yoast_breadcrumb_output fixed, should fix auto insertion of breadcrumbs in supported theme frameworks.
+ * Prevented error when yoast.com/feed/ doesn't work.
+ * Fixed several notices for unset variables.
+ * Added get text calls in several places to allow localization.
+
+* (Inline) Documentation fixes:
+ * Exclusion list in XML sitemap box for post types now shows proper label instead of internal name.
+ * Exclusion list in XML sitemap box for custom taxonomies now shows plural instead of singular form.
+ * Added explanation on how to add breadcrumbs to your theme, as well as link to more explanatory page.
+
+* Changes:
+ * Links to Webmaster Tools etc. now open in new window.
+ * Heavily simplified the javascript used for snippet preview, removing HTML5 placeholder code and instead inserting the title into the input straight away. Lot faster this way.
+ * Removed Anchor text for the blog page option from breadcrumbs section as you can simply set a breadcrumbs title on the blog page itself.
+ * Added option to always remove the Blog page from the breadcrumb.
+
+= 0.1.1 =
+
+* Bugs fixed:
+ * Double comma in robots meta values, as well as index,follow in robots meta.
+ * Oddities with categories in breadcrumbs fixed.
+ * If complete meta value for SE is entered it's now properly stripped, preventing /> from showing up in your page.
+ * Category meta description now shows properly when using category description template.
+ * Removed Hybrid breadcrumb in favor of Yoast breadcrumb when automatically adding breadcrumb to Hybrid based themes.
+ * First stab at fixing trailing slashed URL's in XML sitemaps.
+ * Made %%page%% also work on page 1 of a result set.
+ * Fixed design of broken feed error.
+ * Made sure %%tag%% works too in title templates.
+
+* (Inline) Documentation fixes:
+ * Added this readme.txt file describing all the SEO functionality, and why this is _the_ All in one SEO plugin.
+ * MS Webmaster Central renamed to Bing Webmaster Tools.
+ * Added links to Bing Webmaster Tools and Yahoo! Site explorer to meta values box, as well as an explanation that you do not need to use those values if your site is already verified.
+ * Changed wording on description of clean permalinks.
+ * Added line explaining that SEO title overwrites the SEO title template.
+ * Added line telling to save taxonomy and post_type excludes before rebuilding XML sitemap.
+
+* Changes:
+ * Changed robots meta noindex and nofollow storage for pages to boolean on noindex and nofollow, please check when upgrading.
+ * Now purging W3TC Object Cache when saving taxonomy meta data to make sure new settings are immediately reflected in output.
+ * Namespaced all menu items to prevent collissions with other plugins.
+ * Several code optimizations in admin panels.
+ * Huge code optimizations in breadcrumbs generation and permalink clean up.
+ * Permalink cleaning now works for taxonomies too.
+ * Faked All in One SEO class to make plugin work with themes that check for that.
+
+* New features:
+ * Noindex and nofollow options for taxonomies (noindexing a term automatically removes it from XML sitemap too).
+ * Editable canonicals for taxonomies.
+ * Completed module functionality, using the XML News sitemap as first module.
+ * Added experimental "Find related keywords" feature that'll return keywords that are related to your focus keyword.
+
+* Issues currently in progress:
+ * WPML compatibility for the multilingual SEO's.
+ * XML Sitemap errors in Bing Webmaster Tools (due to use of "caption" for images).
+
+
+= 0.1 =
+
+* Initial beta release.
--- /dev/null
+.wpseo-score-icon {
+ display: inline-block !important;
+ float: left;
+ width: 12px !important;
+ height: 12px !important;
+ margin: 10px 10px 0 4px !important;
+ border-radius: 50% !important;
+ background-color: #999;
+}
+
+.wpseo-score-icon.good {
+ background-color: #7ad03a;
+}
+
+.wpseo-score-icon.ok {
+ background-color: #ffba00;
+}
+
+.wpseo-score-icon.poor {
+ background-color: #ee7c1b;
+}
+
+.wpseo-score-icon.bad {
+ background-color: #dd3d36;
+}
+
+.wpseo-score-icon.na {
+ background-color: #999;
+}
+
+.wpseo-score-icon.noindex {
+ background-color: #1e8cbe;
+}
+
+#wp-admin-bar-wpseo-menu:hover .wpseo-score-icon {
+ background-color: #2ea2cc;
+}
--- /dev/null
+.wpseo-score-icon{display:inline-block!important;float:left;width:12px!important;height:12px!important;margin:10px 10px 0 4px!important;border-radius:50%!important;background-color:#999}.wpseo-score-icon.good{background-color:#7ad03a}.wpseo-score-icon.ok{background-color:#ffba00}.wpseo-score-icon.poor{background-color:#ee7c1b}.wpseo-score-icon.bad{background-color:#dd3d36}.wpseo-score-icon.na{background-color:#999}.wpseo-score-icon.noindex{background-color:#1e8cbe}#wp-admin-bar-wpseo-menu:hover .wpseo-score-icon{background-color:#2ea2cc}
\ No newline at end of file
--- /dev/null
+.wpseo-score-icon {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ margin-left: 6px;
+ border-radius: 50%;
+ background: #888;
+ line-height: 16px;
+}
+
+.wpseo-score-icon.good {
+ background-color: #7ad03a;
+}
+
+.wpseo-score-icon.ok {
+ background-color: #ffba00;
+}
+
+.wpseo-score-icon.poor {
+ background-color: #ee7c1b;
+}
+
+.wpseo-score-icon.bad {
+ background-color: #dd3d36;
+}
+
+.wpseo-score-icon.na {
+ background-color: #888;
+}
+
+.wpseo-score-icon.noindex {
+ background-color: #1e8cbe;
+}
+
+th#wpseo-score {
+ width: 60px;
+}
+
+@media screen and ( max-width: 782px ) {
+ .column-wpseo-title, .column-wpseo-score, .column-wpseo-metadesc, .column-wpseo-focuskw {
+ display: none;
+ }
+}
--- /dev/null
+.wpseo-score-icon{display:inline-block;width:12px;height:12px;margin-left:6px;border-radius:50%;background:#888;line-height:16px}.wpseo-score-icon.good{background-color:#7ad03a}.wpseo-score-icon.ok{background-color:#ffba00}.wpseo-score-icon.poor{background-color:#ee7c1b}.wpseo-score-icon.bad{background-color:#dd3d36}.wpseo-score-icon.na{background-color:#888}.wpseo-score-icon.noindex{background-color:#1e8cbe}th#wpseo-score{width:60px}@media screen and (max-width:782px){.column-wpseo-focuskw,.column-wpseo-metadesc,.column-wpseo-score,.column-wpseo-title{display:none}}
\ No newline at end of file
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+/**
+ * Metabox Tabs
+ */
+
+ul.wpseo-metabox-tabs li.active {
+ background-color: #eff8ff;
+}
+
+ul.wpseo-metabox-tabs li.active, div.wpseo-tab-content {
+ border-color: #d1e5ee;
+}
+
+.wpseo-metabox-tabs .active a {
+ color: #333;
+}
+
+#wpseotab .ui-widget-content .ui-state-hover {
+ border: 1px solid #d1e5ee;
+ color: #333;
+ background: #eff8ff;
+}
--- /dev/null
+ul.wpseo-metabox-tabs li.active{background-color:#eff8ff}div.wpseo-tab-content,ul.wpseo-metabox-tabs li.active{border-color:#d1e5ee}.wpseo-metabox-tabs .active a{color:#333}#wpseotab .ui-widget-content .ui-state-hover{border:1px solid #d1e5ee;color:#333;background:#eff8ff}
\ No newline at end of file
--- /dev/null
+/**
+ * Metabox Tabs
+ */
+
+ul.wpseo-metabox-tabs li.active {
+ background-color: #f1f1f1;
+}
+
+ul.wpseo-metabox-tabs li.active, div.wpseo-tab-content {
+ border-color: #dfdfdf;
+ background-color: #fff;
+}
+
+.wpseo-metabox-tabs .active a {
+ color: #333;
+}
+
+#wpseotab .ui-widget-content .ui-state-hover {
+ border: 1px solid #dfdfdf;
+ color: #333;
+ background: #f1f1f1;
+}
--- /dev/null
+div.wpseo-tab-content,ul.wpseo-metabox-tabs li.active{border-color:#dfdfdf;background-color:#fff}.wpseo-metabox-tabs .active a{color:#333}#wpseotab .ui-widget-content .ui-state-hover{border:1px solid #dfdfdf;color:#333;background:#f1f1f1}
\ No newline at end of file
--- /dev/null
+/**
+ * Metabox Tabs
+ */
+
+ul.wpseo-metabox-tabs {
+ display: none;
+ margin-top: 12px;
+ margin-bottom: 3px;
+}
+
+.wpseo-metabox-tabs-div ul {
+ list-style: none;
+}
+
+.wpseo-metabox-tabs li {
+ display: inline;
+}
+
+ul.wpseo-metabox-tabs li.active {
+ border-width: 1px 1px 0;
+ border-style: solid solid none;
+ background-color: #fdfdfd;
+}
+
+ul.wpseo-metabox-tabs li {
+ padding: 5px;
+}
+
+.wpseo-metabox-tabs a {
+ text-decoration: none;
+}
+
+.wpseo-metabox-tabs-div div.wpseo-tabs-panel {
+ overflow: auto;
+ padding: 0.5em 0.9em;
+ border: 1px solid;
+}
+
+.wpseo-heading {
+ padding-left: 10px;
+}
+
+.wpseotab {
+ display: none;
+}
+
+.wpseotab.active {
+ display: block;
+ overflow: auto;
+ padding: 0.5em 0.9em;
+ border: 1px solid #ddd;
+ background-color: #fdfdfd;
+}
+
+#wpseo_meta .postbox .inside .wpseotab {
+ font-size: 13px !important;
+}
+
+.inside .wpseotab .form-table th {
+ width: 140px !important;
+ font-size: 13px;
+}
+
+.good, .warn, .wrong {
+ font-weight: bold;
+}
+
+.good {
+ color: green;
+}
+
+.warn {
+ color: maroon;
+}
+
+.wrong {
+ color: red;
+}
+
+#current_seo_title span {
+ padding: 2px 5px;
+ background-color: lightyellow;
+}
+
+#wpseosnippet {
+ width: auto;
+ max-width: 520px;
+ margin: 0 0 10px;
+ padding: 0 5px;
+ font-family: Arial, Helvetica, sans-serif;
+ font-style: normal;
+}
+
+#wpseosnippet td {
+ margin: 0;
+ padding: 0;
+}
+
+#wpseosnippet cite.url {
+ font-weight: normal;
+ font-style: normal;
+}
+
+#wpseosnippet a {
+ text-decoration: none;
+}
+
+#wpseosnippet .title {
+ display: block;
+ overflow: hidden;
+ width: 512px;
+ color: #1e0fbe;
+ font-size: 18px !important;
+ line-height: 1.2;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+#wpseosnippet .desc {
+ font-size: small;
+ line-height: 1.4;
+ word-wrap: break-word;
+}
+
+#wpseosnippet .desc .autogen {
+ color: #777;
+}
+
+#wpseosnippet .date {
+ color: #808080;
+}
+
+#wpseosnippet .desc p {
+ color: #545454;
+ font-size: small;
+ line-height: 1.4;
+ word-wrap: break-word;
+}
+
+#wpseosnippet .url {
+ color: #006621;
+ font-size: 13px;
+ line-height: 16px;
+}
+
+#wpseosnippet .meta {
+ color: #767676;
+}
+
+#wpseosnippet .util {
+ color: #4272db;
+}
+
+#wpseosnippet p {
+ margin: 0 !important;
+}
+
+#wpseosnippet a:hover {
+ text-decoration: underline;
+}
+
+#focuskwresults ul {
+ margin: 0;
+}
+
+#focuskwresults p, #focuskwresults li {
+ font-size: 13px;
+}
+
+#focuskwresults li {
+ margin: 0 0 0 20px;
+ list-style-type: disc;
+}
+
+.wpseo_hidden {
+ display: none;
+}
+
+/* Linkdex analysis block */
+
+table.wpseoanalysis th {
+ padding: 15px 0 5px 0;
+ font-size: 14px;
+ text-align: left;
+}
+
+table.wpseoanalysis th.first {
+ padding-top: 0;
+}
+
+table.wpseoanalysis td {
+ margin: 5px 0;
+ font-size: 13px;
+ line-height: 16px;
+}
+
+table.wpseoanalysis td.score {
+ width: 20px;
+ height: 18px;
+ padding-left: 10px;
+}
+
+.wpseo_msg {
+ margin: 5px 0 10px 0;
+ padding: 0 5px;
+ border: 1px solid #e6db55;
+ background-color: lightYellow;
+}
+
+/*
+ * jQuery UI CSS Framework 1.8.12
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Component containers
+----------------------------------*/
+#wpseotab .ui-widget-content {
+ border: 1px solid #ddd;
+ color: #333;
+ background: #f1f1f1;
+}
+
+#wpseotab .ui-widget-content a {
+ color: #333;
+}
+
+/*
+ * jQuery UI Autocomplete 1.8.12
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+#wpseotab .ui-autocomplete {
+ position: absolute;
+ cursor: default;
+}
+
+/* workarounds */
+* html #wpseotab .ui-autocomplete {
+ width: 1px;
+}
+
+/* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.12
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+ display: block;
+ float: left;
+ margin: 0;
+ padding: 2px;
+ list-style: none;
+}
+
+.ui-menu .ui-menu {
+ margin-top: -3px;
+}
+
+.ui-menu .ui-menu-item {
+ float: left;
+ clear: left;
+ zoom: 1;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+.ui-menu .ui-menu-item a {
+ display: block;
+ zoom: 1;
+ padding: 0.2em 0.4em;
+ line-height: 1.5;
+ text-decoration: none;
+}
+
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+ margin: -1px;
+ color: #000;
+}
+
+.wpseo-score-icon {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ margin: 3px 10px 0 3px;
+ border-radius: 50%;
+ background: #888;
+ vertical-align: top;
+}
+
+.wpseo-score-icon.good {
+ background-color: #7ad03a;
+}
+
+.wpseo-score-icon.ok {
+ background-color: #ffba00;
+}
+
+.wpseo-score-icon.poor {
+ background-color: #ee7c1b;
+}
+
+.wpseo-score-icon.bad {
+ background-color: #dd3d36;
+}
+
+.wpseo-score-icon.na {
+ background-color: #888;
+}
+
+.wpseo-score-icon.noindex {
+ background-color: #1e8cbe;
+}
+
+.wpseo-score-title {
+ font-weight: 600;
+}
+
+img.yoast_help {
+ cursor: pointer;
+}
--- /dev/null
+ul.wpseo-metabox-tabs{display:none;margin-top:12px;margin-bottom:3px}.wpseo-metabox-tabs-div ul{list-style:none}.wpseo-metabox-tabs li{display:inline}ul.wpseo-metabox-tabs li.active{border-width:1px 1px 0;border-style:solid solid none;background-color:#fdfdfd}ul.wpseo-metabox-tabs li{padding:5px}.wpseo-metabox-tabs a{text-decoration:none}.wpseo-metabox-tabs-div div.wpseo-tabs-panel{overflow:auto;padding:.5em .9em;border:1px solid}.wpseo-heading{padding-left:10px}.wpseotab{display:none}.wpseotab.active{display:block;overflow:auto;padding:.5em .9em;border:1px solid #ddd;background-color:#fdfdfd}#wpseo_meta .postbox .inside .wpseotab{font-size:13px!important}.inside .wpseotab .form-table th{width:140px!important;font-size:13px}.good,.warn,.wrong{font-weight:700}.good{color:green}.warn{color:maroon}.wrong{color:red}#current_seo_title span{padding:2px 5px;background-color:#ffffe0}#wpseosnippet{width:auto;max-width:520px;margin:0 0 10px;padding:0 5px;font-family:Arial,Helvetica,sans-serif;font-style:normal}#wpseosnippet td{margin:0;padding:0}#wpseosnippet cite.url{font-weight:400;font-style:normal}#wpseosnippet a{text-decoration:none}#wpseosnippet .title{display:block;overflow:hidden;width:512px;color:#1e0fbe;font-size:18px!important;line-height:1.2;white-space:nowrap;text-overflow:ellipsis}#wpseosnippet .desc{font-size:small;line-height:1.4;word-wrap:break-word}#wpseosnippet .desc .autogen{color:#777}#wpseosnippet .date{color:gray}#wpseosnippet .desc p{color:#545454;font-size:small;line-height:1.4;word-wrap:break-word}#wpseosnippet .url{color:#006621;font-size:13px;line-height:16px}#wpseosnippet .meta{color:#767676}#wpseosnippet .util{color:#4272db}#wpseosnippet p{margin:0!important}#wpseosnippet a:hover{text-decoration:underline}#focuskwresults ul{margin:0}#focuskwresults li,#focuskwresults p{font-size:13px}#focuskwresults li{margin:0 0 0 20px;list-style-type:disc}.wpseo_hidden{display:none}table.wpseoanalysis th{padding:15px 0 5px;font-size:14px;text-align:left}table.wpseoanalysis th.first{padding-top:0}table.wpseoanalysis td{margin:5px 0;font-size:13px;line-height:16px}table.wpseoanalysis td.score{width:20px;height:18px;padding-left:10px}.wpseo_msg{margin:5px 0 10px;padding:0 5px;border:1px solid #e6db55;background-color:#ffffe0}#wpseotab .ui-widget-content{border:1px solid #ddd;color:#333;background:#f1f1f1}#wpseotab .ui-widget-content a{color:#333}#wpseotab .ui-autocomplete{position:absolute;cursor:default}* html #wpseotab .ui-autocomplete{width:1px}.ui-menu{display:block;float:left;margin:0;padding:2px;list-style:none}.ui-menu .ui-menu{margin-top:-3px}.ui-menu .ui-menu-item{float:left;clear:left;zoom:1;width:100%;margin:0;padding:0}.ui-menu .ui-menu-item a{display:block;zoom:1;padding:.2em .4em;line-height:1.5;text-decoration:none}.ui-menu .ui-menu-item a.ui-state-active,.ui-menu .ui-menu-item a.ui-state-hover{margin:-1px;color:#000}.wpseo-score-icon{display:inline-block;width:12px;height:12px;margin:3px 10px 0 3px;border-radius:50%;background:#888;vertical-align:top}.wpseo-score-icon.good{background-color:#7ad03a}.wpseo-score-icon.ok{background-color:#ffba00}.wpseo-score-icon.poor{background-color:#ee7c1b}.wpseo-score-icon.bad{background-color:#dd3d36}.wpseo-score-icon.na{background-color:#888}.wpseo-score-icon.noindex{background-color:#1e8cbe}.wpseo-score-title{font-weight:600}img.yoast_help{cursor:pointer}
\ No newline at end of file
--- /dev/null
+.wpseo-taxonomy-form tr,
+.wpseo-taxonomy-form th,
+.wpseo-taxonomy-form td {
+ vertical-align: top;
+}
--- /dev/null
+.wpseo-taxonomy-form td,.wpseo-taxonomy-form th,.wpseo-taxonomy-form tr{vertical-align:top}
\ No newline at end of file
--- /dev/null
+/**
+ * RTL support
+ */
+
+p.desc {
+ padding: 0 22px 8px 0;
+}
+
+p.desc.label {
+ padding: 2px 180px 10px 0;
+}
+
+div.yoastbox ul {
+ margin-right: 20px;
+ margin-left: auto;
+}
+
+.postbox {
+ margin: 10px 0 0 10px;
+}
+
+label {
+ float: right;
+ margin-right: 6px;
+ margin-left: auto;
+}
+
+input.textinput, textarea.textinput, select.select, input.checkbox {
+ float: right;
+ margin: 12px 0 0 3px;
+}
+
+label.radio {
+ margin-left: auto;
+}
+
+table.yoast_help th, table.yoast_help td {
+ text-align: right;
+}
+
+.button.fixit {
+ float: left;
+}
+
+pre {
+ direction: ltr;
+}
+
+#pointer-primary {
+ margin: 0 0 0 5px;
+}
+
+.wpseo-heading {
+ padding-right: 10px;
+ padding-left: 0;
+}
+
+.wpseo_yahoo_kw {
+ margin: 3px 0 3px 8px;
+}
+
+table.wpseoanalysis th {
+ text-align: right;
+}
+
+table.wpseoanalysis td.score {
+ padding-right: 10px;
+ padding-left: 0;
+}
--- /dev/null
+p.desc{padding:0 22px 8px 0}p.desc.label{padding:2px 180px 10px 0}div.yoastbox ul{margin-right:20px;margin-left:auto}.postbox{margin:10px 0 0 10px}label{float:right;margin-right:6px;margin-left:auto}input.checkbox,input.textinput,select.select,textarea.textinput{float:right;margin:12px 0 0 3px}label.radio{margin-left:auto}table.yoast_help td,table.yoast_help th{text-align:right}.button.fixit{float:left}pre{direction:ltr}#pointer-primary{margin:0 0 0 5px}.wpseo-heading{padding-right:10px;padding-left:0}.wpseo_yahoo_kw{margin:3px 0 3px 8px}table.wpseoanalysis th{text-align:right}table.wpseoanalysis td.score{padding-right:10px;padding-left:0}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package XML_Sitemaps
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+// This is to prevent issues with New Relics stupid auto injection of code. It's ugly but I don't want
+// to deal with support requests for other people's wrong code...
+if ( extension_loaded( 'newrelic' ) && function_exists( 'newrelic_disable_autorum' ) ) {
+ newrelic_disable_autorum();
+}
+
+$xsl = '<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="2.0"
+ xmlns:html="http://www.w3.org/TR/REC-html40"
+ xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
+ xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
+ <xsl:template match="/">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>XML Sitemap</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <style type="text/css">
+ body {
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 13px;
+ color: #545353;
+ }
+ table {
+ border: none;
+ border-collapse: collapse;
+ }
+ #sitemap tr.odd td {
+ background-color: #eee !important;
+ }
+ #sitemap tbody tr:hover td {
+ background-color: #ccc;
+ }
+ #sitemap tbody tr:hover td, #sitemap tbody tr:hover td a {
+ color: #000;
+ }
+ #content {
+ margin: 0 auto;
+ width: 1000px;
+ }
+ .expl {
+ margin: 18px 3px;
+ line-height: 1.2em;
+ }
+ .expl a {
+ color: #da3114;
+ font-weight: bold;
+ }
+ .expl a:visited {
+ color: #da3114;
+ }
+ a {
+ color: #000;
+ text-decoration: none;
+ }
+ a:visited {
+ color: #777;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ td {
+ font-size:11px;
+ }
+ th {
+ text-align:left;
+ padding-right:30px;
+ font-size:11px;
+ }
+ thead th {
+ border-bottom: 1px solid #000;
+ cursor: pointer;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="content">
+ <h1>XML Sitemap</h1>
+ <p class="expl">
+ Generated by <a href="https://yoast.com/">Yoast</a>\'s <a href="https://yoast.com/wordpress/plugins/seo/">WordPress SEO plugin</a>, this is an XML Sitemap, meant for consumption by search engines.<br/>
+ You can find more information about XML sitemaps on <a href="http://sitemaps.org">sitemaps.org</a>.
+ </p>
+ <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) > 0">
+ <p class="expl">
+ This XML Sitemap Index file contains <xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> sitemaps.
+ </p>
+ <table id="sitemap" cellpadding="3">
+ <thead>
+ <tr>
+ <th width="75%">Sitemap</th>
+ <th width="25%">Last Modified</th>
+ </tr>
+ </thead>
+ <tbody>
+ <xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
+ <xsl:variable name="sitemapURL">
+ <xsl:value-of select="sitemap:loc"/>
+ </xsl:variable>
+ <tr>
+ <td>
+ <a href="{$sitemapURL}"><xsl:value-of select="sitemap:loc"/></a>
+ </td>
+ <td>
+ <xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(\' \', substring(sitemap:lastmod,12,5)))"/>
+ </td>
+ </tr>
+ </xsl:for-each>
+ </tbody>
+ </table>
+ </xsl:if>
+ <xsl:if test="count(sitemap:sitemapindex/sitemap:sitemap) < 1">
+ <p class="expl">
+ This XML Sitemap contains <xsl:value-of select="count(sitemap:urlset/sitemap:url)"/> URLs.
+ </p>
+ <p class="expl"><a href="' . esc_url( home_url( 'sitemap_index.xml' ) ) . '">↑ Sitemap Index</a></p>
+ <table id="sitemap" cellpadding="3">
+ <thead>
+ <tr>
+ <th width="75%">URL</th>
+ <th title="Index Priority" width="5%">Prio</th>
+ <th width="5%">Images</th>
+ <th title="Change Frequency" width="5%">Ch. Freq.</th>
+ <th title="Last Modification Time" width="10%">Last Mod.</th>
+ </tr>
+ </thead>
+ <tbody>
+ <xsl:variable name="lower" select="\'abcdefghijklmnopqrstuvwxyz\'"/>
+ <xsl:variable name="upper" select="\'ABCDEFGHIJKLMNOPQRSTUVWXYZ\'"/>
+ <xsl:for-each select="sitemap:urlset/sitemap:url">
+ <tr>
+ <td>
+ <xsl:variable name="itemURL">
+ <xsl:value-of select="sitemap:loc"/>
+ </xsl:variable>
+ <a href="{$itemURL}">
+ <xsl:value-of select="sitemap:loc"/>
+ </a>
+ </td>
+ <td>
+ <xsl:value-of select="concat(sitemap:priority*100,\'%\')"/>
+ </td>
+ <td>
+ <xsl:value-of select="count(image:image)"/>
+ </td>
+ <td>
+ <xsl:value-of select="concat(translate(substring(sitemap:changefreq, 1, 1),concat($lower, $upper),concat($upper, $lower)),substring(sitemap:changefreq, 2))"/>
+ </td>
+ <td>
+ <xsl:value-of select="concat(substring(sitemap:lastmod,0,11),concat(\' \', substring(sitemap:lastmod,12,5)))"/>
+ </td>
+ </tr>
+ </xsl:for-each>
+ </tbody>
+ </table>
+ </xsl:if>
+ </div>
+ <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
+ <script type="text/javascript" src="' . plugins_url( 'js/jquery.tablesorter.min.js', WPSEO_FILE ) . '"></script>
+ <script type="text/javascript"><![CDATA[
+ $(document).ready(function() {
+ $("#sitemap").tablesorter( { widgets: [\'zebra\'] } );
+ });
+ ]]></script>
+ </body>
+ </html>
+ </xsl:template>
+</xsl:stylesheet>';
+echo $xsl;
--- /dev/null
+h4 {
+ clear: both;
+ margin: 2em 0 0 0;
+}
+
+p.desc {
+ margin: 6px 0 10px 0;
+ padding: 0 0 8px 25px;
+ border-bottom: 1px solid #ddd;
+}
+
+p.desc.label {
+ margin-bottom: 20px;
+ padding: 2px 0 10px 180px;
+}
+
+tr.yst_row {
+ margin: 5px 0 0 0;
+ padding: 5px 0 0 0;
+}
+
+tr.yst_row.even {
+ background-color: #f6f6f6;
+}
+
+div.yoastbox ul {
+ margin-left: 20px;
+}
+
+.postbox {
+ margin: 10px 10px 0 0;
+}
+
+label {
+ float: left;
+ margin-left: 6px;
+}
+
+label.radio {
+ float: none;
+}
+
+input.textinput, textarea, select {
+ width: 400px;
+}
+
+input.textinput, textarea.textinput, select.select, input.checkbox {
+ float: left;
+ margin: 12px 3px 0 0;
+ padding: 5px;
+}
+
+select.select {
+ padding: 3px;
+}
+
+input.checkbox.double {
+ margin-top: 2px;
+}
+
+.textinput.metadesc {
+ height: 50px;
+}
+
+textarea.import {
+ width: 500px;
+ height: 100px;
+}
+
+label.textinput, label.select, label.checkbox {
+ width: 180px;
+ margin: 10px 0 5px 0;
+ background-color: transparent;
+}
+
+label.radio {
+ margin-right: 20px;
+}
+
+.wpseo_content_wrapper {
+ display: table;
+ width: 100%;
+}
+
+.wpseo_content_cell {
+ display: table-cell;
+ height: 500px;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+}
+
+#wpseo_content_top {
+ min-width: 800px;
+}
+
+.wpseo_content_wrapper div.wpseo-warning {
+ margin: 2em 0 0 0;
+ padding-right: 10px;
+ padding-left: 10px;
+ border: 2px solid red;
+ background-color: #dedede;
+}
+
+.wpseo_content_wrapper div.wpseo-warning h4 {
+ margin: 1em 0 0 0;
+}
+
+.wpseo_content_wrapper div.wpseo-warning p.error-message {
+ font-weight: normal;
+}
+
+#sidebar-container {
+ width: 261px;
+ padding: 0 0 0 20px;
+}
+
+@media (max-width: 1020px) {
+ #wpseo_content_top {
+ margin-left: 0;
+ padding-left: 0;
+ }
+}
+
+.wpseo-admin-page .form-table tr,
+.wpseo-admin-page .form-table th,
+.wpseo-admin-page .form-table td {
+ vertical-align: top;
+}
+
+.postbox form {
+ line-height: 150%;
+}
+
+div.yoastbox ul li {
+ font-size: 11px;
+ line-height: 16px;
+ list-style: square;
+}
+
+div.yoastbox a {
+ font-family: sans-serif;
+ font-size: 12px;
+}
+
+div.yoastbox a:hover {
+ text-decoration: underline;
+}
+
+.text {
+ width: 250px;
+}
+
+div.yoastbox .button:hover, div.yoastbox .button-primary:hover {
+ text-decoration: none;
+}
+
+/*.button, .button-primary {*/
+/*margin-top: 10px;*/
+/*}*/
+
+table.yoast_help, table.yoast_help th, table.yoast_help td {
+ border: 1px solid #ddd;
+ border-collapse: collapse;
+ font-size: 12px;
+}
+
+table.yoast_help th, table.yoast_help td {
+ padding: 5px 10px;
+ text-align: left;
+ vertical-align: text-top;
+}
+
+table.yoast_help tr:nth-child(2n) {
+ background-color: #fbfbfe;
+}
+
+table.yoast_help tr:hover {
+ background-color: #ddd;
+}
+
+.correct {
+ padding: 5px;
+ color: white;
+ background-color: green;
+}
+
+.wrong {
+ padding: 5px;
+ color: white;
+ background-color: red;
+}
+
+.wrong code {
+ padding: 3px 8px;
+ color: #000;
+}
+
+.button.fixit {
+ float: right;
+ margin: 0 5px;
+}
+
+.button.checkit {
+ float: right;
+ margin: 0 5px;
+ padding: 5px 8px;
+}
+
+.fb-buttons .button-primary,
+.fb-buttons .button {
+ margin-right: 8px;
+}
+
+.button {
+ color: #000 !important;
+}
+
+.button#submit {
+ color: #fff !important;
+}
+
+.postbox#donate {
+ border: 2px green;
+}
+
+.wpseotab {
+ display: none;
+ /*max-width: 600px !important;*/
+ ;
+}
+
+.wpseotab.active {
+ display: block;
+}
+
+#sidebar .yoastbox {
+ margin: 0 0 10px 0;
+ padding: 10px 15px;
+ border: 1px solid #ccc;
+ border-radius: 5px 25px;
+}
+
+#sidebar .yoastbox h2 {
+ font-size: 16px;
+}
+
+#sidebar .promo {
+ color: #000;
+}
+
+#donate.yoastbox, #sitereview.yoastbox {
+ border-color: green;
+ background-color: #cfc;
+}
+
+h2 {
+ margin-bottom: 20px;
+}
+
+h2 code {
+ font-size: 23px;
+}
+
+#wpseo-conf {
+ /*max-width: 600px;*/
+ ;
+}
+
+#pointer-primary {
+ margin: 0 5px 0 0;
+}
+
+#wpseo-debug-info {
+ clear: both;
+}
+
+#wpseo-debug-info .hndle {
+ cursor: auto;
+}
+
+#wpseo-debug-info h3 span:last-child {
+ padding-left: 16px;
+}
+
+#wpseo-debug-info .wpseo-debug {
+ display: inline-block;
+ padding-left: 20px;
+ color: #c00;
+}
+
+input.wpseo-new-title, textarea.wpseo-new-metadesc {
+ width: 100%;
+ max-width: 100%;
+}
+
+.wpseotab .extension {
+ float: left;
+ box-sizing: border-box;
+ width: 300px;
+ height: 230px;
+ margin: 10px 20px 10px 0;
+ border: 1px solid #ccc;
+}
+
+.wpseotab .extension p {
+ margin: 0;
+ padding: 10px;
+}
+
+.wpseotab .extension h3 {
+ box-sizing: border-box;
+ height: 110px;
+ margin: 0;
+ padding: 20px 10px 0 120px;
+ border-bottom: 1px solid #ccc;
+ background: #fff no-repeat left 10px;
+ background-size: 130px 100px;
+}
+
+.wpseotab .extension a {
+ text-decoration: none;
+}
+
+.wpseotab .seo-premium h3 {
+ background-image: url(../images/Premium_130x100.png);
+}
+
+.wpseotab .video-seo h3 {
+ background-image: url(../images/Video_130x100.png);
+}
+
+.wpseotab .local-seo h3 {
+ background-image: url(../images/Local_130x100.png);
+}
+
+.wpseotab .woocommerce-seo h3 {
+ background-image: url(../images/Woo_130x100.png);
+}
+
+.wpseotab .news-seo h3 {
+ background-image: url(../images/News_SEO.png);
+ background-size: 115px 100px;
+}
+
+div#separator {
+ clear: right;
+ margin: 0 0 0 25px;
+}
+
+div#separator input.radio {
+ display: none;
+ float: left;
+ width: 0 !important;
+ min-width: 0 !important;
+ opacity: 0;
+}
+
+div#separator input.radio + label {
+ float: left;
+ width: 30px !important;
+ margin: 3px 3px 0 0;
+ padding: 5px 8px;
+ border: 1px solid #ccc;
+ /* Don't change: these mimic Google's font and font size for titles */
+ font-family: Arial,Helvetica,sans-serif !important;
+ font-size: 18px !important;
+ line-height: 25px;
+ text-align: center;
+ cursor: pointer;
+}
+
+div#separator input.radio:checked + label {
+ border: 1px solid green;
+ background-color: #fff;
+ box-shadow: 1px 1px 1px green;
+}
--- /dev/null
+h4{clear:both;margin:2em 0 0}p.desc{margin:6px 0 10px;padding:0 0 8px 25px;border-bottom:1px solid #ddd}p.desc.label{margin-bottom:20px;padding:2px 0 10px 180px}tr.yst_row{margin:5px 0 0;padding:5px 0 0}tr.yst_row.even{background-color:#f6f6f6}div.yoastbox ul{margin-left:20px}.postbox{margin:10px 10px 0 0}label{float:left;margin-left:6px}label.radio{float:none}input.textinput,select,textarea{width:400px}input.checkbox,input.textinput,select.select,textarea.textinput{float:left;margin:12px 3px 0 0;padding:5px}select.select{padding:3px}input.checkbox.double{margin-top:2px}.textinput.metadesc{height:50px}textarea.import{width:500px;height:100px}label.checkbox,label.select,label.textinput{width:180px;margin:10px 0 5px;background-color:transparent}label.radio{margin-right:20px}.wpseo_content_wrapper{display:table;width:100%}.wpseo_content_cell{display:table-cell;height:500px;margin:0;padding:0;vertical-align:top}#wpseo_content_top{min-width:800px}.wpseo_content_wrapper div.wpseo-warning{margin:2em 0 0;padding-right:10px;padding-left:10px;border:2px solid red;background-color:#dedede}.wpseo_content_wrapper div.wpseo-warning h4{margin:1em 0 0}.wpseo_content_wrapper div.wpseo-warning p.error-message{font-weight:400}#sidebar-container{width:261px;padding:0 0 0 20px}@media (max-width:1020px){#wpseo_content_top{margin-left:0;padding-left:0}}.wpseo-admin-page .form-table td,.wpseo-admin-page .form-table th,.wpseo-admin-page .form-table tr{vertical-align:top}.postbox form{line-height:150%}div.yoastbox ul li{font-size:11px;line-height:16px;list-style:square}div.yoastbox a{font-family:sans-serif;font-size:12px}div.yoastbox a:hover{text-decoration:underline}.text{width:250px}div.yoastbox .button-primary:hover,div.yoastbox .button:hover{text-decoration:none}table.yoast_help,table.yoast_help td,table.yoast_help th{border:1px solid #ddd;border-collapse:collapse;font-size:12px}table.yoast_help td,table.yoast_help th{padding:5px 10px;text-align:left;vertical-align:text-top}table.yoast_help tr:nth-child(2n){background-color:#fbfbfe}table.yoast_help tr:hover{background-color:#ddd}.correct{padding:5px;color:#fff;background-color:green}.wrong{padding:5px;color:#fff;background-color:red}.wrong code{padding:3px 8px;color:#000}.button.fixit{float:right;margin:0 5px}.button.checkit{float:right;margin:0 5px;padding:5px 8px}.fb-buttons .button,.fb-buttons .button-primary{margin-right:8px}.button{color:#000!important}.button#submit{color:#fff!important}.postbox#donate{border:2px green}.wpseotab{display:none}.wpseotab.active{display:block}#sidebar .yoastbox{margin:0 0 10px;padding:10px 15px;border:1px solid #ccc;border-radius:5px 25px}#sidebar .yoastbox h2{font-size:16px}#sidebar .promo{color:#000}#donate.yoastbox,#sitereview.yoastbox{border-color:green;background-color:#cfc}h2{margin-bottom:20px}h2 code{font-size:23px}#pointer-primary{margin:0 5px 0 0}#wpseo-debug-info{clear:both}#wpseo-debug-info .hndle{cursor:auto}#wpseo-debug-info h3 span:last-child{padding-left:16px}#wpseo-debug-info .wpseo-debug{display:inline-block;padding-left:20px;color:#c00}input.wpseo-new-title,textarea.wpseo-new-metadesc{width:100%;max-width:100%}.wpseotab .extension{float:left;box-sizing:border-box;width:300px;height:230px;margin:10px 20px 10px 0;border:1px solid #ccc}.wpseotab .extension p{margin:0;padding:10px}.wpseotab .extension h3{box-sizing:border-box;height:110px;margin:0;padding:20px 10px 0 120px;border-bottom:1px solid #ccc;background:#fff no-repeat left 10px;background-size:130px 100px}.wpseotab .extension a{text-decoration:none}.wpseotab .seo-premium h3{background-image:url(../images/Premium_130x100.png)}.wpseotab .video-seo h3{background-image:url(../images/Video_130x100.png)}.wpseotab .local-seo h3{background-image:url(../images/Local_130x100.png)}.wpseotab .woocommerce-seo h3{background-image:url(../images/Woo_130x100.png)}.wpseotab .news-seo h3{background-image:url(../images/News_SEO.png);background-size:115px 100px}div#separator{clear:right;margin:0 0 0 25px}div#separator input.radio{display:none;float:left;width:0!important;min-width:0!important;opacity:0}div#separator input.radio+label{float:left;width:30px!important;margin:3px 3px 0 0;padding:5px 8px;border:1px solid #ccc;font-family:Arial,Helvetica,sans-serif!important;font-size:18px!important;line-height:25px;text-align:center;cursor:pointer}div#separator input.radio:checked+label{border:1px solid green;background-color:#fff;box-shadow:1px 1px 1px green}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Frontend
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Breadcrumbs' ) ) {
+ /**
+ * This class handles the Breadcrumbs generation and display
+ */
+ class WPSEO_Breadcrumbs {
+
+ /**
+ * @var object Instance of this class
+ */
+ public static $instance;
+
+ /**
+ * @var string Last used 'before' string
+ */
+ public static $before = '';
+
+ /**
+ * @var string Last used 'after' string
+ */
+ public static $after = '';
+
+
+ /**
+ * @var string Blog's show on front setting, 'page' or 'posts'
+ */
+ private $show_on_front;
+
+ /**
+ * @var mixed Blog's page for posts setting, page id or false
+ */
+ private $page_for_posts;
+
+ /**
+ * @var mixed Current post object
+ */
+ private $post;
+
+ /**
+ * @var array WPSEO options array from get_all()
+ */
+ private $options;
+
+
+ /**
+ * @var string HTML wrapper element for a single breadcrumb element
+ */
+ private $element = 'span';
+
+ /**
+ * @var string WP SEO breadcrumb separator
+ */
+ private $separator = '';
+
+ /**
+ * @var string HTML wrapper element for the WP SEO breadcrumbs output
+ */
+ private $wrapper = 'span';
+
+
+ /**
+ * @var array Array of crumbs
+ *
+ * Each element of the crumbs array can either have one of these keys:
+ * "id" for post types;
+ * "ptarchive" for a post type archive;
+ * "term" for a taxonomy term.
+ * OR it consists of a predefined set of 'text', 'url' and 'allow_html'
+ */
+ private $crumbs = array();
+
+ /**
+ * @var array Count of the elements in the $crumbs property
+ */
+ private $crumb_count = 0;
+
+ /**
+ * @var array Array of individual (linked) html strings created from crumbs
+ */
+ private $links = array();
+
+ /**
+ * @var string Breadcrumb html string
+ */
+ private $output;
+
+
+ /**
+ * Create the breadcrumb
+ */
+ private function __construct() {
+ $this->options = WPSEO_Options::get_all();
+ $this->post = ( isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null );
+ $this->show_on_front = get_option( 'show_on_front' );
+ $this->page_for_posts = get_option( 'page_for_posts' );
+
+ $this->filter_element();
+ $this->filter_separator();
+ $this->filter_wrapper();
+
+ $this->set_crumbs();
+ $this->prepare_links();
+ $this->links_to_string();
+ $this->wrap_breadcrumb( );
+ }
+
+ /**
+ * Get breadcrumb string using the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function breadcrumb( $before = '', $after = '', $display = true ) {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+ // Remember the last used before/after for use in case the object goes __toString()
+ self::$before = $before;
+ self::$after = $after;
+
+ $output = $before . self::$instance->output . $after;
+
+ if ( $display === true ) {
+ echo $output;
+ return true;
+ }
+ else {
+ return $output;
+ }
+ }
+
+ /**
+ * Magic method to use in case the class would be send to string
+ *
+ * @return string
+ */
+ public function __toString() {
+ return self::$before . $this->output . self::$after;
+ }
+
+
+ /**
+ * Filter: 'wpseo_breadcrumb_single_link_wrapper' - Allows developer to change or wrap each breadcrumb element
+ *
+ * @api string $element
+ */
+ private function filter_element() {
+ $this->element = esc_attr( apply_filters( 'wpseo_breadcrumb_single_link_wrapper', $this->element ) );
+ }
+
+ /**
+ * Filter: 'wpseo_breadcrumb_separator' - Allow (theme) developer to change the WP SEO breadcrumb separator.
+ *
+ * @api string $breadcrumbs_sep Breadcrumbs separator
+ */
+ private function filter_separator() {
+ $separator = apply_filters( 'wpseo_breadcrumb_separator', $this->options['breadcrumbs-sep'] );
+ $this->separator = ' ' . $separator . ' ';
+ }
+
+ /**
+ * Filter: 'wpseo_breadcrumb_output_wrapper' - Allow changing the HTML wrapper element for the WP SEO breadcrumbs output
+ *
+ * @api string $wrapper The wrapper element
+ */
+ private function filter_wrapper() {
+ $wrapper = apply_filters( 'wpseo_breadcrumb_output_wrapper', $this->wrapper );
+ $wrapper = tag_escape( $wrapper );
+ if ( is_string( $wrapper ) && '' !== $wrapper ) {
+ $this->wrapper = $wrapper;
+ }
+ }
+
+
+ /**
+ * Get a term's parents.
+ *
+ * @param object $term Term to get the parents for
+ * @return array
+ */
+ private function get_term_parents( $term ) {
+ $tax = $term->taxonomy;
+ $parents = array();
+ while ( $term->parent != 0 ) {
+ $term = get_term( $term->parent, $tax );
+ $parents[] = $term;
+ }
+ return array_reverse( $parents );
+ }
+
+ /**
+ * Find the deepest term in an array of term objects
+ *
+ * @param array $terms
+ *
+ * @return object
+ */
+ private function find_deepest_term( $terms ) {
+ /* Let's find the deepest term in this array, by looping through and then
+ unsetting every term that is used as a parent by another one in the array. */
+ $terms_by_id = array();
+ foreach ( $terms as $term ) {
+ $terms_by_id[ $term->term_id ] = $term;
+ }
+ foreach ( $terms as $term ) {
+ unset( $terms_by_id[ $term->parent ] );
+ }
+
+ /* As we could still have two subcategories, from different parent categories,
+ let's pick the one with the lowest ordered ancestor. */
+ $parents_count = 0;
+ $term_order = 9999; //because ASC
+ reset( $terms_by_id );
+ $deepest_term = current( $terms_by_id );
+ foreach ( $terms_by_id as $term ) {
+ $parents = $this->get_term_parents( $term );
+
+ if ( count( $parents ) >= $parents_count ) {
+ $parents_count = count( $parents );
+
+ //if higher count
+ if ( count( $parents ) > $parents_count ) {
+ //reset order
+ $term_order = 9999;
+ }
+
+ $parent_order = 9999; //set default order
+ foreach ( $parents as $parent ) {
+ if ( $parent->parent == 0 && isset( $parent->term_order ) ) {
+ $parent_order = $parent->term_order;
+ }
+ }
+
+ //check if parent has lowest order
+ if ( $parent_order < $term_order ) {
+ $term_order = $parent_order;
+
+ $deepest_term = $term;
+ }
+ }
+ }
+ return $deepest_term;
+ }
+
+ /**
+ * Retrieve the hierachical ancestors for the current 'post'
+ *
+ * @return array
+ */
+ private function get_post_ancestors() {
+ $ancestors = array();
+
+ if ( isset( $this->post->ancestors ) ) {
+ if ( is_array( $this->post->ancestors ) ) {
+ $ancestors = array_values( $this->post->ancestors );
+ }
+ else {
+ $ancestors = array( $this->post->ancestors );
+ }
+ }
+ elseif ( isset( $this->post->post_parent ) ) {
+ $ancestors = array( $this->post->post_parent );
+ }
+
+ /**
+ * Filter: Allow changing the ancestors for the WP SEO breadcrumbs output
+ *
+ * @api array $ancestors Ancestors
+ */
+ $ancestors = apply_filters( 'wp_seo_get_bc_ancestors', $ancestors );
+
+ if ( ! is_array( $ancestors ) ) {
+ trigger_error( 'The return value for the "wp_seo_get_bc_ancestors" filter should be an array.', E_USER_WARNING );
+ $ancestors = (array) $ancestors;
+ }
+
+ // Reverse the order so it's oldest to newest
+ $ancestors = array_reverse( $ancestors );
+
+ return $ancestors;
+ }
+
+ /**
+ * Determine the crumbs which should form the breadcrumb.
+ */
+ private function set_crumbs() {
+ global $wp_query;
+
+ $this->add_home_crumb();
+ $this->maybe_add_blog_crumb();
+
+ if ( ( $this->show_on_front === 'page' && is_front_page() ) || ( $this->show_on_front === 'posts' && is_home() ) ) {
+ // do nothing
+ }
+ elseif ( $this->show_on_front == 'page' && is_home() ) {
+ $this->add_blog_crumb();
+ }
+ elseif ( is_singular() ) {
+ $this->maybe_add_pt_archive_crumb_for_post();
+
+ if ( isset( $this->post->post_parent ) && 0 == $this->post->post_parent ) {
+ $this->maybe_add_taxonomy_crumbs_for_post();
+ }
+ else {
+ $this->add_post_ancestor_crumbs();
+ }
+
+ if ( isset( $this->post->ID ) ) {
+ $this->add_single_post_crumb( $this->post->ID );
+ }
+ }
+ else {
+ if ( is_post_type_archive() ) {
+ $this->add_ptarchive_crumb( $wp_query->query['post_type'] );
+ }
+ elseif ( is_tax() || is_tag() || is_category() ) {
+ $this->add_crumbs_for_taxonomy();
+ }
+ elseif ( is_date() ) {
+ if ( is_day() ) {
+ $this->add_linked_month_year_crumb();
+ $this->add_date_crumb();
+ }
+ elseif ( is_month() ) {
+ $this->add_month_crumb();
+ }
+ elseif ( is_year() ) {
+ $this->add_year_crumb();
+ }
+ }
+ elseif ( is_author() ) {
+ $user = $wp_query->get_queried_object();
+ $this->add_predefined_crumb(
+ $this->options['breadcrumbs-archiveprefix'] . ' ' . $user->display_name,
+ null,
+ true
+ );
+ }
+ elseif ( is_search() ) {
+ $this->add_predefined_crumb(
+ $this->options['breadcrumbs-searchprefix'] . ' "' . esc_html( get_search_query() ) . '"',
+ null,
+ true
+ );
+ }
+ elseif ( is_404() ) {
+
+ if ( 0 !== get_query_var( 'year' ) || ( 0 !== get_query_var( 'monthnum' ) || 0 !== get_query_var( 'day' ) ) ) {
+ if ( 'page' == $this->show_on_front && ! is_home() ) {
+ if ( $this->page_for_posts && $this->options['breadcrumbs-blog-remove'] === false ) {
+ $this->add_blog_crumb();
+ }
+ }
+
+ if ( 0 !== get_query_var( 'day' ) ) {
+ $this->add_linked_month_year_crumb();
+
+ $date = sprintf( '%04d-%02d-%02d 00:00:00', get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) );
+ $this->add_date_crumb( $date );
+ }
+ elseif ( 0 !== get_query_var( 'monthnum' ) ) {
+ $this->add_month_crumb();
+ }
+ elseif ( 0 !== get_query_var( 'year' ) ) {
+ $this->add_year_crumb();
+ }
+ }
+ else {
+ $this->add_predefined_crumb(
+ $this->options['breadcrumbs-404crumb'],
+ null,
+ true
+ );
+ }
+ }
+ }
+
+ /**
+ * Filter: 'wpseo_breadcrumb_links' - Allow the developer to filter the WP SEO breadcrumb links, add to them, change order, etc.
+ *
+ * @api array $crumbs The crumbs array
+ */
+ $this->crumbs = apply_filters( 'wpseo_breadcrumb_links', $this->crumbs );
+
+ $this->crumb_count = count( $this->crumbs );
+ }
+
+
+ /**
+ * Add a single id based crumb to the crumbs property
+ */
+ private function add_single_post_crumb( $id ) {
+ $this->crumbs[] = array(
+ 'id' => $id,
+ );
+ }
+
+ /**
+ * Add a term based crumb to the crumbs property
+ */
+ private function add_term_crumb( $term ) {
+ $this->crumbs[] = array(
+ 'term' => $term,
+ );
+ }
+
+ /**
+ * Add a ptarchive based crumb to the crumbs property
+ */
+ private function add_ptarchive_crumb( $pt ) {
+ $this->crumbs[] = array(
+ 'ptarchive' => $pt,
+ );
+ }
+
+ /**
+ * Add a predefined crumb to the crumbs property
+ */
+ private function add_predefined_crumb( $text, $url = '', $allow_html = false ) {
+ $this->crumbs[] = array(
+ 'text' => $text,
+ 'url' => $url,
+ 'allow_html' => $allow_html,
+ );
+ }
+
+ /**
+ * Add Homepage crumb to the crumbs property
+ */
+ private function add_home_crumb() {
+ $this->add_predefined_crumb(
+ $this->options['breadcrumbs-home'],
+ get_home_url(),
+ true
+ );
+ }
+
+ /**
+ * Add Blog crumb to the crumbs property
+ */
+ private function add_blog_crumb() {
+ $this->add_single_post_crumb( $this->page_for_posts );
+ }
+
+ /**
+ * Add Blog crumb to the crumbs property for single posts where Home != blogpage
+ */
+ private function maybe_add_blog_crumb() {
+ if ( ( 'page' === $this->show_on_front && 'post' === get_post_type() ) && ( ! is_home() && ! is_search() ) ) {
+ if ( $this->page_for_posts && $this->options['breadcrumbs-blog-remove'] === false ) {
+ $this->add_blog_crumb();
+ }
+ }
+ }
+
+ /**
+ * Add ptarchive crumb to the crumbs property if it can be linked to, for a single post
+ */
+ private function maybe_add_pt_archive_crumb_for_post() {
+ if ( isset( $this->post->post_type ) && get_post_type_archive_link( $this->post->post_type ) ) {
+ $this->add_ptarchive_crumb( $this->post->post_type );
+ }
+ }
+
+ /**
+ * Add taxonomy crumbs to the crumbs property for a single post
+ */
+ private function maybe_add_taxonomy_crumbs_for_post() {
+ if ( isset( $this->options[ 'post_types-' . $this->post->post_type . '-maintax' ] ) && $this->options[ 'post_types-' . $this->post->post_type . '-maintax' ] != '0' ) {
+ $main_tax = $this->options[ 'post_types-' . $this->post->post_type . '-maintax' ];
+ if ( isset( $this->post->ID ) ) {
+ $terms = wp_get_object_terms( $this->post->ID, $main_tax );
+
+ if ( is_array( $terms ) && $terms !== array() ) {
+
+ $deepest_term = $this->find_deepest_term( $terms );
+
+ if ( is_taxonomy_hierarchical( $main_tax ) && $deepest_term->parent != 0 ) {
+ $parent_terms = $this->get_term_parents( $deepest_term );
+ foreach ( $parent_terms as $parent_term ) {
+ $this->add_term_crumb( $parent_term );
+ }
+ }
+
+ $this->add_term_crumb( $deepest_term );
+ }
+ }
+ }
+ }
+
+ /**
+ * Add hierarchical ancestor crumbs to the crumbs property for a single post
+ */
+ private function add_post_ancestor_crumbs() {
+ $ancestors = $this->get_post_ancestors();
+ if ( is_array( $ancestors ) && $ancestors !== array() ) {
+ foreach ( $ancestors as $ancestor ) {
+ $this->add_single_post_crumb( $ancestor );
+ }
+ }
+ }
+
+ /**
+ * Add taxonomy parent crumbs to the crumbs property for a taxonomy
+ */
+ private function add_crumbs_for_taxonomy() {
+ global $wp_query;
+
+ $term = $wp_query->get_queried_object();
+
+ // @todo adjust function name!!
+ $this->maybe_add_preferred_term_parent_crumb( $term );
+
+ $this->maybe_add_term_parent_crumbs( $term );
+
+ $this->add_term_crumb( $term );
+ }
+
+ /**
+ * Add parent taxonomy crumb based on user defined preference
+ */
+ private function maybe_add_preferred_term_parent_crumb( $term ) {
+ if ( isset( $this->options[ 'taxonomy-' . $term->taxonomy . '-ptparent' ] ) && $this->options[ 'taxonomy-' . $term->taxonomy . '-ptparent' ] != '0' ) {
+ if ( 'post' == $this->options[ 'taxonomy-' . $term->taxonomy . '-ptparent' ] && $this->show_on_front == 'page' ) {
+ if ( $this->page_for_posts ) {
+ $this->add_blog_crumb();
+ }
+ }
+ else {
+ $this->add_ptarchive_crumb( $this->options[ 'taxonomy-' . $term->taxonomy . '-ptparent' ] );
+ }
+ }
+ }
+
+ /**
+ * Add parent taxonomy crumbs to the crumb property for hierachical taxonomy
+ */
+ private function maybe_add_term_parent_crumbs( $term ) {
+ if ( is_taxonomy_hierarchical( $term->taxonomy ) && $term->parent != 0 ) {
+ foreach ( $this->get_term_parents( $term ) as $parent_term ) {
+ $this->add_term_crumb( $parent_term );
+ }
+ }
+ }
+
+ /**
+ * Add month-year crumb to crumbs property
+ */
+ private function add_linked_month_year_crumb() {
+ global $wp_locale;
+
+ $this->add_predefined_crumb(
+ $wp_locale->get_month( get_query_var( 'monthnum' ) ) . ' ' . get_query_var( 'year' ),
+ get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) )
+ );
+ }
+
+ /**
+ * Add (non-link) month crumb to crumbs property
+ */
+ private function add_month_crumb() {
+ $this->add_predefined_crumb(
+ $this->options['breadcrumbs-archiveprefix'] . ' ' . esc_html( single_month_title( ' ', false ) ),
+ null,
+ true
+ );
+ }
+
+ /**
+ * Add (non-link) year crumb to crumbs property
+ */
+ private function add_year_crumb() {
+ $this->add_predefined_crumb(
+ $this->options['breadcrumbs-archiveprefix'] . ' ' . esc_html( get_query_var( 'year' ) ),
+ null,
+ true
+ );
+ }
+
+ /**
+ * Add (non-link) date crumb to crumbs property
+ */
+ private function add_date_crumb( $date = null ) {
+ if ( is_null( $date ) ) {
+ $date = get_the_date();
+ }
+ else {
+ $date = mysql2date( get_option( 'date_format' ), $date, true );
+ $date = apply_filters( 'get_the_date', $date, '' );
+ }
+
+ $this->add_predefined_crumb(
+ $this->options['breadcrumbs-archiveprefix'] . ' ' . esc_html( $date ),
+ null,
+ true
+ );
+ }
+
+
+ /**
+ * Take the crumbs array and convert each crumb to a single breadcrumb string.
+ *
+ * @link http://support.google.com/webmasters/bin/answer.py?hl=en&answer=185417 Google documentation on RDFA
+ */
+ private function prepare_links() {
+ if ( ! is_array( $this->crumbs ) || $this->crumbs === array() ) {
+ return;
+ }
+
+ foreach ( $this->crumbs as $i => $crumb ) {
+ $link_info = $crumb; // Keep pre-set url/text combis
+
+ if ( isset( $crumb['id'] ) ) {
+ $link_info = $this->get_link_info_for_id( $crumb['id'] );
+ }
+ if ( isset( $crumb['term'] ) ) {
+ $link_info = $this->get_link_info_for_term( $crumb['term'] );
+ }
+ if ( isset( $crumb['ptarchive'] ) ) {
+ $link_info = $this->get_link_info_for_ptarchive( $crumb['ptarchive'] );
+ }
+
+ $this->links[] = $this->crumb_to_link( $link_info, $i );
+ }
+ }
+
+ /**
+ * Retrieve link url and text based on post id
+ *
+ * @param int $id Post id
+ *
+ * @return array $link Array of link text and url
+ */
+ private function get_link_info_for_id( $id ) {
+ $link = array();
+
+ $link['url'] = get_permalink( $id );
+ $link['text'] = WPSEO_Meta::get_value( 'bctitle', $id );
+ if ( $link['text'] === '' ) {
+ $link['text'] = strip_tags( get_the_title( $id ) );
+ }
+
+ /**
+ * Filter: 'wp_seo_get_bc_title' - Allow developer to filter the WP SEO Breadcrumb title.
+ *
+ * @api string $link_text The Breadcrumb title text
+ *
+ * @param int $link_id The post ID
+ */
+ $link['text'] = apply_filters( 'wp_seo_get_bc_title', $link['text'], $id );
+
+ return $link;
+ }
+
+ /**
+ * Retrieve link url and text based on term object
+ *
+ * @param object $term Term object
+ *
+ * @return array $link Array of link text and url
+ */
+ private function get_link_info_for_term( $term ) {
+ $link = array();
+
+ $bctitle = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'bctitle' );
+ if ( ! is_string( $bctitle ) || $bctitle === '' ) {
+ $bctitle = $term->name;
+ }
+
+ $link['url'] = get_term_link( $term );
+ $link['text'] = $bctitle;
+
+ return $link;
+ }
+
+ /**
+ * Retrieve link url and text based on post type
+ *
+ * @param string $pt Post type
+ *
+ * @return array $link Array of link text and url
+ */
+ private function get_link_info_for_ptarchive( $pt ) {
+ $link = array();
+ $archive_title = '';
+
+ if ( isset( $this->options[ 'bctitle-ptarchive-' . $pt ] ) && $this->options[ 'bctitle-ptarchive-' . $pt ] !== '' ) {
+
+ $archive_title = $this->options[ 'bctitle-ptarchive-' . $pt ];
+ }
+ else {
+ $post_type_obj = get_post_type_object( $pt );
+ if ( is_object( $post_type_obj ) ) {
+ if ( isset( $post_type_obj->label ) && $post_type_obj->label !== '' ) {
+ $archive_title = $post_type_obj->label;
+ }
+ elseif ( isset( $post_type_obj->labels->menu_name ) && $post_type_obj->labels->menu_name !== '' ) {
+ $archive_title = $post_type_obj->labels->menu_name;
+ }
+ else {
+ $archive_title = $post_type_obj->name;
+ }
+ }
+ }
+
+ $link['url'] = get_post_type_archive_link( $pt );
+ $link['text'] = $archive_title;
+
+ return $link;
+ }
+
+
+ /**
+ * Create a breadcrumb element string
+ *
+ * @param array $link Link info array containing the keys:
+ * 'text' => (string) link text
+ * 'url' => (string) link url
+ * (optional) 'allow_html' => (bool) whether to (not) escape html in the link text
+ * This prevents html stripping from the text strings set in the
+ * WPSEO -> Internal Links options page
+ *
+ * @return string
+ */
+ private function crumb_to_link( $link, $i ) {
+ global $paged; // @todo ? -> only works for archives, not for paged articles
+
+ $link_output = '';
+
+ if ( isset( $link['text'] ) && ( is_string( $link['text'] ) && $link['text'] !== '' ) ) {
+
+ $link['text'] = trim( $link['text'] );
+ if ( ! isset( $link['allow_html'] ) || $link['allow_html'] !== true ) {
+ $link['text'] = esc_html( $link['text'] );
+ }
+
+ $inner_elm = 'span';
+ if ( $this->options['breadcrumbs-boldlast'] === true && $i === ( $this->crumb_count - 1 ) ) {
+ $inner_elm = 'strong';
+ }
+
+ $class = '';
+ if ( $i === ( $this->crumb_count - 1 ) ) {
+ $class = ' class="breadcrumb_last"';
+ }
+
+
+ $link_output = '<' . $this->element . ' typeof="v:Breadcrumb">';
+
+ if ( ( isset( $link['url'] ) && ( is_string( $link['url'] ) && $link['url'] !== '' ) ) &&
+ ( $i < ( $this->crumb_count - 1 ) || $paged ) ) {
+ $link_output .= '<a href="' . esc_url( $link['url'] ) . '"' . $class . ' rel="v:url" property="v:title">' . $link['text'] . '</a>';
+ }
+ else {
+ $link_output .= '<' . $inner_elm . $class . ' property="v:title">' . $link['text'] . '</' . $inner_elm . '>';
+ }
+
+ $link_output .= '</' . $this->element . '>';
+
+ }
+ /**
+ * Filter: 'wpseo_breadcrumb_single_link' - Allow changing of each link being put out by the WP SEO breadcrumbs class
+ *
+ * @api string $link_output The output string
+ *
+ * @param array $link The link array
+ */
+ return apply_filters( 'wpseo_breadcrumb_single_link', $link_output, $link );
+ }
+
+
+ /**
+ * Create a complete breadcrumb string from an array of breadcrumb element strings
+ */
+ private function links_to_string() {
+ if ( is_array( $this->links ) && $this->links !== array() ) {
+ // Remove any effectively empty links
+ $links = array_map( 'trim', $this->links );
+ $links = array_filter( $links );
+
+ $this->output = implode( $this->separator, $links );
+ }
+ }
+
+ /**
+ * Wrap a complete breadcrumb string in a Breadcrumb RDFA wrapper
+ */
+ private function wrap_breadcrumb() {
+ if ( is_string( $this->output ) && $this->output !== '' ) {
+ $output = '
+ <' . $this->wrapper . $this->get_output_id() . $this->get_output_class() . ' prefix="v: http://rdf.data-vocabulary.org/#">
+ ' . $this->output . '
+ </' . $this->wrapper . '>';
+
+ /**
+ * Filter: 'wpseo_breadcrumb_output' - Allow changing the HTML output of the WP SEO breadcrumbs class
+ *
+ * @api string $unsigned HTML output
+ */
+ $output = apply_filters( 'wpseo_breadcrumb_output', $output );
+
+ if ( $this->options['breadcrumbs-prefix'] !== '' ) {
+ $output = "\t" . $this->options['breadcrumbs-prefix'] . "\n" . $output;
+ }
+
+ $this->output = $output;
+ }
+ }
+
+
+ /**
+ * Filter: 'wpseo_breadcrumb_output_id' - Allow changing the HTML ID on the WP SEO breadcrumbs wrapper element
+ *
+ * @api string $unsigned ID to add to the wrapper element
+ */
+ private function get_output_id() {
+ $id = apply_filters( 'wpseo_breadcrumb_output_id', '' );
+ if ( is_string( $id ) && '' !== $id ) {
+ $id = ' id="' . esc_attr( $id ) . '"';
+ }
+ return $id;
+ }
+
+ /**
+ * Filter: 'wpseo_breadcrumb_output_class' - Allow changing the HTML class on the WP SEO breadcrumbs wrapper element
+ *
+ * @api string $unsigned class to add to the wrapper element
+ */
+ private function get_output_class() {
+ $class = apply_filters( 'wpseo_breadcrumb_output_class', '' );
+ if ( is_string( $class ) && '' !== $class ) {
+ $class = ' class="' . esc_attr( $class ) . '"';
+ }
+ return $class;
+ }
+
+
+ /********************** DEPRECATED METHODS **********************/
+
+ /**
+ * Wrapper function for the breadcrumb so it can be output for the supported themes.
+ *
+ * @deprecated 1.5.0
+ */
+ public function breadcrumb_output() {
+ _deprecated_function( __METHOD__, '1.5.0', 'yoast_breadcrumb' );
+ self::breadcrumb( '<div id="wpseobreadcrumb">', '</div>' );
+ }
+
+ /**
+ * Take the links array and return a full breadcrumb string.
+ *
+ * @deprecated 1.5.2.3
+ *
+ * @return string
+ */
+ public function create_breadcrumbs_string( $links, $wrapper = 'span', $element = 'span' ) {
+ _deprecated_function( __METHOD__, 'WPSEO 1.5.2.3', 'yoast_breadcrumbs' );
+ }
+
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Frontend
+ *
+ * Main frontend code.
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Frontend' ) ) {
+ /**
+ * Main frontend class for WordPress SEO, responsible for the SEO output as well as removing
+ * default WordPress output.
+ *
+ * @package WPSEO_Frontend
+ */
+ class WPSEO_Frontend {
+
+ /**
+ * @var array Holds the plugins options.
+ */
+ var $options = array();
+
+ /**
+ * @var boolean Boolean indicating wether output buffering has been started
+ */
+ private $ob_started = false;
+
+ /**
+ * Class constructor
+ *
+ * Adds and removes a lot of filters.
+ */
+ function __construct() {
+
+ $this->options = WPSEO_Options::get_all();
+
+ add_action( 'wp_head', array( $this, 'head' ), 1 );
+
+ // The head function here calls action wpseo_head, to which we hook all our functionality
+ add_action( 'wpseo_head', array( $this, 'debug_marker' ), 2 );
+ add_action( 'wpseo_head', array( $this, 'robots' ), 6 );
+ add_action( 'wpseo_head', array( $this, 'metadesc' ), 10 );
+ add_action( 'wpseo_head', array( $this, 'metakeywords' ), 11 );
+ add_action( 'wpseo_head', array( $this, 'canonical' ), 20 );
+ add_action( 'wpseo_head', array( $this, 'adjacent_rel_links' ), 21 );
+ add_action( 'wpseo_head', array( $this, 'publisher' ), 22 );
+ add_action( 'wpseo_head', array( $this, 'webmaster_tools_authentication' ), 90 );
+ add_action( 'wpseo_head', array( $this, 'internal_search_json_ld' ), 90 );
+
+ // Remove actions that we will handle through our wpseo_head call, and probably change the output of
+ remove_action( 'wp_head', 'rel_canonical' );
+ remove_action( 'wp_head', 'index_rel_link' );
+ remove_action( 'wp_head', 'start_post_rel_link' );
+ remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
+ remove_action( 'wp_head', 'noindex', 1 );
+
+ add_filter( 'wp_title', array( $this, 'title' ), 15, 3 );
+ add_filter( 'thematic_doctitle', array( $this, 'title' ), 15 );
+
+ add_action( 'wp', array( $this, 'page_redirect' ), 99 );
+
+ add_action( 'template_redirect', array( $this, 'noindex_feed' ) );
+
+ add_filter( 'loginout', array( $this, 'nofollow_link' ) );
+ add_filter( 'register', array( $this, 'nofollow_link' ) );
+
+ // Fix the WooThemes woo_title() output
+ add_filter( 'woo_title', array( $this, 'fix_woo_title' ), 99 );
+
+ if ( $this->options['hide-rsdlink'] === true ) {
+ remove_action( 'wp_head', 'rsd_link' );
+ }
+ if ( $this->options['hide-wlwmanifest'] === true ) {
+ remove_action( 'wp_head', 'wlwmanifest_link' );
+ }
+ if ( $this->options['hide-shortlink'] === true ) {
+ remove_action( 'wp_head', 'wp_shortlink_wp_head' );
+ remove_action( 'template_redirect', 'wp_shortlink_header', 11 );
+ }
+ if ( $this->options['hide-feedlinks'] === true ) {
+ // @todo: add option to display just normal feed and hide comment feed.
+ remove_action( 'wp_head', 'feed_links', 2 );
+ remove_action( 'wp_head', 'feed_links_extra', 3 );
+ }
+
+ if ( ( $this->options['disable-date'] === true ||
+ $this->options['disable-author'] === true ) ||
+ ( isset( $this->options['disable-post_formats'] ) && $this->options['disable-post_formats'] )
+ ) {
+ add_action( 'wp', array( $this, 'archive_redirect' ) );
+ }
+ if ( $this->options['redirectattachment'] === true ) {
+ add_action( 'template_redirect', array( $this, 'attachment_redirect' ), 1 );
+ }
+ if ( $this->options['trailingslash'] === true ) {
+ add_filter( 'user_trailingslashit', array( $this, 'add_trailingslash' ), 10, 2 );
+ }
+ if ( $this->options['cleanpermalinks'] === true ) {
+ add_action( 'template_redirect', array( $this, 'clean_permalink' ), 1 );
+ }
+ if ( $this->options['cleanreplytocom'] === true ) {
+ add_filter( 'comment_reply_link', array( $this, 'remove_reply_to_com' ) );
+ }
+ add_filter( 'the_content_feed', array( $this, 'embed_rssfooter' ) );
+ add_filter( 'the_excerpt_rss', array( $this, 'embed_rssfooter_excerpt' ) );
+
+ if ( $this->options['forcerewritetitle'] === true ) {
+ add_action( 'template_redirect', array( $this, 'force_rewrite_output_buffer' ), 99999 );
+ add_action( 'wp_footer', array( $this, 'flush_cache' ), -1 );
+ }
+
+ if ( $this->options['title_test'] > 0 ) {
+ add_filter( 'wpseo_title', array( $this, 'title_test_helper' ) );
+ }
+ if ( isset( $_GET['replytocom'] ) ) {
+ remove_action( 'wp_head', 'wp_no_robots' );
+ add_action( 'template_redirect', array( $this, 'replytocom_redirect' ), 1 );
+ }
+ }
+
+ /**
+ * Override Woo's title with our own.
+ *
+ * @param string $title
+ *
+ * @return string
+ */
+ public function fix_woo_title( $title ) {
+ return $this->title( $title );
+ }
+
+ /**
+ * Determine whether the current page is the homepage and shows posts.
+ *
+ * @return bool
+ */
+ function is_home_posts_page() {
+ return ( is_home() && 'posts' == get_option( 'show_on_front' ) );
+ }
+
+ /**
+ * Determine whether the current page is a static homepage.
+ *
+ * @return bool
+ */
+ function is_home_static_page() {
+ return ( is_front_page() && 'page' == get_option( 'show_on_front' ) && is_page( get_option( 'page_on_front' ) ) );
+ }
+
+ /**
+ * Determine whether this is the posts page, regardless of whether it's the frontpage or not.
+ *
+ * @return bool
+ */
+ function is_posts_page() {
+ return ( is_home() && 'page' == get_option( 'show_on_front' ) );
+ }
+
+ /**
+ * Used for static home and posts pages as well as singular titles.
+ *
+ * @param object|null $object if filled, object to get the title for
+ *
+ * @return string
+ */
+ function get_content_title( $object = null ) {
+ if ( is_null( $object ) ) {
+ global $wp_query;
+ $object = $wp_query->get_queried_object();
+ }
+
+ $title = WPSEO_Meta::get_value( 'title', $object->ID );
+
+ if ( $title !== '' ) {
+ return wpseo_replace_vars( $title, $object );
+ }
+
+ $post_type = ( isset( $object->post_type ) ? $object->post_type : $object->query_var );
+
+ return $this->get_title_from_options( 'title-' . $post_type, $object );
+ }
+
+ /**
+ * Used for category, tag, and tax titles.
+ *
+ * @return string
+ */
+ function get_taxonomy_title() {
+ global $wp_query;
+ $object = $wp_query->get_queried_object();
+
+ $title = WPSEO_Taxonomy_Meta::get_term_meta( $object, $object->taxonomy, 'title' );
+
+ if ( is_string( $title ) && $title !== '' ) {
+ return wpseo_replace_vars( $title, $object );
+ } else {
+ return $this->get_title_from_options( 'title-tax-' . $object->taxonomy, $object );
+ }
+ }
+
+ /**
+ * Used for author titles.
+ *
+ * @return string
+ */
+ function get_author_title() {
+ $author_id = get_query_var( 'author' );
+ $title = trim( get_the_author_meta( 'wpseo_title', $author_id ) );
+
+ if ( $title !== '' ) {
+ return wpseo_replace_vars( $title, array() );
+ }
+
+ return $this->get_title_from_options( 'title-author-wpseo' );
+ }
+
+ /**
+ * Simple function to use to pull data from $options.
+ *
+ * All titles pulled from options will be run through the wpseo_replace_vars function.
+ *
+ * @param string $index name of the page to get the title from the settings for.
+ * @param object|array $var_source possible object to pull variables from.
+ *
+ * @return string
+ */
+ function get_title_from_options( $index, $var_source = array() ) {
+ if ( ! isset( $this->options[ $index ] ) || $this->options[ $index ] === '' ) {
+ if ( is_singular() ) {
+ return wpseo_replace_vars( '%%title%% %%sep%% %%sitename%%', $var_source );
+ } else {
+ return '';
+ }
+ } else {
+ return wpseo_replace_vars( $this->options[ $index ], $var_source );
+ }
+ }
+
+ /**
+ * Get the default title for the current page.
+ *
+ * This is the fallback title generator used when a title hasn't been set for the specific content, taxonomy, author
+ * details, or in the options. It scrubs off any present prefix before or after the title (based on $seplocation) in
+ * order to prevent duplicate seperations from appearing in the title (this happens when a prefix is supplied to the
+ * wp_title call on singular pages).
+ *
+ * @param string $sep the separator used between variables
+ * @param string $seplocation Whether the separator should be left or right.
+ * @param string $title possible title that's already set
+ *
+ * @return string
+ */
+ function get_default_title( $sep, $seplocation, $title = '' ) {
+ if ( 'right' == $seplocation ) {
+ $regex = '`\s*' . preg_quote( trim( $sep ), '`' ) . '\s*`u';
+ } else {
+ $regex = '`^\s*' . preg_quote( trim( $sep ), '`' ) . '\s*`u';
+ }
+ $title = preg_replace( $regex, '', $title );
+
+ if ( ! is_string( $title ) || ( is_string( $title ) && $title === '' ) ) {
+ $title = get_bloginfo( 'name' );
+ $title = $this->add_paging_to_title( $sep, $seplocation, $title );
+ $title = $this->add_to_title( $sep, $seplocation, $title, strip_tags( get_bloginfo( 'description' ) ) );
+
+ return $title;
+ }
+
+ $title = $this->add_paging_to_title( $sep, $seplocation, $title );
+ $title = $this->add_to_title( $sep, $seplocation, $title, strip_tags( get_bloginfo( 'name' ) ) );
+
+ return $title;
+ }
+
+ /**
+ * This function adds paging details to the title.
+ *
+ * @param string $sep separator used in the title
+ * @param string $seplocation Whether the separator should be left or right.
+ * @param string $title the title to append the paging info to
+ *
+ * @return string
+ */
+ function add_paging_to_title( $sep, $seplocation, $title ) {
+ global $wp_query;
+
+ if ( ! empty( $wp_query->query_vars['paged'] ) && $wp_query->query_vars['paged'] > 1 ) {
+ return $this->add_to_title( $sep, $seplocation, $title, $wp_query->query_vars['paged'] . '/' . $wp_query->max_num_pages );
+ }
+
+ return $title;
+ }
+
+ /**
+ * Add part to title, while ensuring that the $seplocation variable is respected.
+ *
+ * @param string $sep separator used in the title
+ * @param string $seplocation Whether the separator should be left or right.
+ * @param string $title the title to append the title_part to
+ * @param string $title_part the part to append to the title
+ *
+ * @return string
+ */
+ function add_to_title( $sep, $seplocation, $title, $title_part ) {
+ if ( 'right' === $seplocation ) {
+ return $title . $sep . $title_part;
+ }
+
+ return $title_part . $sep . $title;
+ }
+
+ /**
+ * Main title function.
+ *
+ * @param string $title Title that might have already been set.
+ * @param string $separator Separator determined in theme (unused)
+ * @param string $separator_location Whether the separator should be left or right.
+ *
+ * @return string
+ */
+ function title( $title, $separator = '', $separator_location = '' ) {
+
+ if ( is_feed() ) {
+ return $title;
+ }
+
+ $separator = wpseo_replace_vars( '%%sep%%', array() );
+ $separator = ' ' . trim( $separator ) . ' ';
+
+ if ( '' === trim( $separator_location ) ) {
+ $separator_location = ( is_rtl() ) ? 'left' : 'right';
+ }
+
+ // This needs to be kept track of in order to generate
+ // default titles for singular pages.
+ $original_title = $title;
+
+ // This flag is used to determine if any additional
+ // processing should be done to the title after the
+ // main section of title generation completes.
+ $modified_title = true;
+
+ // This variable holds the page-specific title part
+ // that is used to generate default titles.
+ $title_part = '';
+
+ if ( $this->is_home_static_page() ) {
+ $title = $this->get_content_title();
+ } elseif ( $this->is_home_posts_page() ) {
+ $title = $this->get_title_from_options( 'title-home-wpseo' );
+ } elseif ( $this->is_posts_page() ) {
+ $title = $this->get_content_title( get_post( get_option( 'page_for_posts' ) ) );
+ } elseif ( is_singular() ) {
+ $title = $this->get_content_title();
+
+ if ( ! is_string( $title ) || '' === $title ) {
+ $title_part = $original_title;
+ }
+ } elseif ( is_search() ) {
+ $title = $this->get_title_from_options( 'title-search-wpseo' );
+
+ if ( ! is_string( $title ) || '' === $title ) {
+ $title_part = sprintf( __( 'Search for "%s"', 'wordpress-seo' ), esc_html( get_search_query() ) );
+ }
+ } elseif ( is_category() || is_tag() || is_tax() ) {
+ $title = $this->get_taxonomy_title();
+
+ if ( ! is_string( $title ) || '' === $title ) {
+ if ( is_category() ) {
+ $title_part = single_cat_title( '', false );
+ } elseif ( is_tag() ) {
+ $title_part = single_tag_title( '', false );
+ } else {
+ $title_part = single_term_title( '', false );
+ if ( $title_part === '' ) {
+ global $wp_query;
+ $term = $wp_query->get_queried_object();
+ $title_part = $term->name;
+ }
+ }
+ }
+ } elseif ( is_author() ) {
+ $title = $this->get_author_title();
+
+ if ( ! is_string( $title ) || '' === $title ) {
+ $title_part = get_the_author_meta( 'display_name', get_query_var( 'author' ) );
+ }
+ } elseif ( is_post_type_archive() ) {
+ $post_type = get_query_var( 'post_type' );
+
+ if ( is_array( $post_type ) ) {
+ $post_type = reset( $post_type );
+ }
+
+ $title = $this->get_title_from_options( 'title-ptarchive-' . $post_type );
+
+ if ( ! is_string( $title ) || '' === $title ) {
+ $post_type_obj = get_post_type_object( $post_type );
+ if ( isset( $post_type_obj->labels->menu_name ) ) {
+ $title_part = $post_type_obj->labels->menu_name;
+ } elseif ( isset( $post_type_obj->name ) ) {
+ $title_part = $post_type_obj->name;
+ } else {
+ $title_part = ''; //To be determined what this should be
+ }
+ }
+ } elseif ( is_archive() ) {
+ $title = $this->get_title_from_options( 'title-archive-wpseo' );
+
+ // @todo [JRF => Yoast] Should these not use the archive default if no title found ?
+ // WPSEO_Options::get_default( 'wpseo_titles', 'title-archive-wpseo' )
+ // Replacement would be needed!
+ if ( empty( $title ) ) {
+ if ( is_month() ) {
+ $title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), single_month_title( ' ', false ) );
+ } elseif ( is_year() ) {
+ $title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), get_query_var( 'year' ) );
+ } elseif ( is_day() ) {
+ $title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), get_the_date() );
+ } else {
+ $title_part = __( 'Archives', 'wordpress-seo' );
+ }
+ }
+ } elseif ( is_404() ) {
+
+ if ( 0 !== get_query_var( 'year' ) || ( 0 !== get_query_var( 'monthnum' ) || 0 !== get_query_var( 'day' ) ) ) {
+ // @todo [JRF => Yoast] Should these not use the archive default if no title found ?
+ if ( 0 !== get_query_var( 'day' ) ) {
+ $date = sprintf( '%04d-%02d-%02d 00:00:00', get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) );
+ $date = mysql2date( get_option( 'date_format' ), $date, true );
+ $date = apply_filters( 'get_the_date', $date, '' );
+ $title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), $date );
+ } elseif ( 0 !== get_query_var( 'monthnum' ) ) {
+ $title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), single_month_title( ' ', false ) );
+ } elseif ( 0 !== get_query_var( 'year' ) ) {
+ $title_part = sprintf( __( '%s Archives', 'wordpress-seo' ), get_query_var( 'year' ) );
+ } else {
+ $title_part = __( 'Archives', 'wordpress-seo' );
+ }
+ } else {
+ $title = $this->get_title_from_options( 'title-404-wpseo' );
+
+ // @todo [JRF => Yoast] Should these not use the 404 default if no title found ?
+ // WPSEO_Options::get_default( 'wpseo_titles', 'title-404-wpseo' )
+ // Replacement would be needed!
+ if ( empty( $title ) ) {
+ $title_part = __( 'Page not found', 'wordpress-seo' );
+ }
+ }
+ } else {
+ // In case the page type is unknown, leave the title alone.
+ $modified_title = false;
+
+ // If you would like to generate a default title instead,
+ // the following code could be used instead of the line above:
+ // $title_part = $title;
+ }
+
+ if ( ( $modified_title && empty( $title ) ) || ! empty( $title_part ) ) {
+ $title = $this->get_default_title( $separator, $separator_location, $title_part );
+ }
+
+ if ( defined( 'ICL_LANGUAGE_CODE' ) && false !== strpos( $title, ICL_LANGUAGE_CODE ) ) {
+ $title = str_replace( ' @' . ICL_LANGUAGE_CODE, '', $title );
+ }
+
+ /**
+ * Filter: 'wpseo_title' - Allow changing the WP SEO <title> output
+ *
+ * @api string $title The page title being put out.
+ */
+ return esc_html( strip_tags( stripslashes( apply_filters( 'wpseo_title', $title ) ) ) );
+ }
+
+ /**
+ * Function used when title needs to be force overridden.
+ *
+ * @return string
+ */
+ function force_wp_title() {
+ global $wp_query;
+ $old_wp_query = null;
+
+ if ( ! $wp_query->is_main_query() ) {
+ $old_wp_query = $wp_query;
+ wp_reset_query();
+ }
+
+ $title = $this->title( '' );
+
+ if ( ! empty( $old_wp_query ) ) {
+ $GLOBALS['wp_query'] = $old_wp_query;
+ unset( $old_wp_query );
+ }
+
+ return $title;
+ }
+
+ /**
+ * Outputs or returns the debug marker, which is also used for title replacement when force rewrite is active.
+ *
+ * @param bool $echo Whether or not to echo the debug marker.
+ *
+ * @return string
+ */
+ public function debug_marker( $echo = true ) {
+ $marker = '<!-- This site is optimized with the Yoast WordPress SEO plugin v' . WPSEO_VERSION . ' - https://yoast.com/wordpress/plugins/seo/ -->';
+ if ( $echo === false ) {
+ return $marker;
+ } else {
+ echo "\n${marker}\n";
+ }
+ }
+
+ /**
+ * Output Webmaster Tools authentication strings
+ */
+ public function webmaster_tools_authentication() {
+ if ( is_front_page() ) {
+ // Alexa
+ if ( $this->options['alexaverify'] !== '' ) {
+ echo '<meta name="alexaVerifyID" content="' . esc_attr( $this->options['alexaverify'] ) . "\" />\n";
+ }
+
+ // Bing
+ if ( $this->options['msverify'] !== '' ) {
+ echo '<meta name="msvalidate.01" content="' . esc_attr( $this->options['msverify'] ) . "\" />\n";
+ }
+
+ // Google
+ if ( $this->options['googleverify'] !== '' ) {
+ echo '<meta name="google-site-verification" content="' . esc_attr( $this->options['googleverify'] ) . "\" />\n";
+ }
+
+ // Pinterest
+ if ( $this->options['pinterestverify'] !== '' ) {
+ echo '<meta name="p:domain_verify" content="' . esc_attr( $this->options['pinterestverify'] ) . "\" />\n";
+ }
+
+ // Yandex
+ if ( $this->options['yandexverify'] !== '' ) {
+ echo '<meta name="yandex-verification" content="' . esc_attr( $this->options['yandexverify'] ) . "\" />\n";
+ }
+ }
+ }
+
+ /**
+ * Outputs JSON+LD code to allow recognition of the internal search engine
+ *
+ * @since 1.5.7
+ *
+ * @link https://developers.google.com/webmasters/richsnippets/sitelinkssearch
+ */
+ public function internal_search_json_ld() {
+ if ( ! is_front_page() ) {
+ return;
+ }
+
+ /**
+ * Filter: 'disable_wpseo_json_ld_search' - Allow disabling of the json+ld output
+ *
+ * @api bool $display_search Whether or not to display json+ld search on the frontend
+ */
+ if ( apply_filters( 'disable_wpseo_json_ld_search', false ) === true ) {
+ return;
+ }
+
+ $home_url = trailingslashit( home_url() );
+
+ /**
+ * Filter: 'wpseo_json_ld_search_url' - Allows filtering of the search URL for WP SEO
+ *
+ * @api string $search_url The search URL for this site with a `{search_term}` variable.
+ */
+ $search_url = apply_filters( 'wpseo_json_ld_search_url', $home_url . '?s={search_term}' );
+
+ /**
+ * Filter: 'wpseo_json_ld_search_output' - Allows filtering of the entire output of the function
+ *
+ * @api string $output The output of the function.
+ */
+ echo apply_filters( 'wpseo_json_ld_search_output', '<script type="application/ld+json">{ "@context": "http://schema.org", "@type": "WebSite", "url": "' . $home_url . '", "potentialAction": { "@type": "SearchAction", "target": "' . $search_url .'", "query-input": "required name=search_term" } }</script>' . "\n" );
+ }
+
+ /**
+ * Main wrapper function attached to wp_head. This combines all the output on the frontend of the WP SEO plugin.
+ */
+ public function head() {
+ global $wp_query;
+
+ $old_wp_query = null;
+
+ if ( ! $wp_query->is_main_query() ) {
+ $old_wp_query = $wp_query;
+ wp_reset_query();
+ }
+
+ /**
+ * Action: 'wpseo_head' - Allow other plugins to output inside the WP SEO section of the head section.
+ */
+ do_action( 'wpseo_head' );
+
+ echo "<!-- / Yoast WordPress SEO plugin. -->\n\n";
+
+ if ( ! empty( $old_wp_query ) ) {
+ $GLOBALS['wp_query'] = $old_wp_query;
+ unset( $old_wp_query );
+ }
+
+ return;
+ }
+
+
+ /**
+ * Output the meta robots value.
+ *
+ * @return string
+ */
+ public function robots() {
+ global $wp_query;
+
+ $robots = array();
+ $robots['index'] = 'index';
+ $robots['follow'] = 'follow';
+ $robots['other'] = array();
+
+ if ( is_singular() ) {
+ global $post;
+
+ if ( is_object( $post ) && ( isset( $this->options[ 'noindex-' . $post->post_type ] ) && $this->options[ 'noindex-' . $post->post_type ] === true ) ) {
+ $robots['index'] = 'noindex';
+ }
+
+ if ( 'private' == $post->post_status ) {
+ $robots['index'] = 'noindex';
+ }
+
+ $robots = $this->robots_for_single_post( $robots );
+
+ } else {
+ if ( is_search() ) {
+ $robots['index'] = 'noindex';
+ } elseif ( is_tax() || is_tag() || is_category() ) {
+ $term = $wp_query->get_queried_object();
+ if ( is_object( $term ) && ( isset( $this->options[ 'noindex-tax-' . $term->taxonomy ] ) && $this->options[ 'noindex-tax-' . $term->taxonomy ] === true ) ) {
+ $robots['index'] = 'noindex';
+ }
+
+ // Three possible values, index, noindex and default, do nothing for default
+ $term_meta = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'noindex' );
+ if ( is_string( $term_meta ) && 'default' !== $term_meta ) {
+ $robots['index'] = $term_meta;
+ }
+ } elseif (
+ ( is_author() && $this->options['noindex-author-wpseo'] === true ) ||
+ ( is_date() && $this->options['noindex-archive-wpseo'] === true )
+ ) {
+ $robots['index'] = 'noindex';
+ } elseif ( is_home() ) {
+ if ( get_query_var( 'paged' ) > 1 && $this->options['noindex-subpages-wpseo'] === true ) {
+ $robots['index'] = 'noindex';
+ }
+
+ $page_for_posts = get_option( 'page_for_posts' );
+ if ( $page_for_posts ) {
+ $robots = $this->robots_for_single_post( $robots, $page_for_posts );
+ }
+ unset( $page_for_posts );
+
+ } elseif ( is_post_type_archive() ) {
+ $post_type = get_query_var( 'post_type' );
+
+ if ( is_array( $post_type ) ) {
+ $post_type = reset( $post_type );
+ }
+
+ if ( isset( $this->options[ 'noindex-ptarchive-' . $post_type ] ) && $this->options[ 'noindex-ptarchive-' . $post_type ] === true ) {
+ $robots['index'] = 'noindex';
+ }
+ }
+
+ if ( isset( $wp_query->query_vars['paged'] ) && ( $wp_query->query_vars['paged'] && $wp_query->query_vars['paged'] > 1 ) && ( $this->options['noindex-subpages-wpseo'] === true ) ) {
+ $robots['index'] = 'noindex';
+ $robots['follow'] = 'follow';
+ }
+
+ foreach ( array( 'noodp', 'noydir' ) as $robot ) {
+ if ( $this->options[ $robot ] === true ) {
+ $robots['other'][] = $robot;
+ }
+ }
+ unset( $robot );
+ }
+
+ // Force override to respect the WP settings
+ if ( '0' == get_option( 'blog_public' ) || isset( $_GET['replytocom'] ) ) {
+ $robots['index'] = 'noindex';
+ }
+
+
+ $robotsstr = $robots['index'] . ',' . $robots['follow'];
+
+ if ( $robots['other'] !== array() ) {
+ $robots['other'] = array_unique( $robots['other'] ); // most likely no longer needed, needs testing
+ $robotsstr .= ',' . implode( ',', $robots['other'] );
+ }
+
+ $robotsstr = preg_replace( '`^index,follow,?`', '', $robotsstr );
+
+ /**
+ * Filter: 'wpseo_robots' - Allows filtering of the meta robots output of WP SEO
+ *
+ * @api string $robotsstr The meta robots directives to be echoed.
+ */
+ $robotsstr = apply_filters( 'wpseo_robots', $robotsstr );
+
+ if ( is_string( $robotsstr ) && $robotsstr !== '' ) {
+ echo '<meta name="robots" content="' . esc_attr( $robotsstr ) . '"/>' . "\n";
+ }
+
+ return $robotsstr;
+ }
+
+ /**
+ * Determine $robots values for a single post
+ *
+ * @param array $robots
+ * @param int|string $postid The postid for which to determine the $robots values, defaults to
+ * the current post
+ *
+ * @return array
+ */
+ function robots_for_single_post( $robots, $postid = 0 ) {
+ if ( WPSEO_Meta::get_value( 'meta-robots-noindex', $postid ) === '1' ) {
+ $robots['index'] = 'noindex';
+ } elseif ( WPSEO_Meta::get_value( 'meta-robots-noindex', $postid ) === '2' ) {
+ $robots['index'] = 'index';
+ }
+
+ if ( WPSEO_Meta::get_value( 'meta-robots-nofollow', $postid ) === '1' ) {
+ $robots['follow'] = 'nofollow';
+ }
+
+ $meta_robots_adv = WPSEO_Meta::get_value( 'meta-robots-adv', $postid );
+
+ if ( $meta_robots_adv !== '' && ( $meta_robots_adv !== '-' && $meta_robots_adv !== 'none' ) ) {
+ $meta_robots_adv = explode( ',', $meta_robots_adv );
+ foreach ( $meta_robots_adv as $robot ) {
+ $robots['other'][] = $robot;
+ }
+ unset( $robot );
+ } elseif ( $meta_robots_adv === '' || $meta_robots_adv === '-' ) {
+ foreach ( array( 'noodp', 'noydir' ) as $robot ) {
+ if ( $this->options[ $robot ] === true ) {
+ $robots['other'][] = $robot;
+ }
+ }
+ unset( $robot );
+ }
+ unset( $meta_robots_adv );
+
+ return $robots;
+ }
+
+
+ /**
+ * This function normally outputs the canonical but is also used in other places to retrieve
+ * the canonical URL for the current page.
+ *
+ * @param bool $echo Whether or not to output the canonical element.
+ * @param bool $un_paged Whether or not to return the canonical with or without pagination added to the URL.
+ * @param bool $no_override Whether or not to return a manually overridden canonical
+ *
+ * @return string $canonical
+ */
+ public function canonical( $echo = true, $un_paged = false, $no_override = false ) {
+ $canonical = false;
+ $skip_pagination = false;
+
+ // Set decent canonicals for homepage, singulars and taxonomy pages
+ if ( is_singular() ) {
+ $meta_canon = WPSEO_Meta::get_value( 'canonical' );
+ if ( $no_override === false && $meta_canon !== '' ) {
+ $canonical = $meta_canon;
+ $skip_pagination = true;
+ } else {
+ $obj = get_queried_object();
+ $canonical = get_permalink( $obj->ID );
+
+ // Fix paginated pages canonical, but only if the page is truly paginated.
+ if ( get_query_var( 'page' ) > 1 ) {
+ global $wp_rewrite;
+ $numpages = substr_count( $obj->post_content, '<!--nextpage-->' ) + 1;
+ if ( $numpages && get_query_var( 'page' ) <= $numpages ) {
+ if ( ! $wp_rewrite->using_permalinks() ) {
+ $canonical = add_query_arg( 'page', get_query_var( 'page' ), $canonical );
+ } else {
+ $canonical = user_trailingslashit( trailingslashit( $canonical ) . get_query_var( 'page' ) );
+ }
+ }
+ }
+ }
+ unset( $meta_canon );
+ } else {
+ if ( is_search() ) {
+ $canonical = get_search_link();
+ } elseif ( is_front_page() ) {
+ $canonical = home_url();
+ } elseif ( $this->is_posts_page() ) {
+ $canonical = get_permalink( get_option( 'page_for_posts' ) );
+ } elseif ( is_tax() || is_tag() || is_category() ) {
+ $term = get_queried_object();
+
+ if ( $no_override === false ) {
+ $canonical = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'canonical' );
+ if ( is_string( $canonical ) && $canonical !== '' ) {
+ $skip_pagination = true;
+ }
+ }
+
+ if ( ! is_string( $canonical ) || $canonical === '' ) {
+ $canonical = get_term_link( $term, $term->taxonomy );
+ }
+ } elseif ( is_post_type_archive() ) {
+ $post_type = get_query_var( 'post_type' );
+ if ( is_array( $post_type ) ) {
+ $post_type = reset( $post_type );
+ }
+ $canonical = get_post_type_archive_link( $post_type );
+ } elseif ( is_author() ) {
+ $canonical = get_author_posts_url( get_query_var( 'author' ), get_query_var( 'author_name' ) );
+ } elseif ( is_archive() ) {
+ if ( is_date() ) {
+ if ( is_day() ) {
+ $canonical = get_day_link( get_query_var( 'year' ), get_query_var( 'monthnum' ), get_query_var( 'day' ) );
+ } elseif ( is_month() ) {
+ $canonical = get_month_link( get_query_var( 'year' ), get_query_var( 'monthnum' ) );
+ } elseif ( is_year() ) {
+ $canonical = get_year_link( get_query_var( 'year' ) );
+ }
+ }
+ }
+ }
+
+ if ( $canonical && $un_paged ) {
+ return $canonical;
+ }
+
+ if ( $canonical && ! $skip_pagination && get_query_var( 'paged' ) > 1 ) {
+ global $wp_rewrite;
+ if ( ! $wp_rewrite->using_permalinks() ) {
+ $canonical = add_query_arg( 'paged', get_query_var( 'paged' ), $canonical );
+ } else {
+ if ( is_front_page() ) {
+ $canonical = wpseo_xml_sitemaps_base_url( '' );
+ }
+ $canonical = user_trailingslashit( trailingslashit( $canonical ) . trailingslashit( $wp_rewrite->pagination_base ) . get_query_var( 'paged' ) );
+ }
+ }
+
+ if ( $canonical && 'default' !== $this->options['force_transport'] ) {
+ $canonical = preg_replace( '`^http[s]?`', $this->options['force_transport'], $canonical );
+ }
+
+ /**
+ * Filter: 'wpseo_canonical' - Allow filtering of the canonical URL put out by WP SEO
+ *
+ * @api string $canonical The canonical URL
+ */
+ $canonical = apply_filters( 'wpseo_canonical', $canonical );
+
+ if ( is_string( $canonical ) && $canonical !== '' ) {
+ // Force canonical links to be absolute, relative is NOT an option.
+ if ( wpseo_is_url_relative( $canonical ) === true ) {
+ $canonical = home_url( $canonical );
+ }
+
+ if ( $echo !== false ) {
+ echo '<link rel="canonical" href="' . esc_url( $canonical, null, 'other' ) . '" />' . "\n";
+ } else {
+ return $canonical;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Adds 'prev' and 'next' links to archives.
+ *
+ * @link http://googlewebmastercentral.blogspot.com/2011/09/pagination-with-relnext-and-relprev.html
+ * @since 1.0.3
+ */
+ public function adjacent_rel_links() {
+ // Don't do this for Genesis, as the way Genesis handles homepage functionality is different and causes issues sometimes.
+ /**
+ * Filter 'wpseo_genesis_force_adjacent_rel_home' - Allows devs to allow echoing rel="next" / rel="prev" by WP SEO on Genesis installs
+ *
+ * @api bool $unsigned Whether or not to rel=next / rel=prev
+ */
+ if ( is_home() && function_exists( 'genesis' ) && apply_filters( 'wpseo_genesis_force_adjacent_rel_home', false ) === false ) {
+ return;
+ }
+
+ global $wp_query;
+
+ if ( ! is_singular() ) {
+ $url = $this->canonical( false, true, true );
+
+ if ( is_string( $url ) && $url !== '' ) {
+ $paged = get_query_var( 'paged' );
+
+ if ( 0 == $paged ) {
+ $paged = 1;
+ }
+
+ if ( $paged == 2 ) {
+ $this->adjacent_rel_link( 'prev', $url, $paged - 1, true );
+ }
+
+ // Make sure to use index.php when needed, done after paged == 2 check so the prev links to homepage will not have index.php erroneously.
+ if ( is_front_page() ) {
+ $url = wpseo_xml_sitemaps_base_url( '' );
+ }
+
+ if ( $paged > 2 ) {
+ $this->adjacent_rel_link( 'prev', $url, $paged - 1, true );
+ }
+
+ if ( $paged < $wp_query->max_num_pages ) {
+ $this->adjacent_rel_link( 'next', $url, $paged + 1, true );
+ }
+ }
+ } else {
+ $numpages = 0;
+ if ( isset( $wp_query->post->post_content ) ) {
+ $numpages = substr_count( $wp_query->post->post_content, '<!--nextpage-->' ) + 1;
+ }
+ if ( $numpages > 1 ) {
+ $page = get_query_var( 'page' );
+ if ( ! $page ) {
+ $page = 1;
+ }
+
+ $url = get_permalink( $wp_query->post->ID );
+
+ // If the current page is the frontpage, pagination should use /base/
+ if ( $this->is_home_static_page() ) {
+ $usebase = true;
+ }
+ else {
+ $usebase = false;
+ }
+
+ if ( $page > 1 ) {
+ $this->adjacent_rel_link( 'prev', $url, $page - 1, $usebase, 'single_paged' );
+ }
+ if ( $page < $numpages ) {
+ $this->adjacent_rel_link( 'next', $url, $page + 1, $usebase, 'single_paged' );
+ }
+ }
+ }
+ }
+
+ /**
+ * Get adjacent pages link for archives
+ *
+ * @param string $rel Link relationship, prev or next.
+ * @param string $url the unpaginated URL of the current archive.
+ * @param string $page the page number to add on to $url for the $link tag.
+ * @param boolean $incl_pagination_base whether or not to include /page/ or not.
+ *
+ * @return string $link link element
+ *
+ * @since 1.0.2
+ */
+ private function adjacent_rel_link( $rel, $url, $page, $incl_pagination_base ) {
+ global $wp_rewrite;
+ if ( ! $wp_rewrite->using_permalinks() ) {
+ if ( $page > 1 ) {
+ $url = add_query_arg( 'paged', $page, $url );
+ }
+ } else {
+ if ( $page > 1 ) {
+ $base = '';
+ if ( $incl_pagination_base ) {
+ $base = trailingslashit( $wp_rewrite->pagination_base );
+ }
+ $url = user_trailingslashit( trailingslashit( $url ) . $base . $page );
+ }
+ }
+ /**
+ * Filter: 'wpseo_' . $rel . '_rel_link' - Allow changing link rel output by WP SEO
+ *
+ * @api string $unsigned The full `<link` element.
+ */
+ $link = apply_filters( 'wpseo_' . $rel . '_rel_link', '<link rel="' . $rel . '" href="' . esc_url( $url ) . "\" />\n" );
+
+ if ( is_string( $link ) && $link !== '' ) {
+ echo $link;
+ }
+ }
+
+ /**
+ * Output the rel=publisher code on every page of the site.
+ * @return boolean Boolean indicating whether the publisher link was printed
+ */
+ public function publisher() {
+
+ if ( $this->options['plus-publisher'] !== '' ) {
+ echo '<link rel="publisher" href="' . esc_url( $this->options['plus-publisher'] ) . '"/>' . "\n";
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Outputs the meta keywords element.
+ *
+ * @return string
+ */
+ public function metakeywords() {
+ global $wp_query, $post;
+
+ if ( $this->options['usemetakeywords'] === false ) {
+ return;
+ }
+
+ $keywords = '';
+
+ if ( is_singular() ) {
+ $keywords = WPSEO_Meta::get_value( 'metakeywords' );
+ if ( $keywords === '' && ( is_object( $post ) && ( ( isset( $this->options[ 'metakey-' . $post->post_type ] ) && $this->options[ 'metakey-' . $post->post_type ] !== '' ) ) ) ) {
+ $keywords = wpseo_replace_vars( $this->options[ 'metakey-' . $post->post_type ], $post );
+ }
+ } else {
+ if ( $this->is_home_posts_page() && $this->options['metakey-home-wpseo'] !== '' ) {
+ $keywords = wpseo_replace_vars( $this->options['metakey-home-wpseo'], array() );
+ } elseif ( $this->is_home_static_page() ) {
+ $keywords = WPSEO_Meta::get_value( 'metakeywords' );
+ if ( $keywords === '' && ( is_object( $post ) && ( isset( $this->options[ 'metakey-' . $post->post_type ] ) && $this->options[ 'metakey-' . $post->post_type ] !== '' ) ) ) {
+ $keywords = wpseo_replace_vars( $this->options[ 'metakey-' . $post->post_type ], $post );
+ }
+ } elseif ( is_category() || is_tag() || is_tax() ) {
+ $term = $wp_query->get_queried_object();
+
+ if ( is_object( $term ) ) {
+ $keywords = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'metakey' );
+ if ( ( ! is_string( $keywords ) || $keywords === '' ) && ( isset( $this->options[ 'metakey-tax-' . $term->taxonomy ] ) && $this->options[ 'metakey-tax-' . $term->taxonomy ] !== '' ) ) {
+ $keywords = wpseo_replace_vars( $this->options[ 'metakey-tax-' . $term->taxonomy ], $term );
+ }
+ }
+ } elseif ( is_author() ) {
+ $author_id = get_query_var( 'author' );
+ $keywords = get_the_author_meta( 'metakey', $author_id );
+ if ( ! $keywords && $this->options['metakey-author-wpseo'] !== '' ) {
+ $keywords = wpseo_replace_vars( $this->options['metakey-author-wpseo'], $wp_query->get_queried_object() );
+ }
+ } elseif ( is_post_type_archive() ) {
+ $post_type = get_query_var( 'post_type' );
+ if ( is_array( $post_type ) ) {
+ $post_type = reset( $post_type );
+ }
+ if ( isset( $this->options[ 'metakey-ptarchive-' . $post_type ] ) && $this->options[ 'metakey-ptarchive-' . $post_type ] !== '' ) {
+ $keywords = wpseo_replace_vars( $this->options[ 'metakey-ptarchive-' . $post_type ], $wp_query->get_queried_object() );
+ }
+ }
+ }
+
+ $keywords = apply_filters( 'wpseo_metakey', trim( $keywords ) ); // make deprecated
+
+ /**
+ * Filter: 'wpseo_metakeywords' - Allow changing the WP SEO meta keywords
+ *
+ * @api string $keywords The meta keywords to be echoed.
+ */
+ $keywords = apply_filters( 'wpseo_metakeywords', trim( $keywords ) ); // more appropriately named
+
+ if ( is_string( $keywords ) && $keywords !== '' ) {
+ echo '<meta name="keywords" content="' . esc_attr( strip_tags( stripslashes( $keywords ) ) ) . '"/>' . "\n";
+ }
+ }
+
+ /**
+ * Outputs the meta description element or returns the description text.
+ *
+ * @param bool $echo Whether or not to echo the description.
+ *
+ * @return string
+ */
+ public function metadesc( $echo = true ) {
+ global $post, $wp_query;
+
+ $metadesc = '';
+ $post_type = '';
+ $template = '';
+
+ if ( is_object( $post ) && ( isset( $post->post_type ) && $post->post_type !== '' ) ) {
+ $post_type = $post->post_type;
+ }
+
+ if ( is_singular() ) {
+ $metadesc = WPSEO_Meta::get_value( 'metadesc' );
+ if ( ( $metadesc === '' && $post_type !== '' ) && isset( $this->options[ 'metadesc-' . $post_type ] ) ) {
+ $template = $this->options[ 'metadesc-' . $post_type ];
+ $term = $post;
+ }
+ } else {
+ if ( is_search() ) {
+ return '';
+ } elseif ( $this->is_home_posts_page() ) {
+ $template = $this->options['metadesc-home-wpseo'];
+ $term = array();
+ } elseif ( $this->is_posts_page() ) {
+ $metadesc = WPSEO_Meta::get_value( 'metadesc', get_option( 'page_for_posts' ) );
+ if ( ( $metadesc === '' && $post_type !== '' ) && isset( $this->options[ 'metadesc-' . $post_type ] ) ) {
+ $page = get_post( get_option( 'page_for_posts' ) );
+ $template = $this->options[ 'metadesc-' . $post_type ];
+ $term = $page;
+ }
+ } elseif ( $this->is_home_static_page() ) {
+ $metadesc = WPSEO_Meta::get_value( 'metadesc' );
+ if ( ( $metadesc === '' && $post_type !== '' ) && isset( $this->options[ 'metadesc-' . $post_type ] ) ) {
+ $template = $this->options[ 'metadesc-' . $post_type ];
+ }
+ } elseif ( is_category() || is_tag() || is_tax() ) {
+ $term = $wp_query->get_queried_object();
+ $metadesc = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'desc' );
+ if ( ( ! is_string( $metadesc ) || $metadesc === '' ) && ( ( is_object( $term ) && isset( $term->taxonomy ) ) && isset( $this->options[ 'metadesc-tax-' . $term->taxonomy ] ) ) ) {
+ $template = $this->options[ 'metadesc-tax-' . $term->taxonomy ];
+ }
+ } elseif ( is_author() ) {
+ $author_id = get_query_var( 'author' );
+ $metadesc = get_the_author_meta( 'wpseo_metadesc', $author_id );
+ if ( ( ! is_string( $metadesc ) || $metadesc === '' ) && '' !== $this->options[ 'metadesc-author-wpseo' ] ) {
+ $template = $this->options[ 'metadesc-author-wpseo' ];
+ }
+ } elseif ( is_post_type_archive() ) {
+ $post_type = get_query_var( 'post_type' );
+ if ( is_array( $post_type ) ) {
+ $post_type = reset( $post_type );
+ }
+ if ( isset( $this->options[ 'metadesc-ptarchive-' . $post_type ] ) ) {
+ $template = $this->options[ 'metadesc-ptarchive-' . $post_type ];
+ }
+ } elseif ( is_archive() ) {
+ $template = $this->options['metadesc-archive-wpseo'];
+ }
+
+ // If we're on a paginated page, and the template doesn't change for paginated pages, bail.
+ if ( ( ! is_string( $metadesc ) || $metadesc === '' ) && get_query_var( 'paged' ) && get_query_var( 'paged' ) > 1 && $template !== '' ) {
+ if ( strpos( $template, '%%page' ) === false ) {
+ return '';
+ }
+ }
+ }
+
+ if ( ( ! is_string( $metadesc ) || '' === $metadesc ) && '' !== $template ) {
+ if ( ! isset( $term ) ) {
+ $term = $wp_query->get_queried_object();
+ }
+ $metadesc = wpseo_replace_vars( $template, $term );
+ }
+
+ /**
+ * Filter: 'wpseo_metadesc' - Allow changing the WP SEO meta description sentence.
+ *
+ * @api string $metadesc The description sentence.
+ */
+ $metadesc = apply_filters( 'wpseo_metadesc', trim( $metadesc ) );
+
+ if ( $echo !== false ) {
+ if ( is_string( $metadesc ) && $metadesc !== '' ) {
+ echo '<meta name="description" content="' . esc_attr( strip_tags( stripslashes( $metadesc ) ) ) . '"/>' . "\n";
+ } elseif ( current_user_can( 'manage_options' ) && is_singular() ) {
+ echo '<!-- ' . __( 'Admin only notice: this page doesn\'t show a meta description because it doesn\'t have one, either write it for this page specifically or go into the SEO -> Titles menu and set up a template.', 'wordpress-seo' ) . ' -->' . "\n";
+ }
+ } else {
+ return $metadesc;
+ }
+ }
+
+ /**
+ * Based on the redirect meta value, this function determines whether it should redirect the current post / page.
+ *
+ * @return boolean
+ */
+ function page_redirect() {
+ if ( is_singular() ) {
+ global $post;
+ if ( ! isset( $post ) || ! is_object( $post ) ) {
+ return false;
+ }
+
+ $redir = WPSEO_Meta::get_value( 'redirect', $post->ID );
+ if ( $redir !== '' ) {
+ wp_redirect( $redir, 301 );
+ exit;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Outputs noindex values for the current page.
+ */
+ public function noindex_page() {
+ echo '<meta name="robots" content="noindex" />' . "\n";
+ }
+
+ /**
+ * Send a Robots HTTP header preventing URL from being indexed in the search results while allowing search engines
+ * to follow the links in the object at the URL.
+ *
+ * @since 1.1.7
+ * @return boolean Boolean indicating whether the noindex header was sent
+ */
+ public function noindex_feed() {
+
+ if ( ( is_feed() || is_robots() ) && headers_sent() === false ) {
+ header( 'X-Robots-Tag: noindex,follow', true );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Adds rel="nofollow" to a link, only used for login / registration links.
+ *
+ * @param string $input The link element as a string.
+ *
+ * @return string
+ */
+ public function nofollow_link( $input ) {
+ return str_replace( '<a ', '<a rel="nofollow" ', $input );
+ }
+
+ /**
+ * When certain archives are disabled, this redirects those to the homepage.
+ * @return boolean False when no redirect was triggered
+ */
+ function archive_redirect() {
+ global $wp_query;
+
+ if (
+ ( $this->options['disable-date'] === true && $wp_query->is_date ) ||
+ ( $this->options['disable-author'] === true && $wp_query->is_author ) ||
+ ( isset( $this->options['disable-post_formats'] ) && $this->options['disable-post_formats'] && $wp_query->is_tax( 'post_format' ) )
+ ) {
+ wp_safe_redirect( get_bloginfo( 'url' ), 301 );
+ exit;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the option to redirect attachments to their parent is checked, this performs the redirect.
+ *
+ * An extra check is done for when the attachment has no parent.
+ * @return boolean False when no redirect was triggered
+ */
+ function attachment_redirect() {
+ global $post;
+ if ( is_attachment() && ( ( is_object( $post ) && isset( $post->post_parent ) ) && ( is_numeric( $post->post_parent ) && $post->post_parent != 0 ) ) ) {
+ wp_safe_redirect( get_permalink( $post->post_parent ), 301 );
+ exit;
+ }
+
+ return false;
+ }
+
+ /**
+ * Trailing slashes for everything except is_single().
+ *
+ * Thanks to Mark Jaquith for this code.
+ *
+ * @param string $url
+ * @param string $type
+ *
+ * @return string
+ */
+ function add_trailingslash( $url, $type ) {
+ if ( 'single' === $type || 'single_paged' === $type ) {
+ return $url;
+ } else {
+ return trailingslashit( $url );
+ }
+ }
+
+ /**
+ * Removes the ?replytocom variable from the link, replacing it with a #comment-<number> anchor.
+ *
+ * @todo Should this function also allow for relative urls ?
+ *
+ * @param string $link The comment link as a string.
+ *
+ * @return string
+ */
+ public function remove_reply_to_com( $link ) {
+ return preg_replace( '`href=(["\'])(?:.*(?:\?|&|&)replytocom=(\d+)#respond)`', 'href=$1#comment-$2', $link );
+ }
+
+ /**
+ * Redirect out the ?replytocom variables when cleanreplytocom is enabled
+ *
+ * @since 1.4.13
+ * @return boolean
+ */
+ function replytocom_redirect() {
+
+ if ( $this->options['cleanreplytocom'] !== true ) {
+ return false;
+ }
+
+ if ( isset( $_GET['replytocom'] ) && is_singular() ) {
+ global $post;
+ $url = get_permalink( $post->ID );
+ $hash = sanitize_text_field( $_GET['replytocom'] );
+ $query_string = remove_query_arg( 'replytocom', sanitize_text_field( $_SERVER['QUERY_STRING'] ) );
+ if ( ! empty( $query_string ) ) {
+ $url .= '?' . $query_string;
+ }
+ $url .= '#comment-' . $hash;
+ wp_safe_redirect( $url, 301 );
+ exit;
+ }
+
+ return false;
+ }
+
+ /**
+ * Removes unneeded query variables from the URL.
+ * @return boolean
+ */
+ public function clean_permalink() {
+ if ( is_robots() || get_query_var( 'sitemap' ) ) {
+ return false;
+ }
+
+ global $wp_query;
+
+ // Recreate current URL
+ $cururl = 'http';
+ if ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) {
+ $cururl .= 's';
+ }
+ $cururl .= '://';
+
+ if ( $_SERVER['SERVER_PORT'] != '80' && $_SERVER['SERVER_PORT'] != '443' ) {
+ $cururl .= $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];
+ } else {
+ $cururl .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
+ }
+ $properurl = '';
+
+ if ( is_singular() ) {
+ global $post;
+ if ( empty( $post ) ) {
+ $post = $wp_query->get_queried_object();
+ }
+
+ $properurl = get_permalink( $post->ID );
+
+ $page = get_query_var( 'page' );
+ if ( $page && $page != 1 ) {
+ $post = get_post( $post->ID );
+ $page_count = substr_count( $post->post_content, '<!--nextpage-->' );
+ if ( $page > ( $page_count + 1 ) ) {
+ $properurl = user_trailingslashit( trailingslashit( $properurl ) . ( $page_count + 1 ) );
+ } else {
+ $properurl = user_trailingslashit( trailingslashit( $properurl ) . $page );
+ }
+ }
+
+ // Fix reply to comment links, whoever decided this should be a GET variable?
+ $result = preg_match( '`(\?replytocom=[^&]+)`', sanitize_text_field( $_SERVER['REQUEST_URI'] ), $matches );
+ if ( $result ) {
+ $properurl .= str_replace( '?replytocom=', '#comment-', $matches[0] );
+ }
+
+ // Prevent cleaning out posts & page previews for people capable of viewing them
+ if ( isset( $_GET['preview'] ) && isset( $_GET['preview_nonce'] ) && current_user_can( 'edit_post' ) ) {
+ $properurl = '';
+ }
+ } elseif ( is_front_page() ) {
+ if ( $this->is_home_posts_page() ) {
+ $properurl = get_bloginfo( 'url' ) . '/';
+ } elseif ( $this->is_home_static_page() ) {
+ global $post;
+ $properurl = get_permalink( $post->ID );
+ }
+ } elseif ( is_category() || is_tag() || is_tax() ) {
+ $term = $wp_query->get_queried_object();
+ if ( is_feed() ) {
+ $properurl = get_term_feed_link( $term->term_id, $term->taxonomy );
+ } else {
+ $properurl = get_term_link( $term, $term->taxonomy );
+ }
+ } elseif ( is_search() ) {
+ $s = urlencode( preg_replace( '`(%20|\+)`', ' ', get_search_query() ) );
+ $properurl = get_bloginfo( 'url' ) . '/?s=' . $s;
+ } elseif ( is_404() ) {
+ if ( is_multisite() && ! is_subdomain_install() && is_main_site() ) {
+ if ( $cururl == get_bloginfo( 'url' ) . '/blog/' || $cururl == get_bloginfo( 'url' ) . '/blog' ) {
+ if ( $this->is_home_static_page() ) {
+ $properurl = get_permalink( get_option( 'page_for_posts' ) );
+ } else {
+ $properurl = get_bloginfo( 'url' ) . '/';
+ }
+ }
+ }
+ }
+
+ if ( ! empty( $properurl ) && $wp_query->query_vars['paged'] != 0 && $wp_query->post_count != 0 ) {
+ if ( is_search() && ! empty( $s ) ) {
+ $properurl = get_bloginfo( 'url' ) . '/page/' . $wp_query->query_vars['paged'] . '/?s=' . $s;
+ } else {
+ $properurl = user_trailingslashit( trailingslashit( $properurl ) . 'page/' . $wp_query->query_vars['paged'] );
+ }
+ }
+
+ // Prevent cleaning out the WP Subscription managers interface for everyone
+ if ( isset( $_GET['wp-subscription-manager'] ) ) {
+ $properurl = '';
+ }
+
+ /**
+ * Filter: 'wpseo_whitelist_permalink_vars' - Allow plugins to register their own variables not to clean
+ *
+ * @api array $unsigned Array of permalink variables _not_ to clean. Empty by default.
+ */
+ $whitelisted_extravars = apply_filters( 'wpseo_whitelist_permalink_vars', array() );
+
+ if ( $this->options['cleanpermalink-googlesitesearch'] === true ) {
+ // Prevent cleaning out Google Site searches
+ $whitelisted_extravars = array_merge( $whitelisted_extravars, array( 'q', 'cx', 'debug', 'cof', 'ie', 'sa' ) );
+ }
+
+ if ( $this->options['cleanpermalink-googlecampaign'] === true ) {
+ // Prevent cleaning out Google Analytics campaign variables
+ $whitelisted_extravars = array_merge( $whitelisted_extravars, array( 'utm_campaign', 'utm_medium', 'utm_source', 'utm_content', 'utm_term', 'utm_id', 'gclid' ) );
+ }
+
+ if ( $this->options['cleanpermalink-extravars'] !== '' ) {
+ $extravars = explode( ',', $this->options['cleanpermalink-extravars'] );
+ $extravars = array_map( 'trim', $extravars );
+ $whitelisted_extravars = array_merge( $whitelisted_extravars, $extravars );
+ unset( $extravars );
+ }
+
+ foreach ( $whitelisted_extravars as $get ) {
+ if ( isset( $_GET[ trim( $get ) ] ) ) {
+ $properurl = '';
+ }
+ }
+
+ if ( ! empty( $properurl ) && $cururl != $properurl ) {
+ wp_safe_redirect( $properurl, 301 );
+ exit;
+ }
+ }
+
+ /**
+ * Replaces the possible RSS variables with their actual values.
+ *
+ * @param string $content The RSS content that should have the variables replaced.
+ *
+ * @return string
+ */
+ function rss_replace_vars( $content ) {
+ global $post;
+
+ /**
+ * Allow the developer to determine whether or not to follow the links in the bits WP SEO adds to the RSS feed, defaults to true.
+ *
+ * @api bool $unsigned Whether or not to follow the links in RSS feed, defaults to true.
+ *
+ * @since 1.4.20
+ */
+ $no_follow = apply_filters( 'nofollow_rss_links', true );
+ $no_follow_attr = '';
+ if ( $no_follow === true ) {
+ $no_follow_attr = 'rel="nofollow" ';
+ }
+
+ $author_link = '';
+ if ( is_object( $post ) ) {
+ $author_link = '<a ' . $no_follow_attr . 'href="' . esc_url( get_author_posts_url( $post->post_author ) ) . '">' . get_the_author() . '</a>';
+ }
+
+ $post_link = '<a ' . $no_follow_attr . 'href="' . esc_url( get_permalink() ) . '">' . get_the_title() . '</a>';
+ $blog_link = '<a ' . $no_follow_attr . 'href="' . esc_url( get_bloginfo( 'url' ) ) . '">' . get_bloginfo( 'name' ) . '</a>';
+ $blog_desc_link = '<a ' . $no_follow_attr . 'href="' . esc_url( get_bloginfo( 'url' ) ) . '">' . get_bloginfo( 'name' ) . ' - ' . strip_tags( get_bloginfo( 'description' ) ) . '</a>';
+
+ $content = stripslashes( trim( $content ) );
+ $content = str_replace( '%%AUTHORLINK%%', $author_link, $content );
+ $content = str_replace( '%%POSTLINK%%', $post_link, $content );
+ $content = str_replace( '%%BLOGLINK%%', $blog_link, $content );
+ $content = str_replace( '%%BLOGDESCLINK%%', $blog_desc_link, $content );
+
+ return $content;
+ }
+
+ /**
+ * Adds the RSS footer (or header) to the full RSS feed item.
+ *
+ * @param string $content Feed item content.
+ *
+ * @return string
+ */
+ function embed_rssfooter( $content ) {
+ return $this->embed_rss( $content, 'full' );
+ }
+
+ /**
+ * Adds the RSS footer (or header) to the excerpt RSS feed item.
+ *
+ * @param string $content Feed item excerpt.
+ *
+ * @return string
+ */
+ function embed_rssfooter_excerpt( $content ) {
+ return $this->embed_rss( $content, 'excerpt' );
+ }
+
+ /**
+ * Adds the RSS footer and/or header to an RSS feed item.
+ *
+ * @since 1.4.14
+ *
+ * @param string $content Feed item content.
+ * @param string $context Feed item context, either 'excerpt' or 'full'.
+ *
+ * @return string
+ */
+ function embed_rss( $content, $context = 'full' ) {
+ if ( is_feed() ) {
+
+ $before = '';
+ $after = '';
+
+ if ( $this->options['rssbefore'] !== '' ) {
+ $before = wpautop( $this->rss_replace_vars( $this->options['rssbefore'] ) );
+ }
+ if ( $this->options['rssafter'] !== '' ) {
+ $after = wpautop( $this->rss_replace_vars( $this->options['rssafter'] ) );
+ }
+ if ( $before !== '' || $after !== '' ) {
+ if ( ( isset( $context ) && $context === 'excerpt' ) && trim( $content ) !== '' ) {
+ $content = wpautop( $content );
+ }
+ $content = $before . $content . $after;
+ }
+ }
+
+ return $content;
+ }
+
+
+ /**
+ * Used in the force rewrite functionality this retrieves the output, replaces the title with the proper SEO
+ * title and then flushes the output.
+ */
+ function flush_cache() {
+
+ global $wp_query;
+
+ if ( $this->ob_started !== true ) {
+ return false;
+ }
+
+ $content = ob_get_contents();
+ ob_end_clean();
+
+ $old_wp_query = $wp_query;
+
+ wp_reset_query();
+
+ $title = $this->title( '' );
+
+ // Find all titles, strip them out and add the new one in within the debug marker, so it's easily identified whether a site uses force rewrite.
+ $content = preg_replace( '/<title.*?\/title>/i', '', $content );
+ $content = str_replace( $this->debug_marker( false ), $this->debug_marker( false ) . "\n" . '<title>' . $title . '</title>', $content );
+
+ $GLOBALS['wp_query'] = $old_wp_query;
+
+ echo $content;
+ return true;
+ }
+
+ /**
+ * Starts the output buffer so it can later be fixed by flush_cache()
+ */
+ function force_rewrite_output_buffer() {
+ $this->ob_started = true;
+ ob_start();
+ }
+
+ /**
+ * Function used in testing whether the title should be force rewritten or not.
+ *
+ * @param string $title
+ *
+ * @return string
+ */
+ function title_test_helper( $title ) {
+ $wpseo_titles = get_option( 'wpseo_titles' );
+
+ $wpseo_titles['title_test']++;
+ update_option( 'wpseo_titles', $wpseo_titles );
+
+ // Prevent this setting from being on forever when something breaks, as it breaks caching.
+ if ( $wpseo_titles['title_test'] > 5 ) {
+ $wpseo_titles['title_test'] = 0;
+ update_option( 'wpseo_titles', $wpseo_titles );
+
+ remove_filter( 'wpseo_title', array( $this, 'title_test_helper' ) );
+ return $title;
+ }
+
+ if ( ! defined( 'DONOTCACHEPAGE' ) ) {
+ define( 'DONOTCACHEPAGE', true );
+ }
+ if ( ! defined( 'DONOTCACHCEOBJECT' ) ) {
+ define( 'DONOTCACHCEOBJECT', true );
+ }
+ if ( ! defined( 'DONOTMINIFY' ) ) {
+ define( 'DONOTMINIFY', true );
+ }
+
+ global $wp_version;
+ if ( $_SERVER['HTTP_USER_AGENT'] == "WordPress/${wp_version}; " . get_bloginfo( 'url' ) . ' - Yoast' ) {
+ return 'This is a Yoast Test Title';
+ }
+
+ return $title;
+ }
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Frontend
+ *
+ * This code handles the Google+ specific output that's not covered by OpenGraph.
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_GooglePlus' ) ) {
+ class WPSEO_GooglePlus extends WPSEO_Frontend {
+
+ /**
+ * @var object Instance of this class
+ */
+ public static $instance;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ add_action( 'wpseo_googleplus', array( $this, 'google_plus_title' ), 10 );
+ add_action( 'wpseo_googleplus', array( $this, 'description' ), 11 );
+ add_action( 'wpseo_googleplus', array( $this, 'google_plus_image' ), 12 );
+
+ add_action( 'wpseo_head', array( $this, 'output' ), 40 );
+ }
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Output the Google+ specific content
+ */
+ public function output() {
+ /**
+ * Action: 'wpseo_googleplus' - Hook to add all Google+ specific output to.
+ */
+ do_action( 'wpseo_googleplus' );
+ }
+
+ /**
+ * Output the Google+ specific description
+ */
+ public function description() {
+ if ( is_singular() ) {
+ $desc = WPSEO_Meta::get_value( 'google-plus-description' );
+
+ /**
+ * Filter: 'wpseo_googleplus_desc' - Allow developers to change the Google+ specific description output
+ *
+ * @api string $desc The description string
+ */
+ $desc = trim( apply_filters( 'wpseo_googleplus_desc', $desc ) );
+
+ if ( is_string( $desc ) && '' !== $desc ) {
+ echo '<meta itemprop="description" content="' . esc_attr( $desc ) . '">' . "\n";
+ }
+ }
+ }
+
+ /**
+ * Output the Google+ specific title
+ */
+ public function google_plus_title() {
+ if ( is_singular() ) {
+ $title = WPSEO_Meta::get_value( 'google-plus-title' );
+
+ /**
+ * Filter: 'wpseo_googleplus_title' - Allow developers to change the Google+ specific title
+ *
+ * @api string $title The title string
+ */
+ $title = trim( apply_filters( 'wpseo_googleplus_title', $title ) );
+
+ if ( is_string( $title ) && $title !== '' ) {
+ $title = wpseo_replace_vars( $title, get_post() );
+
+ echo '<meta itemprop="name" content="' . esc_attr( $title ) . '">' . "\n";
+ }
+ }
+ }
+
+ /**
+ * Output the Google+ specific image
+ */
+ public function google_plus_image() {
+ if ( is_singular() ) {
+ $image = WPSEO_Meta::get_value( 'google-plus-image' );
+
+ /**
+ * Filter: 'wpseo_googleplus_image' - Allow changing the Google+ image
+ *
+ * @api string $img Image URL string
+ */
+ $image = trim( apply_filters( 'wpseo_googleplus_image', $image ) );
+
+ if ( is_string( $image ) && $image !== '' ) {
+ echo '<meta itemprop="image" content="' . esc_url( $image ) . '">' . "\n";
+ }
+ }
+ }
+ }
+} // end if class exists
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Frontend
+ *
+ * This code handles the OpenGraph output.
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_OpenGraph' ) ) {
+ /**
+ * Adds the OpenGraph output
+ */
+ class WPSEO_OpenGraph extends WPSEO_Frontend {
+
+ /**
+ * @var array $options Options for the OpenGraph Settings
+ */
+ public $options = array();
+
+ /**
+ * @var array $shown_images Holds the images that have been put out as OG image.
+ */
+ public $shown_images = array();
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->options = WPSEO_Options::get_all();
+
+ global $fb_ver;
+ if ( isset( $fb_ver ) || class_exists( 'Facebook_Loader' ) ) {
+ add_filter( 'fb_meta_tags', array( $this, 'facebook_filter' ), 10, 1 );
+ } else {
+ add_filter( 'language_attributes', array( $this, 'add_opengraph_namespace' ) );
+
+ add_action( 'wpseo_opengraph', array( $this, 'locale' ), 1 );
+ add_action( 'wpseo_opengraph', array( $this, 'type' ), 5 );
+ add_action( 'wpseo_opengraph', array( $this, 'og_title' ), 10 );
+ add_action( 'wpseo_opengraph', array( $this, 'site_owner' ), 20 );
+ add_action( 'wpseo_opengraph', array( $this, 'description' ), 11 );
+ add_action( 'wpseo_opengraph', array( $this, 'url' ), 12 );
+ add_action( 'wpseo_opengraph', array( $this, 'site_name' ), 13 );
+ add_action( 'wpseo_opengraph', array( $this, 'website_facebook' ), 14 );
+ if ( is_singular() && ! is_front_page() ) {
+ add_action( 'wpseo_opengraph', array( $this, 'article_author_facebook' ), 15 );
+ add_action( 'wpseo_opengraph', array( $this, 'tags' ), 16 );
+ add_action( 'wpseo_opengraph', array( $this, 'category' ), 17 );
+ add_action( 'wpseo_opengraph', array( $this, 'publish_date' ), 19 );
+ }
+
+ add_action( 'wpseo_opengraph', array( $this, 'image' ), 30 );
+ }
+ add_filter( 'jetpack_enable_open_graph', '__return_false' );
+ add_action( 'wpseo_head', array( $this, 'opengraph' ), 30 );
+ }
+
+ /**
+ * Main OpenGraph output.
+ */
+ public function opengraph() {
+ wp_reset_query();
+ /**
+ * Action: 'wpseo_opengraph' - Hook to add all Facebook OpenGraph output to so they're close together.
+ */
+ do_action( 'wpseo_opengraph' );
+ }
+
+ /**
+ * Internal function to output FB tags. This also adds an output filter to each bit of output based on the property.
+ *
+ * @param string $property
+ * @param string $content
+ * @return boolean
+ */
+ public function og_tag( $property, $content ) {
+ $og_property = str_replace( ':', '_', $property );
+ /**
+ * Filter: 'wpseo_og_' . $og_property - Allow developers to change the content of specific OG meta tags.
+ *
+ * @api string $content The content of the property
+ */
+ $content = apply_filters( 'wpseo_og_' . $og_property, $content );
+ if ( empty( $content ) ) {
+ return false;
+ }
+
+ echo '<meta property="' . esc_attr( $property ) . '" content="' . esc_attr( $content ) . '" />' . "\n";
+ return true;
+ }
+
+ /**
+ * Filter the Facebook plugins metadata
+ *
+ * @param array $meta_tags the array to fix.
+ *
+ * @return array $meta_tags
+ */
+ public function facebook_filter( $meta_tags ) {
+ $meta_tags['http://ogp.me/ns#type'] = $this->type( false );
+ $meta_tags['http://ogp.me/ns#title'] = $this->og_title( false );
+
+ // Filter the locale too because the Facebook plugin locale code is not as good as ours.
+ $meta_tags['http://ogp.me/ns#locale'] = $this->locale( false );
+
+ $ogdesc = $this->description( false );
+ if ( ! empty( $ogdesc ) ) {
+ $meta_tags['http://ogp.me/ns#description'] = $ogdesc;
+ }
+
+ return $meta_tags;
+ }
+
+ /**
+ * Filter for the namespace, adding the OpenGraph namespace.
+ *
+ * @link https://developers.facebook.com/docs/web/tutorials/scrumptious/open-graph-object/
+ *
+ * @param string $input The input namespace string.
+ *
+ * @return string
+ */
+ public function add_opengraph_namespace( $input ) {
+ return $input . ' prefix="og: http://ogp.me/ns#' . ( ( $this->options['fbadminapp'] != 0 || ( is_array( $this->options['fb_admins'] ) && $this->options['fb_admins'] !== array() ) ) ? ' fb: http://ogp.me/ns/fb#' : '' ) . '"';
+ }
+
+ /**
+ * Outputs the authors FB page.
+ *
+ * @link https://developers.facebook.com/blog/post/2013/06/19/platform-updates--new-open-graph-tags-for-media-publishers-and-more/
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ *
+ * @return boolean
+ */
+ public function article_author_facebook() {
+ if ( ! is_singular() ) {
+ return false;
+ }
+
+ global $post;
+ /**
+ * Filter: 'wpseo_opengraph_author_facebook' - Allow developers to filter the WP SEO post authors facebook profile URL
+ *
+ * @api bool|string $unsigned The Facebook author URL, return false to disable
+ */
+ $facebook = apply_filters( 'wpseo_opengraph_author_facebook', get_the_author_meta( 'facebook', $post->post_author ) );
+
+ if ( $facebook && ( is_string( $facebook ) && $facebook !== '' ) ) {
+ $this->og_tag( 'article:author', $facebook );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Outputs the websites FB page.
+ *
+ * @link https://developers.facebook.com/blog/post/2013/06/19/platform-updates--new-open-graph-tags-for-media-publishers-and-more/
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ * @return boolean
+ */
+ public function website_facebook() {
+
+ if ( $this->options['facebook_site'] !== '' ) {
+ $this->og_tag( 'article:publisher', $this->options['facebook_site'] );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Outputs the site owner
+ *
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ * @return boolean
+ */
+ public function site_owner() {
+ if ( 0 != $this->options['fbadminapp'] ) {
+ $this->og_tag( 'fb:app_id', $this->options['fbadminapp'] );
+ return true;
+ } elseif ( is_array( $this->options['fb_admins'] ) && $this->options['fb_admins'] !== array() ) {
+ $adminstr = implode( ',', array_keys( $this->options['fb_admins'] ) );
+ /**
+ * Filter: 'wpseo_opengraph_admin' - Allow developer to filter the fb:admins string put out by WP SEO
+ *
+ * @api string $adminstr The admin string
+ */
+ $adminstr = apply_filters( 'wpseo_opengraph_admin', $adminstr );
+ if ( is_string( $adminstr ) && $adminstr !== '' ) {
+ $this->og_tag( 'fb:admins', $adminstr );
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Outputs the SEO title as OpenGraph title.
+ *
+ * @param bool $echo Whether or not to echo the output.
+ *
+ * @return string $title
+ *
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ * @return boolean
+ */
+ public function og_title( $echo = true ) {
+ if ( is_singular() ) {
+ $title = WPSEO_Meta::get_value( 'opengraph-title' );
+ if ( $title === '' ) {
+ $title = $this->title( '' );
+ } else {
+ // Replace WP SEO Variables
+ $title = wpseo_replace_vars( $title, get_post() );
+ }
+ } else if ( is_front_page() ) {
+ $title = ( $this->options['og_frontpage_title'] !== '' ) ? $this->options['og_frontpage_title'] : $this->title( '' );
+ } else {
+ $title = $this->title( '' );
+ }
+
+ /**
+ * Filter: 'wpseo_opengraph_title' - Allow changing the title specifically for OpenGraph
+ *
+ * @api string $unsigned The title string
+ */
+ $title = trim( apply_filters( 'wpseo_opengraph_title', $title ) );
+
+ if ( is_string( $title ) && $title !== '' ) {
+ if ( $echo !== false ) {
+ $this->og_tag( 'og:title', $title );
+ return true;
+ }
+ }
+
+ if ( $echo === false ) {
+ return $title;
+ }
+
+ return false;
+ }
+
+ /**
+ * Outputs the canonical URL as OpenGraph URL, which consolidates likes and shares.
+ *
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ * @return boolean
+ */
+ public function url() {
+ /**
+ * Filter: 'wpseo_opengraph_url' - Allow changing the OpenGraph URL
+ *
+ * @api string $unsigned Canonical URL
+ */
+ $url = apply_filters( 'wpseo_opengraph_url', $this->canonical( false ) );
+
+ if ( is_string( $url ) && $url !== '' ) {
+ $this->og_tag( 'og:url', esc_url( $url ) );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Output the locale, doing some conversions to make sure the proper Facebook locale is outputted.
+ *
+ * Last update/compare with FB list done on July 14, 2013 by JRF
+ * Results: 1 new locale added, found 32 in the below list which are not in the FB list (not removed), 76 OK.
+ * @see http://www.facebook.com/translations/FacebookLocales.xml for the list of supported locales
+ *
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ *
+ * @param bool $echo Whether to echo or return the locale
+ *
+ * @return string $locale
+ */
+ public function locale( $echo = true ) {
+ /**
+ * Filter: 'wpseo_locale' - Allow changing the locale output
+ *
+ * @api string $unsigned Locale string
+ */
+ $locale = apply_filters( 'wpseo_locale', get_locale() );
+
+ // catch some weird locales served out by WP that are not easily doubled up.
+ $fix_locales = array(
+ 'ca' => 'ca_ES',
+ 'en' => 'en_US',
+ 'el' => 'el_GR',
+ 'et' => 'et_EE',
+ 'ja' => 'ja_JP',
+ 'sq' => 'sq_AL',
+ 'uk' => 'uk_UA',
+ 'vi' => 'vi_VN',
+ 'zh' => 'zh_CN',
+ );
+
+ if ( isset( $fix_locales[ $locale ] ) ) {
+ $locale = $fix_locales[ $locale ];
+ }
+
+ // convert locales like "es" to "es_ES", in case that works for the given locale (sometimes it does)
+ if ( strlen( $locale ) == 2 ) {
+ $locale = strtolower( $locale ) . '_' . strtoupper( $locale );
+ }
+
+ // These are the locales FB supports
+ $fb_valid_fb_locales = array(
+ 'ca_ES', 'cs_CZ', 'cy_GB', 'da_DK', 'de_DE', 'eu_ES', 'en_PI', 'en_UD', 'ck_US', 'en_US', 'es_LA', 'es_CL', 'es_CO', 'es_ES', 'es_MX',
+ 'es_VE', 'fb_FI', 'fi_FI', 'fr_FR', 'gl_ES', 'hu_HU', 'it_IT', 'ja_JP', 'ko_KR', 'nb_NO', 'nn_NO', 'nl_NL', 'pl_PL', 'pt_BR', 'pt_PT',
+ 'ro_RO', 'ru_RU', 'sk_SK', 'sl_SI', 'sv_SE', 'th_TH', 'tr_TR', 'ku_TR', 'zh_CN', 'zh_HK', 'zh_TW', 'fb_LT', 'af_ZA', 'sq_AL', 'hy_AM',
+ 'az_AZ', 'be_BY', 'bn_IN', 'bs_BA', 'bg_BG', 'hr_HR', 'nl_BE', 'en_GB', 'eo_EO', 'et_EE', 'fo_FO', 'fr_CA', 'ka_GE', 'el_GR', 'gu_IN',
+ 'hi_IN', 'is_IS', 'id_ID', 'ga_IE', 'jv_ID', 'kn_IN', 'kk_KZ', 'la_VA', 'lv_LV', 'li_NL', 'lt_LT', 'mk_MK', 'mg_MG', 'ms_MY', 'mt_MT',
+ 'mr_IN', 'mn_MN', 'ne_NP', 'pa_IN', 'rm_CH', 'sa_IN', 'sr_RS', 'so_SO', 'sw_KE', 'tl_PH', 'ta_IN', 'tt_RU', 'te_IN', 'ml_IN', 'uk_UA',
+ 'uz_UZ', 'vi_VN', 'xh_ZA', 'zu_ZA', 'km_KH', 'tg_TJ', 'ar_AR', 'he_IL', 'ur_PK', 'fa_IR', 'sy_SY', 'yi_DE', 'gn_PY', 'qu_PE', 'ay_BO',
+ 'se_NO', 'ps_AF', 'tl_ST', 'fy_NL',
+ );
+
+ // check to see if the locale is a valid FB one, if not, use en_US as a fallback
+ // check to see if the locale is a valid FB one, if not, use en_US as a fallback
+ if ( ! in_array( $locale, $fb_valid_fb_locales ) ) {
+ $locale = strtolower( substr( $locale, 0, 2 ) ) . '_' . strtoupper( substr( $locale, 0, 2 ) );
+ if ( ! in_array( $locale, $fb_valid_fb_locales ) ) {
+ $locale = 'en_US';
+ }
+ }
+
+ if ( $echo !== false ) {
+ $this->og_tag( 'og:locale', $locale );
+ }
+
+ return $locale;
+ }
+
+ /**
+ * Output the OpenGraph type.
+ *
+ * @param boolean $echo Whether to echo or return the type
+ *
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/object/
+ *
+ * @return string $type
+ */
+ public function type( $echo = true ) {
+
+ if ( is_front_page() || is_home() ) {
+ $type = 'website';
+ } elseif ( is_singular() ) {
+
+ // This'll usually only be changed by plugins right now.
+ $type = WPSEO_Meta::get_value( 'og_type' );
+
+ if ( $type === '' ) {
+ $type = 'article';
+ }
+ } else {
+ // We use "object" for archives etc. as article doesn't apply there
+ $type = 'object';
+ }
+
+ /**
+ * Filter: 'wpseo_opengraph_type' - Allow changing the OpenGraph type of the page
+ *
+ * @api string $type The OpenGraph type string.
+ */
+ $type = apply_filters( 'wpseo_opengraph_type', $type );
+
+ if ( is_string( $type ) && $type !== '' ) {
+ if ( $echo !== false ) {
+ $this->og_tag( 'og:type', $type );
+ } else {
+ return $type;
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Display an OpenGraph image tag
+ *
+ * @param string $img Source URL to the image
+ *
+ * @return bool
+ */
+ function image_output( $img ) {
+ /**
+ * Filter: 'wpseo_opengraph_image' - Allow changing the OpenGraph image
+ *
+ * @api string $img Image URL string
+ */
+ $img = trim( apply_filters( 'wpseo_opengraph_image', $img ) );
+
+ if ( empty( $img ) ) {
+ return false;
+ }
+
+ if ( wpseo_is_url_relative( $img ) === true ) {
+ if ( $img[0] != '/' ) {
+ return false;
+ }
+
+ // If it's a relative URL, it's relative to the domain, not necessarily to the WordPress install, we
+ // want to preserve domain name and URL scheme (http / https) though.
+ $parsed_url = parse_url( home_url() );
+ $img = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $img;
+ }
+
+ if ( in_array( $img, $this->shown_images ) ) {
+ return false;
+ }
+
+ array_push( $this->shown_images, $img );
+
+ $this->og_tag( 'og:image', esc_url( $img ) );
+
+ return true;
+ }
+
+ /**
+ * Output the OpenGraph image elements for all the images within the current post/page.
+ *
+ * @return bool
+ */
+ public function image() {
+
+ global $post;
+
+ if ( is_front_page() ) {
+ if ( $this->options['og_frontpage_image'] !== '' ) {
+ $this->image_output( $this->options['og_frontpage_image'] );
+ }
+ }
+
+ if ( is_singular() ) {
+ $ogimg = WPSEO_Meta::get_value( 'opengraph-image' );
+ if ( $ogimg !== '' ) {
+ $this->image_output( $ogimg );
+
+ return;
+ }
+
+ if ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail( $post->ID ) ) {
+ /**
+ * Filter: 'wpseo_opengraph_image_size' - Allow changing the image size used for OpenGraph sharing
+ *
+ * @api string $unsigned Size string
+ */
+ $thumb = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), apply_filters( 'wpseo_opengraph_image_size', 'original' ) );
+ $this->image_output( $thumb[0] );
+ }
+
+ /**
+ * Filter: 'wpseo_pre_analysis_post_content' - Allow filtering the content before analysis
+ *
+ * @api string $post_content The Post content string
+ *
+ * @param object $post The post object.
+ */
+ $content = apply_filters( 'wpseo_pre_analysis_post_content', $post->post_content, $post );
+
+ if ( preg_match_all( '`<img [^>]+>`', $content, $matches ) ) {
+ foreach ( $matches[0] as $img ) {
+ if ( preg_match( '`src=(["\'])(.*?)\1`', $img, $match ) ) {
+ $this->image_output( $match[2] );
+ }
+ }
+ }
+ }
+
+ if ( count( $this->shown_images ) == 0 && $this->options['og_default_image'] !== '' ) {
+ $this->image_output( $this->options['og_default_image'] );
+ }
+
+ // @TODO add G+ image stuff
+ }
+
+ /**
+ * Output the OpenGraph description, specific OG description first, if not, grab the meta description.
+ *
+ * @param bool $echo Whether to echo or return the description
+ *
+ * @return string $ogdesc
+ */
+ public function description( $echo = true ) {
+ $ogdesc = '';
+
+ if ( is_front_page() ) {
+ $ogdesc = ( $this->options['og_frontpage_desc'] !== '' ) ? $this->options['og_frontpage_desc'] : $this->metadesc( false );
+ }
+
+ if ( is_singular() ) {
+ $ogdesc = WPSEO_Meta::get_value( 'opengraph-description' );
+
+ // Replace WP SEO Variables
+ $ogdesc = wpseo_replace_vars( $ogdesc, get_post() );
+
+ // Use metadesc if $ogdesc is empty
+ if ( $ogdesc === '' ) {
+ $ogdesc = $this->metadesc( false );
+ }
+
+ // og:description is still blank so grab it from get_the_excerpt()
+ if ( ! is_string( $ogdesc ) || ( is_string( $ogdesc ) && $ogdesc === '' ) ) {
+ $ogdesc = str_replace( '[…]', '…', strip_tags( get_the_excerpt() ) );
+ }
+ }
+
+ if ( is_category() || is_tag() || is_tax() ) {
+ $ogdesc = trim( strip_tags( term_description() ) );
+ if ( '' == $ogdesc ) {
+ global $wp_query;
+ $term = $wp_query->get_queried_object();
+ $ogdesc = WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, 'desc' );
+ }
+ }
+
+ // Strip shortcodes if any
+ $ogdesc = strip_shortcodes( $ogdesc );
+
+ /**
+ * Filter: 'wpseo_opengraph_desc' - Allow changing the OpenGraph description
+ *
+ * @api string $ogdesc The description string.
+ */
+ $ogdesc = trim( apply_filters( 'wpseo_opengraph_desc', $ogdesc ) );
+
+ if ( is_string( $ogdesc ) && $ogdesc !== '' ) {
+ if ( $echo !== false ) {
+ $this->og_tag( 'og:description', $ogdesc );
+ }
+ }
+
+ return $ogdesc;
+ }
+
+ /**
+ * Output the site name straight from the blog info.
+ */
+ public function site_name() {
+ /**
+ * Filter: 'wpseo_opengraph_site_name' - Allow changing the OpenGraph site name
+ *
+ * @api string $unsigned Blog name string
+ */
+ $name = apply_filters( 'wpseo_opengraph_site_name', get_bloginfo( 'name' ) );
+ if ( is_string( $name ) && $name !== '' ) {
+ $this->og_tag( 'og:site_name', $name );
+ }
+ }
+
+ /**
+ * Output the article tags as article:tag tags.
+ *
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ * @return boolean
+ */
+ public function tags() {
+ if ( ! is_singular() ) {
+ return false;
+ }
+
+ $tags = get_the_tags();
+ if ( ! is_wp_error( $tags ) && ( is_array( $tags ) && $tags !== array() ) ) {
+
+ foreach ( $tags as $tag ) {
+ $this->og_tag( 'article:tag', $tag->name );
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Output the article category as an article:section tag.
+ *
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ * @return boolean;
+ */
+ public function category() {
+
+ if ( ! is_singular() ) {
+ return false;
+ }
+
+ $terms = get_the_category();
+
+ if ( ! is_wp_error( $terms ) && ( is_array( $terms ) && $terms !== array() ) ) {
+
+ foreach ( $terms as $term ) {
+ $this->og_tag( 'article:section', $term->name );
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Output the article publish and last modification date
+ *
+ * @link https://developers.facebook.com/docs/reference/opengraph/object-type/article/
+ * @return boolean;
+ */
+ public function publish_date() {
+
+ if ( ! is_singular( 'post' ) ) {
+ /**
+ * Filter: 'wpseo_opengraph_show_publish_date' - Allow showing publication date for other post types
+ *
+ * @api bool $unsigned Whether or not to show publish date
+ *
+ * @param string $post_type The current URL's post type.
+ */
+ if ( false === apply_filters( 'wpseo_opengraph_show_publish_date', false, get_post_type() ) ) {
+ return false;
+ }
+ }
+
+ $pub = get_the_date( 'c' );
+ $this->og_tag( 'article:published_time', $pub );
+
+ $mod = get_the_modified_date( 'c' );
+ if ( $mod != $pub ) {
+ $this->og_tag( 'article:modified_time', $mod );
+ $this->og_tag( 'og:updated_time', $mod );
+ }
+
+ return true;
+ }
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Frontend
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_Twitter' ) ) {
+ /**
+ * This class handles the Twitter card functionality.
+ *
+ * @link https://dev.twitter.com/docs/cards
+ */
+ class WPSEO_Twitter extends WPSEO_Frontend {
+
+ /**
+ * @var object Instance of this class
+ */
+ public static $instance;
+
+ /**
+ * @var array Images
+ */
+ public $shown_images;
+
+ /**
+ * @var array $options Holds the options for the Twitter Card functionality
+ */
+ public $options;
+
+ /**
+ * Class constructor
+ */
+ public function __construct() {
+ $this->options = WPSEO_Options::get_all();
+ $this->shown_images = array(); // Instantiate as empty array
+ $this->twitter();
+ }
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Output the metatag
+ *
+ * @param $name
+ * @param $value
+ * @param $escaped
+ */
+ private function output_metatag( $name, $value, $escaped = false ) {
+
+ // Escape the value if not escaped
+ if ( false === $escaped ) {
+ $value = esc_attr( $value );
+ }
+
+ /**
+ * Filter: 'wpseo_twitter_metatag_key' - Make the Twitter metatag key filterable
+ *
+ * @api string $key The Twitter metatag key
+ */
+ $metatag_key = apply_filters( 'wpseo_twitter_metatag_key', 'name' );
+
+ // Output meta
+ echo '<meta ' . esc_attr( $metatag_key ) . '="twitter:' . esc_attr( $name ) . '" content="' . $value . '"/>' . "\n";
+ }
+
+ /**
+ * Outputs the Twitter Card code on singular pages.
+ *
+ * @return void Only shows on singular pages, false on non-singular pages.
+ */
+ public function twitter() {
+ wp_reset_query();
+
+ $this->type();
+ $this->site_twitter();
+ $this->site_domain();
+ $this->author_twitter();
+ if ( 'summary_large_image' === $this->options['twitter_card_type'] ) {
+ $this->image();
+ }
+
+ // No need to show these when OpenGraph is also showing, as it'd be the same contents and Twitter
+ // would fallback to OpenGraph anyway.
+ if ( $this->options['opengraph'] === false ) {
+ if ( 'summary' === $this->options['twitter_card_type'] ) {
+ $this->image();
+ }
+ $this->twitter_description();
+ $this->twitter_title();
+ $this->twitter_url();
+ }
+
+ /**
+ * Action: 'wpseo_twitter' - Hook to add all WP SEO Twitter output to so they're close together.
+ */
+ do_action( 'wpseo_twitter' );
+ }
+
+ /**
+ * Display the Twitter card type.
+ *
+ * This defaults to summary but can be filtered using the <code>wpseo_twitter_card_type</code> filter.
+ *
+ * @link https://dev.twitter.com/docs/cards
+ */
+ public function type() {
+ /**
+ * Filter: 'wpseo_twitter_card_type' - Allow changing the Twitter Card type as output in the Twitter card by WP SEO
+ *
+ * @api string $unsigned The type string
+ */
+ $type = apply_filters( 'wpseo_twitter_card_type', $this->options['twitter_card_type'] );
+ if ( ! in_array( $type, array( 'summary', 'summary_large_image', 'photo', 'gallery', 'app', 'player', 'product' ) ) ) {
+ $type = 'summary';
+ }
+
+ $this->output_metatag( 'card', $type );
+ }
+
+ /**
+ * Displays the Twitter account for the site.
+ */
+ public function site_twitter() {
+ /**
+ * Filter: 'wpseo_twitter_site' - Allow changing the Twitter site account as output in the Twitter card by WP SEO
+ *
+ * @api string $unsigned Twitter site account string
+ */
+ $site = apply_filters( 'wpseo_twitter_site', $this->options['twitter_site'] );
+ $site = $this->get_twitter_id( $site );
+
+ if ( is_string( $site ) && $site !== '' ) {
+ $this->output_metatag( 'site', '@' . $site );
+ }
+ }
+
+ /**
+ * Displays the domain tag for the site.
+ */
+ public function site_domain() {
+ /**
+ * Filter: 'wpseo_twitter_domain' - Allow changing the Twitter domain as output in the Twitter card by WP SEO
+ *
+ * @api string $unsigned Name string
+ */
+ $domain = apply_filters( 'wpseo_twitter_domain', get_bloginfo( 'name' ) );
+ if ( is_string( $domain ) && $domain !== '' ) {
+ $this->output_metatag( 'domain', $domain );
+ }
+ }
+
+ /**
+ * Displays the authors Twitter account.
+ */
+ public function author_twitter() {
+ $twitter = ltrim( trim( get_the_author_meta( 'twitter' ) ), '@' );
+ /**
+ * Filter: 'wpseo_twitter_creator_account' - Allow changing the Twitter account as output in the Twitter card by WP SEO
+ *
+ * @api string $twitter The twitter account name string
+ */
+ $twitter = apply_filters( 'wpseo_twitter_creator_account', $twitter );
+ $twitter = $this->get_twitter_id( $twitter );
+
+ if ( is_string( $twitter ) && $twitter !== '' ) {
+ $this->output_metatag( 'creator', '@' . $twitter );
+ }
+ elseif ( $this->options['twitter_site'] !== '' ) {
+ if ( is_string( $this->options['twitter_site'] ) && $this->options['twitter_site'] !== '' ) {
+ $this->output_metatag( 'creator', '@' . $this->options['twitter_site'] );
+ }
+ }
+ }
+
+ /**
+ * Displays the title for Twitter.
+ *
+ * Only used when OpenGraph is inactive.
+ */
+ public function twitter_title() {
+ /**
+ * Filter: 'wpseo_twitter_title' - Allow changing the Twitter title as output in the Twitter card by WP SEO
+ *
+ * @api string $twitter The title string
+ */
+ $title = apply_filters( 'wpseo_twitter_title', $this->title( '' ) );
+ if ( is_string( $title ) && $title !== '' ) {
+ $this->output_metatag( 'title', $title );
+ }
+ }
+
+ /**
+ * Displays the description for Twitter.
+ *
+ * Only used when OpenGraph is inactive.
+ */
+ public function twitter_description() {
+ $meta_desc = trim( $this->metadesc( false ) );
+ if ( ! is_string( $meta_desc ) || '' === $meta_desc ) {
+ $meta_desc = false;
+ }
+
+ if ( ! $meta_desc ) {
+ $meta_desc = strip_tags( get_the_excerpt() );
+ }
+
+ /**
+ * Filter: 'wpseo_twitter_description' - Allow changing the Twitter description as output in the Twitter card by WP SEO
+ *
+ * @api string $twitter The description string
+ */
+ $meta_desc = apply_filters( 'wpseo_twitter_description', $meta_desc );
+ if ( is_string( $meta_desc ) && $meta_desc !== '' ) {
+ $this->output_metatag( 'description', $meta_desc );
+ }
+ }
+
+ /**
+ * Displays the URL for Twitter.
+ *
+ * Only used when OpenGraph is inactive.
+ */
+ public function twitter_url() {
+ /**
+ * Filter: 'wpseo_twitter_url' - Allow changing the URL as output in the Twitter card by WP SEO
+ *
+ * @api string $unsigned Canonical URL
+ */
+ $url = apply_filters( 'wpseo_twitter_url', $this->canonical( false ) );
+ if ( is_string( $url ) && $url !== '' ) {
+ $this->output_metatag( 'url', esc_url( $url ), true );
+ }
+ }
+
+ /**
+ * Outputs a Twitter image tag for a given image
+ *
+ * @param string $img
+ * @return bool
+ */
+ public function image_output( $img ) {
+
+ /**
+ * Filter: 'wpseo_twitter_image' - Allow changing the Twitter Card image
+ *
+ * @api string $img Image URL string
+ */
+ $img = apply_filters( 'wpseo_twitter_image', $img );
+
+ $escaped_img = esc_url( $img );
+
+ if ( in_array( $escaped_img, $this->shown_images ) ) {
+ return false;
+ }
+
+ if ( is_string( $escaped_img ) && $escaped_img !== '' ) {
+ $this->output_metatag( 'image:src', $escaped_img, true );
+
+ array_push( $this->shown_images, $escaped_img );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Displays the image for Twitter
+ *
+ * Only used when OpenGraph is inactive or Summary Large Image card is chosen.
+ */
+ public function image() {
+ global $post;
+
+ if ( is_singular() ) {
+ if ( is_front_page() ) {
+ if ( $this->options['og_frontpage_image'] !== '' ) {
+ $this->image_output( $this->options['og_frontpage_image'] );
+ }
+ }
+
+ $twitter_img = WPSEO_Meta::get_value( 'twitter-image' );
+ if ( $twitter_img !== '' ) {
+ $this->image_output( $twitter_img );
+ return;
+ }
+ elseif ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail( $post->ID ) ) {
+ /**
+ * Filter: 'wpseo_twitter_image_size' - Allow changing the Twitter Card image size
+ *
+ * @api string $featured_img Image size string
+ */
+ $featured_img = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), apply_filters( 'wpseo_twitter_image_size', 'full' ) );
+
+ if ( $featured_img ) {
+ $this->image_output( $featured_img[0] );
+ }
+ } elseif ( preg_match_all( '`<img [^>]+>`', $post->post_content, $matches ) ) {
+ foreach ( $matches[0] as $img ) {
+ if ( preg_match( '`src=(["\'])(.*?)\1`', $img, $match ) ) {
+ $this->image_output( $match[2] );
+ }
+ }
+ }
+ }
+
+ if ( count( $this->shown_images ) == 0 && $this->options['og_default_image'] !== '' ) {
+ $this->image_output( $this->options['og_default_image'] );
+ }
+ }
+
+
+ /**
+ * Checks if the given id is actually an id or a url and if url, distills the id from it.
+ *
+ * Solves issues with filters returning urls and theme's/other plugins also adding a user meta
+ * twitter field which expects url rather than an id (which is what we expect).
+ *
+ * @param string $id Twitter id or url
+ *
+ * @return string|bool Twitter id or false if it failed to get a valid twitter id
+ */
+ private function get_twitter_id( $id ) {
+ if ( preg_match( '`([A-Za-z0-9_]{1,25})$`', $id, $match ) ) {
+ return $match[1];
+ }
+ else {
+ return false;
+ }
+ }
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Frontend
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+if ( ! class_exists( 'WPSEO_Rewrite' ) ) {
+ /**
+ * This code handles the category rewrites.
+ */
+ class WPSEO_Rewrite {
+
+ /**
+ * Class constructor
+ */
+ function __construct() {
+ add_filter( 'query_vars', array( $this, 'query_vars' ) );
+ add_filter( 'category_link', array( $this, 'no_category_base' ) );
+ add_filter( 'request', array( $this, 'request' ) );
+ add_filter( 'category_rewrite_rules', array( $this, 'category_rewrite_rules' ) );
+
+ add_action( 'created_category', array( $this, 'schedule_flush' ) );
+ add_action( 'edited_category', array( $this, 'schedule_flush' ) );
+ add_action( 'delete_category', array( $this, 'schedule_flush' ) );
+
+ add_action( 'init', array( $this, 'flush' ), 999 );
+ }
+
+ /**
+ * Save an option that triggers a flush on the next init.
+ *
+ * @since 1.2.8
+ */
+ function schedule_flush() {
+ update_option( 'wpseo_flush_rewrite', 1 );
+ }
+
+ /**
+ * If the flush option is set, flush the rewrite rules.
+ *
+ * @since 1.2.8
+ * @return bool
+ */
+ function flush() {
+ if ( get_option( 'wpseo_flush_rewrite' ) ) {
+
+ add_action( 'shutdown', 'flush_rewrite_rules' );
+ delete_option( 'wpseo_flush_rewrite' );
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Override the category link to remove the category base.
+ *
+ * @param string $link Unused, overridden by the function.
+ *
+ * @return string
+ */
+ function no_category_base( $link ) {
+ $category_base = get_option( 'category_base' );
+
+ if ( '' == $category_base ) {
+ $category_base = 'category';
+ }
+
+ // Remove initial slash, if there is one (we remove the trailing slash in the regex replacement and don't want to end up short a slash)
+ if ( '/' == substr( $category_base, 0, 1 ) ) {
+ $category_base = substr( $category_base, 1 );
+ }
+
+ $category_base .= '/';
+
+ return preg_replace( '`' . preg_quote( $category_base, '`' ) . '`u', '', $link, 1 );
+ }
+
+ /**
+ * Update the query vars with the redirect var when stripcategorybase is active
+ *
+ * @param $query_vars
+ *
+ * @return array
+ */
+ function query_vars( $query_vars ) {
+ $options = WPSEO_Options::get_all();
+
+ if ( $options['stripcategorybase'] === true ) {
+ $query_vars[] = 'wpseo_category_redirect';
+ }
+
+ return $query_vars;
+ }
+
+ /**
+ * Redirect the "old" category URL to the new one.
+ *
+ * @param array $query_vars Query vars to check for existence of redirect var
+ *
+ * @return array
+ */
+ function request( $query_vars ) {
+ if ( isset( $query_vars['wpseo_category_redirect'] ) ) {
+ $catlink = trailingslashit( get_option( 'home' ) ) . user_trailingslashit( $query_vars['wpseo_category_redirect'], 'category' );
+
+ wp_redirect( $catlink, 301 );
+ exit;
+ }
+
+ return $query_vars;
+ }
+
+ /**
+ * This function taken and only slightly adapted from WP No Category Base plugin by Saurabh Gupta
+ *
+ * @return array
+ */
+ function category_rewrite_rules() {
+ global $wp_rewrite;
+
+ $category_rewrite = array();
+
+ $taxonomy = get_taxonomy( 'category' );
+
+ $blog_prefix = '';
+ if ( is_multisite() && ! is_subdomain_install() && is_main_site() ) {
+ $blog_prefix = 'blog/';
+ }
+
+ $categories = get_categories( array( 'hide_empty' => false ) );
+ if ( is_array( $categories ) && $categories !== array() ) {
+ foreach ( $categories as $category ) {
+ $category_nicename = $category->slug;
+ if ( $category->parent == $category->cat_ID ) {
+ // recursive recursion
+ $category->parent = 0;
+ } elseif ( $taxonomy->rewrite['hierarchical'] != 0 && $category->parent != 0 ) {
+ $parents = get_category_parents( $category->parent, false, '/', true );
+ if ( ! is_wp_error( $parents ) ) {
+ $category_nicename = $parents . $category_nicename;
+ }
+ unset( $parents );
+ }
+
+ $category_rewrite[ $blog_prefix . '(' . $category_nicename . ')/(?:feed/)?(feed|rdf|rss|rss2|atom)/?$' ] = 'index.php?category_name=$matches[1]&feed=$matches[2]';
+ $category_rewrite[ $blog_prefix . '(' . $category_nicename . ')/' . $wp_rewrite->pagination_base . '/?([0-9]{1,})/?$' ] = 'index.php?category_name=$matches[1]&paged=$matches[2]';
+ $category_rewrite[ $blog_prefix . '(' . $category_nicename . ')/?$' ] = 'index.php?category_name=$matches[1]';
+ }
+ }
+
+ // Redirect support from Old Category Base
+ $old_base = $wp_rewrite->get_category_permastruct();
+ $old_base = str_replace( '%category%', '(.+)', $old_base );
+ $old_base = trim( $old_base, '/' );
+ $category_rewrite[ $old_base . '$' ] = 'index.php?wpseo_category_redirect=$matches[1]';
+
+ return $category_rewrite;
+ }
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package XML_Sitemaps
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+
+ header( 'HTTP/1.1 403 Forbidden', true, 403 );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Sitemaps' ) ) {
+ /**
+ * Class WPSEO_Sitemaps
+ *
+ * @todo: [JRF => whomever] If at all possible, move the adding of rewrite rules, actions and filters
+ * elsewhere and only load this file when an actual sitemap is being requested.
+ */
+ class WPSEO_Sitemaps {
+ /**
+ * Content of the sitemap to output.
+ */
+ private $sitemap = '';
+
+ /**
+ * XSL stylesheet for styling a sitemap for web browsers
+ */
+ private $stylesheet = '';
+
+ /**
+ * Flag to indicate if this is an invalid or empty sitemap.
+ */
+ public $bad_sitemap = false;
+
+ /**
+ * Whether or not the XML sitemap was served from a transient or not.
+ */
+ private $transient = false;
+
+ /**
+ * The maximum number of entries per sitemap page
+ */
+ private $max_entries;
+
+ /**
+ * Holds the post type's newest publish dates
+ */
+ private $post_type_dates;
+
+ /**
+ * Holds the WP SEO options
+ */
+ private $options = array();
+
+ /**
+ * Holds the n variable
+ */
+ private $n = 1;
+
+ /**
+ * Holds the home_url() value to speed up loops
+ * @var string $home_url
+ */
+ private $home_url = '';
+
+ /**
+ * Holds the get_bloginfo( 'charset' ) value to reuse for performance
+ *
+ * @var string $charset
+ */
+ private $charset = '';
+
+ /**
+ * Holds the timezone string value to reuse for performance
+ *
+ * @var string $timezone_string
+ */
+ private $timezone_string = '';
+
+ /**
+ * Class constructor
+ */
+ function __construct() {
+ if ( ! defined( 'ENT_XML1' ) ) {
+ define( 'ENT_XML1', 16 );
+ }
+
+ add_action( 'after_setup_theme', array( $this, 'reduce_query_load' ), 99 );
+ add_filter( 'posts_where', array( $this, 'invalidate_main_query' ) );
+
+ add_action( 'pre_get_posts', array( $this, 'redirect' ), 1 );
+ add_filter( 'redirect_canonical', array( $this, 'canonical' ) );
+ add_action( 'wpseo_hit_sitemap_index', array( $this, 'hit_sitemap_index' ) );
+
+ // default stylesheet
+ $this->stylesheet = '<?xml-stylesheet type="text/xsl" href="' . preg_replace( '/(^http[s]?:)/', '', esc_url( home_url( 'main-sitemap.xsl' ) ) ) . '"?>';
+
+ $this->options = WPSEO_Options::get_all();
+ $this->max_entries = $this->options['entries-per-page'];
+ $this->home_url = home_url();
+ $this->charset = get_bloginfo( 'charset' );
+
+ }
+
+ /**
+ * Check the current request URI, if we can determine it's probably an XML sitemap, kill loading the widgets
+ */
+ public function reduce_query_load() {
+ if ( isset( $_SERVER['REQUEST_URI'] ) && ( in_array( substr( $_SERVER['REQUEST_URI'], - 4 ), array(
+ '.xml',
+ '.xsl',
+ ) ) )
+ ) {
+ remove_all_actions( 'widgets_init' );
+ }
+ }
+
+ /**
+ * This query invalidates the main query on purpose so it returns nice and quickly
+ *
+ * @param string $where
+ *
+ * @return string
+ */
+ function invalidate_main_query( $where ) {
+
+ global $wp_query;
+
+ // check if $wp_query is properly set which isn't always the case in older WP development versions
+ if ( ! is_object( $wp_query ) ) {
+ return $where;
+ }
+
+ if ( is_main_query() && ( get_query_var( 'sitemap' ) != '' || get_query_var( 'xsl' ) != '' ) ) {
+ $where = ' AND 0=1 ' . $where;
+ }
+
+ return $where;
+ }
+
+
+ /**
+ * Returns the server HTTP protocol to use for output, if it's set.
+ *
+ * @return string
+ */
+ private function http_protocol() {
+ return ( isset( $_SERVER['SERVER_PROTOCOL'] ) && $_SERVER['SERVER_PROTOCOL'] !== '' ) ? sanitize_text_field( $_SERVER['SERVER_PROTOCOL'] ) : 'HTTP/1.1';
+ }
+
+ /**
+ * Returns the timezone string for a site, even if it's set to a UTC offset
+ *
+ * Adapted from http://www.php.net/manual/en/function.timezone-name-from-abbr.php#89155
+ *
+ * @return string valid PHP timezone string
+ */
+ private function determine_timezone_string() {
+
+ // if site timezone string exists, return it
+ if ( $timezone = get_option( 'timezone_string' ) ) {
+ return $timezone;
+ }
+
+ // get UTC offset, if it isn't set then return UTC
+ if ( 0 === ( $utc_offset = get_option( 'gmt_offset', 0 ) ) ) {
+ return 'UTC';
+ }
+
+ // adjust UTC offset from hours to seconds
+ $utc_offset *= HOUR_IN_SECONDS;
+
+ // attempt to guess the timezone string from the UTC offset
+ $timezone = timezone_name_from_abbr( '', $utc_offset );
+
+ // last try, guess timezone string manually
+ if ( false === $timezone ) {
+
+ $is_dst = date( 'I' );
+
+ foreach ( timezone_abbreviations_list() as $abbr ) {
+ foreach ( $abbr as $city ) {
+ if ( $city['dst'] == $is_dst && $city['offset'] == $utc_offset ) {
+ return $city['timezone_id'];
+ }
+ }
+ }
+ }
+
+ // fallback to UTC
+ return 'UTC';
+ }
+
+ /**
+ * Returns the correct timezone string
+ *
+ * @return string
+ */
+ private function get_timezone_string() {
+ if ( '' == $this->timezone_string ) {
+ $this->timezone_string = $this->determine_timezone_string();
+ }
+
+ return $this->timezone_string;
+ }
+
+ /**
+ * Register your own sitemap. Call this during 'init'.
+ *
+ * @param string $name The name of the sitemap
+ * @param callback $function Function to build your sitemap
+ * @param string $rewrite Optional. Regular expression to match your sitemap with
+ */
+ function register_sitemap( $name, $function, $rewrite = '' ) {
+ add_action( 'wpseo_do_sitemap_' . $name, $function );
+ if ( ! empty( $rewrite ) ) {
+ add_rewrite_rule( $rewrite, 'index.php?sitemap=' . $name, 'top' );
+ }
+ }
+
+ /**
+ * Register your own XSL file. Call this during 'init'.
+ *
+ * @param string $name The name of the XSL file
+ * @param callback $function Function to build your XSL file
+ * @param string $rewrite Optional. Regular expression to match your sitemap with
+ */
+ function register_xsl( $name, $function, $rewrite = '' ) {
+ add_action( 'wpseo_xsl_' . $name, $function );
+ if ( ! empty( $rewrite ) ) {
+ add_rewrite_rule( $rewrite, 'index.php?xsl=' . $name, 'top' );
+ }
+ }
+
+ /**
+ * Set the sitemap content to display after you have generated it.
+ *
+ * @param string $sitemap The generated sitemap to output
+ */
+ function set_sitemap( $sitemap ) {
+ $this->sitemap = $sitemap;
+ }
+
+ /**
+ * Set a custom stylesheet for this sitemap. Set to empty to just remove
+ * the default stylesheet.
+ *
+ * @param string $stylesheet Full xml-stylesheet declaration
+ */
+ public function set_stylesheet( $stylesheet ) {
+ $this->stylesheet = $stylesheet;
+ }
+
+ /**
+ * Set as true to make the request 404. Used stop the display of empty sitemaps or
+ * invalid requests.
+ *
+ * @param bool $bool Is this a bad request. True or false.
+ */
+ function set_bad_sitemap( $bool ) {
+ $this->bad_sitemap = (bool) $bool;
+ }
+
+ /**
+ * Prevent stupid plugins from running shutdown scripts when we're obviously not outputting HTML.
+ *
+ * @since 1.4.16
+ */
+ function sitemap_close() {
+ remove_all_actions( 'wp_footer' );
+ die();
+ }
+
+ /**
+ * Hijack requests for potential sitemaps and XSL files.
+ */
+ function redirect( $query ) {
+
+ if ( ! $query->is_main_query() ) {
+ return;
+ }
+
+ $xsl = get_query_var( 'xsl' );
+ if ( ! empty( $xsl ) ) {
+ $this->xsl_output( $xsl );
+ $this->sitemap_close();
+ }
+
+ $type = get_query_var( 'sitemap' );
+ if ( empty( $type ) ) {
+ return;
+ }
+
+ $n = get_query_var( 'sitemap_n' );
+ if ( is_scalar( $n ) && intval( $n ) > 0 ) {
+ $this->n = intval( $n );
+ }
+
+ /**
+ * Filter: 'wpseo_enable_xml_sitemap_transient_caching' - Allow disabling the transient cache
+ *
+ * @api bool $unsigned Enable cache or not, defaults to true
+ */
+ $caching = apply_filters( 'wpseo_enable_xml_sitemap_transient_caching', true );
+
+ if ( $caching ) {
+ $this->sitemap = get_transient( 'wpseo_sitemap_cache_' . $type . '_' . $this->n );
+ }
+
+ if ( ! $this->sitemap || '' == $this->sitemap ) {
+ $this->build_sitemap( $type );
+
+ // 404 for invalid or emtpy sitemaps
+ if ( $this->bad_sitemap ) {
+ global $wp_query;
+ $wp_query->set_404();
+ status_header( 404 );
+
+ return;
+ }
+
+ if ( $caching ) {
+ set_transient( 'wpseo_sitemap_cache_' . $type . '_' . $n, $this->sitemap, DAY_IN_SECONDS );
+ }
+ } else {
+ $this->transient = true;
+ }
+
+ $this->output();
+ $this->sitemap_close();
+ }
+
+ /**
+ * Attempt to build the requested sitemap. Sets $bad_sitemap if this isn't
+ * for the root sitemap, a post type or taxonomy.
+ *
+ * @param string $type The requested sitemap's identifier.
+ */
+ function build_sitemap( $type ) {
+
+ $type = apply_filters( 'wpseo_build_sitemap_post_type', $type );
+
+ if ( $type == 1 ) {
+ $this->build_root_map();
+ } elseif ( post_type_exists( $type ) ) {
+ $this->build_post_type_map( $type );
+ } elseif ( $tax = get_taxonomy( $type ) ) {
+ $this->build_tax_map( $tax );
+ } elseif ( $type == 'author' ) {
+ $this->build_user_map();
+ } elseif ( has_action( 'wpseo_do_sitemap_' . $type ) ) {
+ do_action( 'wpseo_do_sitemap_' . $type );
+ } else {
+ $this->bad_sitemap = true;
+ }
+ }
+
+ /**
+ * Build the root sitemap -- example.com/sitemap_index.xml -- which lists sub-sitemaps
+ * for other content types.
+ */
+ function build_root_map() {
+
+ global $wpdb;
+
+ $this->sitemap = '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
+ // reference post type specific sitemaps
+ $post_types = get_post_types( array( 'public' => true ) );
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+
+ foreach ( $post_types as $post_type ) {
+ if ( isset( $this->options[ 'post_types-' . $post_type . '-not_in_sitemap' ] ) && $this->options[ 'post_types-' . $post_type . '-not_in_sitemap' ] === true ) {
+ unset( $post_types[ $post_type ] );
+ } else {
+ if ( apply_filters( 'wpseo_sitemap_exclude_post_type', false, $post_type ) ) {
+ unset( $post_types[ $post_type ] );
+ }
+ }
+ }
+
+ // No prepare here because $wpdb->prepare can't properly prepare IN statements.
+ $query = "SELECT post_type, COUNT(ID) AS count FROM $wpdb->posts WHERE post_status IN ('publish','inherit') AND post_type IN ( '" . implode( "','", $post_types ) . "' ) GROUP BY post_type ";
+ $result = $wpdb->get_results( $query );
+
+ $post_type_counts = array();
+ foreach ( $result as $obj ) {
+ $post_type_counts[ $obj->post_type ] = $obj->count;
+ }
+ unset( $result );
+
+ foreach ( $post_types as $post_type ) {
+
+ $count = false;
+ if ( isset( $post_type_counts[ $post_type ] ) ) {
+ $count = $post_type_counts[ $post_type ];
+ } else {
+ continue;
+ }
+
+ $n = ( $count > $this->max_entries ) ? (int) ceil( $count / $this->max_entries ) : 1;
+ for ( $i = 0; $i < $n; $i ++ ) {
+ $count = ( $n > 1 ) ? $i + 1 : '';
+
+ if ( empty( $count ) || $count == $n ) {
+ $date = $this->get_last_modified( $post_type );
+ } else {
+ if ( ! isset( $all_dates ) ) {
+ $all_dates = $wpdb->get_col( $wpdb->prepare( "SELECT post_modified_gmt FROM (SELECT @rownum:=@rownum+1 rownum, $wpdb->posts.post_modified_gmt FROM (SELECT @rownum:=0) r, $wpdb->posts WHERE post_status IN ('publish','inherit') AND post_type = %s ORDER BY post_modified_gmt ASC) x WHERE rownum %%%d=0", $post_type, $this->max_entries ) );
+ }
+ $datetime = new DateTime( $all_dates[ $i ], new DateTimeZone( $this->get_timezone_string() ) );
+ $date = $datetime->format( 'c' );
+ }
+
+ $this->sitemap .= '<sitemap>' . "\n";
+ $this->sitemap .= '<loc>' . wpseo_xml_sitemaps_base_url( $post_type . '-sitemap' . $count . '.xml' ) . '</loc>' . "\n";
+ $this->sitemap .= '<lastmod>' . htmlspecialchars( $date ) . '</lastmod>' . "\n";
+ $this->sitemap .= '</sitemap>' . "\n";
+ }
+ unset( $all_dates );
+ }
+ }
+ unset( $post_types, $query, $count, $n, $i, $date );
+
+ // reference taxonomy specific sitemaps
+ $taxonomies = get_taxonomies( array( 'public' => true ), 'objects' );
+ $taxonomy_names = array_keys( $taxonomies );
+
+ if ( is_array( $taxonomies ) && $taxonomies !== array() ) {
+ foreach ( $taxonomy_names as $tax ) {
+ if ( in_array( $tax, array( 'link_category', 'nav_menu', 'post_format' ) ) ) {
+ unset( $taxonomy_names[ $tax ], $taxonomies[ $tax ] );
+ continue;
+ }
+
+ if ( apply_filters( 'wpseo_sitemap_exclude_taxonomy', false, $tax ) ) {
+ unset( $taxonomy_names[ $tax ], $taxonomies[ $tax ] );
+ continue;
+ }
+
+ if ( isset( $this->options[ 'taxonomies-' . $tax . '-not_in_sitemap' ] ) && $this->options[ 'taxonomies-' . $tax . '-not_in_sitemap' ] === true ) {
+ unset( $taxonomy_names[ $tax ], $taxonomies[ $tax ] );
+ continue;
+ }
+ }
+
+ // Retrieve all the taxonomies and their terms so we can do a proper count on them.
+ $hide_empty = ( apply_filters( 'wpseo_sitemap_exclude_empty_terms', true, $tax ) ) ? 'count != 0 AND' : '';
+ $query = "SELECT taxonomy, term_id FROM $wpdb->term_taxonomy WHERE $hide_empty taxonomy IN ('" . implode( "','", $taxonomy_names ) . "');";
+ $all_taxonomy_terms = $wpdb->get_results( $query );
+ $all_taxonomies = array();
+ foreach ( $all_taxonomy_terms as $obj ) {
+ $all_taxonomies[ $obj->taxonomy ][] = $obj->term_id;
+ }
+ unset( $all_taxonomy_terms );
+
+ foreach ( $taxonomies as $tax_name => $tax ) {
+
+ $steps = $this->max_entries;
+ $count = ( isset ( $all_taxonomies[ $tax_name ] ) ) ? count( $all_taxonomies[ $tax_name ] ) : 1;
+ $n = ( $count > $this->max_entries ) ? (int) ceil( $count / $this->max_entries ) : 1;
+
+ for ( $i = 0; $i < $n; $i ++ ) {
+ $count = ( $n > 1 ) ? $i + 1 : '';
+
+ if ( ! is_array( $tax->object_type ) || count( $tax->object_type ) == 0 ) {
+ continue;
+ }
+
+ if ( ( empty( $count ) || $count == $n ) ) {
+ $date = $this->get_last_modified( $tax->object_type );
+ } else {
+ $terms = array_splice( $all_taxonomies[ $tax_name ], 0, $steps );
+ if ( ! $terms ) {
+ continue;
+ }
+
+ $args = array(
+ 'post_type' => $tax->object_type,
+ 'tax_query' => array(
+ array(
+ 'taxonomy' => $tax_name,
+ 'field' => 'slug',
+ 'terms' => $terms,
+ ),
+ ),
+ 'orderby' => 'modified',
+ 'order' => 'DESC',
+ );
+ $query = new WP_Query( $args );
+
+ $date = '';
+ if ( $query->have_posts() ) {
+ $datetime = new DateTime( $query->posts[0]->post_modified_gmt, new DateTimeZone( $this->get_timezone_string() ) );
+ $date = $datetime->format( 'c' );
+ } else {
+ $date = $this->get_last_modified( $tax->object_type );
+ }
+ }
+
+ $this->sitemap .= '<sitemap>' . "\n";
+ $this->sitemap .= '<loc>' . wpseo_xml_sitemaps_base_url( $tax_name . '-sitemap' . $count . '.xml' ) . '</loc>' . "\n";
+ $this->sitemap .= '<lastmod>' . htmlspecialchars( $date ) . '</lastmod>' . "\n";
+ $this->sitemap .= '</sitemap>' . "\n";
+ }
+ }
+ }
+ unset( $taxonomies, $tax, $all_terms, $steps, $count, $n, $i, $tax_object, $date, $terms, $args, $query );
+
+ if ( $this->options['disable-author'] === false && $this->options['disable_author_sitemap'] === false ) {
+
+ // reference user profile specific sitemaps
+ $users = get_users( array( 'who' => 'authors', 'fields' => 'id' ) );
+
+ $count = count( $users );
+ $n = ( $count > $this->max_entries ) ? (int) ceil( $count / $this->max_entries ) : 1;
+
+ for ( $i = 0; $i < $n; $i ++ ) {
+ $count = ( $n > 1 ) ? $i + 1 : '';
+
+ // must use custom raw query because WP User Query does not support ordering by usermeta
+ // Retrieve the newest updated profile timestamp overall
+ if ( empty( $count ) || $count == $n ) {
+ $date = $wpdb->get_var(
+ $wpdb->prepare(
+ "
+ SELECT mt1.meta_value FROM $wpdb->users
+ INNER JOIN $wpdb->usermeta ON ($wpdb->users.ID = $wpdb->usermeta.user_id)
+ INNER JOIN $wpdb->usermeta AS mt1 ON ($wpdb->users.ID = mt1.user_id) WHERE 1=1
+ AND ( ($wpdb->usermeta.meta_key = %s AND CAST($wpdb->usermeta.meta_value AS CHAR) != '0')
+ AND mt1.meta_key = '_yoast_wpseo_profile_updated' ) ORDER BY mt1.meta_value DESC LIMIT 1
+ ",
+ $wpdb->get_blog_prefix() . 'user_level'
+ )
+ );
+ $date = new DateTime( date( 'y-m-d H:i:s', $date ), new DateTimeZone( $this->get_timezone_string() ) );
+
+ // Retrieve the newest updated profile timestamp by an offset
+ } else {
+ $date = $wpdb->get_var(
+ $wpdb->prepare(
+ "
+ SELECT mt1.meta_value FROM $wpdb->users
+ INNER JOIN $wpdb->usermeta ON ($wpdb->users.ID = $wpdb->usermeta.user_id)
+ INNER JOIN $wpdb->usermeta AS mt1 ON ($wpdb->users.ID = mt1.user_id) WHERE 1=1
+ AND ( ($wpdb->usermeta.meta_key = %s AND CAST($wpdb->usermeta.meta_value AS CHAR) != '0')
+ AND mt1.meta_key = '_yoast_wpseo_profile_updated' ) ORDER BY mt1.meta_value ASC LIMIT 1 OFFSET %d
+ ",
+ $wpdb->get_blog_prefix() . 'user_level',
+ $this->max_entries * ( $i + 1 ) - 1
+ )
+ );
+ $date = new DateTime( date( 'y-m-d H:i:s', $date ), new DateTimeZone( $this->get_timezone_string() ) );
+ }
+
+ $this->sitemap .= '<sitemap>' . "\n";
+ $this->sitemap .= '<loc>' . wpseo_xml_sitemaps_base_url( 'author-sitemap' . $count . '.xml' ) . '</loc>' . "\n";
+ $this->sitemap .= '<lastmod>' . htmlspecialchars( $date->format( 'c' ) ) . '</lastmod>' . "\n";
+ $this->sitemap .= '</sitemap>' . "\n";
+ }
+ unset( $users, $count, $n, $i, $date );
+ }
+
+ // allow other plugins to add their sitemaps to the index
+ $this->sitemap .= apply_filters( 'wpseo_sitemap_index', '' );
+ $this->sitemap .= '</sitemapindex>';
+ }
+
+ /**
+ * Function to dynamically filter the change frequency
+ *
+ * @param string $filter Expands to wpseo_sitemap_$filter_change_freq, allowing for a change of the frequency for numerous specific URLs
+ * @param string $default The default value for the frequency
+ * @param string $url The URL of the currenty entry
+ *
+ * @return mixed|void
+ */
+ private function filter_frequency( $filter, $default, $url ) {
+ /**
+ * Filter: 'wpseo_sitemap_' . $filter . '_change_freq' - Allow filtering of the specific change frequency
+ *
+ * @api string $default The default change frequency
+ */
+ $change_freq = apply_filters( 'wpseo_sitemap_' . $filter . '_change_freq', $default, $url );
+
+ if ( ! in_array( $change_freq, array(
+ 'always',
+ 'hourly',
+ 'daily',
+ 'weekly',
+ 'monthly',
+ 'yearly',
+ 'never',
+ ) )
+ ) {
+ $change_freq = $default;
+ }
+
+ return $change_freq;
+ }
+
+ /**
+ * Build a sub-sitemap for a specific post type -- example.com/post_type-sitemap.xml
+ *
+ * @param string $post_type Registered post type's slug
+ */
+ function build_post_type_map( $post_type ) {
+ global $wpdb;
+
+ if (
+ ( isset( $this->options[ 'post_types-' . $post_type . '-not_in_sitemap' ] ) && $this->options[ 'post_types-' . $post_type . '-not_in_sitemap' ] === true )
+ || in_array( $post_type, array( 'revision', 'nav_menu_item' ) )
+ || apply_filters( 'wpseo_sitemap_exclude_post_type', false, $post_type )
+ ) {
+ $this->bad_sitemap = true;
+
+ return;
+ }
+
+ $output = '';
+
+ $steps = ( 100 > $this->max_entries ) ? $this->max_entries : 100;
+ $n = (int) $this->n;
+ $offset = ( $n > 1 ) ? ( $n - 1 ) * $this->max_entries : 0;
+ $total = $offset + $this->max_entries;
+
+ $join_filter = '';
+ $join_filter = apply_filters( 'wpseo_typecount_join', $join_filter, $post_type );
+ $where_filter = '';
+ $where_filter = apply_filters( 'wpseo_typecount_where', $where_filter, $post_type );
+
+ $query = $wpdb->prepare( "SELECT COUNT(ID) FROM $wpdb->posts {$join_filter} WHERE post_status IN ('publish','inherit') AND post_password = '' AND post_author != 0 AND post_date != '0000-00-00 00:00:00' AND post_type = %s " . $where_filter, $post_type );
+
+ $typecount = $wpdb->get_var( $query );
+
+ if ( $total > $typecount ) {
+ $total = $typecount;
+ }
+
+ if ( $n === 1 ) {
+ $front_id = get_option( 'page_on_front' );
+ if ( ! $front_id && ( $post_type == 'post' || $post_type == 'page' ) ) {
+ $output .= $this->sitemap_url(
+ array(
+ 'loc' => $this->home_url,
+ 'pri' => 1,
+ 'chf' => $this->filter_frequency( 'homepage', 'daily', $this->home_url ),
+ )
+ );
+ } elseif ( $front_id && $post_type == 'post' ) {
+ $page_for_posts = get_option( 'page_for_posts' );
+ if ( $page_for_posts ) {
+ $page_for_posts_url = get_permalink( $page_for_posts );
+ $output .= $this->sitemap_url(
+ array(
+ 'loc' => $page_for_posts_url,
+ 'pri' => 1,
+ 'chf' => $change_freq = $this->filter_frequency( 'blogpage', 'daily', $page_for_posts_url ),
+ )
+ );
+ }
+ }
+
+ $archive = get_post_type_archive_link( $post_type );
+ if ( $archive ) {
+ /**
+ * Filter: 'wpseo_xml_post_type_archive_priority' - Allow changing the priority of the URL WordPress SEO uses in the XML sitemap.
+ *
+ * @api float $priority The priority for this URL, ranging from 0 to 1
+ *
+ * @param string $post_type The post type this archive is for
+ */
+ $output .= $this->sitemap_url(
+ array(
+ 'loc' => $archive,
+ 'pri' => apply_filters( 'wpseo_xml_post_type_archive_priority', 0.8, $post_type ),
+ 'chf' => $this->filter_frequency( $post_type . '_archive', 'weekly', $archive ),
+ 'mod' => $this->get_last_modified( $post_type ),
+ // get_lastpostmodified( 'gmt', $post_type ) #17455
+ )
+ );
+ }
+ }
+
+ if ( $typecount == 0 && empty( $archive ) ) {
+ $this->bad_sitemap = true;
+
+ return;
+ }
+
+ $stackedurls = array();
+
+ // Make sure you're wpdb->preparing everything you throw into this!!
+ $join_filter = apply_filters( 'wpseo_posts_join', false, $post_type );
+ $where_filter = apply_filters( 'wpseo_posts_where', false, $post_type );
+
+ $status = ( $post_type == 'attachment' ) ? 'inherit' : 'publish';
+
+ $parsed_home = parse_url( $this->home_url );
+ $host = '';
+ $scheme = 'http';
+ if ( isset( $parsed_home['host'] ) && ! empty( $parsed_home['host'] ) ) {
+ $host = str_replace( 'www.', '', $parsed_home['host'] );
+ }
+ if ( isset( $parsed_home['scheme'] ) && ! empty( $parsed_home['scheme'] ) ) {
+ $scheme = $parsed_home['scheme'];
+ }
+
+
+ /**
+ * We grab post_date, post_name, post_author and post_status too so we can throw these objects
+ * into get_permalink, which saves a get_post call for each permalink.
+ */
+ while ( $total > $offset ) {
+
+ // Optimized query per this thread: http://wordpress.org/support/topic/plugin-wordpress-seo-by-yoast-performance-suggestion
+ // Also see http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
+ $query = $wpdb->prepare( "SELECT l.ID, post_title, post_content, post_name, post_author, post_parent, post_modified_gmt, post_date, post_date_gmt FROM ( SELECT ID FROM $wpdb->posts {$join_filter} WHERE post_status = '%s' AND post_password = '' AND post_type = '%s' AND post_author != 0 AND post_date != '0000-00-00 00:00:00' {$where_filter} ORDER BY post_modified ASC LIMIT %d OFFSET %d ) o JOIN $wpdb->posts l ON l.ID = o.ID ORDER BY l.ID",
+ $status, $post_type, $steps, $offset
+ );
+
+ $posts = $wpdb->get_results( $query );
+
+ $post_ids = array();
+ foreach ( $posts as $p ) {
+ $post_ids[] = $p->ID;
+ }
+
+ if ( count( $post_ids ) > 0 ) {
+ update_meta_cache( 'post', $post_ids );
+
+ $child_query = "SELECT ID, post_title, post_parent FROM $wpdb->posts WHERE post_status = 'inherit' AND post_type = 'attachment' AND post_parent IN (" . implode( $post_ids, ',' ) . ')';
+ $wpdb->query( $child_query );
+ $attachments = $wpdb->get_results( $child_query );
+ $attachment_ids = wp_list_pluck( $attachments, 'ID' );
+
+ $thumbnail_query = "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = '_thumbnail_id' AND post_id IN (" . implode( $post_ids, ',' ) . ')';
+ $wpdb->query( $thumbnail_query );
+ $thumbnails = $wpdb->get_results( $thumbnail_query );
+ $thumbnail_ids = wp_list_pluck( $thumbnails, 'meta_value' );
+
+ $attachment_ids = array_merge( $thumbnail_ids, $attachment_ids );
+
+ _prime_post_caches( $attachment_ids );
+ update_meta_cache( 'post', $attachment_ids );
+ }
+
+ $offset = $offset + $steps;
+
+ if ( is_array( $posts ) && $posts !== array() ) {
+ foreach ( $posts as $p ) {
+ $p->post_type = $post_type;
+ $p->post_status = 'publish';
+ $p->filter = 'sample';
+
+ if ( WPSEO_Meta::get_value( 'meta-robots-noindex', $p->ID ) === '1' && WPSEO_Meta::get_value( 'sitemap-include', $p->ID ) !== 'always' ) {
+ continue;
+ }
+ if ( WPSEO_Meta::get_value( 'sitemap-include', $p->ID ) === 'never' ) {
+ continue;
+ }
+ if ( WPSEO_Meta::get_value( 'redirect', $p->ID ) !== '' ) {
+ continue;
+ }
+
+ $url = array();
+
+ $url['mod'] = ( isset( $p->post_modified_gmt ) && $p->post_modified_gmt != '0000-00-00 00:00:00' && $p->post_modified_gmt > $p->post_date_gmt ) ? $p->post_modified_gmt : ( ( '0000-00-00 00:00:00' != $p->post_date_gmt ) ? $p->post_date_gmt : $p->post_date );
+ $url['loc'] = get_permalink( $p );
+
+ /**
+ * Filter: 'wpseo_xml_sitemap_post_url' - Allow changing the URL WordPress SEO uses in the XML sitemap.
+ *
+ * Note that only absolute local URLs are allowed as the check after this removes external URLs.
+ *
+ * @api string $url URL to use in the XML sitemap
+ *
+ * @param object $p Post object for the URL
+ */
+ $url['loc'] = apply_filters( 'wpseo_xml_sitemap_post_url', $url['loc'], $p );
+
+ $url['chf'] = $this->filter_frequency( $post_type . '_single', 'weekly', $url['loc'] );
+
+ /**
+ * Do not include external URLs.
+ * @see https://wordpress.org/plugins/page-links-to/ can rewrite permalinks to external URLs.
+ */
+ if ( false === strpos( $url['loc'], $this->home_url ) ) {
+ continue;
+ }
+
+ $canonical = WPSEO_Meta::get_value( 'canonical', $p->ID );
+ if ( $canonical !== '' && $canonical !== $url['loc'] ) {
+ /* Let's assume that if a canonical is set for this page and it's different from
+ the URL of this post, that page is either already in the XML sitemap OR is on
+ an external site, either way, we shouldn't include it here. */
+ continue;
+ } else {
+ if ( $this->options['trailingslash'] === true && $p->post_type != 'post' ) {
+ $url['loc'] = trailingslashit( $url['loc'] );
+ }
+ }
+
+ $pri = WPSEO_Meta::get_value( 'sitemap-prio', $p->ID );
+ if ( is_numeric( $pri ) ) {
+ $url['pri'] = (float) $pri;
+ } else {
+ if ( $p->post_parent == 0 && $p->post_type == 'page' ) {
+ $url['pri'] = 0.8;
+ } else {
+ $url['pri'] = 0.6;
+ }
+ }
+
+ if ( isset( $front_id ) && $p->ID == $front_id ) {
+ $url['pri'] = 1.0;
+ }
+
+ /**
+ * Filter: 'wpseo_xml_post_type_archive_priority' - Allow changing the priority of the URL WordPress SEO uses in the XML sitemap.
+ *
+ * @api float $priority The priority for this URL, ranging from 0 to 1
+ *
+ * @param string $post_type The post type this archive is for
+ * @param object $p The post object
+ */
+ $url['pri'] = apply_filters( 'wpseo_xml_sitemap_post_priority', $url['pri'], $p->post_type, $p );
+
+ $url['images'] = array();
+
+ $content = $p->post_content;
+ $content = '<p><img src="' . $this->image_url( get_post_thumbnail_id( $p->ID ) ) . '" alt="' . $p->post_title . '" /></p>' . $content;
+
+ if ( preg_match_all( '`<img [^>]+>`', $content, $matches ) ) {
+ foreach ( $matches[0] as $img ) {
+ if ( preg_match( '`src=["\']([^"\']+)["\']`', $img, $match ) ) {
+ $src = $match[1];
+ if ( wpseo_is_url_relative( $src ) === true ) {
+ if ( $src[0] !== '/' ) {
+ continue;
+ } else {
+ // The URL is relative, we'll have to make it absolute
+ $src = $this->home_url . $src;
+ }
+ }
+ elseif ( strpos( $src, 'http' ) !== 0 ) {
+ // Protocol relative url, we add the scheme as the standard requires a protocol
+ $src = $scheme . ':' . $src;
+
+ }
+
+ if ( strpos( $src, $host ) === false ) {
+ continue;
+ }
+
+ if ( $src != esc_url( $src ) ) {
+ continue;
+ }
+
+ if ( isset( $url['images'][ $src ] ) ) {
+ continue;
+ }
+
+ $image = array(
+ 'src' => apply_filters( 'wpseo_xml_sitemap_img_src', $src, $p )
+ );
+
+ if ( preg_match( '`title=["\']([^"\']+)["\']`', $img, $match ) ) {
+ $image['title'] = str_replace( array( '-', '_' ), ' ', $match[1] );
+ }
+
+ if ( preg_match( '`alt=["\']([^"\']+)["\']`', $img, $match ) ) {
+ $image['alt'] = str_replace( array( '-', '_' ), ' ', $match[1] );
+ }
+
+ $image = apply_filters( 'wpseo_xml_sitemap_img', $image, $p );
+
+ $url['images'][] = $image;
+ }
+ }
+ }
+
+ if ( strpos( $p->post_content, '[gallery' ) !== false ) {
+ if ( is_array( $attachments ) && $attachments !== array() ) {
+
+ foreach ( $attachments as $attachment ) {
+ if ( $attachment->post_parent !== $p->ID ) {
+ continue;
+ }
+
+ $src = $this->image_url( $attachment->ID );
+ $image = array(
+ 'src' => apply_filters( 'wpseo_xml_sitemap_img_src', $src, $p )
+ );
+
+ $alt = get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true );
+ if ( $alt !== '' ) {
+ $image['alt'] = $alt;
+ }
+ unset( $alt );
+
+ $image['title'] = $attachment->post_title;
+
+ $image = apply_filters( 'wpseo_xml_sitemap_img', $image, $p );
+
+ $url['images'][] = $image;
+ }
+ }
+ unset( $attachment, $src, $image, $alt );
+ }
+
+ $url['images'] = apply_filters( 'wpseo_sitemap_urlimages', $url['images'], $p->ID );
+
+ if ( ! in_array( $url['loc'], $stackedurls ) ) {
+ // Use this filter to adjust the entry before it gets added to the sitemap
+ $url = apply_filters( 'wpseo_sitemap_entry', $url, 'post', $p );
+ if ( is_array( $url ) && $url !== array() ) {
+ $output .= $this->sitemap_url( $url );
+ $stackedurls[] = $url['loc'];
+ }
+ }
+
+ // Clear the post_meta and the term cache for the post, as we no longer need it now.
+ // wp_cache_delete( $p->ID, 'post_meta' );
+ // clean_object_term_cache( $p->ID, $post_type );
+ }
+ }
+ }
+
+ if ( empty( $output ) ) {
+ $this->bad_sitemap = true;
+
+ return;
+ }
+
+ $this->sitemap = '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" ';
+ $this->sitemap .= 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" ';
+ $this->sitemap .= 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
+ $this->sitemap .= $output;
+
+ // Filter to allow adding extra URLs, only do this on the first XML sitemap, not on all.
+ if ( $n === 1 ) {
+ $this->sitemap .= apply_filters( 'wpseo_sitemap_' . $post_type . '_content', '' );
+ }
+
+ $this->sitemap .= '</urlset>';
+ }
+
+ /**
+ * Build a sub-sitemap for a specific taxonomy -- example.com/tax-sitemap.xml
+ *
+ * @param string $taxonomy Registered taxonomy's slug
+ */
+ function build_tax_map( $taxonomy ) {
+ if (
+ ( isset( $this->options[ 'taxonomies-' . $taxonomy->name . '-not_in_sitemap' ] ) && $this->options[ 'taxonomies-' . $taxonomy->name . '-not_in_sitemap' ] === true )
+ || in_array( $taxonomy, array( 'link_category', 'nav_menu', 'post_format' ) )
+ || apply_filters( 'wpseo_sitemap_exclude_taxonomy', false, $taxonomy->name )
+ ) {
+ $this->bad_sitemap = true;
+
+ return;
+ }
+
+ global $wpdb;
+ $output = '';
+
+ $steps = $this->max_entries;
+ $n = (int) $this->n;
+ $offset = ( $n > 1 ) ? ( $n - 1 ) * $this->max_entries : 0;
+
+ /**
+ * Filter: 'wpseo_sitemap_exclude_empty_terms' - Allow people to include empty terms in sitemap
+ *
+ * @api bool $hide_empty Whether or not to hide empty terms, defaults to true.
+ *
+ * @param object $taxonomy The taxonomy we're getting terms for.
+ */
+ $hide_empty = apply_filters( 'wpseo_sitemap_exclude_empty_terms', true, $taxonomy );
+ $terms = get_terms( $taxonomy->name, array( 'hide_empty' => $hide_empty ) );
+ $terms = array_splice( $terms, $offset, $steps );
+
+ if ( is_array( $terms ) && $terms !== array() ) {
+ foreach ( $terms as $c ) {
+ $url = array();
+
+ $tax_noindex = WPSEO_Taxonomy_Meta::get_term_meta( $c, $c->taxonomy, 'noindex' );
+ $tax_sitemap_inc = WPSEO_Taxonomy_Meta::get_term_meta( $c, $c->taxonomy, 'sitemap_include' );
+
+ if ( ( is_string( $tax_noindex ) && $tax_noindex === 'noindex' ) && ( ! is_string( $tax_sitemap_inc ) || $tax_sitemap_inc !== 'always' ) ) {
+ continue;
+ }
+
+ if ( $tax_sitemap_inc === 'never' ) {
+ continue;
+ }
+
+ $url['loc'] = WPSEO_Taxonomy_Meta::get_term_meta( $c, $c->taxonomy, 'canonical' );
+ if ( ! is_string( $url['loc'] ) || $url['loc'] === '' ) {
+ $url['loc'] = get_term_link( $c, $c->taxonomy );
+ if ( $this->options['trailingslash'] === true ) {
+ $url['loc'] = trailingslashit( $url['loc'] );
+ }
+ }
+ if ( $c->count > 10 ) {
+ $url['pri'] = 0.6;
+ } else {
+ if ( $c->count > 3 ) {
+ $url['pri'] = 0.4;
+ } else {
+ $url['pri'] = 0.2;
+ }
+ }
+
+ // Grab last modified date
+ $sql = $wpdb->prepare(
+ "
+ SELECT MAX(p.post_modified_gmt) AS lastmod
+ FROM $wpdb->posts AS p
+ INNER JOIN $wpdb->term_relationships AS term_rel
+ ON term_rel.object_id = p.ID
+ INNER JOIN $wpdb->term_taxonomy AS term_tax
+ ON term_tax.term_taxonomy_id = term_rel.term_taxonomy_id
+ AND term_tax.taxonomy = %s
+ AND term_tax.term_id = %d
+ WHERE p.post_status IN ('publish','inherit')
+ AND p.post_password = ''",
+ $c->taxonomy,
+ $c->term_id
+ );
+ $url['mod'] = $wpdb->get_var( $sql );
+ $url['chf'] = $this->filter_frequency( $c->taxonomy . '_term', 'weekly', $url['loc'] );
+
+ // Use this filter to adjust the entry before it gets added to the sitemap
+ $url = apply_filters( 'wpseo_sitemap_entry', $url, 'term', $c );
+
+ if ( is_array( $url ) && $url !== array() ) {
+ $output .= $this->sitemap_url( $url );
+ }
+ }
+ }
+
+ if ( empty( $output ) ) {
+ $this->bad_sitemap = true;
+
+ return;
+ }
+
+ $this->sitemap = '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ';
+ $this->sitemap .= 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" ';
+ $this->sitemap .= 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
+ if ( is_string( $output ) && trim( $output ) !== '' ) {
+ $this->sitemap .= $output;
+ } else {
+ // If the sitemap is empty, add the homepage URL to make sure it doesn't throw errors in GWT.
+ $this->sitemap .= $this->sitemap_url( home_url() );
+ }
+ $this->sitemap .= '</urlset>';
+ }
+
+
+ /**
+ * Build the sub-sitemap for authors
+ *
+ * @since 1.4.8
+ */
+ function build_user_map() {
+ if ( $this->options['disable-author'] === true || $this->options['disable_author_sitemap'] === true ) {
+ $this->bad_sitemap = true;
+
+ return;
+ }
+
+ $output = '';
+
+ $steps = $this->max_entries;
+ $n = (int) $this->n;
+ $offset = ( $n > 1 ) ? ( $n - 1 ) * $this->max_entries : 0;
+
+ // initial query to fill in missing usermeta with the current timestamp
+ $users = get_users(
+ array(
+ 'who' => 'authors',
+ 'meta_query' => array(
+ array(
+ 'key' => '_yoast_wpseo_profile_updated',
+ 'value' => 'needs-a-value-anyway', // This is ignored, but is necessary...
+ 'compare' => 'NOT EXISTS',
+ ),
+ )
+ )
+ );
+
+ if ( is_array( $users ) && $users !== array() ) {
+ foreach ( $users as $user ) {
+ update_user_meta( $user->ID, '_yoast_wpseo_profile_updated', time() );
+ }
+ }
+ unset( $users, $user );
+
+ // query for users with this meta
+ $users = get_users(
+ array(
+ 'who' => 'authors',
+ 'offset' => $offset,
+ 'number' => $steps,
+ 'meta_key' => '_yoast_wpseo_profile_updated',
+ 'orderby' => 'meta_value_num',
+ 'order' => 'ASC',
+ )
+ );
+
+ add_filter( 'wpseo_sitemap_exclude_author', array( $this, 'user_sitemap_remove_excluded_authors' ), 8 );
+
+ $users = apply_filters( 'wpseo_sitemap_exclude_author', $users );
+
+ // ascending sort
+ usort( $users, array( $this, 'user_map_sorter' ) );
+
+ if ( is_array( $users ) && $users !== array() ) {
+ foreach ( $users as $user ) {
+ $author_link = get_author_posts_url( $user->ID );
+ if ( $author_link !== '' ) {
+ $url = array(
+ 'loc' => $author_link,
+ 'pri' => 0.8,
+ 'chf' => $change_freq = $this->filter_frequency( 'author_archive', 'daily', $author_link ),
+ 'mod' => date( 'c', isset( $user->_yoast_wpseo_profile_updated ) ? $user->_yoast_wpseo_profile_updated : time() ),
+ );
+ // Use this filter to adjust the entry before it gets added to the sitemap
+ $url = apply_filters( 'wpseo_sitemap_entry', $url, 'user', $user );
+
+ if ( is_array( $url ) && $url !== array() ) {
+ $output .= $this->sitemap_url( $url );
+ }
+ }
+ }
+ unset( $user, $author_link );
+ }
+
+ if ( empty( $output ) ) {
+ $this->bad_sitemap = true;
+
+ return;
+ }
+
+ $this->sitemap = '<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" ';
+ $this->sitemap .= 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" ';
+ $this->sitemap .= 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
+ $this->sitemap .= $output;
+
+ // Filter to allow adding extra URLs, only do this on the first XML sitemap, not on all.
+ if ( $n === 1 ) {
+ $this->sitemap .= apply_filters( 'wpseo_sitemap_author_content', '' );
+ }
+
+ $this->sitemap .= '</urlset>';
+ }
+
+ /**
+ * Spits out the XSL for the XML sitemap.
+ *
+ * @param string $type
+ *
+ * @since 1.4.13
+ */
+ function xsl_output( $type ) {
+ if ( $type == 'main' ) {
+ header( $this->http_protocol() . ' 200 OK', true, 200 );
+ // Prevent the search engines from indexing the XML Sitemap.
+ header( 'X-Robots-Tag: noindex, follow', true );
+ header( 'Content-Type: text/xml' );
+
+ // Make the browser cache this file properly.
+ $expires = YEAR_IN_SECONDS;
+ header( 'Pragma: public' );
+ header( 'Cache-Control: maxage=' . $expires );
+ header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expires ) . ' GMT' );
+
+ require_once( WPSEO_PATH . 'css/xml-sitemap-xsl.php' );
+ } else {
+ do_action( 'wpseo_xsl_' . $type );
+ }
+ }
+
+ /**
+ * Spit out the generated sitemap and relevant headers and encoding information.
+ */
+ function output() {
+ header( $this->http_protocol() . ' 200 OK', true, 200 );
+ // Prevent the search engines from indexing the XML Sitemap.
+ header( 'X-Robots-Tag: noindex,follow', true );
+ header( 'Content-Type: text/xml' );
+ echo '<?xml version="1.0" encoding="' . esc_attr( $this->charset ) . '"?>';
+ if ( $this->stylesheet ) {
+ echo apply_filters( 'wpseo_stylesheet_url', $this->stylesheet ) . "\n";
+ }
+ echo $this->sitemap;
+ echo "\n" . '<!-- XML Sitemap generated by Yoast WordPress SEO -->';
+
+ $debug_display = defined( 'WP_DEBUG_DISPLAY' ) && true === WP_DEBUG_DISPLAY;
+ $debug = defined( 'WP_DEBUG' ) && true === WP_DEBUG;
+ $wpseo_debug = defined( 'WPSEO_DEBUG' ) && true === WPSEO_DEBUG;
+
+ if ( $debug_display && ( $debug || $wpseo_debug ) ) {
+ if ( $this->transient ) {
+ echo "\n" . '<!-- ' . number_format( ( memory_get_peak_usage() / 1024 / 1024 ), 2 ) . 'MB | Served from transient cache -->';
+ } else {
+ global $wpdb;
+ echo "\n" . '<!-- ' . number_format( ( memory_get_peak_usage() / 1024 / 1024 ), 2 ) . 'MB | ' . esc_attr( $wpdb->num_queries ) . ' -->';
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
+ echo "\n" . '<!--' . print_r( $wpdb->queries, true ) . '-->';
+ }
+ }
+ }
+ }
+
+ /**
+ * Build the <url> tag for a given URL.
+ *
+ * @param array $url Array of parts that make up this entry
+ *
+ * @return string
+ */
+ function sitemap_url( $url ) {
+
+ // Create a DateTime object date in the correct timezone
+ if ( isset( $url['mod'] ) ) {
+ $date = new DateTime( $url['mod'], new DateTimeZone( $this->get_timezone_string() ) );
+ } else {
+ $date = new DateTime( date( 'y-m-d H:i:s' ), new DateTimeZone( $this->get_timezone_string() ) );
+ }
+
+ $url['loc'] = htmlspecialchars( $url['loc'] );
+
+ $output = "\t<url>\n";
+ $output .= "\t\t<loc>" . $url['loc'] . "</loc>\n";
+ $output .= "\t\t<lastmod>" . $date->format( 'c' ) . "</lastmod>\n";
+ $output .= "\t\t<changefreq>" . $url['chf'] . "</changefreq>\n";
+ $output .= "\t\t<priority>" . str_replace( ',', '.', $url['pri'] ) . "</priority>\n";
+
+ if ( isset( $url['images'] ) && ( is_array( $url['images'] ) && $url['images'] !== array() ) ) {
+ foreach ( $url['images'] as $img ) {
+ if ( ! isset( $img['src'] ) || empty( $img['src'] ) ) {
+ continue;
+ }
+ $output .= "\t\t<image:image>\n";
+ $output .= "\t\t\t<image:loc>" . esc_html( $img['src'] ) . "</image:loc>\n";
+ if ( isset( $img['title'] ) && ! empty( $img['title'] ) ) {
+ $output .= "\t\t\t<image:title><![CDATA[" . _wp_specialchars( html_entity_decode( $img['title'], ENT_QUOTES, $this->charset ) ) . "]]></image:title>\n";
+ }
+ if ( isset( $img['alt'] ) && ! empty( $img['alt'] ) ) {
+ $output .= "\t\t\t<image:caption><![CDATA[" . _wp_specialchars( html_entity_decode( $img['alt'], ENT_QUOTES, $this->charset ) ) . "]]></image:caption>\n";
+ }
+ $output .= "\t\t</image:image>\n";
+ }
+ }
+ $output .= "\t</url>\n";
+
+ return $output;
+ }
+
+ /**
+ * Make a request for the sitemap index so as to cache it before the arrival of the search engines.
+ */
+ function hit_sitemap_index() {
+ $url = wpseo_xml_sitemaps_base_url( 'sitemap_index.xml' );
+ wp_remote_get( $url );
+ }
+
+ /**
+ * Hook into redirect_canonical to stop trailing slashes on sitemap.xml URLs
+ *
+ * @param string $redirect The redirect URL currently determined.
+ *
+ * @return bool|string $redirect
+ */
+ function canonical( $redirect ) {
+ $sitemap = get_query_var( 'sitemap' );
+ if ( ! empty( $sitemap ) ) {
+ return false;
+ }
+
+ $xsl = get_query_var( 'xsl' );
+ if ( ! empty( $xsl ) ) {
+ return false;
+ }
+
+ return $redirect;
+ }
+
+ /**
+ * Get the modification date for the last modified post in the post type:
+ *
+ * @param array $post_types Post types to get the last modification date for
+ *
+ * @return string
+ */
+ function get_last_modified( $post_types ) {
+ global $wpdb;
+ if ( ! is_array( $post_types ) ) {
+ $post_types = array( $post_types );
+ }
+
+ // We need to do this only once, as otherwise we'd be doing a query for each post type
+ if ( ! is_array( $this->post_type_dates ) ) {
+ $this->post_type_dates = array();
+ $query = "SELECT post_type, MAX(post_modified_gmt) AS date FROM $wpdb->posts WHERE post_status IN ('publish','inherit') AND post_type IN ('" . implode( "','", get_post_types( array( 'public' => true ) ) ) . "') GROUP BY post_type ORDER BY post_modified_gmt DESC";
+ $results = $wpdb->get_results( $query );
+ foreach ( $results as $obj ) {
+ $this->post_type_dates[ $obj->post_type ] = $obj->date;
+ }
+ unset( $results );
+ }
+
+ if ( count( $post_types ) === 1 && isset( $this->post_type_dates[ $post_types[0] ] ) ) {
+ $result = $this->post_type_dates[ $post_types[0] ];
+ } else {
+ $result = null;
+ foreach ( $post_types as $post_type ) {
+ if ( isset( $this->post_type_dates[ $post_type ] ) && strtotime( $this->post_type_dates[ $post_type ] ) > $result ) {
+ $result = $this->post_type_dates[ $post_type ];
+ }
+ }
+ }
+
+ $date = new DateTime( $result, new DateTimeZone( $this->get_timezone_string() ) );
+
+ return $date->format( 'c' );
+ }
+
+ /**
+ * Sorts an array of WP_User by the _yoast_wpseo_profile_updated meta field
+ *
+ * since 1.6
+ *
+ * @param Wp_User $a The first WP user
+ * @param Wp_User $b The second WP user
+ *
+ * @return int 0 if equal, 1 if $a is larger else or -1;
+ */
+ private function user_map_sorter( $a, $b ) {
+ if ( ! isset( $a->_yoast_wpseo_profile_updated ) ) {
+ $a->_yoast_wpseo_profile_updated = time();
+ }
+ if ( ! isset( $b->_yoast_wpseo_profile_updated ) ) {
+ $b->_yoast_wpseo_profile_updated = time();
+ }
+
+ if ( $a->_yoast_wpseo_profile_updated == $b->_yoast_wpseo_profile_updated ) {
+ return 0;
+ }
+
+ return ( $a->_yoast_wpseo_profile_updated > $b->_yoast_wpseo_profile_updated ) ? 1 : - 1;
+ }
+
+ /**
+ * Filter users that should be excluded from the sitemap (by author metatag: wpseo_excludeauthorsitemap).
+ *
+ * Also filtering users that should be exclude by excluded role.
+ *
+ * @param array $users
+ *
+ * @return array all the user that aren't excluded from the sitemap
+ */
+ public function user_sitemap_remove_excluded_authors( $users ) {
+
+ if ( is_array( $users ) && $users !== array() ) {
+ $options = get_option( 'wpseo_xml' );
+
+ foreach ( $users as $user_key => $user ) {
+ $exclude_user = false;
+
+ $is_exclude_on = get_the_author_meta( 'wpseo_excludeauthorsitemap', $user->ID );
+ if ( $is_exclude_on === 'on' ) {
+ $exclude_user = true;
+ } elseif ( $options['disable_author_noposts'] === true ) {
+ $count_posts = count_user_posts( $user->ID );
+ $exclude_user = $count_posts == 0;
+ } else {
+ $user_role = $user->roles[0];
+ $target_key = "user_role-{$user_role}-not_in_sitemap";
+ $exclude_user = $options[$target_key];
+ }
+
+ if ( $exclude_user === true ) {
+ unset( $users[$user_key] );
+ }
+ }
+ }
+
+ return $users;
+ }
+
+ /**
+ * Get attached image URL - Adapted from core for speed
+ *
+ * @param int $post_id
+ *
+ * @return string
+ */
+ private function image_url( $post_id ) {
+
+ static $uploads;
+
+ if ( empty( $uploads ) ) {
+ $uploads = wp_upload_dir();
+ }
+
+ if ( false !== $uploads['error'] ) {
+ return '';
+ }
+
+ $url = '';
+
+ if ( $file = get_post_meta( $post_id, '_wp_attached_file', true ) ) { //Get attached file
+ if ( 0 === strpos( $file, $uploads['basedir'] ) ) { //Check that the upload base exists in the file location
+ $url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
+ } //replace file location with url location
+ elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) {
+ $url = $uploads['baseurl'] . substr( $file, strpos( $file, 'wp-content/uploads' ) + 18 );
+ } else {
+ $url = $uploads['baseurl'] . "/$file";
+ } //Its a newly uploaded file, therefore $file is relative to the basedir.
+ }
+
+ return $url;
+ }
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Internals
+ */
+
+// Avoid direct calls to this file
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Meta' ) ) {
+ /**
+ * @package WordPress\Plugins\WPSeo
+ * @subpackage Internals
+ * @since 1.5.0
+ * @version 1.5.0
+ *
+ * This class implements defaults and value validation for all WPSEO Post Meta values.
+ *
+ * Some guidelines:
+ * - To update a meta value, you can just use update_post_meta() with the full (prefixed) meta key
+ * or the convenience method WPSEO_Meta::set_value() with the internal key.
+ * All updates will be automatically validated.
+ * Meta values will only be saved to the database if they are *not* the same as the default to
+ * keep database load low.
+ * - To retrieve a WPSEO meta value, you **must** use WPSEO_Meta::get_value() which will always return a
+ * string value, either the saved value or the default.
+ * This method can also retrieve a complete set of WPSEO meta values for one specific post, see
+ * the method documentation for the parameters.
+ *
+ * @internal Unfortunately there isn't a filter available to hook into before returning the results
+ * for get_post_meta(), get_post_custom() and the likes. That would have been the preferred solution.
+ *
+ * @internal all WP native get_meta() results get cached internally, so no need to cache locally.
+ * @internal use $key when the key is the WPSEO internal name (without prefix), $meta_key when it
+ * includes the prefix
+ */
+ class WPSEO_Meta {
+
+ /**
+ * @static
+ * @var string Prefix for all WPSEO meta values in the database
+ *
+ * @internal if at any point this would change, quite apart from an upgrade routine, this also will need to
+ * be changed in the wpml-config.xml file.
+ */
+ public static $meta_prefix = '_yoast_wpseo_';
+
+
+ /**
+ * @static
+ * @var string Prefix for all WPSEO meta value form field names and ids
+ */
+ public static $form_prefix = 'yoast_wpseo_';
+
+
+ /**
+ * @static
+ * @var int Allowed length of the meta description.
+ */
+ public static $meta_length = 156;
+
+
+ /**
+ * @static
+ * @var string Reason the meta description is not the default length.
+ */
+ public static $meta_length_reason = '';
+
+
+ /**
+ * @static
+ * @var array $meta_fields Meta box field definitions for the meta box form
+ *
+ * Array format:
+ * (required) 'type' => (string) field type. i.e. text / textarea / checkbox /
+ * radio / select / multiselect / upload / snippetpreview etc
+ * (required) 'title' => (string) table row title
+ * (recommended) 'default_value' => (string|array) default value for the field
+ * IMPORTANT:
+ * - if the field has options, the default has to be the
+ * key of one of the options
+ * - if the field is a text field, the default **has** to be
+ * an empty string as otherwise the user can't save
+ * an empty value/delete the meta value
+ * - if the field is a checkbox, the only valid values
+ * are 'on' or 'off'
+ * (semi-required) 'options' => (array) options for used with (multi-)select and radio
+ * fields, required if that's the field type
+ * key = (string) value which will be saved to db
+ * value = (string) text label for the option
+ * (optional) 'autocomplete' => (bool) whether autocomplete is on for text fields,
+ * defaults to true
+ * (optional) 'class' => (string) classname(s) to add to the actual <input> tag
+ * (optional) 'description' => (string) description to show underneath the field
+ * (optional) 'expl' => (string) label for a checkbox
+ * (optional) 'help' => (string) help text to show on mouse over ? image
+ * (optional) 'rows' => (int) number of rows for a textarea, defaults to 3
+ *
+ * (optional) 'placeholder' => (string) Currently only used by add-on plugins
+ * (optional) 'serialized' => (bool) whether the value is expected to be serialized,
+ * i.e. an array or object, defaults to false
+ * Currently only used by add-on plugins
+ *
+ * @internal
+ * - Titles, help texts, description text and option labels are added via a translate_meta_boxes() method
+ * in the relevant child classes (WPSEO_Metabox and WPSEO_Social_admin) as they are only needed there.
+ * - Beware: even though the meta keys are divided into subsets, they still have to be uniquely named!
+ */
+ public static $meta_fields = array(
+ 'general' => array(
+ 'snippetpreview' => array(
+ 'type' => 'snippetpreview',
+ 'title' => '', // translation added later
+ 'help' => '', // translation added later
+ ),
+ 'focuskw' => array(
+ 'type' => 'text',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'autocomplete' => false,
+ 'help' => '', // translation added later
+ 'description' => '<div id="focuskwresults"></div>',
+ ),
+ 'title' => array(
+ 'type' => 'text',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ 'help' => '', // translation added later
+ ),
+ 'metadesc' => array(
+ 'type' => 'textarea',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'class' => 'metadesc',
+ 'rows' => 2,
+ 'description' => '', // translation added later
+ 'help' => '', // translation added later
+ ),
+ 'metakeywords' => array(
+ 'type' => 'text',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'class' => 'metakeywords',
+ 'description' => '', // translation added later
+ ),
+ ),
+ 'advanced' => array(
+ 'meta-robots-noindex' => array(
+ 'type' => 'select',
+ 'title' => '', // translation added later
+ 'default_value' => '0', // = post-type default
+ 'options' => array(
+ '0' => '', // post type default - translation added later
+ '2' => '', // index - translation added later
+ '1' => '', // no-index - translation added later
+ ),
+ ),
+ 'meta-robots-nofollow' => array(
+ 'type' => 'radio',
+ 'title' => '', // translation added later
+ 'default_value' => '0', // = follow
+ 'options' => array(
+ '0' => '', // follow - translation added later
+ '1' => '', // no-follow - translation added later
+ ),
+ ),
+ 'meta-robots-adv' => array(
+ 'type' => 'multiselect',
+ 'title' => '', // translation added later
+ 'default_value' => '-', // = site-wide default
+ 'description' => '', // translation added later
+ 'options' => array(
+ '-' => '', // site-wide default - translation added later
+ 'none' => '', // translation added later
+ 'noodp' => '', // translation added later
+ 'noydir' => '', // translation added later
+ 'noimageindex' => '', // translation added later
+ 'noarchive' => '', // translation added later
+ 'nosnippet' => '', // translation added later
+ ),
+ ),
+ 'bctitle' => array(
+ 'type' => 'text',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+ 'sitemap-include' => array(
+ 'type' => 'select',
+ 'title' => '', // translation added later
+ 'default_value' => '-',
+ 'description' => '', // translation added later
+ 'options' => array(
+ '-' => '', // translation added later
+ 'always' => '', // translation added later
+ 'never' => '', // translation added later
+ ),
+ ),
+ 'sitemap-prio' => array(
+ 'type' => 'select',
+ 'title' => '', // translation added later
+ 'default_value' => '-',
+ 'description' => '', // translation added later
+ 'options' => array(
+ '-' => '', // translation added later
+ '1' => '', // translation added later
+ '0.9' => '0.9',
+ '0.8' => '0.8 - ', // translation added later
+ '0.7' => '0.7',
+ '0.6' => '0.6 - ', // translation added later
+ '0.5' => '0.5 - ', // translation added later
+ '0.4' => '0.4',
+ '0.3' => '0.3',
+ '0.2' => '0.2',
+ '0.1' => '0.1 - ', // translation added later
+ ),
+ ),
+ 'canonical' => array(
+ 'type' => 'text',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+ 'redirect' => array(
+ 'type' => 'text',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+ ),
+ 'social' => array(
+ 'opengraph-title' => array(
+ 'type' => 'text',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+ 'opengraph-description' => array(
+ 'type' => 'textarea',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+ 'opengraph-image' => array(
+ 'type' => 'upload',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+ 'google-plus-title' => array(
+ 'type' => 'text',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+ 'google-plus-description' => array(
+ 'type' => 'textarea',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+ 'google-plus-image' => array(
+ 'type' => 'upload',
+ 'title' => '', // translation added later
+ 'default_value' => '',
+ 'description' => '', // translation added later
+ ),
+
+ ),
+
+ /* Fields we should validate & save, but not show on any form */
+ 'non_form' => array(
+ 'linkdex' => array(
+ 'type' => null,
+ 'default_value' => '0',
+ ),
+ ),
+ );
+
+
+ /**
+ * @static
+ * @var array Helper property - reverse index of the definition array
+ * Format: [full meta key including prefix] => array
+ * ['subset'] => (string) primary index
+ * ['key'] => (string) internal key
+ */
+ public static $fields_index = array();
+
+
+ /**
+ * @static
+ * @var array Helper property - array containing only the defaults in the format:
+ * [full meta key including prefix] => (string) default value
+ */
+ public static $defaults = array();
+
+
+ /**
+ * Register our actions and filters
+ *
+ * @static
+ * @return void
+ */
+ public static function init() {
+ /**
+ * Allow add-on plugins to register their meta fields for management by this class
+ * add_filter() calls must be made before plugins_loaded prio 14
+ */
+ $extra_fields = apply_filters( 'add_extra_wpseo_meta_fields', array() );
+ if ( is_array( $extra_fields ) ) {
+ self::$meta_fields = self::array_merge_recursive_distinct( $extra_fields, self::$meta_fields );
+ }
+
+ $register = function_exists( 'register_meta' );
+
+ foreach ( self::$meta_fields as $subset => $field_group ) {
+ foreach ( $field_group as $key => $field_def ) {
+ if ( $field_def['type'] !== 'snippetpreview' ) {
+ /* register_meta() is undocumented and not used by WP internally, wrapped in
+ function_exists as a precaution in case they remove it. */
+ if ( $register === true ) {
+ register_meta( 'post', self::$meta_prefix . $key, array( __CLASS__, 'sanitize_post_meta' ) );
+ }
+ else {
+ add_filter( 'sanitize_post_meta_' . self::$meta_prefix . $key, array( __CLASS__, 'sanitize_post_meta' ), 10, 2 );
+ }
+
+ // Set the $fields_index property for efficiency
+ self::$fields_index[ self::$meta_prefix . $key ] = array(
+ 'subset' => $subset,
+ 'key' => $key,
+ );
+
+ // Set the $defaults property for efficiency
+ if ( isset( $field_def['default_value'] ) ) {
+ self::$defaults[ self::$meta_prefix . $key ] = $field_def['default_value'];
+ }
+ else {
+ // meta will always be a string, so let's make the meta meta default also a string
+ self::$defaults[ self::$meta_prefix . $key ] = '';
+ }
+ }
+ }
+ }
+
+ add_filter( 'update_post_metadata', array( __CLASS__, 'remove_meta_if_default' ), 10, 5 );
+ add_filter( 'add_post_metadata', array( __CLASS__, 'dont_save_meta_if_default' ), 10, 4 );
+ }
+
+
+ /**
+ * Retrieve the meta box form field definitions for the given tab and post type.
+ *
+ * @static
+ *
+ * @param string $tab Tab for which to retrieve the field definitions
+ * @param string $post_type Post type of the current post
+ * @return array Array containing the meta box field definitions
+ */
+ public static function get_meta_field_defs( $tab, $post_type = 'post' ) {
+ if ( ! isset( self::$meta_fields[ $tab ] ) ) {
+ return array();
+ }
+
+ $field_defs = self::$meta_fields[ $tab ];
+
+ switch ( $tab ) {
+ case 'non-form':
+ // prevent non-form fields from being passed to forms
+ $field_defs = array();
+ break;
+
+
+ case 'general':
+ $options = get_option( 'wpseo_titles' );
+ if ( $options['usemetakeywords'] === true ) {
+ /* Adjust the link in the keywords description text string based on the post type */
+ $field_defs['metakeywords']['description'] = sprintf( $field_defs['metakeywords']['description'], '<a target="_blank" href="' . esc_url( admin_url( 'admin.php?page=wpseo_titles#' . urlencode( $post_type ) ) ) . '">', '</a>' );
+ }
+ else {
+ /* Don't show the keywords field if keywords aren't enabled */
+ unset( $field_defs['metakeywords'] );
+ }
+ /**
+ * Filter the WPSEO metabox form field definitions for the general tab, backward compatibility
+ *
+ * @deprecated 1.5.0
+ * @deprecated use the 'wpseo_metabox_entries_general' filter instead
+ * @see WPSEO_Meta::get_meta_field_defs()
+ *
+ * @param array $field_defs metabox form definitions
+ * @return array
+ */
+ $field_defs = apply_filters( 'wpseo_metabox_entries', $field_defs );
+ break;
+
+
+ case 'advanced':
+ global $post;
+
+ $options = WPSEO_Options::get_all();
+
+ $post_type = '';
+ if ( isset( $post->post_type ) ) {
+ $post_type = $post->post_type;
+ }
+ elseif ( ! isset( $post->post_type ) && isset( $_GET['post_type'] ) ) {
+ $post_type = sanitize_text_field( $_GET['post_type'] );
+ }
+
+ /* Adjust the no-index 'default for post type' text string based on the post type */
+ $field_defs['meta-robots-noindex']['options']['0'] = sprintf( $field_defs['meta-robots-noindex']['options']['0'], ( ( isset( $options[ 'noindex-' . $post_type ] ) && $options[ 'noindex-' . $post_type ] === true ) ? 'noindex' : 'index' ) );
+
+ /* Adjust the robots advanced 'site-wide default' text string based on those settings */
+ if ( $options['noodp'] !== false || $options['noydir'] !== false ) {
+ $robots_adv = array();
+ foreach ( array( 'noodp', 'noydir' ) as $robot ) {
+ if ( $options[ $robot ] === true ) {
+ // use translation from field def options - mind that $options and $field_def['options'] keys should be the same!
+ $robots_adv[] = $field_defs['meta-robots-adv']['options'][ $robot ];
+ }
+ }
+ $robots_adv = implode( ', ', $robots_adv );
+ }
+ else {
+ $robots_adv = __( 'None', 'wordpress-seo' );
+ }
+ $field_defs['meta-robots-adv']['options']['-'] = sprintf( $field_defs['meta-robots-adv']['options']['-'], $robots_adv );
+ unset( $robots_adv );
+
+
+ /* Don't show the breadcrumb title field if breadcrumbs aren't enabled */
+ if ( $options['breadcrumbs-enable'] !== true ) {
+ unset( $field_defs['bctitle'] );
+ }
+
+ /* Don't show the xml sitemap fields, if xml sitemaps aren't enabled */
+ if ( $options['enablexmlsitemap'] !== true ) {
+ unset(
+ $field_defs['sitemap-include'],
+ $field_defs['sitemap-prio']
+ );
+ }
+
+ break;
+ }
+
+ /**
+ * Filter the WPSEO metabox form field definitions for a tab
+ * {tab} can be 'general', 'advanced' or 'social'
+ *
+ * @param array $field_defs metabox form definitions
+ * @param string $post_type post type of the post the metabox is for, defaults to 'post'
+ * @return array
+ */
+ return apply_filters( 'wpseo_metabox_entries_' . $tab, $field_defs, $post_type );
+ }
+
+
+ /**
+ * Validate the post meta values
+ *
+ * @static
+ *
+ * @param mixed $meta_value The new value
+ * @param string $meta_key The full meta key (including prefix)
+ * @return string Validated meta value
+ */
+ public static function sanitize_post_meta( $meta_value, $meta_key ) {
+ $field_def = self::$meta_fields[ self::$fields_index[ $meta_key ]['subset'] ][ self::$fields_index[ $meta_key ]['key'] ];
+ $clean = self::$defaults[ $meta_key ];
+
+ switch ( true ) {
+ case ( $meta_key === self::$meta_prefix . 'linkdex' ):
+ $int = WPSEO_Option::validate_int( $meta_value );
+ if ( $int !== false && $int >= 0 ) {
+ $clean = strval( $int ); // Convert to string to make sure default check works
+ }
+ break;
+
+
+ case ( $field_def['type'] === 'checkbox' ):
+ // Only allow value if it's one of the predefined options
+ if ( in_array( $meta_value, array( 'on', 'off' ), true ) ) {
+ $clean = $meta_value;
+ }
+ break;
+
+
+ case ( $field_def['type'] === 'select' || $field_def['type'] === 'radio' ):
+ // Only allow value if it's one of the predefined options
+ if ( isset( $field_def['options'][ $meta_value ] ) ) {
+ $clean = $meta_value;
+ }
+ break;
+
+
+ case ( $field_def['type'] === 'multiselect' && $meta_key === self::$meta_prefix . 'meta-robots-adv' ):
+ $clean = self::validate_meta_robots_adv( $meta_value );
+ break;
+
+
+ case ( $field_def['type'] === 'text' && $meta_key === self::$meta_prefix . 'canonical' ):
+ case ( $field_def['type'] === 'text' && $meta_key === self::$meta_prefix . 'redirect' ):
+ // Validate as url(-part)
+ $url = WPSEO_Option::sanitize_url( $meta_value );
+ if ( $url !== '' ) {
+ $clean = $url;
+ }
+ break;
+
+
+ case ( $field_def['type'] === 'upload' && $meta_key === self::$meta_prefix . 'opengraph-image' ):
+ // Validate as url
+ $url = WPSEO_Option::sanitize_url( $meta_value, array( 'http', 'https', 'ftp', 'ftps' ) );
+ if ( $url !== '' ) {
+ $clean = $url;
+ }
+ break;
+
+
+ case ( $field_def['type'] === 'textarea' ):
+ if ( is_string( $meta_value ) ) {
+ // Remove line breaks and tabs
+ // @todo [JRF => Yoast] verify that line breaks and the likes aren't allowed/recommended in meta header fields
+ $meta_value = str_replace( array( "\n", "\r", "\t", ' ' ), ' ', $meta_value );
+ $clean = WPSEO_Option::sanitize_text_field( trim( $meta_value ) );
+ }
+ break;
+
+ case ( 'multiselect' === $field_def['type'] ) :
+ $clean = $meta_value;
+ break;
+
+
+ case ( $field_def['type'] === 'text' ):
+ default:
+ if ( is_string( $meta_value ) ) {
+ $clean = WPSEO_Option::sanitize_text_field( trim( $meta_value ) );
+ }
+ break;
+ }
+
+ $clean = apply_filters( 'wpseo_sanitize_post_meta_' . $meta_key, $clean, $meta_value, $field_def, $meta_key );
+
+ return $clean;
+ }
+
+
+ /**
+ * Validate a meta-robots-adv meta value
+ *
+ * @todo [JRF => Yoast] Verify that this logic for the prioritisation is correct
+ *
+ * @static
+ *
+ * @param array|string $meta_value The value to validate
+ * @return string Clean value
+ */
+ public static function validate_meta_robots_adv( $meta_value ) {
+ $clean = self::$meta_fields['advanced']['meta-robots-adv']['default_value'];
+ $options = self::$meta_fields['advanced']['meta-robots-adv']['options'];
+
+ if ( is_string( $meta_value ) ) {
+ $meta_value = explode( ',', $meta_value );
+ }
+
+ if ( is_array( $meta_value ) && $meta_value !== array() ) {
+ $meta_value = array_map( 'trim', $meta_value );
+
+ if ( in_array( 'none', $meta_value, true ) ) {
+ // None is one of the selected values, takes priority over everything else
+ $clean = 'none';
+ }
+ elseif ( in_array( '-', $meta_value, true ) ) {
+ // Site-wide defaults is one of the selected values, takes priority over
+ // individual selected entries
+ $clean = '-';
+ }
+ else {
+ // Individual selected entries
+ $cleaning = array();
+ foreach ( $meta_value as $value ) {
+ if ( isset( $options[ $value ] ) ) {
+ $cleaning[] = $value;
+ }
+ }
+
+ if ( $cleaning !== array() ) {
+ $clean = implode( ',', $cleaning );
+ }
+ unset( $cleaning, $value );
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Prevent saving of default values and remove potential old value from the database if replaced by a default
+ *
+ * @static
+ *
+ * @param null $null old, disregard
+ * @param int $object_id ID of the current object for which the meta is being updated
+ * @param string $meta_key The full meta key (including prefix)
+ * @param string $meta_value New meta value
+ * @param string $prev_value The old meta value
+ * @return null|bool true = stop saving, null = continue saving
+ */
+ public static function remove_meta_if_default( $null, $object_id, $meta_key, $meta_value, $prev_value = '' ) {
+ /* If it's one of our meta fields, check against default */
+ if ( isset( self::$fields_index[ $meta_key ] ) && self::meta_value_is_default( $meta_key, $meta_value ) === true ) {
+ if ( $prev_value !== '' ) {
+ delete_post_meta( $object_id, $meta_key, $prev_value );
+ }
+ else {
+ delete_post_meta( $object_id, $meta_key );
+ }
+ return true; // stop saving the value
+ }
+
+ return null; // go on with the normal execution (update) in meta.php
+ }
+
+
+ /**
+ * Prevent adding of default values to the database
+ *
+ * @static
+ *
+ * @param null $null old, disregard
+ * @param int $object_id ID of the current object for which the meta is being added
+ * @param string $meta_key The full meta key (including prefix)
+ * @param string $meta_value New meta value
+ * @return null|bool true = stop saving, null = continue saving
+ */
+ public static function dont_save_meta_if_default( $null, $object_id, $meta_key, $meta_value ) {
+ /* If it's one of our meta fields, check against default */
+ if ( isset( self::$fields_index[ $meta_key ] ) && self::meta_value_is_default( $meta_key, $meta_value ) === true ) {
+ return true; // stop saving the value
+ }
+
+ return null; // go on with the normal execution (add) in meta.php
+ }
+
+
+ /**
+ * Is the given meta value the same as the default value ?
+ *
+ * @static
+ *
+ * @param string $meta_key The full meta key (including prefix)
+ * @param mixed $meta_value The value to check
+ * @return bool
+ */
+ public static function meta_value_is_default( $meta_key, $meta_value ) {
+ return ( isset( self::$defaults[ $meta_key ] ) && $meta_value === self::$defaults[ $meta_key ] );
+ }
+
+
+ /**
+ * Get a custom post meta value
+ * Returns the default value if the meta value has not been set
+ *
+ * @internal Unfortunately there isn't a filter available to hook into before returning the results
+ * for get_post_meta(), get_post_custom() and the likes. That would have been the preferred solution.
+ *
+ * @static
+ *
+ * @param string $key internal key of the value to get (without prefix)
+ * @param int $postid post ID of the post to get the value for
+ * @return string All 'normal' values returned from get_post_meta() are strings.
+ * Objects and arrays are possible, but not used by this plugin
+ * and therefore discarted (except when the special 'serialized' field def
+ * value is set to true - only used by add-on plugins for now)
+ * Will return the default value if no value was found.
+ * Will return empty string if no default was found (not one of our keys) or
+ * if the post does not exist
+ */
+ public static function get_value( $key, $postid = 0 ) {
+ global $post;
+
+ $postid = absint( $postid );
+ if ( $postid === 0 ) {
+ if ( ( isset( $post ) && is_object( $post ) ) && ( isset( $post->post_status ) && $post->post_status !== 'auto-draft' ) ){
+ $postid = $post->ID;
+ }
+ else {
+ return '';
+ }
+ }
+
+ $custom = get_post_custom( $postid ); // array of strings or empty array
+
+ if ( isset( $custom[ self::$meta_prefix . $key ][0] ) ) {
+ $unserialized = maybe_unserialize( $custom[ self::$meta_prefix . $key ][0] );
+ if ( $custom[ self::$meta_prefix . $key ][0] === $unserialized ) {
+ return $custom[ self::$meta_prefix . $key ][0];
+ }
+ else {
+ $field_def = self::$meta_fields[ self::$fields_index[ self::$meta_prefix . $key ]['subset'] ][ self::$fields_index[ self::$meta_prefix . $key ]['key'] ];
+ if ( isset( $field_def['serialized'] ) && $field_def['serialized'] === true ) {
+ // Ok, serialize value expected/allowed
+ return $unserialized;
+ }
+ }
+ }
+
+ // Meta was either not found or found, but object/array while not allowed to be
+ if ( isset( self::$defaults[ self::$meta_prefix . $key ] ) ) {
+ return self::$defaults[ self::$meta_prefix . $key ];
+ }
+ else {
+ /* Shouldn't ever happen, means not one of our keys as there will always be a default available
+ for all our keys */
+ return '';
+ }
+ }
+
+
+ /**
+ * Update a meta value for a post
+ *
+ * @static
+ *
+ * @param string $key the internal key of the meta value to change (without prefix)
+ * @param mixed $meta_value the value to set the meta to
+ * @param int $post_id the ID of the post to change the meta for.
+ * @return bool whether the value was changed
+ */
+ public static function set_value( $key, $meta_value, $post_id ) {
+ return update_post_meta( $post_id, self::$meta_prefix . $key, $meta_value );
+ }
+
+
+ /**
+ * Used for imports, this functions imports the value of $old_metakey into $new_metakey for those post
+ * where no WPSEO meta data has been set.
+ * Optionally deletes the $old_metakey values.
+ *
+ * @static
+ *
+ * @param string $old_metakey The old key of the meta value.
+ * @param string $new_metakey The new key, usually the WPSEO meta key (including prefix).
+ * @param bool $delete_old Whether to delete the old meta key/value-sets.
+ * @return void
+ */
+ public static function replace_meta( $old_metakey, $new_metakey, $delete_old = false ) {
+ global $wpdb;
+
+ /* Get only those rows where no wpseo meta values exist for the same post
+ (with the exception of linkdex as that will be set independently of whether the post has been edited)
+ @internal Query is pretty well optimized this way */
+ $query = $wpdb->prepare(
+ "
+ SELECT `a`.*
+ FROM {$wpdb->postmeta} AS a
+ WHERE `a`.`meta_key` = %s
+ AND NOT EXISTS (
+ SELECT DISTINCT `post_id` , count( `meta_id` ) AS count
+ FROM {$wpdb->postmeta} AS b
+ WHERE `a`.`post_id` = `b`.`post_id`
+ AND `meta_key` LIKE %s
+ AND `meta_key` <> %s
+ GROUP BY `post_id`
+ )
+ ;",
+ $old_metakey,
+ like_escape( self::$meta_prefix . '%' ),
+ self::$meta_prefix . 'linkdex'
+ );
+ $oldies = $wpdb->get_results( $query );
+
+ if ( is_array( $oldies ) && $oldies !== array() ) {
+ foreach ( $oldies as $old ) {
+ update_post_meta( $old->post_id, $new_metakey, $old->meta_value );
+ }
+ }
+
+ // Delete old keys
+ if ( $delete_old === true ) {
+ delete_post_meta_by_key( $old_metakey );
+ }
+ }
+
+
+ /**
+ * General clean-up of the saved meta values
+ * - Remove potentially lingering old meta keys
+ * - Remove all default and invalid values
+ *
+ * @static
+ * @return void
+ */
+ public static function clean_up() {
+ global $wpdb;
+
+ /**
+ * Clean up '_yoast_wpseo_meta-robots'
+ *
+ * Retrieve all '_yoast_wpseo_meta-robots' meta values and convert if no new values found
+ * @internal Query is pretty well optimized this way
+ *
+ * @todo [JRF => Yoast] find out all possible values which the old '_yoast_wpseo_meta-robots' could contain
+ * to convert the data correctly
+ */
+ $query = $wpdb->prepare(
+ "
+ SELECT `a`.*
+ FROM {$wpdb->postmeta} AS a
+ WHERE `a`.`meta_key` = %s
+ AND NOT EXISTS (
+ SELECT DISTINCT `post_id` , count( `meta_id` ) AS count
+ FROM {$wpdb->postmeta} AS b
+ WHERE `a`.`post_id` = `b`.`post_id`
+ AND ( `meta_key` = %s
+ OR `meta_key` = %s )
+ GROUP BY `post_id`
+ )
+ ;",
+ self::$meta_prefix . 'meta-robots',
+ self::$meta_prefix . 'meta-robots-noindex',
+ self::$meta_prefix . 'meta-robots-nofollow'
+ );
+ $oldies = $wpdb->get_results( $query );
+
+ if ( is_array( $oldies ) && $oldies !== array() ) {
+ foreach ( $oldies as $old ) {
+ $old_values = explode( ',', $old->meta_value );
+ foreach ( $old_values as $value ) {
+ if ( $value === 'noindex' ) {
+ update_post_meta( $old->post_id, self::$meta_prefix . 'meta-robots-noindex', 1 );
+ }
+ elseif ( $value === 'nofollow' ) {
+ update_post_meta( $old->post_id, self::$meta_prefix . 'meta-robots-nofollow', 1 );
+ }
+ }
+ }
+ }
+ unset( $query, $oldies, $old, $old_values, $value );
+
+ // Delete old keys
+ delete_post_meta_by_key( self::$meta_prefix . 'meta-robots' );
+
+
+ /**
+ * Remove all default values and (most) invalid option values
+ * Invalid option values for the multiselect (meta-robots-adv) field will be dealt with seperately
+ *
+ * @internal some of the defaults have changed in v1.5, but as the defaults will be removed and
+ * new defaults will now automatically be passed when no data found, this update is automatic
+ * (as long as we remove the old values which we do in the below routine)
+ *
+ * @internal unfortunately we can't use the normal delete_meta() with key/value combination as ''
+ * (empty string) values will be ignored and would result in all metas with that key being deleted,
+ * not just the empty fields.
+ * Still, the below implementation is largely based on the delete_meta() function
+ */
+ $query = array();
+
+ foreach ( self::$meta_fields as $subset => $field_group ) {
+ foreach ( $field_group as $key => $field_def ) {
+ if ( $field_def['type'] === 'snippetpreview' || ! isset( $field_def['default_value'] ) ) {
+ continue;
+ }
+
+ if ( $key === 'meta-robots-adv' ) {
+ $query[] = $wpdb->prepare(
+ "( meta_key = %s AND ( meta_value = 'none' OR meta_value = '-' ) )",
+ self::$meta_prefix . $key
+ );
+ }
+ elseif ( isset( $field_def['options'] ) && is_array( $field_def['options'] ) && $field_def['options'] !== array() ) {
+ $valid = $field_def['options'];
+ // remove the default value from the valid options
+ unset( $valid[ $field_def['default_value'] ] );
+ $valid = array_keys( $valid );
+
+ $query[] = $wpdb->prepare(
+ "( meta_key = %s AND meta_value NOT IN ( '" . implode( "','", esc_sql( $valid ) ) . "' ) )",
+ self::$meta_prefix . $key
+ );
+ unset( $valid );
+ }
+ elseif ( is_string( $field_def['default_value'] ) && $field_def['default_value'] !== '' ) {
+ $query[] = $wpdb->prepare(
+ '( meta_key = %s AND meta_value = %s )',
+ self::$meta_prefix . $key,
+ $field_def['default_value']
+ );
+ }
+ else {
+ $query[] = $wpdb->prepare(
+ "( meta_key = %s AND meta_value = '' )",
+ self::$meta_prefix . $key
+ );
+ }
+ }
+ }
+ unset( $subset, $field_group, $key, $field_def, $where_or_or );
+
+ $query = "SELECT meta_id FROM {$wpdb->postmeta} WHERE " . implode( ' OR ', $query ) . ';';
+ $meta_ids = $wpdb->get_col( $query );
+
+ if ( is_array( $meta_ids ) && $meta_ids !== array() ) {
+ // wp native action
+ do_action( 'delete_post_meta', $meta_ids, null, null, null );
+
+ $query = "DELETE FROM {$wpdb->postmeta} WHERE meta_id IN( " . implode( ',', $meta_ids ) . ' )';
+ $count = $wpdb->query( $query );
+
+ if ( $count ) {
+ foreach ( $meta_ids as $object_id ) {
+ wp_cache_delete( $object_id, 'post_meta' );
+ }
+
+ // wp native action
+ do_action( 'deleted_post_meta', $meta_ids, null, null, null );
+ }
+ }
+ unset( $query, $meta_ids, $count, $object_id );
+
+
+ /**
+ * Deal with the multiselect (meta-robots-adv) field
+ *
+ * Removes invalid option combinations, such as 'none,noarchive'
+ *
+ * Default values have already been removed, so we should have a small result set and
+ * (hopefully) even smaller set of invalid results.
+ */
+ $query = $wpdb->prepare(
+ "SELECT meta_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s",
+ self::$meta_prefix . 'meta-robots-adv'
+ );
+ $oldies = $wpdb->get_results( $query );
+
+ if ( is_array( $oldies ) && $oldies !== array() ) {
+ foreach ( $oldies as $old ) {
+ $clean = self::validate_meta_robots_adv( $old->meta_value );
+
+ if ( $clean !== $old->meta_value ) {
+ if ( $clean !== self::$meta_fields['advanced']['meta-robots-adv']['default_value'] ) {
+ update_metadata_by_mid( 'post', $old->meta_id, $clean );
+ }
+ else {
+ delete_metadata_by_mid( 'post', $old->meta_id );
+ }
+ }
+ }
+ }
+ unset( $query, $oldies, $old, $clean );
+
+ do_action( 'wpseo_meta_clean_up' );
+ }
+
+
+ /**
+ * Recursively merge a variable number of arrays, using the left array as base,
+ * giving priority to the right array.
+ *
+ * Difference with native array_merge_recursive():
+ * array_merge_recursive converts values with duplicate keys to arrays rather than
+ * overwriting the value in the first array with the duplicate value in the second array.
+ *
+ * array_merge_recursive_distinct does not change the data types of the values in the arrays.
+ * Matching keys' values in the second array overwrite those in the first array, as is the
+ * case with array_merge.
+ *
+ * Freely based on information found on http://www.php.net/manual/en/function.array-merge-recursive.php
+ *
+ * @internal Should be moved to a general utility class
+ *
+ * @param array 2 or more arrays to merge
+ * @return array
+ */
+ public static function array_merge_recursive_distinct() {
+
+ $arrays = func_get_args();
+ if ( count( $arrays ) < 2 ) {
+ if ( $arrays === array() ) {
+ return array();
+ }
+ else {
+ return $arrays[0];
+ }
+ }
+
+ $merged = array_shift( $arrays );
+
+ foreach ( $arrays as $array ) {
+ foreach ( $array as $key => $value ) {
+ if ( is_array( $value ) && ( isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) ) {
+ $merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value );
+ }
+ else {
+ $merged[ $key ] = $value;
+ }
+ }
+ unset( $key, $value );
+ }
+ return $merged;
+ }
+
+
+ } /* End of class */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Internals
+ */
+
+// Avoid direct calls to this file
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Option' ) ) {
+ /**
+ * @package WordPress\Plugins\WPSeo
+ * @subpackage Internals
+ * @since 1.5.0
+ * @version 1.5.0
+ *
+ * This abstract class and it's concrete classes implement defaults and value validation for
+ * all WPSEO options and subkeys within options.
+ *
+ * Some guidelines:
+ * [Retrieving options]
+ * - Use the normal get_option() to retrieve an option. You will receive a complete array for the option.
+ * Any subkeys which were not set, will have their default values in place.
+ * - In other words, you will normally not have to check whether a subkey isset() as they will *always* be set.
+ * They will also *always* be of the correct variable type.
+ * The only exception to this are the options with variable option names based on post_type or taxonomy
+ * as those will not always be available before the taxonomy/post_type is registered.
+ * (they will be available if a value was set, they won't be if it wasn't as the class won't know
+ * that a default needs to be injected).
+ * Oh and the very few options where the default value is null, i.e. wpseo->'theme_has_description'
+ *
+ * [Updating/Adding options]
+ * - For multisite site_options, please use the WPSEO_Options::update_site_option() method.
+ * - For normal options, use the normal add/update_option() functions. As long a the classes here
+ * are instantiated, validation for all options and their subkeys will be automatic.
+ * - On (succesfull) update of a couple of options, certain related actions will be run automatically.
+ * Some examples:
+ * - on change of wpseo[yoast_tracking], the cron schedule will be adjusted accordingly
+ * - on change of wpseo_permalinks and wpseo_xml, the rewrite rules will be flushed
+ * - on change of wpseo and wpseo_title, some caches will be cleared
+ *
+ *
+ * [Important information about add/updating/changing these classes]
+ * - Make sure that option array key names are unique across options. The WPSEO_Options::get_all()
+ * method merges most options together. If any of them have non-unique names, even if they
+ * are in a different option, they *will* overwrite each other.
+ * - When you add a new array key in an option: make sure you add proper defaults and add the key
+ * to the validation routine in the proper place or add a new validation case.
+ * You don't need to do any upgrading as any option returned will always be merged with the
+ * defaults, so new options will automatically be available.
+ * If the default value is a string which need translating, add this to the concrete class
+ * translate_defaults() method.
+ * - When you remove an array key from an option: if it's important that the option is really removed,
+ * add the WPSEO_Option::clean_up( $option_name ) method to the upgrade run.
+ * This will re-save the option and automatically remove the array key no longer in existance.
+ * - When you rename a sub-option: add it to the clean_option() routine and run that in the upgrade run.
+ * - When you change the default for an option sub-key, make sure you verify that the validation routine will
+ * still work the way it should.
+ * Example: changing a default from '' (empty string) to 'text' with a validation routine with tests
+ * for an empty string will prevent a user from saving an empty string as the real value. So the
+ * test for '' with the validation routine would have to be removed in that case.
+ * - If an option needs specific actions different from defined in this abstract class, you can just overrule
+ * a method by defining it in the concrete class.
+ *
+ *
+ * @todo - [JRF => testers] double check that validation will not cause errors when called
+ * from upgrade routine (some of the WP functions may not yet be available)
+ */
+ abstract class WPSEO_Option {
+
+ /**
+ * @var string Option name - MUST be set in concrete class and set to public.
+ */
+ protected $option_name;
+
+ /**
+ * @var string Option group name for use in settings forms
+ * - will be set automagically if not set in concrete class
+ * (i.e. if it confirm to the normal pattern 'yoast' . $option_name . 'options',
+ * only set in conrete class if it doesn't)
+ */
+ public $group_name;
+
+ /**
+ * @var bool Whether to include the option in the return for WPSEO_Options::get_all().
+ * Also determines which options are copied over for ms_(re)set_blog().
+ */
+ public $include_in_all = true;
+
+ /**
+ * @var bool Whether this option is only for when the install is multisite.
+ */
+ public $multisite_only = false;
+
+ /**
+ * @var array Array of defaults for the option - MUST be set in concrete class.
+ * Shouldn't be requested directly, use $this->get_defaults();
+ */
+ protected $defaults;
+
+ /**
+ * @var array Array of variable option name patterns for the option - if any -
+ * Set this when the option contains array keys which vary based on post_type
+ * or taxonomy
+ */
+ protected $variable_array_key_patterns;
+
+ /**
+ * @var array Array of sub-options which should not be overloaded with multi-site defaults
+ */
+ public $ms_exclude = array();
+
+ /**
+ * @var object Instance of this class
+ */
+ protected static $instance;
+
+ /**
+ *
+ * @var bool Whether the filter extension is loaded
+ */
+ public static $has_filters = true;
+
+
+ /* *********** INSTANTIATION METHODS *********** */
+
+ /**
+ * Add all the actions and filters for the option
+ *
+ * @return \WPSEO_Option
+ */
+ protected function __construct() {
+
+ self::$has_filters = extension_loaded( 'filter' );
+
+ /* Add filters which get applied to the get_options() results */
+ $this->add_default_filters(); // return defaults if option not set
+ $this->add_option_filters(); // merge with defaults if option *is* set
+
+
+ if ( $this->multisite_only !== true ) {
+ /* The option validation routines remove the default filters to prevent failing
+ to insert an option if it's new. Let's add them back afterwards. */
+ add_action( 'add_option', array( $this, 'add_default_filters' ) ); // adding back after INSERT
+
+ if ( version_compare( $GLOBALS['wp_version'], '3.7', '!=' ) ) { // adding back after non-WP 3.7 UPDATE
+ add_action( 'update_option', array( $this, 'add_default_filters' ) );
+ } else { // adding back after WP 3.7 UPDATE
+ add_filter( 'pre_update_option_' . $this->option_name, array( $this, 'wp37_add_default_filters' ) );
+ }
+ }
+ else if ( is_multisite() ) {
+ /* The option validation routines remove the default filters to prevent failing
+ to insert an option if it's new. Let's add them back afterwards.
+
+ For site_options, this method is not foolproof as these actions are not fired
+ on an insert/update failure. Please use the WPSEO_Options::update_site_option() method
+ for updating site options to make sure the filters are in place. */
+ add_action( 'add_site_option_' . $this->option_name, array( $this, 'add_default_filters' ) );
+ add_action( 'update_site_option_' . $this->option_name, array( $this, 'add_default_filters' ) );
+ }
+
+
+
+ /* Make sure the option will always get validated, independently of register_setting()
+ (only available on back-end) */
+ add_filter( 'sanitize_option_' . $this->option_name, array( $this, 'validate' ) );
+
+ /* Register our option for the admin pages */
+ add_action( 'admin_init', array( $this, 'register_setting' ) );
+
+
+ /* Set option group name if not given */
+ if ( ! isset( $this->group_name ) || $this->group_name === '' ) {
+ $this->group_name = 'yoast_' . $this->option_name . '_options';
+ }
+
+ /* Translate some defaults as early as possible - textdomain is loaded in init on priority 1 */
+ if ( method_exists( $this, 'translate_defaults' ) ) {
+ add_action( 'init', array( $this, 'translate_defaults' ), 2 );
+ }
+
+ /**
+ * Enrich defaults once custom post types and taxonomies have been registered
+ * which is normally done on the init action.
+ *
+ * @todo - [JRF/testers] verify that none of the options which are only available after
+ * enrichment are used before the enriching
+ */
+ if ( method_exists( $this, 'enrich_defaults' ) ) {
+ add_action( 'init', array( $this, 'enrich_defaults' ), 99 );
+ }
+ }
+
+
+ /**
+ * All concrete classes *must* contain the get_instance method
+ * @internal Unfortunately I can't define it as an abstract as it also *has* to be static....
+ */
+ //abstract protected static function get_instance();
+
+
+ /**
+ * Concrete classes *may* contain a translate_defaults method
+ */
+ //abstract public function translate_defaults();
+
+
+ /**
+ * Concrete classes *may* contain a enrich_defaults method to add additional defaults once
+ * all post_types and taxonomies have been registered
+ */
+ //abstract public function enrich_defaults();
+
+
+ /* *********** METHODS INFLUENCING get_option() *********** */
+
+ /**
+ * Add filters to make sure that the option default is returned if the option is not set
+ *
+ * @return void
+ */
+ public function add_default_filters() {
+ // Don't change, needs to check for false as could return prio 0 which would evaluate to false
+ if ( has_filter( 'default_option_' . $this->option_name, array( $this, 'get_defaults' ) ) === false ) {
+ add_filter( 'default_option_' . $this->option_name, array( $this, 'get_defaults' ) );
+ }
+ }
+
+
+ /**
+ * Abusing a filter to re-add our default filters
+ * WP 3.7 specific as update_option action hook was in the wrong place temporarily
+ * @see http://core.trac.wordpress.org/ticket/25705
+ *
+ * @param mixed $new_value
+ *
+ * @return mixed unchanged value
+ */
+ public function wp37_add_default_filters( $new_value ) {
+ $this->add_default_filters();
+
+ return $new_value;
+ }
+
+
+ /**
+ * Remove the default filters.
+ * Called from the validate() method to prevent failure to add new options
+ *
+ * @return void
+ */
+ public function remove_default_filters() {
+ remove_filter( 'default_option_' . $this->option_name, array( $this, 'get_defaults' ) );
+ }
+
+
+ /**
+ * Get the enriched default value for an option
+ *
+ * Checks if the concrete class contains an enrich_defaults() method and if so, runs it.
+ *
+ * @internal the enrich_defaults method is used to set defaults for variable array keys in an option,
+ * such as array keys depending on post_types and/or taxonomies
+ *
+ * @return array
+ */
+ public function get_defaults() {
+ if ( method_exists( $this, 'translate_defaults' ) ) {
+ $this->translate_defaults();
+ }
+
+ if ( method_exists( $this, 'enrich_defaults' ) ) {
+ $this->enrich_defaults();
+ }
+
+ return apply_filters( 'wpseo_defaults', $this->defaults, $this->option_name );
+ }
+
+
+ /**
+ * Add filters to make sure that the option is merged with its defaults before being returned
+ *
+ * @return void
+ */
+ public function add_option_filters() {
+ // Don't change, needs to check for false as could return prio 0 which would evaluate to false
+ if ( has_filter( 'option_' . $this->option_name, array( $this, 'get_option' ) ) === false ) {
+ add_filter( 'option_' . $this->option_name, array( $this, 'get_option' ) );
+ }
+ }
+
+
+ /**
+ * Remove the option filters.
+ * Called from the clean_up methods to make sure we retrieve the original old option
+ *
+ * @return void
+ */
+ public function remove_option_filters() {
+ remove_filter( 'option_' . $this->option_name, array( $this, 'get_option' ) );
+ }
+
+
+ /**
+ * Merge an option with its default values
+ *
+ * This method should *not* be called directly!!! It is only meant to filter the get_option() results
+ *
+ * @param mixed $options Option value
+ *
+ * @return mixed Option merged with the defaults for that option
+ */
+ public function get_option( $options = null ) {
+ $filtered = $this->array_filter_merge( $options );
+
+ /* If the option contains variable option keys, make sure we don't remove those settings
+ - even if the defaults are not complete yet.
+ Unfortunately this means we also won't be removing the settings for post types or taxonomies
+ which are no longer in the WP install, but rather that than the other way around */
+ if ( isset( $this->variable_array_key_patterns ) ) {
+ $filtered = $this->retain_variable_keys( $options, $filtered );
+ }
+
+ return $filtered;
+ }
+
+
+ /* *********** METHODS influencing add_uption(), update_option() and saving from admin pages *********** */
+
+ /**
+ * Register (whitelist) the option for the configuration pages.
+ * The validation callback is already registered separately on the sanitize_option hook,
+ * so no need to double register.
+ *
+ * @return void
+ */
+ public function register_setting() {
+ if ( WPSEO_Options::grant_access() ) {
+ register_setting( $this->group_name, $this->option_name );
+ }
+ }
+
+
+ /**
+ * Validate the option
+ *
+ * @param mixed $option_value The unvalidated new value for the option
+ *
+ * @return array Validated new value for the option
+ */
+ public function validate( $option_value ) {
+ $clean = $this->get_defaults();
+
+ /* Return the defaults if the new value is empty */
+ if ( ! is_array( $option_value ) || $option_value === array() ) {
+ return $clean;
+ }
+
+
+ $option_value = array_map( array( __CLASS__, 'trim_recursive' ), $option_value );
+ if ( $this->multisite_only !== true ) {
+ $old = get_option( $this->option_name );
+ }
+ else {
+ $old = get_site_option( $this->option_name );
+ }
+ $clean = $this->validate_option( $option_value, $clean, $old );
+
+ /* Retain the values for variable array keys even when the post type/taxonomy is not yet registered */
+ if ( isset( $this->variable_array_key_patterns ) ) {
+ $clean = $this->retain_variable_keys( $option_value, $clean );
+ }
+
+ $this->remove_default_filters();
+
+ return $clean;
+ }
+
+
+ /**
+ * All concrete classes must contain a validate_option() method which validates all
+ * values within the option
+ */
+ abstract protected function validate_option( $dirty, $clean, $old );
+
+
+ /* *********** METHODS for ADDING/UPDATING/UPGRADING the option *********** */
+
+ /**
+ * Retrieve the real old value (unmerged with defaults)
+ *
+ * @return array|bool the original option value (which can be false if the option doesn't exist)
+ */
+ protected function get_original_option() {
+ $this->remove_default_filters();
+ $this->remove_option_filters();
+
+ // Get (unvalidated) array, NOT merged with defaults
+ if ( $this->multisite_only !== true ) {
+ $option_value = get_option( $this->option_name );
+ }
+ else {
+ $option_value = get_site_option( $this->option_name );
+ }
+
+ $this->add_option_filters();
+ $this->add_default_filters();
+
+ return $option_value;
+ }
+
+ /**
+ * Add the option if it doesn't exist for some strange reason
+ *
+ * @uses WPSEO_Option::get_original_option()
+ *
+ * @return void
+ */
+ public function maybe_add_option() {
+ if ( $this->get_original_option() === false ) {
+ if ( $this->multisite_only !== true ) {
+ update_option( $this->option_name, $this->get_defaults() );
+ }
+ else {
+ $this->update_site_option( $this->get_defaults() );
+ }
+ }
+ }
+
+
+ /**
+ * Update a site_option
+ *
+ * @internal This special method is only needed for multisite options, but very needed indeed there.
+ * The order in which certain functions and hooks are run is different between get_option() and
+ * get_site_option() which means in practice that the removing of the default filters would be
+ * done too late and the re-adding of the default filters might not be done at all.
+ * Aka: use the WPSEO_Options::update_site_option() method (which calls this method) for
+ * safely adding/updating multisite options.
+ *
+ * @return bool whether the update was succesfull
+ */
+ public function update_site_option( $value ) {
+ if ( $this->multisite_only === true && is_multisite() ) {
+ $this->remove_default_filters();
+ $result = update_site_option( $this->option_name, $value );
+ $this->add_default_filters();
+ return $result;
+ }
+ else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Retrieve the real old value (unmerged with defaults), clean and re-save the option
+ *
+ * @uses WPSEO_Option::get_original_option()
+ * @uses WPSEO_Option::import()
+ *
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ *
+ * @return void
+ */
+ public function clean( $current_version = null ) {
+ $option_value = $this->get_original_option();
+ $this->import( $option_value, $current_version );
+ }
+
+
+ /**
+ * Clean and re-save the option
+ * @uses clean_option() method from concrete class if it exists
+ *
+ * @todo [JRF/whomever] Figure out a way to show settings error during/after the upgrade - maybe
+ * something along the lines of:
+ * -> add them to a property in this class
+ * -> if that property isset at the end of the routine and add_settings_error function does not exist,
+ * save as transient (or update the transient if one already exists)
+ * -> next time an admin is in the WP back-end, show the errors and delete the transient or only delete it
+ * once the admin has dismissed the message (add ajax function)
+ * Important: all validation routines which add_settings_errors would need to be changed for this to work
+ *
+ * @param array $option_value Option value to be imported
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return void
+ */
+ public function import( $option_value, $current_version = null, $all_old_option_values = null ) {
+ if ( $option_value === false ) {
+ $option_value = $this->get_defaults();
+ } elseif ( is_array( $option_value ) && method_exists( $this, 'clean_option' ) ) {
+ $option_value = $this->clean_option( $option_value, $current_version, $all_old_option_values );
+ }
+
+ /* Save the cleaned value - validation will take care of cleaning out array keys which
+ should no longer be there */
+ if ( $this->multisite_only !== true ) {
+ update_option( $this->option_name, $option_value );
+ }
+ else {
+ $this->update_site_option( $this->option_name, $option_value );
+ }
+ }
+
+
+ /**
+ * Concrete classes *may* contain a clean_option method which will clean out old/renamed
+ * values within the option
+ */
+ //abstract public function clean_option( $option_value, $current_version = null, $all_old_option_values = null );
+
+
+ /* *********** HELPER METHODS for internal use *********** */
+
+ /**
+ * Helper method - Combines a fixed array of default values with an options array
+ * while filtering out any keys which are not in the defaults array.
+ *
+ * @todo [JRF] - shouldn't this be a straight array merge ? at the end of the day, the validation
+ * removes any invalid keys on save
+ *
+ * @param array $options (Optional) Current options
+ * - if not set, the option defaults for the $option_key will be returned.
+ *
+ * @return array Combined and filtered options array.
+ */
+ protected function array_filter_merge( $options = null ) {
+
+ $defaults = $this->get_defaults();
+
+ if ( ! isset( $options ) || $options === false || $options === array() ) {
+ return $defaults;
+ }
+
+ $options = (array) $options;
+ /*$filtered = array();
+
+ if ( $defaults !== array() ) {
+ foreach ( $defaults as $key => $default_value ) {
+ // @todo should this walk through array subkeys ?
+ $filtered[ $key ] = ( isset( $options[ $key ] ) ? $options[ $key ] : $default_value );
+ }
+ }*/
+ $filtered = array_merge( $defaults, $options );
+
+ return $filtered;
+ }
+
+
+ /**
+ * Make sure that any set option values relating to post_types and/or taxonomies are retained,
+ * even when that post_type or taxonomy may not yet have been registered.
+ *
+ * @internal The wpseo_titles concrete class overrules this method. Make sure that any changes
+ * applied here, also get ported to that version.
+ *
+ * @param array $dirty Original option as retrieved from the database
+ * @param array $clean Filtered option where any options which shouldn't be in our option
+ * have already been removed and any options which weren't set
+ * have been set to their defaults
+ *
+ * @return array
+ */
+ protected function retain_variable_keys( $dirty, $clean ) {
+ if ( ( is_array( $this->variable_array_key_patterns ) && $this->variable_array_key_patterns !== array() ) && ( is_array( $dirty ) && $dirty !== array() ) ) {
+ foreach ( $dirty as $key => $value ) {
+ foreach ( $this->variable_array_key_patterns as $pattern ) {
+ if ( strpos( $key, $pattern ) === 0 && ! isset( $clean[ $key ] ) ) {
+ $clean[ $key ] = $value;
+ break;
+ }
+ }
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Check whether a given array key conforms to one of the variable array key patterns for this option
+ *
+ * @usedby validate_option() methods for options with variable array keys
+ *
+ * @param string $key Array key to check
+ *
+ * @return string Pattern if it conforms, original array key if it doesn't or if the option
+ * does not have variable array keys
+ */
+ protected function get_switch_key( $key ) {
+ if ( ! isset( $this->variable_array_key_patterns ) || ( ! is_array( $this->variable_array_key_patterns ) || $this->variable_array_key_patterns === array() ) ) {
+ return $key;
+ }
+
+ foreach ( $this->variable_array_key_patterns as $pattern ) {
+ if ( strpos( $key, $pattern ) === 0 ) {
+ return $pattern;
+ }
+ }
+
+ return $key;
+ }
+
+
+ /* *********** GENERIC HELPER METHODS *********** */
+
+ /**
+ * Emulate the WP native sanitize_text_field function in a %%variable%% safe way
+ * @see https://core.trac.wordpress.org/browser/trunk/src/wp-includes/formatting.php for the original
+ *
+ * Sanitize a string from user input or from the db
+ *
+ * check for invalid UTF-8,
+ * Convert single < characters to entity,
+ * strip all tags,
+ * remove line breaks, tabs and extra white space,
+ * strip octets - BUT DO NOT REMOVE (part of) VARIABLES WHICH WILL BE REPLACED.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public static function sanitize_text_field( $value ) {
+ $filtered = wp_check_invalid_utf8( $value );
+
+ if ( strpos( $filtered, '<' ) !== false ) {
+ $filtered = wp_pre_kses_less_than( $filtered );
+ // This will strip extra whitespace for us.
+ $filtered = wp_strip_all_tags( $filtered, true );
+ } else {
+ $filtered = trim( preg_replace( '`[\r\n\t ]+`', ' ', $filtered ) );
+ }
+
+ $found = false;
+ while ( preg_match( '`[^%](%[a-f0-9]{2})`i', $filtered, $match ) ) {
+ $filtered = str_replace( $match[1], '', $filtered );
+ $found = true;
+ }
+
+ if ( $found ) {
+ // Strip out the whitespace that may now exist after removing the octets.
+ $filtered = trim( preg_replace( '` +`', ' ', $filtered ) );
+ }
+
+ /**
+ * Filter a sanitized text field string.
+ *
+ * @since WP 2.9.0
+ *
+ * @param string $filtered The sanitized string.
+ * @param string $str The string prior to being sanitized.
+ */
+ return apply_filters( 'sanitize_text_field', $filtered, $value );
+ }
+
+
+ /**
+ * Sanitize a url for saving to the database
+ * Not to be confused with the old native WP function
+ *
+ * @todo [JRF => whomever] check/improve url verification
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this should maybe
+ * be moved to a general WPSEO_Utils class. Obviously all calls to this method should be
+ * adjusted in that case.
+ *
+ * @param string $value
+ * @param array $allowed_protocols
+ *
+ * @return string
+ */
+ public static function sanitize_url( $value, $allowed_protocols = array( 'http', 'https' ) ) {
+ return esc_url_raw( sanitize_text_field( rawurldecode( $value ) ), $allowed_protocols );
+ }
+
+ /**
+ * Validate a value as boolean
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this (and the emulate method
+ * below) should maybe be moved to a general WPSEO_Utils class. Obviously all calls to this method
+ * should be adjusted in that case.
+ *
+ * @static
+ *
+ * @param mixed $value
+ *
+ * @return bool
+ */
+ public static function validate_bool( $value ) {
+ if ( self::$has_filters ) {
+ return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
+ } else {
+ return self::emulate_filter_bool( $value );
+ }
+ }
+
+ /**
+ * Cast a value to bool
+ *
+ * @static
+ *
+ * @param mixed $value Value to cast
+ *
+ * @return bool
+ */
+ public static function emulate_filter_bool( $value ) {
+ $true = array(
+ '1',
+ 'true',
+ 'True',
+ 'TRUE',
+ 'y',
+ 'Y',
+ 'yes',
+ 'Yes',
+ 'YES',
+ 'on',
+ 'On',
+ 'On',
+
+ );
+ $false = array(
+ '0',
+ 'false',
+ 'False',
+ 'FALSE',
+ 'n',
+ 'N',
+ 'no',
+ 'No',
+ 'NO',
+ 'off',
+ 'Off',
+ 'OFF',
+ );
+
+ if ( is_bool( $value ) ) {
+ return $value;
+ } else if ( is_int( $value ) && ( $value === 0 || $value === 1 ) ) {
+ return (bool) $value;
+ } else if ( ( is_float( $value ) && ! is_nan( $value ) ) && ( $value === (float) 0 || $value === (float) 1 ) ) {
+ return (bool) $value;
+ } else if ( is_string( $value ) ) {
+ $value = trim( $value );
+ if ( in_array( $value, $true, true ) ) {
+ return true;
+ } else if ( in_array( $value, $false, true ) ) {
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Validate a value as integer
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this (and the emulate method
+ * below) should maybe be moved to a general WPSEO_Utils class. Obviously all calls to this method
+ * should be adjusted in that case.
+ *
+ * @static
+ *
+ * @param mixed $value
+ *
+ * @return mixed int or false in case of failure to convert to int
+ */
+ public static function validate_int( $value ) {
+ if ( self::$has_filters ) {
+ return filter_var( $value, FILTER_VALIDATE_INT );
+ } else {
+ return self::emulate_filter_int( $value );
+ }
+ }
+
+ /**
+ * Cast a value to integer
+ *
+ * @static
+ *
+ * @param mixed $value Value to cast
+ *
+ * @return int|bool
+ */
+ public static function emulate_filter_int( $value ) {
+ if ( is_int( $value ) ) {
+ return $value;
+ } else if ( is_float( $value ) ) {
+ if ( (int) $value == $value && ! is_nan( $value ) ) {
+ return (int) $value;
+ } else {
+ return false;
+ }
+ } else if ( is_string( $value ) ) {
+ $value = trim( $value );
+ if ( $value === '' ) {
+ return false;
+ } else if ( ctype_digit( $value ) ) {
+ return (int) $value;
+ } else if ( strpos( $value, '-' ) === 0 && ctype_digit( substr( $value, 1 ) ) ) {
+ return (int) $value;
+ } else {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Recursively trim whitespace round a string value or of string values within an array
+ * Only trims strings to avoid typecasting a variable (to string)
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this should maybe
+ * be moved to a general WPSEO_Utils class. Obviously all calls to this method should be
+ * adjusted in that case.
+ *
+ * @static
+ *
+ * @param mixed $value Value to trim or array of values to trim
+ *
+ * @return mixed Trimmed value or array of trimmed values
+ */
+ public static function trim_recursive( $value ) {
+ if ( is_string( $value ) ) {
+ $value = trim( $value );
+ } elseif ( is_array( $value ) ) {
+ $value = array_map( array( __CLASS__, 'trim_recursive' ), $value );
+ }
+
+ return $value;
+ }
+
+ } /* End of class WPSEO_Option */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Option_Wpseo' ) ) {
+
+ class WPSEO_Option_Wpseo extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo';
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ */
+ protected $defaults = array(
+ // Non-form fields, set via (ajax) function
+ 'blocking_files' => array(),
+ 'ignore_blog_public_warning' => false,
+ 'ignore_meta_description_warning' => null, // overwrite in __construct()
+ 'ignore_page_comments' => false,
+ 'ignore_permalink' => false,
+ 'ignore_tour' => false,
+ 'ms_defaults_set' => false,
+ 'theme_description_found' => null, // overwrite in __construct()
+ 'theme_has_description' => null, // overwrite in __construct()
+ 'tracking_popup_done' => false,
+ // Non-form field, should only be set via validation routine
+ 'version' => '', // leave default as empty to ensure activation/upgrade works
+
+ // Form fields:
+ 'alexaverify' => '', // text field
+ 'disableadvanced_meta' => true,
+ 'googleverify' => '', // text field
+ 'msverify' => '', // text field
+ 'pinterestverify' => '',
+ 'yandexverify' => '',
+ 'yoast_tracking' => false,
+ );
+
+ public static $desc_defaults = array(
+ 'ignore_meta_description_warning' => false,
+ 'theme_description_found' => '', // text string description
+ 'theme_has_description' => null,
+ );
+
+ /**
+ * @var array Array of sub-options which should not be overloaded with multi-site defaults
+ */
+ public $ms_exclude = array(
+ 'ignore_blog_public_warning',
+ 'ignore_meta_description_warning',
+ 'ignore_page_comments',
+ 'ignore_permalink',
+ 'ignore_tour',
+
+ /* theme dependent */
+ 'theme_description_found',
+ 'theme_has_description',
+
+ /* privacy */
+ 'alexaverify',
+ 'googleverify',
+ 'msverify',
+ 'pinterestverify',
+ 'yandexverify',
+ );
+
+
+ /**
+ * Add the actions and filters for the option
+ *
+ * @todo [JRF => testers] Check if the extra actions below would run into problems if an option
+ * is updated early on and if so, change the call to schedule these for a later action on add/update
+ * instead of running them straight away
+ *
+ * @return \WPSEO_Option_Wpseo
+ */
+ protected function __construct() {
+ /* Dirty fix for making certain defaults available during activation while still only
+ defining them once */
+ foreach ( self::$desc_defaults as $key => $value ) {
+ $this->defaults[ $key ] = $value;
+ }
+
+ parent::__construct();
+
+ /* Clear the cache on update/add */
+ add_action( 'add_option_' . $this->option_name, array( 'WPSEO_Options', 'clear_cache' ) );
+ add_action( 'update_option_' . $this->option_name, array( 'WPSEO_Options', 'clear_cache' ) );
+
+
+ /* Check if the yoast tracking cron job needs adding/removing on successfull option add/update */
+ add_action( 'add_option_' . $this->option_name, array(
+ 'WPSEO_Options',
+ 'schedule_yoast_tracking',
+ ), 15, 2 );
+ add_action( 'update_option_' . $this->option_name, array(
+ 'WPSEO_Options',
+ 'schedule_yoast_tracking',
+ ), 15, 2 );
+ }
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+
+ foreach ( $clean as $key => $value ) {
+ switch ( $key ) {
+ case 'version':
+ $clean[ $key ] = WPSEO_VERSION;
+ break;
+
+
+ case 'blocking_files':
+ /* @internal [JRF] to really validate this we should also do a file_exists()
+ * on each array entry and remove files which no longer exist, but that might be overkill */
+ if ( isset( $dirty[ $key ] ) && is_array( $dirty[ $key ] ) ) {
+ $clean[ $key ] = array_unique( $dirty[ $key ] );
+ } elseif ( isset( $old[ $key ] ) && is_array( $old[ $key ] ) ) {
+ $clean[ $key ] = array_unique( $old[ $key ] );
+ }
+ break;
+
+
+ case 'theme_description_found':
+ if ( isset( $dirty[ $key ] ) && is_string( $dirty[ $key ] ) ) {
+ $clean[ $key ] = $dirty[ $key ]; // @todo [JRF/whomever] maybe do wp_kses ?
+ } elseif ( isset( $old[ $key ] ) && is_string( $old[ $key ] ) ) {
+ $clean[ $key ] = $old[ $key ];
+ }
+ break;
+
+
+ /* text fields */
+ case 'alexaverify':
+ case 'googleverify':
+ case 'msverify':
+ case 'pinterestverify':
+ case 'yandexverify':
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
+ $meta = $dirty[ $key ];
+ if ( strpos( $meta, 'content=' ) ) {
+ // Make sure we only have the real key, not a complete meta tag
+ preg_match( '`content=([\'"])?([^\'"> ]+)(?:\1|[ />])`', $meta, $match );
+ if ( isset( $match[2] ) ) {
+ $meta = $match[2];
+ }
+ unset( $match );
+ }
+
+ $meta = sanitize_text_field( $meta );
+ if ( $meta !== '' ) {
+ $regex = '`^[A-Fa-f0-9_-]+$`';
+ $service = '';
+
+ switch ( $key ) {
+ case 'googleverify':
+ $regex = '`^[A-Za-z0-9_-]+$`';
+ $service = 'Google Webmaster tools';
+ break;
+
+ case 'msverify':
+ $service = 'Bing Webmaster tools';
+ break;
+
+ case 'pinterestverify':
+ $service = 'Pinterest';
+ break;
+
+ case 'yandexverify':
+ $service = 'Yandex Webmaster tools';
+ break;
+
+ case 'alexaverify':
+ $regex = '`^[A-Za-z0-9_-]{20,}$`';
+ $service = 'Alexa ID';
+ }
+
+ if ( preg_match( $regex, $meta ) ) {
+ $clean[ $key ] = $meta;
+ } else {
+ if ( isset( $old[ $key ] ) && preg_match( $regex, $old[ $key ] ) ) {
+ $clean[ $key ] = $old[ $key ];
+ }
+ if ( function_exists( 'add_settings_error' ) ) {
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ sprintf( __( '%s does not seem to be a valid %s verification string. Please correct.', 'wordpress-seo' ), '<strong>' . esc_html( $meta ) . '</strong>', $service ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ }
+ }
+ unset( $meta, $regex, $service );
+ }
+ break;
+
+
+ /* boolean|null fields - if set a check was done, if null, it hasn't */
+ case 'theme_has_description':
+ if ( isset( $dirty[ $key ] ) ) {
+ $clean[ $key ] = self::validate_bool( $dirty[ $key ] );
+ } elseif ( isset( $old[ $key ] ) ) {
+ $clean[ $key ] = self::validate_bool( $old[ $key ] );
+ }
+ break;
+
+
+ /* boolean dismiss warnings - not fields - may not be in form
+ (and don't need to be either as long as the default is false) */
+ case 'ignore_blog_public_warning':
+ case 'ignore_meta_description_warning':
+ case 'ignore_page_comments':
+ case 'ignore_permalink':
+ case 'ignore_tour':
+ case 'ms_defaults_set':
+ case 'tracking_popup_done':
+ if ( isset( $dirty[ $key ] ) ) {
+ $clean[ $key ] = self::validate_bool( $dirty[ $key ] );
+ } elseif ( isset( $old[ $key ] ) ) {
+ $clean[ $key ] = self::validate_bool( $old[ $key ] );
+ }
+ break;
+
+
+ /* boolean (checkbox) fields */
+ case 'disableadvanced_meta':
+ case 'yoast_tracking':
+ default:
+ $clean[ $key ] = ( isset( $dirty[ $key ] ) ? self::validate_bool( $dirty[ $key ] ) : false );
+ break;
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Clean a given option value
+ *
+ * @param array $option_value Old (not merged with defaults or filtered) option value to
+ * clean according to the rules for this option
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return array Cleaned option
+ */
+ protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
+
+ // Rename some options *and* change their value
+ $rename = array(
+ 'presstrends' => array(
+ 'new_name' => 'yoast_tracking',
+ 'new_value' => true,
+ ),
+ 'presstrends_popup' => array(
+ 'new_name' => 'tracking_popup_done',
+ 'new_value' => true,
+ ),
+ );
+ foreach ( $rename as $old => $new ) {
+ if ( isset( $option_value[ $old ] ) && ! isset( $option_value[ $new['new_name'] ] ) ) {
+ $option_value[ $new['new_name'] ] = $new['new_value'];
+ unset( $option_value[ $old ] );
+ }
+ }
+ unset( $rename, $old, $new );
+
+
+ // Deal with renaming of some options without losing the settings
+ $rename = array(
+ 'tracking_popup' => 'tracking_popup_done',
+ 'meta_description_warning' => 'ignore_meta_description_warning',
+ );
+ foreach ( $rename as $old => $new ) {
+ if ( isset( $option_value[ $old ] ) && ! isset( $option_value[ $new ] ) ) {
+ $option_value[ $new ] = $option_value[ $old ];
+ unset( $option_value[ $old ] );
+ }
+ }
+ unset( $rename, $old, $new );
+
+
+ // Change a array sub-option to two straight sub-options
+ if ( isset( $option_value['theme_check']['description'] ) && ! isset( $option_value['theme_has_description'] ) ) {
+ // @internal the negate is by design!
+ $option_value['theme_has_description'] = ! $option_value['theme_check']['description'];
+ }
+ if ( isset( $option_values['theme_check']['description_found'] ) && ! isset( $option_value['theme_description_found'] ) ) {
+ $option_value['theme_description_found'] = $option_value['theme_check']['description_found'];
+ }
+
+
+ // Deal with value change from text string to boolean
+ $value_change = array(
+ 'ignore_blog_public_warning',
+ 'ignore_meta_description_warning',
+ 'ignore_page_comments',
+ 'ignore_permalink',
+ 'ignore_tour',
+ //'disableadvanced_meta', => not needed as is 'on' which will auto-convert to true
+ 'tracking_popup_done',
+ );
+ foreach ( $value_change as $key ) {
+ if ( isset( $option_value[ $key ] ) && in_array( $option_value[ $key ], array(
+ 'ignore',
+ 'done',
+ ), true )
+ ) {
+ $option_value[ $key ] = true;
+ }
+ }
+
+ return $option_value;
+ }
+
+ } /* End of class WPSEO_Option_Wpseo */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo_permalinks
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Option_Permalinks' ) ) {
+
+ /**
+ * @internal Clean routine for 1.5 not needed as values used to be saved as string 'on' and those will convert
+ * automatically
+ */
+ class WPSEO_Option_Permalinks extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo_permalinks';
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ */
+ protected $defaults = array(
+ 'cleanpermalinks' => false,
+ 'cleanpermalink-extravars' => '', // text field
+ 'cleanpermalink-googlecampaign' => false,
+ 'cleanpermalink-googlesitesearch' => false,
+ 'cleanreplytocom' => false,
+ 'cleanslugs' => true,
+ 'force_transport' => 'default',
+ 'redirectattachment' => false,
+ 'stripcategorybase' => false,
+ 'trailingslash' => false,
+ );
+
+
+ /**
+ * @static
+ * @var array $force_transport_options Available options for the force_transport setting
+ * Used for input validation
+ *
+ * @internal Important: Make sure the options added to the array here are in line with the keys
+ * for the options set for the select box in the admin/pages/permalinks.php file
+ */
+ public static $force_transport_options = array(
+ 'default', // = leave as-is
+ 'http',
+ 'https',
+ );
+
+
+ /**
+ * Add the actions and filters for the option
+ *
+ * @todo [JRF => testers] Check if the extra actions below would run into problems if an option
+ * is updated early on and if so, change the call to schedule these for a later action on add/update
+ * instead of running them straight away
+ *
+ * @return \WPSEO_Option_Permalinks
+ */
+ protected function __construct() {
+ parent::__construct();
+ add_action( 'update_option_' . $this->option_name, array( 'WPSEO_Options', 'clear_rewrites' ) );
+ }
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option (not used here as all fields will always be in the form)
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+
+ foreach ( $clean as $key => $value ) {
+ switch ( $key ) {
+ case 'force_transport':
+ if ( isset( $dirty[ $key ] ) && in_array( $dirty[ $key ], self::$force_transport_options, true ) ) {
+ $clean[ $key ] = $dirty[ $key ];
+ } else {
+ if ( isset( $old[ $key ] ) && in_array( $old[ $key ], self::$force_transport_options, true ) ) {
+ $clean[ $key ] = $old[ $key ];
+ }
+ if ( function_exists( 'add_settings_error' ) ) {
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ __( 'Invalid transport mode set for the canonical settings. Value reset to default.', 'wordpress-seo' ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ }
+ break;
+
+ /* text fields */
+ case 'cleanpermalink-extravars':
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
+ $clean[ $key ] = sanitize_text_field( $dirty[ $key ] );
+ }
+ break;
+
+ /* boolean (checkbox) fields */
+ case 'cleanpermalinks':
+ case 'cleanpermalink-googlesitesearch':
+ case 'cleanpermalink-googlecampaign':
+ case 'cleanreplytocom':
+ case 'cleanslugs':
+ case 'redirectattachment':
+ case 'stripcategorybase':
+ case 'trailingslash':
+ default:
+ $clean[ $key ] = ( isset( $dirty[ $key ] ) ? self::validate_bool( $dirty[ $key ] ) : false );
+ break;
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Clean a given option value
+ *
+ * @param array $option_value Old (not merged with defaults or filtered) option value to
+ * clean according to the rules for this option
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return array Cleaned option
+ */
+ /*protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
+
+ return $option_value;
+ }*/
+
+
+ } /* End of class WPSEO_Option_Permalinks */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo_titles
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Option_Titles' ) ) {
+
+ class WPSEO_Option_Titles extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo_titles';
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ * @internal Note: Some of the default values are added via the translate_defaults() method
+ */
+ protected $defaults = array(
+ // Non-form fields, set via (ajax) function
+ 'title_test' => 0,
+ // Form fields
+ 'forcerewritetitle' => false,
+ 'separator' => 'sc-dash',
+ 'hide-feedlinks' => false,
+ 'hide-rsdlink' => false,
+ 'hide-shortlink' => false,
+ 'hide-wlwmanifest' => false,
+ 'noodp' => false,
+ 'noydir' => false,
+ 'usemetakeywords' => false,
+ 'title-home-wpseo' => '%%sitename%% %%page%% %%sep%% %%sitedesc%%', // text field
+ 'title-author-wpseo' => '', // text field
+ 'title-archive-wpseo' => '%%date%% %%page%% %%sep%% %%sitename%%', // text field
+ 'title-search-wpseo' => '', // text field
+ 'title-404-wpseo' => '', // text field
+
+ 'metadesc-home-wpseo' => '', // text area
+ 'metadesc-author-wpseo' => '', // text area
+ 'metadesc-archive-wpseo' => '', // text area
+
+ 'metakey-home-wpseo' => '', // text field
+ 'metakey-author-wpseo' => '', // text field
+
+ 'noindex-subpages-wpseo' => false,
+ 'noindex-author-wpseo' => false,
+ 'noindex-archive-wpseo' => true,
+ 'disable-author' => false,
+ 'disable-date' => false,
+
+
+ /**
+ * Uses enrich_defaults to add more along the lines of:
+ * - 'title-' . $pt->name => ''; // text field
+ * - 'metadesc-' . $pt->name => ''; // text field
+ * - 'metakey-' . $pt->name => ''; // text field
+ * - 'noindex-' . $pt->name => false;
+ * - 'showdate-' . $pt->name => false;
+ * - 'hideeditbox-' . $pt->name => false;
+ *
+ * - 'title-ptarchive-' . $pt->name => ''; // text field
+ * - 'metadesc-ptarchive-' . $pt->name => ''; // text field
+ * - 'metakey-ptarchive-' . $pt->name => ''; // text field
+ * - 'bctitle-ptarchive-' . $pt->name => ''; // text field
+ * - 'noindex-ptarchive-' . $pt->name => false;
+ *
+ * - 'title-tax-' . $tax->name => '''; // text field
+ * - 'metadesc-tax-' . $tax->name => ''; // text field
+ * - 'metakey-tax-' . $tax->name => ''; // text field
+ * - 'noindex-tax-' . $tax->name => false;
+ * - 'hideeditbox-tax-' . $tax->name => false;
+ */
+ );
+
+ /**
+ * @var array Array of variable option name patterns for the option
+ */
+ protected $variable_array_key_patterns = array(
+ 'title-',
+ 'metadesc-',
+ 'metakey-',
+ 'noindex-',
+ 'showdate-',
+ 'hideeditbox-',
+ 'bctitle-ptarchive-',
+ );
+
+ /**
+ * @var array Array of sub-options which should not be overloaded with multi-site defaults
+ */
+ public $ms_exclude = array(
+ /* theme dependent */
+ 'title_test',
+ 'forcerewritetitle',
+ );
+
+ /**
+ * @var array Array of the separator options. To get these options use WPSEO_Option_Titles::get_instance()->get_separator_options()
+ */
+ private $separator_options = array(
+ 'sc-dash' => '-',
+ 'sc-ndash' => '–',
+ 'sc-mdash' => '—',
+ 'sc-middot' => '·',
+ 'sc-bull' => '•',
+ 'sc-star' => '*',
+ 'sc-smstar' => '⋆',
+ 'sc-pipe' => '|',
+ 'sc-tilde' => '~',
+ 'sc-laquo' => '«',
+ 'sc-raquo' => '»',
+ 'sc-lt' => '<',
+ 'sc-gt' => '>',
+ );
+
+ /**
+ * Add the actions and filters for the option
+ *
+ * @todo [JRF => testers] Check if the extra actions below would run into problems if an option
+ * is updated early on and if so, change the call to schedule these for a later action on add/update
+ * instead of running them straight away
+ *
+ * @return \WPSEO_Option_Titles
+ */
+ protected function __construct() {
+ parent::__construct();
+ add_action( 'update_option_' . $this->option_name, array( 'WPSEO_Options', 'clear_cache' ) );
+ add_action( 'init', array( $this, 'end_of_init' ), 999 );
+ }
+
+
+ /**
+ * Make sure we can recognize the right action for the double cleaning
+ */
+ public function end_of_init() {
+ do_action( 'wpseo_double_clean_titles' );
+ }
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Get the available separator options
+ *
+ * @return array
+ */
+ public function get_separator_options() {
+ $separators = $this->separator_options;
+
+ /**
+ * Allow altering the array with separator options
+ * @api array $separator_options Array with the separator options
+ */
+ $filtered_separators = apply_filters( 'wpseo_separator_options', $separators );
+
+ if ( is_array( $filtered_separators ) && $filtered_separators !== array() ) {
+ $separators = array_merge( $separators, $filtered_separators );
+ }
+
+ return $separators;
+ }
+
+ /**
+ * Translate strings used in the option defaults
+ *
+ * @return void
+ */
+ public function translate_defaults() {
+ $this->defaults['title-author-wpseo'] = sprintf( __( '%s, Author at %s', 'wordpress-seo' ), '%%name%%', '%%sitename%%' ) . ' %%page%% ';
+ $this->defaults['title-search-wpseo'] = sprintf( __( 'You searched for %s', 'wordpress-seo' ), '%%searchphrase%%' ) . ' %%page%% %%sep%% %%sitename%%';
+ $this->defaults['title-404-wpseo'] = __( 'Page Not Found', 'wordpress-seo' ) . ' %%sep%% %%sitename%%';
+ }
+
+
+ /**
+ * Add dynamically created default options based on available post types and taxonomies
+ *
+ * @return void
+ */
+ public function enrich_defaults() {
+
+ // Retrieve all the relevant post type and taxonomy arrays
+ $post_type_names = get_post_types( array( 'public' => true ), 'names' );
+
+ $post_type_objects_custom = get_post_types( array( 'public' => true, '_builtin' => false ), 'objects' );
+
+ $taxonomy_names = get_taxonomies( array( 'public' => true ), 'names' );
+
+
+ if ( $post_type_names !== array() ) {
+ foreach ( $post_type_names as $pt ) {
+ $this->defaults[ 'title-' . $pt ] = '%%title%% %%page%% %%sep%% %%sitename%%'; // text field
+ $this->defaults[ 'metadesc-' . $pt ] = ''; // text area
+ $this->defaults[ 'metakey-' . $pt ] = ''; // text field
+ $this->defaults[ 'noindex-' . $pt ] = false;
+ $this->defaults[ 'showdate-' . $pt ] = false;
+ $this->defaults[ 'hideeditbox-' . $pt ] = false;
+ }
+ unset( $pt );
+ }
+
+ if ( $post_type_objects_custom !== array() ) {
+ foreach ( $post_type_objects_custom as $pt ) {
+ if ( ! $pt->has_archive ) {
+ continue;
+ }
+
+ $this->defaults[ 'title-ptarchive-' . $pt->name ] = sprintf( __( '%s Archive', 'wordpress-seo' ), '%%pt_plural%%' ) . ' %%page%% %%sep%% %%sitename%%'; // text field
+ $this->defaults[ 'metadesc-ptarchive-' . $pt->name ] = ''; // text area
+ $this->defaults[ 'metakey-ptarchive-' . $pt->name ] = ''; // text field
+ $this->defaults[ 'bctitle-ptarchive-' . $pt->name ] = ''; // text field
+ $this->defaults[ 'noindex-ptarchive-' . $pt->name ] = false;
+ }
+ unset( $pt );
+ }
+
+ if ( $taxonomy_names !== array() ) {
+ foreach ( $taxonomy_names as $tax ) {
+ $this->defaults[ 'title-tax-' . $tax ] = sprintf( __( '%s Archives', 'wordpress-seo' ), '%%term_title%%' ) . ' %%page%% %%sep%% %%sitename%%'; // text field
+ $this->defaults[ 'metadesc-tax-' . $tax ] = ''; // text area
+ $this->defaults[ 'metakey-tax-' . $tax ] = ''; // text field
+ $this->defaults[ 'hideeditbox-tax-' . $tax ] = false;
+
+ if ( $tax !== 'post_format' ) {
+ $this->defaults[ 'noindex-tax-' . $tax ] = false;
+ } else {
+ $this->defaults[ 'noindex-tax-' . $tax ] = true;
+ }
+ }
+ unset( $tax );
+ }
+ }
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+
+ foreach ( $clean as $key => $value ) {
+ $switch_key = $this->get_switch_key( $key );
+
+ switch ( $switch_key ) {
+ /* text fields */
+ /* Covers:
+ 'title-home-wpseo', 'title-author-wpseo', 'title-archive-wpseo',
+ 'title-search-wpseo', 'title-404-wpseo'
+ 'title-' . $pt->name
+ 'title-ptarchive-' . $pt->name
+ 'title-tax-' . $tax->name */
+ case 'title-':
+ if ( isset( $dirty[ $key ] ) ) {
+ $clean[ $key ] = self::sanitize_text_field( $dirty[ $key ] );
+ }
+ break;
+
+ /* Covers:
+ 'metadesc-home-wpseo', 'metadesc-author-wpseo', 'metadesc-archive-wpseo'
+ 'metadesc-' . $pt->name
+ 'metadesc-ptarchive-' . $pt->name
+ 'metadesc-tax-' . $tax->name */
+ case 'metadesc-':
+ /* Covers:
+ 'metakey-home-wpseo', 'metakey-author-wpseo'
+ 'metakey-' . $pt->name
+ 'metakey-ptarchive-' . $pt->name
+ 'metakey-tax-' . $tax->name */
+ case 'metakey-':
+ /* Covers:
+ ''bctitle-ptarchive-' . $pt->name */
+ case 'bctitle-ptarchive-':
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
+ $clean[ $key ] = self::sanitize_text_field( $dirty[ $key ] );
+ }
+ break;
+
+
+ /* integer field - not in form*/
+ case 'title_test':
+ if ( isset( $dirty[ $key ] ) ) {
+ $int = self::validate_int( $dirty[ $key ] );
+ if ( $int !== false && $int >= 0 ) {
+ $clean[ $key ] = $int;
+ }
+ } elseif ( isset( $old[ $key ] ) ) {
+ $int = self::validate_int( $old[ $key ] );
+ if ( $int !== false && $int >= 0 ) {
+ $clean[ $key ] = $int;
+ }
+ }
+ break;
+
+ /* Separator field - Radio */
+ case 'separator':
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
+
+ // Get separator fields
+ $separator_fields = $this->get_separator_options();
+
+ // Check if the given separator is exists
+ if ( isset( $separator_fields[ $dirty[ $key ] ] ) ) {
+ $clean[ $key ] = $dirty[ $key ];
+ }
+ }
+ break;
+
+ /* boolean fields */
+ case 'forcerewritetitle':
+ case 'usemetakeywords':
+ case 'noodp':
+ case 'noydir':
+ case 'hide-rsdlink':
+ case 'hide-wlwmanifest':
+ case 'hide-shortlink':
+ case 'hide-feedlinks':
+ case 'disable-author':
+ case 'disable-date':
+ /* Covers:
+ 'noindex-subpages-wpseo', 'noindex-author-wpseo', 'noindex-archive-wpseo'
+ 'noindex-' . $pt->name
+ 'noindex-ptarchive-' . $pt->name
+ 'noindex-tax-' . $tax->name */
+ case 'noindex-':
+ case 'showdate-': /* 'showdate-'. $pt->name */
+ /* Covers:
+ 'hideeditbox-'. $pt->name
+ 'hideeditbox-tax-' . $tax->name */
+ case 'hideeditbox-':
+ default:
+ $clean[ $key ] = ( isset( $dirty[ $key ] ) ? self::validate_bool( $dirty[ $key ] ) : false );
+ break;
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Clean a given option value
+ *
+ * @param array $option_value Old (not merged with defaults or filtered) option value to
+ * clean according to the rules for this option
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return array Cleaned option
+ */
+ protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
+ static $original = null;
+
+ // Double-run this function to ensure renaming of the taxonomy options will work
+ if ( ! isset( $original ) && has_action( 'wpseo_double_clean_titles', array(
+ $this,
+ 'clean',
+ ) ) === false
+ ) {
+ add_action( 'wpseo_double_clean_titles', array( $this, 'clean' ) );
+ $original = $option_value;
+ }
+
+ /* Move options from very old option to this one
+ @internal Don't rename to the 'current' names straight away as that would prevent
+ the rename/unset combi below from working
+ @todo [JRF] maybe figure out a smarter way to deal with this */
+ $old_option = null;
+ if ( isset( $all_old_option_values ) ) {
+ // Ok, we have an import
+ if ( isset( $all_old_option_values['wpseo_indexation'] ) && is_array( $all_old_option_values['wpseo_indexation'] ) && $all_old_option_values['wpseo_indexation'] !== array() ) {
+ $old_option = $all_old_option_values['wpseo_indexation'];
+ }
+ } else {
+ $old_option = get_option( 'wpseo_indexation' );
+ }
+ if ( is_array( $old_option ) && $old_option !== array() ) {
+ $move = array(
+ 'noindexauthor' => 'noindex-author',
+ 'disableauthor' => 'disable-author',
+ 'noindexdate' => 'noindex-archive',
+ 'noindexcat' => 'noindex-category',
+ 'noindextag' => 'noindex-post_tag',
+ 'noindexpostformat' => 'noindex-post_format',
+ 'noindexsubpages' => 'noindex-subpages',
+ 'hidersdlink' => 'hide-rsdlink',
+ 'hidefeedlinks' => 'hide-feedlinks',
+ 'hidewlwmanifest' => 'hide-wlwmanifest',
+ 'hideshortlink' => 'hide-shortlink',
+ );
+ foreach ( $move as $old => $new ) {
+ if ( isset( $old_option[ $old ] ) && ! isset( $option_value[ $new ] ) ) {
+ $option_value[ $new ] = $old_option[ $old ];
+ }
+ }
+ unset( $move, $old, $new );
+ }
+ unset( $old_option );
+
+
+ // Fix wrongness created by buggy version 1.2.2
+ if ( isset( $option_value['title-home'] ) && $option_value['title-home'] === '%%sitename%% - %%sitedesc%% - 12345' ) {
+ $option_value['title-home-wpseo'] = '%%sitename%% - %%sitedesc%%';
+ }
+
+
+ /* Renaming these options to avoid ever overwritting these if a (bloody stupid) user /
+ programmer would use any of the following as a custom post type or custom taxonomy:
+ 'home', 'author', 'archive', 'search', '404', 'subpages'
+
+ Similarly, renaming the tax options to avoid a custom post type and a taxonomy
+ with the same name occupying the same option */
+ $rename = array(
+ 'title-home' => 'title-home-wpseo',
+ 'title-author' => 'title-author-wpseo',
+ 'title-archive' => 'title-archive-wpseo',
+ 'title-search' => 'title-search-wpseo',
+ 'title-404' => 'title-404-wpseo',
+ 'metadesc-home' => 'metadesc-home-wpseo',
+ 'metadesc-author' => 'metadesc-author-wpseo',
+ 'metadesc-archive' => 'metadesc-archive-wpseo',
+ 'metakey-home' => 'metakey-home-wpseo',
+ 'metakey-author' => 'metakey-author-wpseo',
+ 'noindex-subpages' => 'noindex-subpages-wpseo',
+ 'noindex-author' => 'noindex-author-wpseo',
+ 'noindex-archive' => 'noindex-archive-wpseo',
+ );
+ foreach ( $rename as $old => $new ) {
+ if ( isset( $option_value[ $old ] ) && ! isset( $option_value[ $new ] ) ) {
+ $option_value[ $new ] = $option_value[ $old ];
+ unset( $option_value[ $old ] );
+ }
+ }
+ unset( $rename, $old, $new );
+
+
+ /* @internal This clean-up action can only be done effectively once the taxonomies and post_types
+ * have been registered, i.e. at the end of the init action. */
+ if ( isset( $original ) && current_filter() === 'wpseo_double_clean_titles' || did_action( 'wpseo_double_clean_titles' ) > 0 ) {
+ $rename = array(
+ 'title-' => 'title-tax-',
+ 'metadesc-' => 'metadesc-tax-',
+ 'metakey-' => 'metakey-tax-',
+ 'noindex-' => 'noindex-tax-',
+ 'tax-hideeditbox-' => 'hideeditbox-tax-',
+
+ );
+ $taxonomy_names = get_taxonomies( array( 'public' => true ), 'names' );
+ $post_type_names = get_post_types( array( 'public' => true ), 'names' );
+ $defaults = $this->get_defaults();
+ if ( $taxonomy_names !== array() ) {
+ foreach ( $taxonomy_names as $tax ) {
+ foreach ( $rename as $old_prefix => $new_prefix ) {
+ if (
+ ( isset( $original[ $old_prefix . $tax ] ) && ! isset( $original[ $new_prefix . $tax ] ) )
+ && ( ! isset( $option_value[ $new_prefix . $tax ] )
+ || ( isset( $option_value[ $new_prefix . $tax ] )
+ && $option_value[ $new_prefix . $tax ] === $defaults[ $new_prefix . $tax ] ) )
+ ) {
+ $option_value[ $new_prefix . $tax ] = $original[ $old_prefix . $tax ];
+
+ /* Check if there is a cpt with the same name as the tax,
+ if so, we should make sure that the old setting hasn't been removed */
+ if ( ! isset( $post_type_names[ $tax ] ) && isset( $option_value[ $old_prefix . $tax ] ) ) {
+ unset( $option_value[ $old_prefix . $tax ] );
+ } else {
+ if ( isset( $post_type_names[ $tax ] ) && ! isset( $option_value[ $old_prefix . $tax ] ) ) {
+ $option_value[ $old_prefix . $tax ] = $original[ $old_prefix . $tax ];
+ }
+ }
+
+ if ( $old_prefix === 'tax-hideeditbox-' ) {
+ unset( $option_value[ $old_prefix . $tax ] );
+ }
+ }
+ }
+ }
+ }
+ unset( $rename, $taxonomy_names, $post_type_names, $tax, $old_prefix, $new_prefix );
+ }
+
+
+ /* Make sure the values of the variable option key options are cleaned as they
+ may be retained and would not be cleaned/validated then */
+ if ( is_array( $option_value ) && $option_value !== array() ) {
+ foreach ( $option_value as $key => $value ) {
+ $switch_key = $this->get_switch_key( $key );
+
+ // Similar to validation routine - any changes made there should be made here too
+ switch ( $switch_key ) {
+ /* text fields */
+ case 'title-':
+ case 'metadesc-':
+ case 'metakey-':
+ case 'bctitle-ptarchive-':
+ $option_value[ $key ] = self::sanitize_text_field( $value );
+ break;
+
+
+ /* boolean fields */
+ case 'noindex-':
+ case 'showdate-':
+ case 'hideeditbox-':
+ default:
+ $option_value[ $key ] = self::validate_bool( $value );
+ break;
+ }
+ }
+ }
+
+ return $option_value;
+ }
+
+
+ /**
+ * Make sure that any set option values relating to post_types and/or taxonomies are retained,
+ * even when that post_type or taxonomy may not yet have been registered.
+ *
+ * @internal Overrule the abstract class version of this to make sure one extra renamed variable key
+ * does not get removed. IMPORTANT: keep this method in line with the parent on which it is based!
+ *
+ * @param array $dirty Original option as retrieved from the database
+ * @param array $clean Filtered option where any options which shouldn't be in our option
+ * have already been removed and any options which weren't set
+ * have been set to their defaults
+ *
+ * @return array
+ */
+ protected function retain_variable_keys( $dirty, $clean ) {
+ if ( ( is_array( $this->variable_array_key_patterns ) && $this->variable_array_key_patterns !== array() ) && ( is_array( $dirty ) && $dirty !== array() ) ) {
+
+ // Add the extra pattern
+ $patterns = $this->variable_array_key_patterns;
+ $patterns[] = 'tax-hideeditbox-';
+
+ /**
+ * Allow altering the array with variable array key patterns
+ * @api array $patterns Array with the variable array key patterns
+ */
+ $patterns = apply_filters( 'wpseo_option_titles_variable_array_key_patterns', $patterns );
+
+ foreach ( $dirty as $key => $value ) {
+ foreach ( $patterns as $pattern ) {
+ if ( strpos( $key, $pattern ) === 0 && ! isset( $clean[ $key ] ) ) {
+ $clean[ $key ] = $value;
+ break;
+ }
+ }
+ }
+ }
+
+ return $clean;
+ }
+
+
+ } /* End of class WPSEO_Option_Titles */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo_rss
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Option_RSS' ) ) {
+
+ class WPSEO_Option_RSS extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo_rss';
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ * @internal Note: Some of the default values are added via the translate_defaults() method
+ */
+ protected $defaults = array(
+ 'rssbefore' => '', // text area
+ 'rssafter' => '', // text area
+ );
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Translate strings used in the option defaults
+ *
+ * @return void
+ */
+ public function translate_defaults() {
+ $this->defaults['rssafter'] = sprintf( __( 'The post %s appeared first on %s.', 'wordpress-seo' ), '%%POSTLINK%%', '%%BLOGLINK%%' );
+ }
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+ foreach ( $clean as $key => $value ) {
+ if ( isset( $dirty[ $key ] ) ) {
+ $clean[ $key ] = wp_kses_post( $dirty[ $key ] );
+ }
+ }
+
+ return $clean;
+ }
+
+ } /* End of class WPSEO_Option_RSS */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo_internallinks
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Option_InternalLinks' ) ) {
+
+ class WPSEO_Option_InternalLinks extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo_internallinks';
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ * @internal Note: Some of the default values are added via the translate_defaults() method
+ */
+ protected $defaults = array(
+ 'breadcrumbs-404crumb' => '', // text field
+ 'breadcrumbs-blog-remove' => false,
+ 'breadcrumbs-boldlast' => false,
+ 'breadcrumbs-archiveprefix' => '', // text field
+ 'breadcrumbs-enable' => false,
+ 'breadcrumbs-home' => '', // text field
+ 'breadcrumbs-prefix' => '', // text field
+ 'breadcrumbs-searchprefix' => '', // text field
+ 'breadcrumbs-sep' => '»', // text field
+
+ /**
+ * Uses enrich_defaults() to add more along the lines of:
+ * - 'post_types-' . $pt->name . '-maintax' => 0 / string
+ * - 'taxonomy-' . $tax->name . '-ptparent' => 0 / string
+ */
+ );
+
+ /**
+ * @var array Array of variable option name patterns for the option
+ */
+ protected $variable_array_key_patterns = array(
+ 'post_types-',
+ 'taxonomy-',
+ );
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Translate strings used in the option defaults
+ *
+ * @return void
+ */
+ public function translate_defaults() {
+ $this->defaults['breadcrumbs-404crumb'] = __( 'Error 404: Page not found', 'wordpress-seo' );
+ $this->defaults['breadcrumbs-archiveprefix'] = __( 'Archives for', 'wordpress-seo' );
+ $this->defaults['breadcrumbs-home'] = __( 'Home', 'wordpress-seo' );
+ $this->defaults['breadcrumbs-searchprefix'] = __( 'You searched for', 'wordpress-seo' );
+ }
+
+
+ /**
+ * Add dynamically created default options based on available post types and taxonomies
+ *
+ * @return void
+ */
+ public function enrich_defaults() {
+
+ // Retrieve all the relevant post type and taxonomy arrays
+ $post_type_names = get_post_types( array( 'public' => true ), 'names' );
+ $taxonomy_names_custom = get_taxonomies( array( 'public' => true, '_builtin' => false ), 'names' );
+
+ if ( $post_type_names !== array() ) {
+ foreach ( $post_type_names as $pt ) {
+ $pto_taxonomies = get_object_taxonomies( $pt, 'names' );
+ if ( $pto_taxonomies !== array() ) {
+ $this->defaults[ 'post_types-' . $pt . '-maintax' ] = 0; // select box
+ }
+ unset( $pto_taxonomies );
+ }
+ unset( $pt );
+ }
+
+ if ( $taxonomy_names_custom !== array() ) {
+ foreach ( $taxonomy_names_custom as $tax ) {
+ $this->defaults[ 'taxonomy-' . $tax . '-ptparent' ] = 0; // select box;
+ }
+ unset( $tax );
+ }
+ }
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+
+ $allowed_post_types = $this->get_allowed_post_types();
+
+ foreach ( $clean as $key => $value ) {
+
+ $switch_key = $this->get_switch_key( $key );
+
+ switch ( $switch_key ) {
+ /* text fields */
+ case 'breadcrumbs-404crumb':
+ case 'breadcrumbs-archiveprefix':
+ case 'breadcrumbs-home':
+ case 'breadcrumbs-prefix':
+ case 'breadcrumbs-searchprefix':
+ case 'breadcrumbs-sep':
+ if ( isset( $dirty[ $key ] ) ) {
+ $clean[ $key ] = wp_kses_post( $dirty[ $key ] );
+ }
+ break;
+
+
+ /* 'post_types-' . $pt->name . '-maintax' fields */
+ case 'post_types-':
+ $post_type = str_replace( array( 'post_types-', '-maintax' ), '', $key );
+ $taxonomies = get_object_taxonomies( $post_type, 'names' );
+
+ if ( isset( $dirty[ $key ] ) ) {
+ if ( $taxonomies !== array() && in_array( $dirty[ $key ], $taxonomies, true ) ) {
+ $clean[ $key ] = $dirty[ $key ];
+ } elseif ( (string) $dirty[ $key ] === '0' || (string) $dirty[ $key ] === '' ) {
+ $clean[ $key ] = 0;
+ } elseif ( sanitize_title_with_dashes( $dirty[ $key ] ) === $dirty[ $key ] ) {
+ // Allow taxonomies which may not be registered yet
+ $clean[ $key ] = $dirty[ $key ];
+ } else {
+ if ( isset( $old[ $key ] ) ) {
+ $clean[ $key ] = sanitize_title_with_dashes( $old[ $key ] );
+ }
+ if ( function_exists( 'add_settings_error' ) ) {
+ /* @todo [JRF => whomever] maybe change the untranslated $pt name in the
+ * error message to the nicely translated label ? */
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ sprintf( __( 'Please select a valid taxonomy for post type "%s"', 'wordpress-seo' ), $post_type ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ }
+ } elseif ( isset( $old[ $key ] ) ) {
+ $clean[ $key ] = sanitize_title_with_dashes( $old[ $key ] );
+ }
+ unset( $taxonomies, $post_type );
+ break;
+
+
+ /* 'taxonomy-' . $tax->name . '-ptparent' fields */
+ case 'taxonomy-':
+ if ( isset( $dirty[ $key ] ) ) {
+ if ( $allowed_post_types !== array() && in_array( $dirty[ $key ], $allowed_post_types, true ) ) {
+ $clean[ $key ] = $dirty[ $key ];
+ } elseif ( (string) $dirty[ $key ] === '0' || (string) $dirty[ $key ] === '' ) {
+ $clean[ $key ] = 0;
+ } elseif ( sanitize_key( $dirty[ $key ] ) === $dirty[ $key ] ) {
+ // Allow taxonomies which may not be registered yet
+ $clean[ $key ] = $dirty[ $key ];
+ } else {
+ if ( isset( $old[ $key ] ) ) {
+ $clean[ $key ] = sanitize_key( $old[ $key ] );
+ }
+ if ( function_exists( 'add_settings_error' ) ) {
+ /* @todo [JRF =? whomever] maybe change the untranslated $tax name in the
+ * error message to the nicely translated label ? */
+ $tax = str_replace( array( 'taxonomy-', '-ptparent' ), '', $key );
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $tax, // suffix-id for the error message box
+ sprintf( __( 'Please select a valid post type for taxonomy "%s"', 'wordpress-seo' ), $tax ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ unset( $tax );
+ }
+ }
+ } elseif ( isset( $old[ $key ] ) ) {
+ $clean[ $key ] = sanitize_key( $old[ $key ] );
+ }
+ break;
+
+
+ /* boolean fields */
+ case 'breadcrumbs-blog-remove':
+ case 'breadcrumbs-boldlast':
+ case 'breadcrumbs-enable':
+ default:
+ $clean[ $key ] = ( isset( $dirty[ $key ] ) ? self::validate_bool( $dirty[ $key ] ) : false );
+ break;
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Retrieve a list of the allowed post types as breadcrumb parent for a taxonomy
+ * Helper method for validation
+ * @internal don't make static as new types may still be registered
+ */
+ protected function get_allowed_post_types() {
+ $allowed_post_types = array();
+
+ $post_types = get_post_types( array( 'public' => true ), 'objects' );
+
+ if ( get_option( 'show_on_front' ) == 'page' && get_option( 'page_for_posts' ) > 0 ) {
+ $allowed_post_types[] = 'post';
+ }
+
+ if ( is_array( $post_types ) && $post_types !== array() ) {
+ foreach ( $post_types as $type ) {
+ if ( $type->has_archive ) {
+ $allowed_post_types[] = $type->name;
+ }
+ }
+ }
+
+ return $allowed_post_types;
+ }
+
+
+ /**
+ * Clean a given option value
+ *
+ * @param array $option_value Old (not merged with defaults or filtered) option value to
+ * clean according to the rules for this option
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return array Cleaned option
+ */
+ protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
+
+ /* Make sure the old fall-back defaults for empty option keys are now added to the option */
+ if ( isset( $current_version ) && version_compare( $current_version, '1.5.2.3', '<' ) ) {
+ if ( has_action( 'init', array( 'WPSEO_Options', 'bring_back_breadcrumb_defaults' ) ) === false ) {
+ add_action( 'init', array( 'WPSEO_Options', 'bring_back_breadcrumb_defaults' ), 3 );
+ }
+ }
+
+ /* Make sure the values of the variable option key options are cleaned as they
+ may be retained and would not be cleaned/validated then */
+ if ( is_array( $option_value ) && $option_value !== array() ) {
+
+ $allowed_post_types = $this->get_allowed_post_types();
+
+ foreach ( $option_value as $key => $value ) {
+ $switch_key = $this->get_switch_key( $key );
+
+ // Similar to validation routine - any changes made there should be made here too
+ switch ( $switch_key ) {
+ /* 'post_types-' . $pt->name . '-maintax' fields */
+ case 'post_types-':
+ $post_type = str_replace( array( 'post_types-', '-maintax' ), '', $key );
+ $taxonomies = get_object_taxonomies( $post_type, 'names' );
+
+ if ( $taxonomies !== array() && in_array( $value, $taxonomies, true ) ) {
+ $option_value[ $key ] = $value;
+ } elseif ( (string) $value === '0' || (string) $value === '' ) {
+ $option_value[ $key ] = 0;
+ } elseif ( sanitize_title_with_dashes( $value ) === $value ) {
+ // Allow taxonomies which may not be registered yet
+ $option_value[ $key ] = $value;
+ }
+ unset( $taxonomies, $post_type );
+ break;
+
+
+ /* 'taxonomy-' . $tax->name . '-ptparent' fields */
+ case 'taxonomy-':
+ if ( $allowed_post_types !== array() && in_array( $value, $allowed_post_types, true ) ) {
+ $option_value[ $key ] = $value;
+ } elseif ( (string) $value === '0' || (string) $value === '' ) {
+ $option_value[ $key ] = 0;
+ } elseif ( sanitize_key( $option_value[ $key ] ) === $option_value[ $key ] ) {
+ // Allow post types which may not be registered yet
+ $option_value[ $key ] = $value;
+ }
+ break;
+ }
+ }
+ }
+
+ return $option_value;
+ }
+
+ /**
+ * With the changes to v1.5, the defaults for some of the textual breadcrumb settings are added
+ * dynamically, but empty strings are allowed.
+ * This caused issues for people who left the fields empty on purpose relying on the defaults.
+ * This little routine fixes that.
+ * Needs to be run on 'init' hook at prio 3 to make sure the defaults are translated.
+ */
+ public function bring_back_defaults() {
+ $option = get_option( $this->option_name );
+
+ $values_to_bring_back = array(
+ 'breadcrumbs-404crumb',
+ 'breadcrumbs-archiveprefix',
+ 'breadcrumbs-home',
+ 'breadcrumbs-searchprefix',
+ 'breadcrumbs-sep',
+ );
+ foreach ( $values_to_bring_back as $key ) {
+ if ( $option[ $key ] === '' && $this->defaults[ $key ] !== '' ) {
+ $option[ $key ] = $this->defaults[ $key ];
+ }
+ }
+ update_option( $this->option_name, $option );
+ }
+
+ } /* End of class WPSEO_Option_InternalLinks */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo_xml
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Option_XML' ) ) {
+
+ class WPSEO_Option_XML extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo_xml';
+
+ /**
+ * @var string option group name for use in settings forms
+ */
+ public $group_name = 'yoast_wpseo_xml_sitemap_options';
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ */
+ protected $defaults = array(
+ 'disable_author_sitemap' => true,
+ 'disable_author_noposts' => true,
+ 'enablexmlsitemap' => true,
+ 'entries-per-page' => 1000,
+ 'xml_ping_yahoo' => false,
+ 'xml_ping_ask' => false,
+
+ /**
+ * Uses enrich_defaults to add more along the lines of:
+ * - 'user_role-' . $role_name . '-not_in_sitemap' => bool
+ * - 'post_types-' . $pt->name . '-not_in_sitemap' => bool
+ * - 'taxonomies-' . $tax->name . '-not_in_sitemap' => bool
+ */
+ );
+
+ /**
+ * @var array Array of variable option name patterns for the option
+ */
+ protected $variable_array_key_patterns = array(
+ 'user_role-',
+ 'post_types-',
+ 'taxonomies-',
+ );
+
+
+ /**
+ * Add the actions and filters for the option
+ *
+ * @todo [JRF => testers] Check if the extra actions below would run into problems if an option
+ * is updated early on and if so, change the call to schedule these for a later action on add/update
+ * instead of running them straight away
+ *
+ * @return \WPSEO_Option_XML
+ */
+ protected function __construct() {
+ parent::__construct();
+ add_action( 'update_option_' . $this->option_name, array( 'WPSEO_Options', 'clear_rewrites' ) );
+ }
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Add dynamically created default options based on available post types and taxonomies
+ *
+ * @return void
+ */
+ public function enrich_defaults() {
+
+ $user_roles = wpseo_get_roles();
+ $filtered_user_roles = apply_filters( 'wpseo_sitemaps_supported_user_roles', $user_roles );
+ if ( is_array( $filtered_user_roles ) && $filtered_user_roles !== array() ) {
+ foreach ( $filtered_user_roles as $role_name => $role_value ) {
+ $this->defaults['user_role-' . $role_name . '-not_in_sitemap'] = false;
+
+ unset( $user_role );
+ }
+ }
+ unset( $filtered_user_roles );
+
+ $post_type_names = get_post_types( array( 'public' => true ), 'names' );
+ $filtered_post_types = apply_filters( 'wpseo_sitemaps_supported_post_types', $post_type_names );
+
+ if ( is_array( $filtered_post_types ) && $filtered_post_types !== array() ) {
+ foreach ( $filtered_post_types as $pt ) {
+ if ( $pt !== 'attachment' ) {
+ $this->defaults[ 'post_types-' . $pt . '-not_in_sitemap' ] = false;
+ } else {
+ $this->defaults[ 'post_types-' . $pt . '-not_in_sitemap' ] = true;
+ }
+ }
+ unset( $pt );
+ }
+ unset( $filtered_post_types );
+
+ $taxonomy_objects = get_taxonomies( array( 'public' => true ), 'objects' );
+ $filtered_taxonomies = apply_filters( 'wpseo_sitemaps_supported_taxonomies', $taxonomy_objects );
+ if ( is_array( $filtered_taxonomies ) && $filtered_taxonomies !== array() ) {
+ foreach ( $filtered_taxonomies as $tax ) {
+ if ( isset( $tax->labels->name ) && trim( $tax->labels->name ) != '' ) {
+ $this->defaults[ 'taxonomies-' . $tax->name . '-not_in_sitemap' ] = false;
+ }
+ }
+ unset( $tax );
+ }
+ unset( $filtered_taxonomies );
+
+ }
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+
+ foreach ( $clean as $key => $value ) {
+ $switch_key = $this->get_switch_key( $key );
+
+ switch ( $switch_key ) {
+ /* integer fields */
+ case 'entries-per-page':
+ /* @todo [JRF/JRF => Yoast] add some more rules (minimum 50 or something
+ * - what should be the guideline?) and adjust error message */
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
+ $int = self::validate_int( $dirty[ $key ] );
+ if ( $int !== false && $int > 0 ) {
+ $clean[ $key ] = $int;
+ } else {
+ if ( isset( $old[ $key ] ) && $old[ $key ] !== '' ) {
+ $int = self::validate_int( $old[ $key ] );
+ if ( $int !== false && $int > 0 ) {
+ $clean[ $key ] = $int;
+ }
+ }
+ if ( function_exists( 'add_settings_error' ) ) {
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ sprintf( __( '"Max entries per sitemap page" should be a positive number, which %s is not. Please correct.', 'wordpress-seo' ), '<strong>' . esc_html( sanitize_text_field( $dirty[ $key ] ) ) . '</strong>' ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ }
+ unset( $int );
+ }
+ break;
+
+
+ /* boolean fields */
+ case 'disable_author_sitemap':
+ case 'disable_author_noposts':
+ case 'enablexmlsitemap':
+ case 'user_role-': /* 'user_role' . $role_name . '-not_in_sitemap' fields */
+ case 'post_types-': /* 'post_types-' . $pt->name . '-not_in_sitemap' fields */
+ case 'taxonomies-': /* 'taxonomies-' . $tax->name . '-not_in_sitemap' fields */
+ case 'xml_ping_yahoo':
+ case 'xml_ping_ask':
+ default:
+ $clean[ $key ] = ( isset( $dirty[ $key ] ) ? self::validate_bool( $dirty[ $key ] ) : false );
+ break;
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Clean a given option value
+ *
+ * @param array $option_value Old (not merged with defaults or filtered) option value to
+ * clean according to the rules for this option
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return array Cleaned option
+ */
+ protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
+
+ /* Make sure the values of the variable option key options are cleaned as they
+ may be retained and would not be cleaned/validated then */
+ if ( is_array( $option_value ) && $option_value !== array() ) {
+
+ foreach ( $option_value as $key => $value ) {
+ $switch_key = $this->get_switch_key( $key );
+
+ // Similar to validation routine - any changes made there should be made here too
+ switch ( $switch_key ) {
+ case 'user_role-': /* 'user_role-' . $role_name. '-not_in_sitemap' fields */
+ case 'post_types-': /* 'post_types-' . $pt->name . '-not_in_sitemap' fields */
+ case 'taxonomies-': /* 'taxonomies-' . $tax->name . '-not_in_sitemap' fields */
+ $option_value[ $key ] = self::validate_bool( $value );
+ break;
+ }
+ }
+ }
+
+ return $option_value;
+ }
+
+
+ } /* End of class WPSEO_Option_XML */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo_social
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Option_Social' ) ) {
+
+ class WPSEO_Option_Social extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo_social';
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ */
+ protected $defaults = array(
+ // Non-form fields, set via procedural code in admin/pages/social.php
+ 'fb_admins' => array(), // array of user id's => array( name => '', link => '' )
+ 'fbapps' => array(), // array of linked fb apps id's => fb app display names
+
+ // Non-form field, set via translate_defaults() and validate_option() methods
+ 'fbconnectkey' => '',
+ // Form fields:
+ 'facebook_site' => '', // text field
+ 'og_default_image' => '', // text field
+ 'og_frontpage_title' => '', // text field
+ 'og_frontpage_desc' => '', // text field
+ 'og_frontpage_image' => '', // text field
+ 'opengraph' => true,
+ 'googleplus' => false,
+ 'plus-publisher' => '', // text field
+ 'twitter' => false,
+ 'twitter_site' => '', // text field
+ 'twitter_card_type' => 'summary',
+ // Form field, but not always available:
+ 'fbadminapp' => 0, // app id from fbapps list
+ );
+
+ /**
+ * @var array Array of sub-options which should not be overloaded with multi-site defaults
+ */
+ public $ms_exclude = array(
+ /* privacy */
+ 'fb_admins',
+ 'fbapps',
+ 'fbconnectkey',
+ 'fbadminapp',
+ );
+
+
+ /**
+ * @var array Array of allowed twitter card types
+ * While we only have the options summary and summary_large_image in the
+ * interface now, we might change that at some point.
+ *
+ * @internal Uncomment any of these to allow them in validation *and* automatically add them as a choice
+ * in the options page
+ */
+ public static $twitter_card_types = array(
+ 'summary' => '',
+ 'summary_large_image' => '',
+ //'photo' => '',
+ //'gallery' => '',
+ //'app' => '',
+ //'player' => '',
+ //'product' => '',
+ );
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Translate/set strings used in the option defaults
+ *
+ * @return void
+ */
+ public function translate_defaults() {
+ /* Auto-magically set the fb connect key */
+ $this->defaults['fbconnectkey'] = self::get_fbconnectkey();
+
+ self::$twitter_card_types['summary'] = __( 'Summary', 'wordpress-seo' );
+ self::$twitter_card_types['summary_large_image'] = __( 'Summary with large image', 'wordpress-seo' );
+ }
+
+
+ /**
+ * Get a Facebook connect key for the blog
+ *
+ * @static
+ * @return string
+ */
+ public static function get_fbconnectkey() {
+ return md5( get_bloginfo( 'url' ) . rand() );
+ }
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+
+ foreach ( $clean as $key => $value ) {
+ switch ( $key ) {
+ /* Automagic Facebook connect key */
+ case 'fbconnectkey':
+ if ( ( isset( $old[ $key ] ) && $old[ $key ] !== '' ) && preg_match( '`^[a-f0-9]{32}$`', $old[ $key ] ) > 0 ) {
+ $clean[ $key ] = $old[ $key ];
+ } else {
+ $clean[ $key ] = self::get_fbconnectkey();
+ }
+ break;
+
+
+ /* Will not always exist in form */
+ case 'fb_admins':
+ if ( isset( $dirty[ $key ] ) && is_array( $dirty[ $key ] ) ) {
+ if ( $dirty[ $key ] === array() ) {
+ $clean[ $key ] = array();
+ } else {
+ foreach ( $dirty[ $key ] as $user_id => $fb_array ) {
+ /* @todo [JRF/JRF => Yoast/whomever] add user_id validation -
+ * are these WP user-ids or FB user-ids ? Probably FB user-ids,
+ * if so, find out the rules for FB user-ids
+ */
+ if ( is_array( $fb_array ) && $fb_array !== array() ) {
+ foreach ( $fb_array as $fb_key => $fb_value ) {
+ switch ( $fb_key ) {
+ case 'name':
+ /* @todo [JRF => whomever] add validation for name based
+ * on rules if there are any
+ * Input comes from: $_GET['userrealname'] */
+ $clean[ $key ][ $user_id ][ $fb_key ] = sanitize_text_field( $fb_value );
+ break;
+
+ case 'link':
+ $clean[ $key ][ $user_id ][ $fb_key ] = self::sanitize_url( $fb_value );
+ break;
+ }
+ }
+ }
+ }
+ unset( $user_id, $fb_array, $fb_key, $fb_value );
+ }
+ } elseif ( isset( $old[ $key ] ) && is_array( $old[ $key ] ) ) {
+ $clean[ $key ] = $old[ $key ];
+ }
+ break;
+
+
+ /* Will not always exist in form */
+ case 'fbapps':
+ if ( isset( $dirty[ $key ] ) && is_array( $dirty[ $key ] ) ) {
+ if ( $dirty[ $key ] === array() ) {
+ $clean[ $key ] = array();
+ } else {
+ $clean[ $key ] = array();
+ foreach ( $dirty[ $key ] as $app_id => $display_name ) {
+ if ( ctype_digit( (string) $app_id ) !== false ) {
+ $clean[ $key ][ $app_id ] = sanitize_text_field( $display_name );
+ }
+ }
+ unset( $app_id, $display_name );
+ }
+ } elseif ( isset( $old[ $key ] ) && is_array( $old[ $key ] ) ) {
+ $clean[ $key ] = $old[ $key ];
+ }
+ break;
+
+
+ /* text fields */
+ case 'og_frontpage_desc':
+ case 'og_frontpage_title':
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
+ $clean[ $key ] = self::sanitize_text_field( $dirty[ $key ] );
+ }
+ break;
+
+
+ /* url text fields - no ftp allowed */
+ case 'facebook_site':
+ case 'plus-publisher':
+ case 'og_default_image':
+ case 'og_frontpage_image':
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
+ $url = self::sanitize_url( $dirty[ $key ] );
+ if ( $url !== '' ) {
+ $clean[ $key ] = $url;
+ } else {
+ if ( isset( $old[ $key ] ) && $old[ $key ] !== '' ) {
+ $url = self::sanitize_url( $old[ $key ] );
+ if ( $url !== '' ) {
+ $clean[ $key ] = $url;
+ }
+ }
+ if ( function_exists( 'add_settings_error' ) ) {
+ $url = self::sanitize_url( $dirty[ $key ] );
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ sprintf( __( '%s does not seem to be a valid url. Please correct.', 'wordpress-seo' ), '<strong>' . esc_html( $url ) . '</strong>' ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ }
+ unset( $url );
+ }
+ break;
+
+
+ /* twitter user name */
+ case 'twitter_site':
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
+ $twitter_id = sanitize_text_field( ltrim( $dirty[ $key ], '@' ) );
+ /**
+ * From the Twitter documentation about twitter screen names:
+ * Typically a maximum of 15 characters long, but some historical accounts
+ * may exist with longer names.
+ * A username can only contain alphanumeric characters (letters A-Z, numbers 0-9)
+ * with the exception of underscores
+ * @link https://support.twitter.com/articles/101299-why-can-t-i-register-certain-usernames
+ * @link https://dev.twitter.com/docs/platform-objects/users
+ */
+ if ( preg_match( '`^[A-Za-z0-9_]{1,25}$`', $twitter_id ) ) {
+ $clean[ $key ] = $twitter_id;
+ } else {
+ if ( isset( $old[ $key ] ) && $old[ $key ] !== '' ) {
+ $twitter_id = sanitize_text_field( ltrim( $old[ $key ], '@' ) );
+ if ( preg_match( '`^[A-Za-z0-9_]{1,25}$`', $twitter_id ) ) {
+ $clean[ $key ] = $twitter_id;
+ }
+ }
+ if ( function_exists( 'add_settings_error' ) ) {
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ sprintf( __( '%s does not seem to be a valid Twitter user-id. Please correct.', 'wordpress-seo' ), '<strong>' . esc_html( sanitize_text_field( $dirty[ $key ] ) ) . '</strong>' ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ }
+ unset( $twitter_id );
+ }
+ break;
+
+ case 'twitter_card_type':
+ if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' && isset( self::$twitter_card_types[ $dirty[ $key ] ] ) ) {
+ $clean[ $key ] = $dirty[ $key ];
+ }
+ break;
+
+ /* boolean fields */
+ case 'googleplus':
+ case 'opengraph':
+ case 'twitter':
+ $clean[ $key ] = ( isset( $dirty[ $key ] ) ? self::validate_bool( $dirty[ $key ] ) : false );
+ break;
+ }
+ }
+
+ /**
+ * Only validate 'fbadminapp' once we are sure that 'fbapps' has been validated already.
+ * Will not always exist in form - if not available it means that fbapps is empty,
+ * so leave the clean default.
+ */
+ if ( ( isset( $dirty['fbadminapp'] ) && $dirty['fbadminapp'] != 0 ) && isset( $clean['fbapps'][ $dirty['fbadminapp'] ] ) ) {
+ $clean['fbadminapp'] = $dirty['fbadminapp'];
+ }
+
+
+ return $clean;
+ }
+
+
+ /**
+ * Clean a given option value
+ *
+ * @param array $option_value Old (not merged with defaults or filtered) option value to
+ * clean according to the rules for this option
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return array Cleaned option
+ */
+ protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
+
+ /* Move options from very old option to this one */
+ $old_option = null;
+ if ( isset( $all_old_option_values ) ) {
+ // Ok, we have an import
+ if ( isset( $all_old_option_values['wpseo_indexation'] ) && is_array( $all_old_option_values['wpseo_indexation'] ) && $all_old_option_values['wpseo_indexation'] !== array() ) {
+ $old_option = $all_old_option_values['wpseo_indexation'];
+ }
+ } else {
+ $old_option = get_option( 'wpseo_indexation' );
+ }
+
+ if ( is_array( $old_option ) && $old_option !== array() ) {
+ $move = array(
+ 'opengraph',
+ 'fb_adminid',
+ 'fb_appid',
+ );
+ foreach ( $move as $key ) {
+ if ( isset( $old_option[ $key ] ) && ! isset( $option_value[ $key ] ) ) {
+ $option_value[ $key ] = $old_option[ $key ];
+ }
+ }
+ unset( $move, $key );
+ }
+ unset( $old_option );
+
+
+ /* Clean some values which may not always be in form and may otherwise not be cleaned/validated */
+ if ( isset( $option_value['fbapps'] ) && ( is_array( $option_value['fbapps'] ) && $option_value['fbapps'] !== array() ) ) {
+ $fbapps = array();
+ foreach ( $option_value['fbapps'] as $app_id => $display_name ) {
+ if ( ctype_digit( (string) $app_id ) !== false ) {
+ $fbapps[ $app_id ] = sanitize_text_field( $display_name );
+ }
+ }
+ unset( $app_id, $display_name );
+
+ $option_value['fbapps'] = $fbapps;
+ }
+
+ return $option_value;
+ }
+
+
+ } /* End of class WPSEO_Option_Social */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo_ms
+ *******************************************************************/
+if ( is_multisite() && ! class_exists( 'WPSEO_Option_MS' ) ) {
+
+ /**
+ * Site option for Multisite installs only
+ *
+ * This class will not even be available/loaded if not on multisite, so none of the actions will
+ * register if not on multisite.
+ *
+ * Overloads a number of methods of the abstract class to ensure the use of the correct site_option
+ * WP functions.
+ */
+ class WPSEO_Option_MS extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo_ms';
+
+ /**
+ * @var string option group name for use in settings forms
+ */
+ public $group_name = 'yoast_wpseo_multisite_options';
+
+ /**
+ * @var bool whether to include the option in the return for WPSEO_Options::get_all()
+ */
+ public $include_in_all = false;
+
+ /**
+ * @var bool whether this option is only for when the install is multisite
+ */
+ public $multisite_only = true;
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ */
+ protected $defaults = array(
+ 'access' => 'admin',
+ 'defaultblog' => '', //numeric blog id or empty
+ );
+
+ /**
+ * @static
+ * @var array $allowed_access_options Available options for the 'access' setting
+ * Used for input validation
+ *
+ * @internal Important: Make sure the options added to the array here are in line with the keys
+ * for the options set for the select box in the admin/pages/network.php file
+ */
+ public static $allowed_access_options = array(
+ 'admin',
+ 'superadmin',
+ );
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Add filters to make sure that the option default is returned if the option is not set
+ *
+ * @return void
+ */
+ public function add_default_filters() {
+ // Don't change, needs to check for false as could return prio 0 which would evaluate to false
+ if ( has_filter( 'default_site_option_' . $this->option_name, array( $this, 'get_defaults' ) ) === false ) {
+ add_filter( 'default_site_option_' . $this->option_name, array( $this, 'get_defaults' ) );
+ }
+ }
+
+
+ /**
+ * Remove the default filters.
+ * Called from the validate() method to prevent failure to add new options
+ *
+ * @return void
+ */
+ public function remove_default_filters() {
+ remove_filter( 'default_site_option_' . $this->option_name, array( $this, 'get_defaults' ) );
+ }
+
+
+ /**
+ * Add filters to make sure that the option is merged with its defaults before being returned
+ *
+ * @return void
+ */
+ public function add_option_filters() {
+ // Don't change, needs to check for false as could return prio 0 which would evaluate to false
+ if ( has_filter( 'site_option_' . $this->option_name, array( $this, 'get_option' ) ) === false ) {
+ add_filter( 'site_option_' . $this->option_name, array( $this, 'get_option' ) );
+ }
+ }
+
+
+ /**
+ * Remove the option filters.
+ * Called from the clean_up methods to make sure we retrieve the original old option
+ *
+ * @return void
+ */
+ public function remove_option_filters() {
+ remove_filter( 'site_option_' . $this->option_name, array( $this, 'get_option' ) );
+ }
+
+
+ /* *********** METHODS influencing add_uption(), update_option() and saving from admin pages *********** */
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+
+ foreach ( $clean as $key => $value ) {
+ switch ( $key ) {
+ case 'access':
+ if ( isset( $dirty[ $key ] ) && in_array( $dirty[ $key ], self::$allowed_access_options, true ) ) {
+ $clean[ $key ] = $dirty[ $key ];
+ } elseif ( function_exists( 'add_settings_error' ) ) {
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ sprintf( __( '%s is not a valid choice for who should be allowed access to the WP SEO settings. Value reset to the default.', 'wordpress-seo' ), esc_html( sanitize_text_field( $dirty[ $key ] ) ) ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ break;
+
+
+ case 'defaultblog':
+ if ( isset( $dirty[ $key ] ) && ( $dirty[ $key ] !== '' && $dirty[ $key ] !== '-' ) ) {
+ $int = self::validate_int( $dirty[ $key ] );
+ if ( $int !== false && $int > 0 ) {
+ // Check if a valid blog number has been received
+ $exists = get_blog_details( $int, false );
+ if ( $exists && $exists->deleted == 0 ) {
+ $clean[ $key ] = $int;
+ } elseif ( function_exists( 'add_settings_error' ) ) {
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ esc_html__( 'The default blog setting must be the numeric blog id of the blog you want to use as default.', 'wordpress-seo' ) . '<br>' . sprintf( esc_html__( 'This must be an existing blog. Blog %s does not exist or has been marked as deleted.', 'wordpress-seo' ), '<strong>' . esc_html( sanitize_text_field( $dirty[ $key ] ) ) . '</strong>' ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ unset( $exists );
+ } elseif ( function_exists( 'add_settings_error' ) ) {
+ add_settings_error(
+ $this->group_name, // slug title of the setting
+ '_' . $key, // suffix-id for the error message box
+ esc_html__( 'The default blog setting must be the numeric blog id of the blog you want to use as default.', 'wordpress-seo' ) . '<br>' . esc_html__( 'No numeric value was received.', 'wordpress-seo' ), // the error message
+ 'error' // error type, either 'error' or 'updated'
+ );
+ }
+ unset( $int );
+ }
+ break;
+
+ default:
+ $clean[ $key ] = ( isset( $dirty[ $key ] ) ? self::validate_bool( $dirty[ $key ] ) : false );
+ break;
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Clean a given option value
+ *
+ * @param array $option_value Old (not merged with defaults or filtered) option value to
+ * clean according to the rules for this option
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return array Cleaned option
+ */
+ /*protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
+
+ return $option_value;
+ }*/
+
+
+ } /* End of class WPSEO_Option_MS */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Option: wpseo_taxonomy_meta
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Taxonomy_Meta' ) ) {
+
+ class WPSEO_Taxonomy_Meta extends WPSEO_Option {
+
+ /**
+ * @var string option name
+ */
+ public $option_name = 'wpseo_taxonomy_meta';
+
+ /**
+ * @var bool whether to include the option in the return for WPSEO_Options::get_all()
+ */
+ public $include_in_all = false;
+
+ /**
+ * @var array Array of defaults for the option
+ * Shouldn't be requested directly, use $this->get_defaults();
+ * @internal Important: in contrast to most defaults, the below array format is
+ * very bare. The real option is in the format [taxonomy_name][term_id][...]
+ * where [...] is any of the $defaults_per_term options shown below.
+ * This is of course taken into account in the below methods.
+ */
+ protected $defaults = array();
+
+
+ /**
+ * @static
+ * @var string Option name - same as $option_name property, but now also available to static
+ * methods
+ */
+ public static $name;
+
+ /**
+ * @static
+ * @var array Array of defaults for individual taxonomy meta entries
+ */
+ public static $defaults_per_term = array(
+ 'wpseo_title' => '',
+ 'wpseo_desc' => '',
+ 'wpseo_metakey' => '',
+ 'wpseo_canonical' => '',
+ 'wpseo_bctitle' => '',
+ 'wpseo_noindex' => 'default',
+ 'wpseo_sitemap_include' => '-',
+ );
+
+ /**
+ * @static
+ * @var array Available index options
+ * Used for form generation and input validation
+ * @internal Labels (translation) added on admin_init via WPSEO_Taxonomy::translate_meta_options()
+ */
+ public static $no_index_options = array(
+ 'default' => '',
+ 'index' => '',
+ 'noindex' => '',
+ );
+
+ /**
+ * @static
+ * @var array Available sitemap include options
+ * Used for form generation and input validation
+ * @internal Labels (translation) added on admin_init via WPSEO_Taxonomy::translate_meta_options()
+ */
+ public static $sitemap_include_options = array(
+ '-' => '',
+ 'always' => '',
+ 'never' => '',
+ );
+
+
+ /**
+ * Add the actions and filters for the option
+ *
+ * @todo [JRF => testers] Check if the extra actions below would run into problems if an option
+ * is updated early on and if so, change the call to schedule these for a later action on add/update
+ * instead of running them straight away
+ *
+ * @return \WPSEO_Taxonomy_Meta
+ */
+ protected function __construct() {
+ parent::__construct();
+
+ /* On succesfull update/add of the option, flush the W3TC cache */
+ add_action( 'add_option_' . $this->option_name, array( 'WPSEO_Options', 'flush_w3tc_cache' ) );
+ add_action( 'update_option_' . $this->option_name, array( 'WPSEO_Options', 'flush_w3tc_cache' ) );
+ }
+
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ self::$name = self::$instance->option_name;
+ }
+
+ return self::$instance;
+ }
+
+
+ public function enrich_defaults() {
+ $extra_defaults_per_term = apply_filters( 'wpseo_add_extra_taxmeta_term_defaults', array() );
+ if ( is_array( $extra_defaults_per_term ) ) {
+ self::$defaults_per_term = array_merge( $extra_defaults_per_term, self::$defaults_per_term );
+ }
+ }
+
+
+ /**
+ * Helper method - Combines a fixed array of default values with an options array
+ * while filtering out any keys which are not in the defaults array.
+ *
+ * @static
+ *
+ * @param string $option_key Option name of the option we're doing the merge for
+ * @param array $options (Optional) Current options
+ * - if not set, the option defaults for the $option_key will be returned.
+ *
+ * @return array Combined and filtered options array.
+ */
+ /*public function array_filter_merge( $option_key, $options = null ) {
+
+ $defaults = $this->get_defaults( $option_key );
+
+ if ( ! isset( $options ) || $options === false ) {
+ return $defaults;
+ }
+
+ / *
+ @internal Adding the defaults to all taxonomy terms each time the option is retrieved
+ will be quite inefficient if there are a lot of taxonomy terms
+ As long as taxonomy_meta is only retrieved via methods in this class, we shouldn't need this
+
+ $options = (array) $options;
+ $filtered = array();
+
+ if ( $options !== array() ) {
+ foreach ( $options as $taxonomy => $terms ) {
+ if ( is_array( $terms ) && $terms !== array() ) {
+ foreach ( $terms as $id => $term_meta ) {
+ foreach ( self::$defaults_per_term as $name => $default ) {
+ if ( isset( $options[ $taxonomy ][ $id ][ $name ] ) ) {
+ $filtered[ $taxonomy ][ $id ][ $name ] = $options[ $taxonomy ][ $id ][ $name ];
+ }
+ else {
+ $filtered[ $name ] = $default;
+ }
+ }
+ }
+ }
+ }
+ unset( $taxonomy, $terms, $id, $term_meta, $name, $default );
+ }
+ // end of may be remove
+
+ return $filtered;
+ * /
+
+ return (array) $options;
+ }*/
+
+
+ /**
+ * Validate the option
+ *
+ * @param array $dirty New value for the option
+ * @param array $clean Clean value for the option, normally the defaults
+ * @param array $old Old value of the option
+ *
+ * @return array Validated clean value for the option to be saved to the database
+ */
+ protected function validate_option( $dirty, $clean, $old ) {
+
+ /* Prevent complete validation (which can be expensive when there are lots of terms)
+ if only one item has changed and has already been validated */
+ if ( isset( $dirty['wpseo_already_validated'] ) && $dirty['wpseo_already_validated'] === true ) {
+ unset( $dirty['wpseo_already_validated'] );
+
+ return $dirty;
+ }
+
+
+ foreach ( $dirty as $taxonomy => $terms ) {
+ /* Don't validate taxonomy - may not be registered yet and we don't want to remove valid ones */
+ if ( is_array( $terms ) && $terms !== array() ) {
+ foreach ( $terms as $term_id => $meta_data ) {
+ /* Only validate term if the taxonomy exists */
+ if ( taxonomy_exists( $taxonomy ) && get_term_by( 'id', $term_id, $taxonomy ) === false ) {
+ /* Is this term id a special case ? */
+ if ( has_filter( 'wpseo_tax_meta_special_term_id_validation_' . $term_id ) !== false ) {
+ $clean[ $taxonomy ][ $term_id ] = apply_filters( 'wpseo_tax_meta_special_term_id_validation_' . $term_id, $meta_data, $taxonomy, $term_id );
+ }
+ continue;
+ }
+
+ if ( is_array( $meta_data ) && $meta_data !== array() ) {
+ /* Validate meta data */
+ $old_meta = self::get_term_meta( $term_id, $taxonomy );
+ $meta_data = self::validate_term_meta_data( $meta_data, $old_meta );
+ if ( $meta_data !== array() ) {
+ $clean[ $taxonomy ][ $term_id ] = $meta_data;
+ }
+ }
+
+ // Deal with special cases (for when taxonomy doesn't exist yet)
+ if ( ! isset( $clean[ $taxonomy ][ $term_id ] ) && has_filter( 'wpseo_tax_meta_special_term_id_validation_' . $term_id ) !== false ) {
+ $clean[ $taxonomy ][ $term_id ] = apply_filters( 'wpseo_tax_meta_special_term_id_validation_' . $term_id, $meta_data, $taxonomy, $term_id );
+ }
+ }
+ }
+ }
+
+ return $clean;
+ }
+
+
+ /**
+ * Validate the meta data for one individual term and removes default values (no need to save those)
+ *
+ * @static
+ *
+ * @param array $meta_data New values
+ * @param array $old_meta The original values
+ *
+ * @return array Validated and filtered value
+ */
+ public static function validate_term_meta_data( $meta_data, $old_meta ) {
+
+ $clean = self::$defaults_per_term;
+ $meta_data = array_map( array( __CLASS__, 'trim_recursive' ), $meta_data );
+
+ if ( ! is_array( $meta_data ) || $meta_data === array() ) {
+ return $clean;
+ }
+
+ foreach ( $clean as $key => $value ) {
+ switch ( $key ) {
+
+ case 'wpseo_noindex':
+ if ( isset( $meta_data[ $key ] ) ) {
+ if ( isset( self::$no_index_options[ $meta_data[ $key ] ] ) ) {
+ $clean[ $key ] = $meta_data[ $key ];
+ }
+ } elseif ( isset( $old_meta[ $key ] ) ) {
+ // Retain old value if field currently not in use
+ $clean[ $key ] = $old_meta[ $key ];
+ }
+ break;
+
+ case 'wpseo_sitemap_include':
+ if ( isset( $meta_data[ $key ] ) && isset( self::$sitemap_include_options[ $meta_data[ $key ] ] ) ) {
+ $clean[ $key ] = $meta_data[ $key ];
+ }
+ break;
+
+ case 'wpseo_canonical':
+ if ( isset( $meta_data[ $key ] ) && $meta_data[ $key ] !== '' ) {
+ $url = self::sanitize_url( $meta_data[ $key ] );
+ if ( $url !== '' ) {
+ $clean[ $key ] = $url;
+ }
+ }
+ break;
+
+ case 'wpseo_metakey':
+ case 'wpseo_bctitle':
+ if ( isset( $meta_data[ $key ] ) ) {
+ $clean[ $key ] = self::sanitize_text_field( stripslashes( $meta_data[ $key ] ) );
+ } elseif ( isset( $old_meta[ $key ] ) ) {
+ // Retain old value if field currently not in use
+ $clean[ $key ] = $old_meta[ $key ];
+ }
+ break;
+
+ case 'wpseo_title':
+ case 'wpseo_desc':
+ default:
+ if ( isset( $meta_data[ $key ] ) && is_string( $meta_data[ $key ] ) ) {
+ $clean[ $key ] = self::sanitize_text_field( stripslashes( $meta_data[ $key ] ) );
+ }
+ break;
+ }
+
+ $clean[ $key ] = apply_filters( 'wpseo_sanitize_tax_meta_' . $key, $clean[ $key ], ( isset( $meta_data[ $key ] ) ? $meta_data[ $key ] : null ), ( isset( $old_meta[ $key ] ) ? $old_meta[ $key ] : null ) );
+ }
+
+ // Only save the non-default values
+ return array_diff_assoc( $clean, self::$defaults_per_term );
+ }
+
+
+ /**
+ * Clean a given option value
+ * - Convert old option values to new
+ * - Fixes strings which were escaped (should have been sanitized - escaping is for output)
+ *
+ * @param array $option_value Old (not merged with defaults or filtered) option value to
+ * clean according to the rules for this option
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ * @param array $all_old_option_values (optional) Only used when importing old options to have
+ * access to the real old values, in contrast to the saved ones
+ *
+ * @return array Cleaned option
+ */
+ protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
+
+ /* Clean up old values and remove empty arrays */
+ if ( is_array( $option_value ) && $option_value !== array() ) {
+
+ foreach ( $option_value as $taxonomy => $terms ) {
+
+ if ( is_array( $terms ) && $terms !== array() ) {
+
+ foreach ( $terms as $term_id => $meta_data ) {
+ if ( ! is_array( $meta_data ) || $meta_data === array() ) {
+ // Remove empty term arrays
+ unset( $option_value[ $taxonomy ][ $term_id ] );
+ } else {
+ foreach ( $meta_data as $key => $value ) {
+
+ switch ( $key ) {
+ case 'noindex':
+ if ( $value === 'on' ) {
+ // Convert 'on' to 'noindex'
+ $option_value[ $taxonomy ][ $term_id ][ $key ] = 'noindex';
+ }
+ break;
+
+ case 'canonical':
+ case 'wpseo_metakey':
+ case 'wpseo_bctitle':
+ case 'wpseo_title':
+ case 'wpseo_desc':
+ // @todo [JRF => whomever] needs checking, I don't have example data [JRF]
+ if ( $value !== '' ) {
+ // Fix incorrectly saved (encoded) canonical urls and texts
+ $option_value[ $taxonomy ][ $term_id ][ $key ] = wp_specialchars_decode( stripslashes( $value ), ENT_QUOTES );
+ }
+ break;
+
+ default:
+ // @todo [JRF => whomever] needs checking, I don't have example data [JRF]
+ if ( $value !== '' ) {
+ // Fix incorrectly saved (escaped) text strings
+ $option_value[ $taxonomy ][ $term_id ][ $key ] = wp_specialchars_decode( $value, ENT_QUOTES );
+ }
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ // Remove empty taxonomy arrays
+ unset( $option_value[ $taxonomy ] );
+ }
+ }
+ }
+
+ return $option_value;
+ }
+
+
+ /**
+ * Retrieve a taxonomy term's meta value(s).
+ *
+ * @static
+ *
+ * @param mixed $term Term to get the meta value for
+ * either (string) term name, (int) term id or (object) term
+ * @param string $taxonomy Name of the taxonomy to which the term is attached
+ * @param string $meta (optional) Meta value to get (without prefix)
+ *
+ * @return mixed|bool Value for the $meta if one is given, might be the default
+ * If no meta is given, an array of all the meta data for the term
+ * False if the term does not exist or the $meta provided is invalid
+ */
+ public static function get_term_meta( $term, $taxonomy, $meta = null ) {
+ /* Figure out the term id */
+ if ( is_int( $term ) ) {
+ $term = get_term_by( 'id', $term, $taxonomy );
+ } elseif ( is_string( $term ) ) {
+ $term = get_term_by( 'slug', $term, $taxonomy );
+ }
+
+ if ( is_object( $term ) && isset( $term->term_id ) ) {
+ $term_id = $term->term_id;
+ } else {
+ return false;
+ }
+
+
+ $tax_meta = get_option( self::$name );
+
+ /* If we have data for the term, merge with defaults for complete array, otherwise set defaults */
+ if ( isset( $tax_meta[ $taxonomy ][ $term_id ] ) ) {
+ $tax_meta = array_merge( self::$defaults_per_term, $tax_meta[ $taxonomy ][ $term_id ] );
+ } else {
+ $tax_meta = self::$defaults_per_term;
+ }
+
+ /* Either return the complete array or a single value from it or false if the value does not exist
+ (shouldn't happen after merge with defaults, indicates typo in request) */
+ if ( ! isset( $meta ) ) {
+ return $tax_meta;
+ } else {
+ if ( isset( $tax_meta[ 'wpseo_' . $meta ] ) ) {
+ return $tax_meta[ 'wpseo_' . $meta ];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ } /* End of class WPSEO_Taxonomy_Meta */
+
+} /* End of class-exists wrapper */
+
+
+/*******************************************************************
+ * Overall Option Management
+ *******************************************************************/
+if ( ! class_exists( 'WPSEO_Options' ) ) {
+ /**
+ * Overal Option Management class
+ *
+ * Instantiates all the options and offers a number of utility methods to work with the options
+ */
+ class WPSEO_Options {
+
+
+ /**
+ * @static
+ * @var array Options this class uses
+ * Array format: (string) option_name => (string) name of concrete class for the option
+ */
+ public static $options = array(
+ 'wpseo' => 'WPSEO_Option_Wpseo',
+ 'wpseo_permalinks' => 'WPSEO_Option_Permalinks',
+ 'wpseo_titles' => 'WPSEO_Option_Titles',
+ 'wpseo_social' => 'WPSEO_Option_Social',
+ 'wpseo_rss' => 'WPSEO_Option_RSS',
+ 'wpseo_internallinks' => 'WPSEO_Option_InternalLinks',
+ 'wpseo_xml' => 'WPSEO_Option_XML',
+ 'wpseo_ms' => 'WPSEO_Option_MS',
+ 'wpseo_taxonomy_meta' => 'WPSEO_Taxonomy_Meta',
+ );
+
+ protected static $option_instances;
+
+ /**
+ * @var object Instance of this class
+ */
+ protected static $instance;
+
+
+ /**
+ * Instantiate all the WPSEO option management classes
+ */
+ protected function __construct() {
+ foreach ( self::$options as $option_name => $option_class ) {
+ if ( class_exists( $option_class ) ) {
+ self::$option_instances[ $option_name ] = call_user_func( array( $option_class, 'get_instance' ) );
+ }
+ else {
+ unset( self::$options[ $option_name ] );
+ }
+ }
+ }
+
+ /**
+ * Get the singleton instance of this class
+ *
+ * @return object
+ */
+ public static function get_instance() {
+ if ( ! ( self::$instance instanceof self ) ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+
+ /**
+ * Check whether the current user is allowed to access the configuration.
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this should maybe
+ * be moved to a general WPSEO_Utils class. Obviously all calls to this method should be
+ * adjusted in that case.
+ *
+ * @return boolean
+ */
+ public static function grant_access() {
+ if ( ! is_multisite() ) {
+ return true;
+ }
+
+ $options = get_site_option( 'wpseo_ms' );
+ if ( $options['access'] === 'admin' && current_user_can( 'manage_options' ) ) {
+ return true;
+ }
+
+ if ( $options['access'] === 'superadmin' && ! is_super_admin() ) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Get the group name of an option for use in the settings form
+ *
+ * @param string $option_name the option for which you want to retrieve the option group name
+ *
+ * @return string|bool
+ */
+ public static function get_group_name( $option_name ) {
+ if ( isset( self::$option_instances[ $option_name ] ) ) {
+ return self::$option_instances[ $option_name ]->group_name;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Get a specific default value for an option
+ *
+ * @param string $option_name The option for which you want to retrieve a default
+ * @param string $key The key within the option who's default you want
+ *
+ * @return mixed
+ */
+ public static function get_default( $option_name, $key ) {
+ if ( isset( self::$option_instances[ $option_name ] ) ) {
+ $defaults = self::$option_instances[ $option_name ]->get_defaults();
+ if ( isset( $defaults[ $key ] ) ) {
+ return $defaults[ $key ];
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Update a site_option
+ *
+ * @param string $option_name The option name of the option to save
+ * @param mized $value The new value for the option
+ *
+ * @return bool
+ */
+ public static function update_site_option( $option_name, $value ) {
+ if ( is_network_admin() && isset( self::$option_instances[ $option_name ] ) ) {
+ return self::$option_instances[ $option_name ]->update_site_option( $value );
+ }
+ else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Get the instantiated option instance
+ *
+ * @param string $option_name The option for which you want to retrieve the instance
+ *
+ * @return object|bool
+ */
+ public static function get_option_instance( $option_name ) {
+ if ( isset( self::$option_instances[ $option_name ] ) ) {
+ return self::$option_instances[ $option_name ];
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Retrieve an array of the options which should be included in get_all() and reset().
+ *
+ * @static
+ * @return array Array of option names
+ */
+ public static function get_option_names() {
+ static $option_names = array();
+
+ if ( $option_names === array() ) {
+ foreach ( self::$option_instances as $option_name => $option_object ) {
+ if ( $option_object->include_in_all === true ) {
+ $option_names[] = $option_name;
+ }
+ }
+ $option_names = apply_filters( 'wpseo_options', $option_names );
+ }
+
+ return $option_names;
+ }
+
+
+ /**
+ * Retrieve all the options for the SEO plugin in one go.
+ *
+ * @todo [JRF] see if we can get some extra efficiency for this one, though probably not as options may
+ * well change between calls (enriched defaults and such)
+ *
+ * @static
+ * @return array Array combining the values of (nearly) all the options
+ */
+ public static function get_all() {
+ $all_options = array();
+ $option_names = self::get_option_names();
+
+ if ( is_array( $option_names ) && $option_names !== array() ) {
+ foreach ( $option_names as $option_name ) {
+ if ( self::$option_instances[ $option_name ]->multisite_only !== true ) {
+ $option = get_option( $option_name );
+ }
+ else {
+ $option = get_site_option( $option_name );
+ }
+
+ if ( is_array( $option ) && $option !== array() ) {
+ $all_options = array_merge( $all_options, $option );
+ }
+ }
+ }
+
+ return $all_options;
+ }
+
+
+ /**
+ * Run the clean up routine for one or all options
+ *
+ * @param array|string $option_name (optional) the option you want to clean or an array of
+ * option names for the options you want to clean.
+ * If not set, all options will be cleaned
+ * @param string $current_version (optional) Version from which to upgrade, if not set,
+ * version specific upgrades will be disregarded
+ *
+ * @return void
+ */
+ public static function clean_up( $option_name = null, $current_version = null ) {
+ if ( isset( $option_name ) && is_string( $option_name ) && $option_name !== '' ) {
+ if ( isset( self::$option_instances[ $option_name ] ) ) {
+ self::$option_instances[ $option_name ]->clean( $current_version );
+ }
+ } elseif ( isset( $option_name ) && is_array( $option_name ) && $option_name !== array() ) {
+ foreach ( $option_name as $option ) {
+ if ( isset( self::$option_instances[ $option ] ) ) {
+ self::$option_instances[ $option ]->clean( $current_version );
+ }
+ }
+ } else {
+ foreach ( self::$option_instances as $instance ) {
+ $instance->clean( $current_version );
+ }
+
+ // If we've done a full clean-up, we can safely remove this really old option
+ delete_option( 'wpseo_indexation' );
+ }
+ }
+
+
+ /**
+ * Check that all options exist in the database and add any which don't
+ *
+ * @return void
+ */
+ public static function ensure_options_exist() {
+ foreach ( self::$option_instances as $instance ) {
+ $instance->maybe_add_option();
+ }
+ }
+
+
+ /**
+ * Correct the inadvertent removal of the fallback to default values from the breadcrumbs
+ *
+ * @since 1.5.2.3
+ */
+ public static function bring_back_breadcrumb_defaults() {
+ if ( isset( self::$option_instances['wpseo_internallinks'] ) ) {
+ self::$option_instances['wpseo_internallinks']->bring_back_defaults();
+ }
+ }
+
+
+ /**
+ * Initialize some options on first install/activate/reset
+ *
+ * @static
+ * @return void
+ */
+ public static function initialize() {
+ /* Make sure title_test and description_test function are available even when called
+ from the isolated activation */
+ require_once( WPSEO_PATH . 'inc/wpseo-non-ajax-functions.php' );
+
+// wpseo_title_test();
+ wpseo_description_test();
+
+ /* Force WooThemes to use WordPress SEO data. */
+ if ( function_exists( 'woo_version_init' ) ) {
+ update_option( 'seo_woo_use_third_party_data', 'true' );
+ }
+ }
+
+
+ /**
+ * Reset all options to their default values and rerun some tests
+ *
+ * @static
+ * @return void
+ */
+ public static function reset() {
+ if ( ! is_multisite() ) {
+ $option_names = self::get_option_names();
+ if ( is_array( $option_names ) && $option_names !== array() ) {
+ foreach ( $option_names as $option_name ) {
+ delete_option( $option_name );
+ update_option( $option_name, get_option( $option_name ) );
+ }
+ }
+ }
+ else {
+ // Reset MS blog based on network default blog setting
+ self::reset_ms_blog( get_current_blog_id() );
+ }
+
+ self::initialize();
+ }
+
+
+ /**
+ * Initialize default values for a new multisite blog
+ *
+ * @static
+ *
+ * @param bool $force_init Whether to always do the initialization routine (title/desc test)
+ * @return void
+ */
+ public static function maybe_set_multisite_defaults( $force_init = false ) {
+ $option = get_option( 'wpseo' );
+
+ if ( is_multisite() ) {
+ if ( $option['ms_defaults_set'] === false ) {
+ self::reset_ms_blog( get_current_blog_id() );
+ self::initialize();
+ }
+ else if ( $force_init === true ) {
+ self::initialize();
+ }
+ }
+ }
+
+
+ /**
+ * Reset all options for a specific multisite blog to their default values based upon a
+ * specified default blog if one was chosen on the network page or the plugin defaults if it was not
+ *
+ * @static
+ *
+ * @param int|string $blog_id Blog id of the blog for which to reset the options
+ *
+ * @return void
+ */
+ public static function reset_ms_blog( $blog_id ) {
+ if ( is_multisite() ) {
+ $options = get_site_option( 'wpseo_ms' );
+ $option_names = self::get_option_names();
+
+ if ( is_array( $option_names ) && $option_names !== array() ) {
+ $base_blog_id = $blog_id;
+ if ( $options['defaultblog'] !== '' && $options['defaultblog'] != 0 ) {
+ $base_blog_id = $options['defaultblog'];
+ }
+
+ foreach ( $option_names as $option_name ) {
+ delete_blog_option( $blog_id, $option_name );
+
+ $new_option = get_blog_option( $base_blog_id, $option_name );
+
+ /* Remove sensitive, theme dependent and site dependent info */
+ if ( isset( self::$option_instances[ $option_name ] ) && self::$option_instances[ $option_name ]->ms_exclude !== array() ) {
+ foreach ( self::$option_instances[ $option_name ]->ms_exclude as $key ) {
+ unset( $new_option[ $key ] );
+ }
+ }
+
+ if ( $option_name === 'wpseo' ) {
+ $new_option['ms_defaults_set'] = true;
+ }
+
+ update_blog_option( $blog_id, $option_name, $new_option );
+ }
+ }
+ }
+ }
+
+
+ /* ************** METHODS FOR ACTIONS TO TAKE ON CERTAIN OPTION UPDATES ****************/
+
+ /**
+ * (Un-)schedule the yoast tracking cronjob if the tracking option has changed
+ *
+ * @internal Better to be done here, rather than in the Yoast_Tracking class as
+ * class-tracking.php may not be loaded and might not need to be (lean loading).
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this should maybe
+ * be moved to a general WPSEO_Utils class. Obviously all calls to this method should be
+ * adjusted in that case.
+ *
+ * @todo - [JRF => Yoast] check if this has any impact on other Yoast plugins which may
+ * use the same tracking schedule hook. If so, maybe get any other yoast plugin options,
+ * check for the tracking status and unschedule based on the combined status.
+ *
+ * @static
+ *
+ * @param mixed $disregard Not needed - passed by add/update_option action call
+ * Option name if option was added, old value if option was updated
+ * @param array $value The (new/current) value of the wpseo option
+ * @param bool $force_unschedule Whether to force an unschedule (i.e. on deactivate)
+ *
+ * @return void
+ */
+ public static function schedule_yoast_tracking( $disregard, $value, $force_unschedule = false ) {
+ $current_schedule = wp_next_scheduled( 'yoast_tracking' );
+
+ if ( $force_unschedule !== true && ( $value['yoast_tracking'] === true && $current_schedule === false ) ) {
+ // The tracking checks daily, but only sends new data every 7 days.
+ wp_schedule_event( time(), 'daily', 'yoast_tracking' );
+ } elseif ( $force_unschedule === true || ( $value['yoast_tracking'] === false && $current_schedule !== false ) ) {
+ wp_clear_scheduled_hook( 'yoast_tracking' );
+ }
+ }
+
+
+ /**
+ * Clears the WP or W3TC cache depending on which is used
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this should maybe
+ * be moved to a general WPSEO_Utils class. Obviously all calls to this method should be
+ * adjusted in that case.
+ *
+ * @static
+ * @return void
+ */
+ public static function clear_cache() {
+ if ( function_exists( 'w3tc_pgcache_flush' ) ) {
+ w3tc_pgcache_flush();
+ } elseif ( function_exists( 'wp_cache_clear_cache' ) ) {
+ wp_cache_clear_cache();
+ }
+ }
+
+
+ /**
+ * Flush W3TC cache after succesfull update/add of taxonomy meta option
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this should maybe
+ * be moved to a general WPSEO_Utils class. Obviously all calls to this method should be
+ * adjusted in that case.
+ *
+ * @todo [JRF => whomever] check the above and this function to see if they should be combined or really
+ * do something significantly different
+ *
+ * @static
+ * @return void
+ */
+ public static function flush_w3tc_cache() {
+ if ( defined( 'W3TC_DIR' ) && function_exists( 'w3tc_objectcache_flush' ) ) {
+ w3tc_objectcache_flush();
+ }
+ }
+
+
+ /**
+ * Clear rewrite rules
+ *
+ * @todo [JRF => whomever] when someone would reorganize the classes, this should maybe
+ * be moved to a general WPSEO_Utils class. Obviously all calls to this method should be
+ * adjusted in that case.
+ *
+ * @static
+ * @return void
+ */
+ public static function clear_rewrites() {
+ delete_option( 'rewrite_rules' );
+ }
+
+
+ } /* End of class WPSEO_Options */
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+/**
+ * @package Internals
+ */
+
+// Avoid direct calls to this file
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! class_exists( 'WPSEO_Replace_Vars' ) ) {
+ /**
+ * @package WordPress\Plugins\WPSeo
+ * @subpackage Internals
+ * @since 1.5.4
+ * @version 1.5.4
+ *
+ */
+ class WPSEO_Replace_Vars {
+
+ /**
+ * @var array Default post/page/cpt information
+ */
+ protected $defaults = array(
+ 'ID' => '',
+ 'name' => '',
+ 'post_author' => '',
+ 'post_content' => '',
+ 'post_date' => '',
+ 'post_excerpt' => '',
+ 'post_modified' => '',
+ 'post_title' => '',
+ 'taxonomy' => '',
+ 'term_id' => '',
+ 'term404' => '',
+ );
+
+ /**
+ * @var object Current post/page/cpt information
+ */
+ protected $args;
+
+ /**
+ * @var array Help texts for use in WPSEO -> Titles and Meta's help tabs
+ */
+ protected static $help_texts = array();
+
+ /**
+ * @var array Register of additional variable replacements registered by other plugins/themes
+ */
+ protected static $external_replacements = array();
+
+
+ /**
+ * Constructor
+ *
+ * @return \WPSEO_Replace_Vars
+ */
+ public function __construct() {
+ }
+
+
+ /**
+ * Setup the help texts and external replacements as statics so they will be available to all instances
+ */
+ public static function setup_statics_once() {
+ //
+ if ( self::$help_texts === array() ) {
+ self::set_basic_help_texts();
+ self::set_advanced_help_texts();
+ }
+
+ if ( self::$external_replacements === array() ) {
+ /**
+ * Action: 'wpseo_register_extra_replacements' - Allows for registration of additional
+ * variables to replace
+ */
+ do_action( 'wpseo_register_extra_replacements' );
+ }
+ }
+
+
+ /**
+ * Register new replacement %%variables%%
+ * For use by other plugins/themes to register extra variables
+ *
+ * @see wpseo_register_var_replacement() for a usage example
+ *
+ * @param string $var The name of the variable to replace, i.e. '%%var%%'
+ * - the surrounding %% are optional
+ * @param mixed $replace_function Function or method to call to retrieve the replacement value for the variable
+ * Uses the same format as add_filter/add_action function parameter and
+ * should *return* the replacement value. DON'T echo it!
+ * @param string $type Type of variable: 'basic' or 'advanced', defaults to 'advanced'
+ * @param string $help_text Help text to be added to the help tab for this variable
+ *
+ * @return bool Whether the replacement function was succesfully registered
+ */
+ public static function register_replacement( $var, $replace_function, $type = 'advanced', $help_text = '' ) {
+ $success = false;
+
+ if ( is_string( $var ) && $var !== '' ) {
+ $var = self::remove_var_delimiter( $var );
+
+ if ( preg_match( '`^[A-Z0-9_-]+$`i', $var ) === false ) {
+ trigger_error( __( 'A replacement variable can only contain alphanumeric characters, an underscore or a dash. Try renaming your variable.', 'wordpress-seo' ), E_USER_WARNING );
+ } elseif ( strpos( $var, 'cf_' ) === 0 || strpos( $var, 'ct_' ) === 0 ) {
+ trigger_error( __( 'A replacement variable can not start with "%%cf_" or "%%ct_" as these are reserved for the WPSEO standard variable variables for custom fields and custom taxonomies. Try making your variable name unique.', 'wordpress-seo' ), E_USER_WARNING );
+ } elseif ( ! method_exists( __CLASS__, 'retrieve_' . $var ) ) {
+ if ( ! isset( self::$external_replacements[ $var ] ) ) {
+ self::$external_replacements[ $var ] = $replace_function;
+ self::register_help_text( $type, $var, $help_text );
+ $success = true;
+ } else {
+ trigger_error( __( 'A replacement variable with the same name has already been registered. Try making your variable name more unique.', 'wordpress-seo' ), E_USER_WARNING );
+ }
+ } else {
+ trigger_error( __( 'You cannot overrule a WPSEO standard variable replacement by registering a variable with the same name. Use the "wpseo_replacements" filter instead to adjust the replacement value.', 'wordpress-seo' ), E_USER_WARNING );
+ }
+ }
+
+ return $success;
+ }
+
+
+ /**
+ * Replace `%%variable_placeholders%%` with their real value based on the current requested page/post/cpt/etc
+ *
+ * @param string $string the string to replace the variables in.
+ * @param array $args the object some of the replacement values might come from,
+ * could be a post, taxonomy or term.
+ * @param array $omit variables that should not be replaced by this function.
+ *
+ * @return string
+ */
+ public function replace( $string, $args, $omit = array() ) {
+
+ $string = strip_tags( $string );
+
+ // Let's see if we can bail super early.
+ if ( strpos( $string, '%%' ) === false ) {
+ return wpseo_standardize_whitespace( $string );
+ }
+
+ $args = (array) $args;
+ if ( isset( $args['post_content'] ) && ! empty( $args['post_content'] ) ) {
+ $args['post_content'] = wpseo_strip_shortcode( $args['post_content'] );
+ }
+ if ( isset( $args['post_excerpt'] ) && ! empty( $args['post_excerpt'] ) ) {
+ $args['post_excerpt'] = wpseo_strip_shortcode( $args['post_excerpt'] );
+ }
+ $this->args = (object) wp_parse_args( $args, $this->defaults );
+
+ // Clean $omit array
+ if ( is_array( $omit ) && $omit !== array() ) {
+ $omit = array_map( array( __CLASS__, 'remove_var_delimiter' ), $omit );
+ }
+
+ $replacements = array();
+ if ( preg_match_all( '`%%([^%]+(%%single)?)%%?`iu', $string, $matches ) ) {
+ $replacements = $this->set_up_replacements( $matches, $omit );
+ }
+
+ /**
+ * Filter: 'wpseo_replacements' - Allow customization of the replacements before they are applied
+ *
+ * @api array $replacements The replacements
+ */
+ $replacements = apply_filters( 'wpseo_replacements', $replacements );
+
+ // Do the actual replacements
+ if ( is_array( $replacements ) && $replacements !== array() ) {
+ $string = str_replace( array_keys( $replacements ), array_values( $replacements ), $string );
+ }
+
+ /**
+ * Filter: 'wpseo_replacements_final' - Allow overruling of whether or not to remove placeholders
+ * which didn't yield a replacement
+ *
+ * @example <code>add_filter( 'wpseo_replacements_final', '__return_false' );</code>
+ *
+ * @api bool $final
+ */
+ if ( apply_filters( 'wpseo_replacements_final', true ) === true && ( isset( $matches[1] ) && is_array( $matches[1] ) ) ) {
+ // Remove non-replaced variables
+ $remove = array_diff( $matches[1], $omit ); // Make sure the $omit variables do not get removed
+ $remove = array_map( array( __CLASS__, 'add_var_delimiter' ), $remove );
+ $string = str_replace( $remove, '', $string );
+ }
+
+ // Undouble separators which have nothing between them, i.e. where a non-replaced variable was removed
+ if ( isset( $replacements['%%sep%%'] ) && ( is_string( $replacements['%%sep%%'] ) && $replacements['%%sep%%'] !== '' ) ) {
+ $q_sep = preg_quote( $replacements['%%sep%%'], '`' );
+ $string = preg_replace( '`' . $q_sep . '(?:\s*' . $q_sep . ')*`u', $replacements['%%sep%%'], $string );
+ }
+
+ // Remove superfluous whitespace
+ $string = wpseo_standardize_whitespace( $string );
+
+ return trim( $string );
+ }
+
+
+ /**
+ * Retrieve the replacements for the variables found.
+ *
+ * @param array $matches variables found in the original string - regex result.
+ * @param array $omit variables that should not be replaced by this function.
+ *
+ * @return array retrieved replacements - this might be a smaller array as some variables
+ * may not yield a replacement in certain contexts.
+ */
+ private function set_up_replacements( $matches, $omit ) {
+
+ $replacements = array();
+
+ // @todo -> figure out a way to deal with external functions starting with cf_/ct_
+ foreach ( $matches[1] as $k => $var ) {
+
+ // Don't set up replacements which should be omitted
+ if ( in_array( $var, $omit, true ) ) {
+ continue;
+ }
+
+ // Deal with variable variable names first
+ if ( strpos( $var, 'cf_' ) === 0 ) {
+ $replacement = $this->retrieve_cf_custom_field_name( $var );
+ } elseif ( strpos( $var, 'ct_desc_' ) === 0 ) {
+ $replacement = $this->retrieve_ct_desc_custom_tax_name( $var );
+ } elseif ( strpos( $var, 'ct_' ) === 0 ) {
+ $single = ( isset( $matches[2][ $k ] ) && $matches[2][ $k ] !== '' ) ? true : false;
+ $replacement = $this->retrieve_ct_custom_tax_name( $var, $single );
+ } // Deal with non-variable variable names
+ elseif ( method_exists( $this, 'retrieve_' . $var ) ) {
+ $method_name = 'retrieve_' . $var;
+ $replacement = $this->$method_name();
+ } // Deal with externally defined variable names
+ elseif ( isset( self::$external_replacements[ $var ] ) && ! is_null( self::$external_replacements[ $var ] ) ) {
+ $replacement = call_user_func( self::$external_replacements[ $var ], $var );
+ }
+
+ // Replacement retrievals can return null if no replacement can be determined, root those outs
+ if ( isset( $replacement ) ) {
+ $var = self::add_var_delimiter( $var );
+ $replacements[ $var ] = $replacement;
+ }
+ }
+
+ return $replacements;
+ }
+
+
+
+ /* *********************** BASIC VARIABLES ************************** */
+
+ /**
+ * Retrieve the post/cpt categories (comma separated) for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_category() {
+ $replacement = null;
+
+ if ( ! empty( $this->args->ID ) ) {
+ $cat = $this->get_terms( $this->args->ID, 'category' );
+ if ( $cat !== '' ) {
+ $replacement = $cat;
+ }
+ }
+
+ if ( ( ! isset( $replacement ) || $replacement === '' ) && ( isset( $this->args->cat_name ) && ! empty( $this->args->cat_name ) ) ) {
+ $replacement = $this->args->cat_name;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the category description for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_category_description() {
+ return $this->retrieve_term_description();
+ }
+
+ /**
+ * Retrieve the date of the post/page/cpt for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_date() {
+ $replacement = null;
+
+ if ( $this->args->post_date != '' ) {
+ $replacement = mysql2date( get_option( 'date_format' ), $this->args->post_date, true );
+ } else {
+ if ( get_query_var( 'day' ) && get_query_var( 'day' ) != '' ) {
+ $replacement = get_the_date();
+ } else {
+ if ( single_month_title( ' ', false ) && single_month_title( ' ', false ) != '' ) {
+ $replacement = single_month_title( ' ', false );
+ } elseif ( get_query_var( 'year' ) != '' ) {
+ $replacement = get_query_var( 'year' );
+ }
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post/page/cpt excerpt for use as replacement string.
+ * The excerpt will be auto-generated if it does not exist.
+ *
+ * @return string|null
+ */
+ private function retrieve_excerpt() {
+ $replacement = null;
+
+ if ( ! empty( $this->args->ID ) ) {
+ if ( $this->args->post_excerpt !== '' ) {
+ $replacement = strip_tags( $this->args->post_excerpt );
+ } elseif ( $this->args->post_content !== '' ) {
+ $replacement = wp_html_excerpt( strip_shortcodes( $this->args->post_content ), 155 );
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post/page/cpt excerpt for use as replacement string (without auto-generation).
+ *
+ * @return string|null
+ */
+ private function retrieve_excerpt_only() {
+ $replacement = null;
+
+ if ( ! empty( $this->args->ID ) && $this->args->post_excerpt !== '' ) {
+ $replacement = strip_tags( $this->args->post_excerpt );
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the title of the parent page of the current page/cpt for use as replacement string.
+ * Only applicable for hierarchical post types.
+ *
+ * @todo - check: shouldn't this use $this->args as well ?
+ *
+ * @return string|null
+ */
+ private function retrieve_parent_title() {
+ $replacement = null;
+
+ if ( ! isset( $replacement ) && ( ( is_singular() || is_admin() ) && isset( $GLOBALS['post'] ) ) ) {
+ if ( isset( $GLOBALS['post']->post_parent ) && 0 != $GLOBALS['post']->post_parent ) {
+ $replacement = get_the_title( $GLOBALS['post']->post_parent );
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current search phrase for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_searchphrase() {
+ $replacement = null;
+
+ if ( ! isset( $replacement ) ) {
+ $search = get_query_var( 's' );
+ if ( $search !== '' ) {
+ $replacement = esc_html( $search );
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the separator for use as replacement string.
+ *
+ * @return string
+ */
+ private function retrieve_sep() {
+ $replacement = WPSEO_Options::get_default( 'wpseo_titles', 'separator' );
+
+ // Get the titles option and the separator options
+ $titles_options = get_option( 'wpseo_titles' );
+ $seperator_options = WPSEO_Option_Titles::get_instance()->get_separator_options();
+
+ // This should always be set, but just to be sure
+ if ( isset( $seperator_options[ $titles_options['separator'] ] ) ) {
+ // Set the new replacement
+ $replacement = $seperator_options[ $titles_options['separator'] ];
+ }
+
+ /**
+ * Filter: 'wpseo_replacements_filter_sep' - Allow customization of the separator character(s)
+ *
+ * @api string $replacement The current separator
+ */
+
+ return apply_filters( 'wpseo_replacements_filter_sep', $replacement );
+ }
+
+ /**
+ * Retrieve the site's tag line / description for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_sitedesc() {
+ static $replacement;
+
+ if ( ! isset( $replacement ) ) {
+ $description = trim( strip_tags( get_bloginfo( 'description' ) ) );
+ if ( $description !== '' ) {
+ $replacement = $description;
+ }
+ }
+
+ return $replacement;
+ }
+
+
+ /**
+ * Retrieve the site's name for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_sitename() {
+ static $replacement;
+
+ if ( ! isset( $replacement ) ) {
+ $sitename = trim( strip_tags( get_bloginfo( 'name' ) ) );
+ if ( $sitename !== '' ) {
+ $replacement = $sitename;
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current tag/tags for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_tag() {
+ $replacement = null;
+
+ if ( isset( $this->args->ID ) ) {
+ $tags = $this->get_terms( $this->args->ID, 'post_tag' );
+ if ( $tags !== '' ) {
+ $replacement = $tags;
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the tag description for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_tag_description() {
+ return $this->retrieve_term_description();
+ }
+
+ /**
+ * Retrieve the term description for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_term_description() {
+ $replacement = null;
+
+ if ( isset( $this->args->term_id ) && ! empty( $this->args->taxonomy ) ) {
+ $term_desc = get_term_field( 'description', $this->args->term_id, $this->args->taxonomy );
+ if ( $term_desc !== '' ) {
+ $replacement = trim( strip_tags( $term_desc ) );
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the term name for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_term_title() {
+ $replacement = null;
+
+ if ( ! empty( $this->args->taxonomy ) && ! empty( $this->args->name ) ) {
+ $replacement = $this->args->name;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the title of the post/page/cpt for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_title() {
+ $replacement = null;
+
+ if ( is_string( $this->args->post_title ) && $this->args->post_title !== '' ) {
+ $replacement = stripslashes( $this->args->post_title );
+ }
+
+ return $replacement;
+ }
+
+
+
+ /* *********************** ADVANCED VARIABLES ************************** */
+
+ /**
+ * Determine the page numbering of the current post/page/cpt
+ *
+ * @param string $request 'nr'|'max' - whether to return the page number or the max number of pages
+ *
+ * @return int|null
+ */
+ private function determine_pagenumbering( $request = 'nr' ) {
+ global $wp_query, $post;
+ $max_num_pages = null;
+ $page_number = null;
+
+ $max_num_pages = 1;
+
+ if ( ! is_singular() ) {
+ $page_number = get_query_var( 'paged' );
+ if ( $page_number === 0 || $page_number === '' ) {
+ $page_number = 1;
+ }
+
+ if ( isset( $wp_query->max_num_pages ) && ( $wp_query->max_num_pages != '' && $wp_query->max_num_pages != 0 ) ) {
+ $max_num_pages = $wp_query->max_num_pages;
+ }
+ } else {
+ $page_number = get_query_var( 'page' );
+ if ( $page_number === 0 || $page_number === '' ) {
+ $page_number = 1;
+ }
+
+ if ( isset( $post->post_content ) ) {
+ $max_num_pages = substr_count( $post->post_content, '<!--nextpage-->' ) + 1;
+ }
+ }
+
+ $return = null;
+
+ switch ( $request ) {
+ case 'nr':
+ $return = $page_number;
+ break;
+ case 'max':
+ $return = $max_num_pages;
+ break;
+ }
+
+ return $return;
+ }
+
+
+ /**
+ * Determine the post type names for the current post/page/cpt
+ *
+ * @param string $request 'single'|'plural' - whether to return the single or plural form
+ *
+ * @return string|null
+ */
+ private function determine_pt_names( $request = 'single' ) {
+ global $wp_query;
+ $pt_single = null;
+ $pt_plural = null;
+
+ if ( isset( $wp_query->query_vars['post_type'] ) && ( ( is_string( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] !== '' ) || ( is_array( $wp_query->query_vars['post_type'] ) && $wp_query->query_vars['post_type'] !== array() ) ) ) {
+ $post_type = $wp_query->query_vars['post_type'];
+ } else {
+ // Make it work in preview mode
+ $post_type = $wp_query->get_queried_object()->post_type;
+ }
+
+ if ( is_array( $post_type ) ) {
+ $post_type = reset( $post_type );
+ }
+
+ if ( $post_type !== '' ) {
+ $pt = get_post_type_object( $post_type );
+ $pt_plural = $pt_single = $pt->name;
+ if ( isset( $pt->labels->singular_name ) ) {
+ $pt_single = $pt->labels->singular_name;
+ }
+ if ( isset( $pt->labels->name ) ) {
+ $pt_plural = $pt->labels->name;
+ }
+ }
+
+ $return = null;
+
+ switch ( $request ) {
+ case 'single':
+ $return = $pt_single;
+ break;
+ case 'plural':
+ $return = $pt_plural;
+ break;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Retrieve the attachment caption for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_caption() {
+ return $this->retrieve_excerpt_only();
+ }
+
+
+ /**
+ * Retrieve a post/page/cpt's custom field value for use as replacement string
+ *
+ * @param string $var The complete variable to replace which includes the name of
+ * the custom field which value is to be retrieved.
+ *
+ * @return string|null
+ */
+ private function retrieve_cf_custom_field_name( $var ) {
+ global $post;
+ $replacement = null;
+
+ if ( is_string( $var ) && $var !== '' ) {
+ $field = substr( $var, 3 );
+ if ( ( is_singular() || is_admin() ) && ( is_object( $post ) && isset( $post->ID ) ) ) {
+ $name = get_post_meta( $post->ID, $field, true );
+ if ( $name !== '' ) {
+ $replacement = $name;
+ }
+ }
+ }
+
+ return $replacement;
+ }
+
+
+ /**
+ * Retrieve a post/page/cpt's custom taxonomies for use as replacement string
+ *
+ * @param string $var The complete variable to replace which includes the name of
+ * the custom taxonomy which value(s) is to be retrieved.
+ * @param bool $single Whether to retrieve only the first or all values for the taxonomy
+ *
+ * @return string|null
+ */
+ private function retrieve_ct_custom_tax_name( $var, $single = false ) {
+ $replacement = null;
+
+ if ( ( is_string( $var ) && $var !== '' ) && ! empty( $this->args->ID ) ) {
+ $tax = substr( $var, 3 );
+ $name = $this->get_terms( $this->args->ID, $tax, $single );
+ if ( $name !== '' ) {
+ $replacement = $name;
+ }
+ }
+
+ return $replacement;
+ }
+
+
+ /**
+ * Retrieve a post/page/cpt's custom taxonomies description for use as replacement string
+ *
+ * @param string $var The complete variable to replace which includes the name of
+ * the custom taxonomy which description is to be retrieved.
+ *
+ * @return string|null
+ */
+ private function retrieve_ct_desc_custom_tax_name( $var ) {
+ global $post;
+ $replacement = null;
+
+ if ( is_string( $var ) && $var !== '' ) {
+ $tax = substr( $var, 8 );
+ if ( is_object( $post ) && isset( $post->ID ) ) {
+ $terms = get_the_terms( $post->ID, $tax );
+ if ( is_array( $terms ) && $terms !== array() ) {
+ $term = current( $terms );
+ $term_desc = get_term_field( 'description', $term->term_id, $tax );
+ if ( $term_desc !== '' ) {
+ $replacement = $term_desc;
+ }
+ }
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current date for use as replacement string.
+ *
+ * @return string
+ */
+ private function retrieve_currentdate() {
+ static $replacement;
+
+ if ( ! isset( $replacement ) ) {
+ $replacement = date_i18n( get_option( 'date_format' ) );
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current day for use as replacement string.
+ *
+ * @return string
+ */
+ private function retrieve_currentday() {
+ static $replacement;
+
+ if ( ! isset( $replacement ) ) {
+ $replacement = date_i18n( 'j' );
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current month for use as replacement string.
+ *
+ * @return string
+ */
+ private function retrieve_currentmonth() {
+ static $replacement;
+
+ if ( ! isset( $replacement ) ) {
+ $replacement = date_i18n( 'F' );
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current time for use as replacement string.
+ *
+ * @return string
+ */
+ private function retrieve_currenttime() {
+ static $replacement;
+
+ if ( ! isset( $replacement ) ) {
+ $replacement = date_i18n( get_option( 'time_format' ) );
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current year for use as replacement string.
+ *
+ * @return string
+ */
+ private function retrieve_currentyear() {
+ static $replacement;
+
+ if ( ! isset( $replacement ) ) {
+ $replacement = date_i18n( 'Y' );
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post/page/cpt's focus keyword for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_focuskw() {
+ $replacement = null;
+
+ if ( ! empty( $this->args->ID ) ) {
+ $focus_kw = WPSEO_Meta::get_value( 'focuskw', $this->args->ID );
+ if ( $focus_kw !== '' ) {
+ $replacement = $focus_kw;
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post/page/cpt ID for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_id() {
+ $replacement = null;
+
+ if ( ! empty( $this->args->ID ) ) {
+ $replacement = $this->args->ID;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post/page/cpt modified time for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_modified() {
+ $replacement = null;
+
+ if ( ! empty( $this->args->post_modified ) ) {
+ $replacement = mysql2date( get_option( 'date_format' ), $this->args->post_modified, true );
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post/page/cpt author's "nice name" for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_name() {
+ $replacement = null;
+
+ $user_id = $this->retrieve_userid();
+ $name = get_the_author_meta( 'display_name', $user_id );
+ if ( $name !== '' ) {
+ $replacement = $name;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post/page/cpt author's users description for use as a replacement string.
+ *
+ * @return null|string
+ */
+ private function retrieve_user_description() {
+ $replacement = null;
+
+ $user_id = $this->retrieve_userid();
+ $description = get_the_author_meta( 'description', $user_id );
+ if ( $description != '' ) {
+ $replacement = $description;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current page number with context (i.e. 'page 2 of 4') for use as replacement string.
+ *
+ * @return string
+ */
+ private function retrieve_page() {
+ $replacement = null;
+
+ $max = $this->determine_pagenumbering( 'max' );
+ $nr = $this->determine_pagenumbering( 'nr' );
+ $sep = $this->retrieve_sep();
+
+ if ( $max > 1 && $nr > 1 ) {
+ $replacement = sprintf( $sep . ' ' . __( 'Page %d of %d', 'wordpress-seo' ), $nr, $max );
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current page number for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_pagenumber() {
+ $replacement = null;
+
+ $nr = $this->determine_pagenumbering( 'nr' );
+ if ( isset( $nr ) && $nr > 0 ) {
+ $replacement = (string) $nr;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the current page total for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_pagetotal() {
+ $replacement = null;
+
+ $max = $this->determine_pagenumbering( 'max' );
+ if ( isset( $max ) && $max > 0 ) {
+ $replacement = (string) $max;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post type plural label for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_pt_plural() {
+ $replacement = null;
+
+ $name = $this->determine_pt_names( 'plural' );
+ if ( isset( $name ) && $name !== '' ) {
+ $replacement = $name;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post type single label for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_pt_single() {
+ $replacement = null;
+
+ $name = $this->determine_pt_names( 'single' );
+ if ( isset( $name ) && $name !== '' ) {
+ $replacement = $name;
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the slug which caused the 404 for use as replacement string.
+ *
+ * @return string|null
+ */
+ private function retrieve_term404() {
+ $replacement = null;
+
+ if ( $this->args->term404 !== '' ) {
+ $replacement = sanitize_text_field( str_replace( '-', ' ', $this->args->term404 ) );
+ } else {
+ $error_request = get_query_var( 'pagename' );
+ if ( $error_request !== '' ) {
+ $replacement = sanitize_text_field( str_replace( '-', ' ', $error_request ) );
+ } else {
+ $error_request = get_query_var( 'name' );
+ if ( $error_request !== '' ) {
+ $replacement = sanitize_text_field( str_replace( '-', ' ', $error_request ) );
+ }
+ }
+ }
+
+ return $replacement;
+ }
+
+ /**
+ * Retrieve the post/page/cpt author's user id for use as replacement string.
+ *
+ * @return string
+ */
+ private function retrieve_userid() {
+ $replacement = ! empty( $this->args->post_author ) ? $this->args->post_author : get_query_var( 'author' );
+
+ return $replacement;
+ }
+
+
+
+ /* *********************** HELP TEXT RELATED ************************** */
+
+ /**
+ * Create a variable help text table
+ *
+ * @param string $type Either 'basic' or 'advanced'
+ *
+ * @return string Help text table
+ */
+ private static function create_variable_help_table( $type ) {
+ if ( ! in_array( $type, array( 'basic', 'advanced' ), true ) ) {
+ return '';
+ }
+
+ $table = '
+ <table class="yoast_help">';
+
+ foreach ( self::$help_texts[ $type ] as $replace => $help_text ) {
+ $table .= '
+ <tr>
+ <th>%%' . esc_html( $replace ) . '%%</th>
+ <td>' . $help_text . '</td>
+ </tr>';
+ }
+
+ $table .= '
+ </table>';
+
+ return $table;
+ }
+
+ /**
+ * Create the help text table for the basic variables for use in a help tab
+ *
+ * @return string
+ */
+ public static function get_basic_help_texts() {
+ return self::create_variable_help_table( 'basic' );
+ }
+
+
+ /**
+ * Create the help text table for the advanced variables for use in a help tab
+ *
+ * @return string
+ */
+ public static function get_advanced_help_texts() {
+ return self::create_variable_help_table( 'advanced' );
+ }
+
+
+ /**
+ * Set the help text for a user/plugin/theme defined extra variable.
+ *
+ * @param string $type Type of variable: 'basic' or 'advanced'
+ * @param string $replace Variable to replace, i.e. '%%var%%'
+ * @param string $help_text The actual help text string
+ */
+ private static function register_help_text( $type, $replace, $help_text = '' ) {
+ if ( is_string( $replace ) && $replace !== '' ) {
+ $replace = self::remove_var_delimiter( $replace );
+
+ if ( ( is_string( $type ) && in_array( $type, array( 'basic', 'advanced' ), true ) ) && ( $replace !== '' && ! isset( self::$help_texts[ $type ][ $replace ] ) )
+ ) {
+ self::$help_texts[ $type ][ $replace ] = $help_text;
+ }
+ }
+ }
+
+
+ /**
+ * Set/translate the help texts for the WPSEO standard basic variables.
+ */
+ private static function set_basic_help_texts() {
+ self::$help_texts['basic'] = array(
+ 'date' => __( 'Replaced with the date of the post/page', 'wordpress-seo' ),
+ 'title' => __( 'Replaced with the title of the post/page', 'wordpress-seo' ),
+ 'parent_title' => __( 'Replaced with the title of the parent page of the current page', 'wordpress-seo' ),
+ 'sitename' => __( 'The site\'s name', 'wordpress-seo' ),
+ 'sitedesc' => __( 'The site\'s tag line / description', 'wordpress-seo' ),
+ 'excerpt' => __( 'Replaced with the post/page excerpt (or auto-generated if it does not exist)', 'wordpress-seo' ),
+ 'excerpt_only' => __( 'Replaced with the post/page excerpt (without auto-generation)', 'wordpress-seo' ),
+ 'tag' => __( 'Replaced with the current tag/tags', 'wordpress-seo' ),
+ 'category' => __( 'Replaced with the post categories (comma separated)', 'wordpress-seo' ),
+ 'category_description' => __( 'Replaced with the category description', 'wordpress-seo' ),
+ 'tag_description' => __( 'Replaced with the tag description', 'wordpress-seo' ),
+ 'term_description' => __( 'Replaced with the term description', 'wordpress-seo' ),
+ 'term_title' => __( 'Replaced with the term name', 'wordpress-seo' ),
+ 'searchphrase' => __( 'Replaced with the current search phrase', 'wordpress-seo' ),
+ 'sep' => __( 'The separator defined in your theme\'s <code>wp_title()</code> tag.', 'wordpress-seo' ),
+ );
+ }
+
+ /**
+ * Set/translate the help texts for the WPSEO standard advanced variables.
+ */
+ private static function set_advanced_help_texts() {
+ self::$help_texts['advanced'] = array(
+ 'pt_single' => __( 'Replaced with the post type single label', 'wordpress-seo' ),
+ 'pt_plural' => __( 'Replaced with the post type plural label', 'wordpress-seo' ),
+ 'modified' => __( 'Replaced with the post/page modified time', 'wordpress-seo' ),
+ 'id' => __( 'Replaced with the post/page ID', 'wordpress-seo' ),
+ 'name' => __( 'Replaced with the post/page author\'s \'nicename\'', 'wordpress-seo' ),
+ 'user_description' => __( 'Replaced with the post/page author\'s \'Biographical Info\'', 'wordpress-seo' ),
+ 'userid' => __( 'Replaced with the post/page author\'s userid', 'wordpress-seo' ),
+ 'currenttime' => __( 'Replaced with the current time', 'wordpress-seo' ),
+ 'currentdate' => __( 'Replaced with the current date', 'wordpress-seo' ),
+ 'currentday' => __( 'Replaced with the current day', 'wordpress-seo' ),
+ 'currentmonth' => __( 'Replaced with the current month', 'wordpress-seo' ),
+ 'currentyear' => __( 'Replaced with the current year', 'wordpress-seo' ),
+ 'page' => __( 'Replaced with the current page number with context (i.e. page 2 of 4)', 'wordpress-seo' ),
+ 'pagetotal' => __( 'Replaced with the current page total', 'wordpress-seo' ),
+ 'pagenumber' => __( 'Replaced with the current page number', 'wordpress-seo' ),
+ 'caption' => __( 'Attachment caption', 'wordpress-seo' ),
+ 'focuskw' => __( 'Replaced with the posts focus keyword', 'wordpress-seo' ),
+ 'term404' => __( 'Replaced with the slug which caused the 404', 'wordpress-seo' ),
+ 'cf_<custom-field-name>' => __( 'Replaced with a posts custom field value', 'wordpress-seo' ),
+ 'ct_<custom-tax-name>' => __( 'Replaced with a posts custom taxonomies, comma separated.', 'wordpress-seo' ),
+ 'ct_desc_<custom-tax-name>' => __( 'Replaced with a custom taxonomies description', 'wordpress-seo' ),
+ );
+ }
+
+
+
+
+ /* *********************** GENERAL HELPER METHODS ************************** */
+
+ /**
+ * Remove the '%%' delimiters from a variable string
+ *
+ * @param string $string Variable string to be cleaned
+ *
+ * @return string
+ */
+ private static function remove_var_delimiter( $string ) {
+ return trim( $string, '%' );
+ }
+
+ /**
+ * Add the '%%' delimiters to a variable string
+ *
+ * @param string $string Variable string to be delimited
+ *
+ * @return string
+ */
+ private static function add_var_delimiter( $string ) {
+ return '%%' . $string . '%%';
+ }
+
+ /**
+ * Retrieve a post's terms, comma delimited.
+ *
+ * @param int $id ID of the post to get the terms for.
+ * @param string $taxonomy The taxonomy to get the terms for this post from.
+ * @param bool $return_single If true, return the first term.
+ *
+ * @return string either a single term or a comma delimited string of terms.
+ */
+ public function get_terms( $id, $taxonomy, $return_single = false ) {
+
+ $output = '';
+
+ // If we're on a specific tag, category or taxonomy page, use that.
+ if ( is_category() || is_tag() || is_tax() ) {
+ global $wp_query;
+ $term = $wp_query->get_queried_object();
+ $output = $term->name;
+ } elseif ( ! empty( $id ) && ! empty( $taxonomy ) ) {
+ $terms = get_the_terms( $id, $taxonomy );
+ if ( is_array( $terms ) && $terms !== array() ) {
+ foreach ( $terms as $term ) {
+ if ( $return_single ) {
+ $output = $term->name;
+ break;
+ } else {
+ $output .= $term->name . ', ';
+ }
+ }
+ $output = rtrim( trim( $output ), ',' );
+ }
+ }
+
+ /**
+ * Allows filtering of the terms list used to replace %%category%%, %%tag%% and %%ct_<custom-tax-name>%% variables
+ * @api string $output Comma-delimited string containing the terms
+ */
+
+ return apply_filters( 'wpseo_terms', $output );
+ }
+
+ } /* End of class WPSEO_Replace_Vars */
+
+
+ /**
+ * Setup the class statics when the file is first loaded
+ */
+ WPSEO_Replace_Vars::setup_statics_once();
+
+} /* End of class-exists wrapper */
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * @package Internals
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+/**
+ * Run the upgrade procedures.
+ *
+ * @todo - [JRF => Yoast] check: if upgrade is run on multi-site installation, upgrade for all sites ?
+ * Maybe not necessary as it is now run on plugins_loaded, so upgrade will run as soon as any page
+ * on a site is requested.
+ */
+function wpseo_do_upgrade() {
+ /* Make sure title_test and description_test functions are available */
+ require_once( WPSEO_PATH . 'inc/wpseo-non-ajax-functions.php' );
+
+ $option_wpseo = get_option( 'wpseo' );
+
+ WPSEO_Options::maybe_set_multisite_defaults( false );
+
+// if ( $option_wpseo['version'] === '' || version_compare( $option_wpseo['version'], '1.2', '<' ) ) {
+// add_action( 'init', 'wpseo_title_test' );
+// }
+
+ if ( $option_wpseo['version'] === '' || version_compare( $option_wpseo['version'], '1.4.13', '<' ) ) {
+ // Run description test once theme has loaded
+ add_action( 'init', 'wpseo_description_test' );
+ }
+
+ if ( $option_wpseo['version'] === '' || version_compare( $option_wpseo['version'], '1.4.15', '<' ) ) {
+ add_action( 'shutdown', 'flush_rewrite_rules' );
+ }
+
+ if ( version_compare( $option_wpseo['version'], '1.5.0', '<' ) ) {
+
+ // Clean up options and meta
+ WPSEO_Options::clean_up( null, $option_wpseo['version'] );
+ WPSEO_Meta::clean_up();
+
+ // Add new capabilities on upgrade
+ wpseo_add_capabilities();
+ }
+
+ /* Only correct the breadcrumb defaults for upgrades from v1.5+ to v1.5.2.3, upgrades from earlier version
+ will already get this functionality in the clean_up routine. */
+ if ( version_compare( $option_wpseo['version'], '1.4.25', '>' ) && version_compare( $option_wpseo['version'], '1.5.2.3', '<' ) ) {
+ add_action( 'init', array( 'WPSEO_Options', 'bring_back_breadcrumb_defaults' ), 3 );
+ }
+
+ if ( version_compare( $option_wpseo['version'], '1.4.25', '>' ) && version_compare( $option_wpseo['version'], '1.5.2.4', '<' ) ) {
+ /* Make sure empty maintax/mainpt strings will convert to 0 */
+ WPSEO_Options::clean_up( 'wpseo_internallinks', $option_wpseo['version'] );
+
+ /* Remove slashes from taxonomy meta texts */
+ WPSEO_Options::clean_up( 'wpseo_taxonomy_meta', $option_wpseo['version'] );
+ }
+
+ /* Clean up stray wpseo_ms options from the options table, option should only exist in the sitemeta table */
+ delete_option( 'wpseo_ms' );
+
+
+ // Make sure version nr gets updated for any version without specific upgrades
+ $option_wpseo = get_option( 'wpseo' ); // re-get to make sure we have the latest version
+ if ( version_compare( $option_wpseo['version'], WPSEO_VERSION, '<' ) ) {
+ update_option( 'wpseo', $option_wpseo );
+ }
+
+ // Make sure all our options always exist - issue #1245
+ WPSEO_Options::ensure_options_exist();
+}
+
+
+if ( ! function_exists( 'initialize_wpseo_front' ) ) {
+ function initialize_wpseo_front() {
+ $GLOBALS['wpseo_front'] = new WPSEO_Frontend;
+ }
+}
+
+
+if ( ! function_exists( 'yoast_breadcrumb' ) ) {
+ /**
+ * Template tag for breadcrumbs.
+ *
+ * @todo [JRF => Yoast/whomever] We could probably get rid of the 'breadcrumbs-enable' option key
+ * as the file is now only loaded when the template tag is encountered anyway.
+ * Only issue with that would be the removal of the bbPress crumb from within wpseo_frontend_init()
+ * in wpseo.php which is also based on this setting.
+ * Whether or not to show the bctitle field within meta boxes is also based on this setting, but
+ * showing these when someone hasn't implemented the template tag shouldn't really give cause for concern.
+ * Other than that, leaving the setting is an easy way to enable/disable the bc without having to
+ * edit the template files again, but having to manually enable when you've added the template tag
+ * in your theme is kind of double, so I'm undecided about what to do.
+ * I guess I'm leaning towards removing the option key.
+ *
+ * @param string $before What to show before the breadcrumb.
+ * @param string $after What to show after the breadcrumb.
+ * @param bool $display Whether to display the breadcrumb (true) or return it (false).
+ *
+ * @return string
+ */
+ function yoast_breadcrumb( $before = '', $after = '', $display = true ) {
+ $options = get_option( 'wpseo_internallinks' );
+
+ if ( $options['breadcrumbs-enable'] === true ) {
+ return WPSEO_Breadcrumbs::breadcrumb( $before, $after, $display );
+ }
+ }
+}
+
+/**
+ * Add the bulk edit capability to the proper default roles.
+ */
+function wpseo_add_capabilities() {
+ $roles = array(
+ 'administrator',
+ 'editor',
+ 'author',
+ );
+
+ $roles = apply_filters( 'wpseo_bulk_edit_roles', $roles );
+
+ foreach ( $roles as $role ) {
+ $r = get_role( $role );
+ if ( $r ) {
+ $r->add_cap( 'wpseo_bulk_edit' );
+ }
+ }
+}
+
+
+/**
+ * Remove the bulk edit capability from the proper default roles.
+ *
+ * Contributor is still removed for legacy reasons.
+ */
+function wpseo_remove_capabilities() {
+ $roles = array(
+ 'administrator',
+ 'editor',
+ 'author',
+ 'contributor',
+ );
+
+ $roles = apply_filters( 'wpseo_bulk_edit_roles', $roles );
+
+ foreach ( $roles as $role ) {
+ $r = get_role( $role );
+ if ( $r ) {
+ $r->remove_cap( 'wpseo_bulk_edit' );
+ }
+ }
+}
+
+
+/**
+ * Replace `%%variable_placeholders%%` with their real value based on the current requested page/post/cpt
+ *
+ * @param string $string the string to replace the variables in.
+ * @param object $args the object some of the replacement values might come from, could be a post, taxonomy or term.
+ * @param array $omit variables that should not be replaced by this function.
+ * @return string
+ */
+function wpseo_replace_vars( $string, $args, $omit = array() ) {
+ $replacer = new WPSEO_Replace_Vars;
+ return $replacer->replace( $string, $args, $omit );
+}
+
+/**
+ * Register a new variable replacement
+ *
+ * This function is for use by other plugins/themes to easily add their own additional variables to replace.
+ * This function should be called from a function on the 'wpseo_register_extra_replacements' action hook.
+ * The use of this function is preferred over the older 'wpseo_replacements' filter as a way to add new replacements.
+ * The 'wpseo_replacements' filter should still be used to adjust standard WPSEO replacement values.
+ * The function can not be used to replace standard WPSEO replacement value functions and will thrown a warning
+ * if you accidently try.
+ * To avoid conflicts with variables registered by WPSEO and other themes/plugins, try and make the
+ * name of your variable unique. Variable names also can not start with "%%cf_" or "%%ct_" as these are reserved
+ * for the standard WPSEO variable variables 'cf_<custom-field-name>', 'ct_<custom-tax-name>' and
+ * 'ct_desc_<custom-tax-name>'.
+ * The replacement function will be passed the undelimited name (i.e. stripped of the %%) of the variable
+ * to replace in case you need it.
+ *
+ * Example code:
+ * <code>
+ * <?php
+ * function retrieve_var1_replacement( $var1 ) {
+ * return 'your replacement value';
+ * }
+ *
+ * function register_my_plugin_extra_replacements() {
+ * wpseo_register_var_replacement( '%%myvar1%%', 'retrieve_var1_replacement', 'advanced', 'this is a help text for myvar1' );
+ * wpseo_register_var_replacement( 'myvar2', array( 'class', 'method_name' ), 'basic', 'this is a help text for myvar2' );
+ * }
+ * add_action( 'wpseo_register_extra_replacements', 'register_my_plugin_extra_replacements' );
+ * ?>
+ * </code>
+ *
+ * @since 1.5.4
+ *
+ * @param string $var The name of the variable to replace, i.e. '%%var%%'
+ * - the surrounding %% are optional, name can only contain [A-Za-z0-9_-]
+ * @param mixed $replace_function Function or method to call to retrieve the replacement value for the variable
+ * Uses the same format as add_filter/add_action function parameter and
+ * should *return* the replacement value. DON'T echo it!
+ * @param string $type Type of variable: 'basic' or 'advanced', defaults to 'advanced'
+ * @param string $help_text Help text to be added to the help tab for this variable
+ * @return bool Whether the replacement function was succesfully registered
+ */
+function wpseo_register_var_replacement( $var, $replace_function, $type = 'advanced', $help_text = '' ) {
+ return WPSEO_Replace_Vars::register_replacement( $var, $replace_function, $type, $help_text );
+}
+
+/**
+ * Strip out the shortcodes with a filthy regex, because people don't properly register their shortcodes.
+ *
+ * @param string $text input string that might contain shortcodes
+ * @return string $text string without shortcodes
+ */
+function wpseo_strip_shortcode( $text ) {
+ return preg_replace( '`\[[^\]]+\]`s', '', $text );
+}
+
+/**
+ * Redirect /sitemap.xml to /sitemap_index.xml
+ */
+function wpseo_xml_redirect_sitemap() {
+ global $wp_query;
+
+ $current_url = ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ? 'https://' : 'http://';
+ $current_url .= sanitize_text_field( $_SERVER['SERVER_NAME'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
+
+ // must be 'sitemap.xml' and must be 404
+ if ( home_url( '/sitemap.xml' ) == $current_url && $wp_query->is_404 ) {
+ wp_redirect( home_url( '/sitemap_index.xml' ) );
+ exit;
+ }
+}
+
+/**
+ * Create base URL for the sitemaps and applies filters
+ *
+ * @since 1.5.7
+ *
+ * @param string $page page to append to the base URL
+ *
+ * @return string base URL (incl page) for the sitemaps
+ */
+function wpseo_xml_sitemaps_base_url( $page ) {
+ $base = $GLOBALS['wp_rewrite']->using_index_permalinks() ? 'index.php/' : '/';
+ $base = apply_filters( 'wpseo_sitemaps_base_url', $base );
+
+ return home_url( $base . $page );
+}
+
+/**
+ * Initialize sitemaps. Add sitemap & XSL rewrite rules and query vars
+ */
+function wpseo_xml_sitemaps_init() {
+ $options = get_option( 'wpseo_xml' );
+ if ( $options['enablexmlsitemap'] !== true ) {
+ return;
+ }
+
+ // redirects sitemap.xml to sitemap_index.xml
+ add_action( 'template_redirect', 'wpseo_xml_redirect_sitemap', 0 );
+
+ if ( ! is_object( $GLOBALS['wp'] ) ) {
+ return;
+ }
+
+ $GLOBALS['wp']->add_query_var( 'sitemap' );
+ $GLOBALS['wp']->add_query_var( 'sitemap_n' );
+ $GLOBALS['wp']->add_query_var( 'xsl' );
+ add_rewrite_rule( 'sitemap_index\.xml$', 'index.php?sitemap=1', 'top' );
+ add_rewrite_rule( '([^/]+?)-sitemap([0-9]+)?\.xml$', 'index.php?sitemap=$matches[1]&sitemap_n=$matches[2]', 'top' );
+ add_rewrite_rule( '([a-z]+)?-?sitemap\.xsl$', 'index.php?xsl=$matches[1]', 'top' );
+}
+
+add_action( 'init', 'wpseo_xml_sitemaps_init', 1 );
+
+/**
+ * Notify search engines of the updated sitemap.
+ */
+function wpseo_ping_search_engines( $sitemapurl = null ) {
+ // Don't ping if blog is not public
+ if ( '0' == get_option( 'blog_public' ) ) {
+ return;
+ }
+
+ $options = get_option( 'wpseo_xml' );
+ if ( $sitemapurl == null ) {
+ $sitemapurl = urlencode( wpseo_xml_sitemaps_base_url( 'sitemap_index.xml' ) );
+ }
+
+ // Always ping Google and Bing, optionally ping Ask and Yahoo!
+ wp_remote_get( 'http://www.google.com/webmasters/tools/ping?sitemap=' . $sitemapurl );
+ wp_remote_get( 'http://www.bing.com/ping?sitemap=' . $sitemapurl );
+
+ if ( $options['xml_ping_yahoo'] === true ) {
+ wp_remote_get( 'http://search.yahooapis.com/SiteExplorerService/V1/updateNotification?appid=3usdTDLV34HbjQpIBuzMM1UkECFl5KDN7fogidABihmHBfqaebDuZk1vpLDR64I-&url=' . $sitemapurl );
+ }
+
+ if ( $options['xml_ping_ask'] === true ) {
+ wp_remote_get( 'http://submissions.ask.com/ping?sitemap=' . $sitemapurl );
+ }
+}
+add_action( 'wpseo_ping_search_engines', 'wpseo_ping_search_engines' );
+
+
+function wpseo_store_tracking_response() {
+ if ( ! wp_verify_nonce( $_POST['nonce'], 'wpseo_activate_tracking' ) ) {
+ die();
+ }
+
+ $options = get_option( 'wpseo' );
+ $options['tracking_popup_done'] = true;
+
+ if ( $_POST['allow_tracking'] == 'yes' ) {
+ $options['yoast_tracking'] = true;
+ }
+ else {
+ $options['yoast_tracking'] = false;
+ }
+
+ update_option( 'wpseo', $options );
+}
+add_action( 'wp_ajax_wpseo_allow_tracking', 'wpseo_store_tracking_response' );
+
+/**
+ * WPML plugin support: Set titles for custom types / taxonomies as translatable.
+ * It adds new keys to a wpml-config.xml file for a custom post type title, metadesc, title-ptarchive and metadesc-ptarchive fields translation.
+ * Documentation: http://wpml.org/documentation/support/language-configuration-files/
+ *
+ * @global $sitepress
+ * @param array $config
+ * @return array
+ */
+function wpseo_wpml_config( $config ) {
+ global $sitepress;
+
+ if ( ( is_array( $config ) && isset( $config['wpml-config']['admin-texts']['key'] ) ) && ( is_array( $config['wpml-config']['admin-texts']['key'] ) && $config['wpml-config']['admin-texts']['key'] !== array() ) ) {
+ $admin_texts = $config['wpml-config']['admin-texts']['key'];
+ foreach ( $admin_texts as $k => $val ) {
+ if ( $val['attr']['name'] === 'wpseo_titles' ) {
+ $translate_cp = array_keys( $sitepress->get_translatable_documents() );
+ if ( is_array( $translate_cp ) && $translate_cp !== array() ) {
+ foreach ( $translate_cp as $post_type ) {
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'title-'. $post_type;
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'metadesc-'. $post_type;
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'metakey-'. $post_type;
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'title-ptarchive-'. $post_type;
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'metadesc-ptarchive-'. $post_type;
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'metakey-ptarchive-'. $post_type;
+
+ $translate_tax = $sitepress->get_translatable_taxonomies( false, $post_type );
+ if ( is_array( $translate_tax ) && $translate_tax !== array() ) {
+ foreach ( $translate_tax as $taxonomy ) {
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'title-tax-'. $taxonomy;
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'metadesc-tax-'. $taxonomy;
+ $admin_texts[ $k ]['key'][]['attr']['name'] = 'metakey-tax-'. $taxonomy;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ $config['wpml-config']['admin-texts']['key'] = $admin_texts;
+ }
+
+ return $config;
+}
+add_filter( 'icl_wpml_config_array', 'wpseo_wpml_config' );
+
+if ( ! function_exists( 'wpseo_calc' ) ) {
+ /**
+ * Do simple reliable math calculations without the risk of wrong results
+ * @see http://floating-point-gui.de/
+ * @see the big red warning on http://php.net/language.types.float.php
+ *
+ * In the rare case that the bcmath extension would not be loaded, it will return the normal calculation results
+ *
+ * @since 1.5.0
+ *
+ * @param mixed $number1 Scalar (string/int/float/bool)
+ * @param string $action Calculation action to execute. Valid input:
+ * '+' or 'add' or 'addition',
+ * '-' or 'sub' or 'subtract',
+ * '*' or 'mul' or 'multiply',
+ * '/' or 'div' or 'divide',
+ * '%' or 'mod' or 'modulus'
+ * '=' or 'comp' or 'compare'
+ * @param mixed $number2 Scalar (string/int/float/bool)
+ * @param bool $round Whether or not to round the result. Defaults to false.
+ * Will be disregarded for a compare operation
+ * @param int $decimals Decimals for rounding operation. Defaults to 0.
+ * @param int $precision Calculation precision. Defaults to 10.
+ * @return mixed Calculation Result or false if either or the numbers isn't scalar or
+ * an invalid operation was passed
+ * - for compare the result will always be an integer
+ * - for all other operations, the result will either be an integer (preferred)
+ * or a float
+ */
+ function wpseo_calc( $number1, $action, $number2, $round = false, $decimals = 0, $precision = 10 ) {
+ static $bc;
+
+ if ( ! is_scalar( $number1 ) || ! is_scalar( $number2 ) ) {
+ return false;
+ }
+
+ if ( ! isset( $bc ) ) {
+ $bc = extension_loaded( 'bcmath' );
+ }
+
+ if ( $bc ) {
+ $number1 = strval( $number1 );
+ $number2 = strval( $number2 );
+ }
+
+ $result = null;
+ $compare = false;
+
+ switch ( $action ) {
+ case '+':
+ case 'add':
+ case 'addition':
+ $result = ( $bc ) ? bcadd( $number1, $number2, $precision ) /* string */ : ( $number1 + $number2 );
+ break;
+
+ case '-':
+ case 'sub':
+ case 'subtract':
+ $result = ( $bc ) ? bcsub( $number1, $number2, $precision ) /* string */ : ( $number1 - $number2 );
+ break;
+
+ case '*':
+ case 'mul':
+ case 'multiply':
+ $result = ( $bc ) ? bcmul( $number1, $number2, $precision ) /* string */ : ( $number1 * $number2 );
+ break;
+
+ case '/':
+ case 'div':
+ case 'divide':
+ if ( $bc ) {
+ $result = bcdiv( $number1, $number2, $precision ); // string, or NULL if right_operand is 0
+ }
+ elseif ( $number2 != 0 ) {
+ $result = $number1 / $number2;
+ }
+
+ if ( ! isset( $result ) ) {
+ $result = 0;
+ }
+ break;
+
+ case '%':
+ case 'mod':
+ case 'modulus':
+ if ( $bc ) {
+ $result = bcmod( $number1, $number2, $precision ); // string, or NULL if modulus is 0.
+ }
+ elseif ( $number2 != 0 ) {
+ $result = $number1 % $number2;
+ }
+
+ if ( ! isset( $result ) ) {
+ $result = 0;
+ }
+ break;
+
+ case '=':
+ case 'comp':
+ case 'compare':
+ $compare = true;
+ if ( $bc ) {
+ $result = bccomp( $number1, $number2, $precision ); // returns int 0, 1 or -1
+ }
+ else {
+ $result = ( $number1 == $number2 ) ? 0 : ( ( $number1 > $number2 ) ? 1 : -1 );
+ }
+ break;
+ }
+
+ if ( isset( $result ) ) {
+ if ( $compare === false ) {
+ if ( $round === true ) {
+ $result = round( floatval( $result ), $decimals );
+ if ( $decimals === 0 ) {
+ $result = (int) $result;
+ }
+ }
+ else {
+ $result = ( intval( $result ) == $result ) ? intval( $result ) : floatval( $result );
+ }
+ }
+ return $result;
+ }
+ return false;
+ }
+}
+
+/**
+ * Check if the web server is running on Apache
+ * @return bool
+ */
+function wpseo_is_apache() {
+ if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && stristr( $_SERVER['SERVER_SOFTWARE'], 'apache' ) !== false ) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Check if the web service is running on Nginx
+ *
+ * @return bool
+ */
+function wpseo_is_nginx() {
+ if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && stristr( $_SERVER['SERVER_SOFTWARE'], 'nginx' ) !== false ) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * WordPress SEO breadcrumb shortcode
+ * [wpseo_breadcrumb]
+ *
+ * @return string
+ */
+function wpseo_shortcode_yoast_breadcrumb() {
+ return yoast_breadcrumb( '', '', false );
+}
+add_shortcode( 'wpseo_breadcrumb', 'wpseo_shortcode_yoast_breadcrumb' );
+
+
+/**
+ * This invalidates our XML Sitemaps cache.
+ *
+ * @param $type
+ */
+function wpseo_invalidate_sitemap_cache( $type ) {
+ // Always delete the main index sitemaps cache, as that's always invalidated by any other change
+ delete_transient( 'wpseo_sitemap_cache_1' );
+ delete_transient( 'wpseo_sitemap_cache_' . $type );
+}
+
+add_action( 'deleted_term_relationships', 'wpseo_invalidate_sitemap_cache' );
+
+/**
+ * Invalidate XML sitemap cache for taxonomy / term actions
+ *
+ * @param unsigned $unused
+ * @param string $type
+ */
+function wpseo_invalidate_sitemap_cache_terms( $unused, $type ) {
+ wpseo_invalidate_sitemap_cache( $type );
+}
+
+add_action( 'edited_terms', 'wpseo_invalidate_sitemap_cache_terms', 10, 2 );
+add_action( 'clean_term_cache', 'wpseo_invalidate_sitemap_cache_terms', 10, 2 );
+add_action( 'clean_object_term_cache', 'wpseo_invalidate_sitemap_cache_terms', 10, 2 );
+
+/**
+ * Invalidate the XML sitemap cache for a post type when publishing or updating a post
+ *
+ * @param int $post_id
+ */
+function wpseo_invalidate_sitemap_cache_on_save_post( $post_id ) {
+
+ // If this is just a revision, don't invalidate the sitemap cache yet.
+ if ( wp_is_post_revision( $post_id ) ) {
+ return;
+ }
+
+ wpseo_invalidate_sitemap_cache( get_post_type( $post_id ) );
+}
+
+add_action( 'save_post', 'wpseo_invalidate_sitemap_cache_on_save_post' );
+
+/**
+ * List all the available user roles
+ *
+ * @return array $roles
+ */
+function wpseo_get_roles() {
+ global $wp_roles;
+
+ if ( ! isset( $wp_roles ) ) {
+ $wp_roles = new WP_Roles();
+ }
+
+ $roles = $wp_roles->get_names();
+
+ return $roles;
+}
+
+/**
+ * Check whether a url is relative
+ *
+ * @param string $url
+ *
+ * @return bool
+ */
+function wpseo_is_url_relative( $url ) {
+ return ( strpos( $url, 'http' ) !== 0 && strpos( $url, '//' ) !== 0 );
+}
+
+/**
+ * Standardize whitespace in a string
+ *
+ * Replace line breaks, carriage returns, tabs with a space, then remove double spaces.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+function wpseo_standardize_whitespace( $string ) {
+ return trim( str_replace( ' ', ' ', str_replace( array( "\t", "\n", "\r", "\f" ), ' ', $string ) ) );
+}
+
+/**
+ * Emulate PHP native ctype_digit() function for when the ctype extension would be disabled *sigh*
+ * Only emulates the behaviour for when the input is a string, does not handle integer input as ascii value
+ *
+ * @param string $string
+ *
+ * @return bool
+ */
+if ( ! extension_loaded( 'ctype' ) || ! function_exists( 'ctype_digit' ) ) {
+ function ctype_digit( $string ) {
+ $return = false;
+ if ( ( is_string( $string ) && $string !== '' ) && preg_match( '`^\d+$`', $string ) === 1 ){
+ $return = true;
+ }
+ return $return;
+ }
+}
+
+
+/********************** DEPRECATED FUNCTIONS **********************/
+
+
+/**
+ * Get the value from the post custom values
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Meta::get_value()
+ * @see WPSEO_Meta::get_value()
+ *
+ * @param string $val internal name of the value to get
+ * @param int $postid post ID of the post to get the value for
+ * @return string
+ */
+function wpseo_get_value( $val, $postid = 0 ) {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.0', 'WPSEO_Meta::get_value()' );
+ return WPSEO_Meta::get_value( $val, $postid );
+}
+
+
+/**
+ * Save a custom meta value
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Meta::set_value() or just use update_post_meta()
+ * @see WPSEO_Meta::set_value()
+ *
+ * @param string $meta_key the meta to change
+ * @param mixed $meta_value the value to set the meta to
+ * @param int $post_id the ID of the post to change the meta for.
+ * @return bool whether the value was changed
+ */
+function wpseo_set_value( $meta_key, $meta_value, $post_id ) {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.0', 'WPSEO_Meta::set_value()' );
+ return WPSEO_Meta::set_value( $meta_key, $meta_value, $post_id );
+}
+
+
+/**
+ * Retrieve an array of all the options the plugin uses. It can't use only one due to limitations of the options API.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Options::get_option_names()
+ * @see WPSEO_Options::get_option_names()
+ *
+ * @return array of options.
+ */
+function get_wpseo_options_arr() {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.0', 'WPSEO_Options::get_option_names()' );
+ return WPSEO_Options::get_option_names();
+}
+
+
+/**
+ * Retrieve all the options for the SEO plugin in one go.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Options::get_all()
+ * @see WPSEO_Options::get_all()
+ *
+ * @return array of options
+ */
+function get_wpseo_options() {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.0', 'WPSEO_Options::get_all()' );
+ return WPSEO_Options::get_all();
+}
+
+/**
+ * Used for imports, both in dashboard and import settings pages, this functions either copies
+ * $old_metakey into $new_metakey or just plain replaces $old_metakey with $new_metakey
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Meta::replace_meta()
+ * @see WPSEO_Meta::replace_meta()
+ *
+ * @param string $old_metakey The old name of the meta value.
+ * @param string $new_metakey The new name of the meta value, usually the WP SEO name.
+ * @param bool $replace Whether to replace or to copy the values.
+ */
+function replace_meta( $old_metakey, $new_metakey, $replace = false ) {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.0', 'WPSEO_Meta::replace_meta()' );
+ WPSEO_Meta::replace_meta( $old_metakey, $new_metakey, $replace );
+}
+
+
+/**
+ * Retrieve a taxonomy term's meta value.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Taxonomy_Meta::get_term_meta()
+ * @see WPSEO_Taxonomy_Meta::get_term_meta()
+ *
+ * @param string|object $term term to get the meta value for
+ * @param string $taxonomy name of the taxonomy to which the term is attached
+ * @param string $meta meta value to get
+ * @return bool|mixed value when the meta exists, false when it does not
+ */
+function wpseo_get_term_meta( $term, $taxonomy, $meta ) {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.0', 'WPSEO_Taxonomy_Meta::get_term_meta' );
+ WPSEO_Taxonomy_Meta::get_term_meta( $term, $taxonomy, $meta );
+}
+
+/**
+ * Throw a notice about an invalid custom taxonomy used
+ *
+ * @since 1.4.14
+ * @deprecated 1.5.4 (removed)
+ */
+function wpseo_invalid_custom_taxonomy() {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.4' );
+}
+
+/**
+ * Retrieve a post's terms, comma delimited.
+ *
+ * @deprecated 1.5.4
+ * @deprecated use WPSEO_Replace_Vars::get_terms()
+ * @see WPSEO_Replace_Vars::get_terms()
+ *
+ * @param int $id ID of the post to get the terms for.
+ * @param string $taxonomy The taxonomy to get the terms for this post from.
+ * @param bool $return_single If true, return the first term.
+ * @return string either a single term or a comma delimited string of terms.
+ */
+function wpseo_get_terms( $id, $taxonomy, $return_single = false ) {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.4', 'WPSEO_Replace_Vars::get_terms' );
+ $replacer = new WPSEO_Replace_Vars;
+ return $replacer->get_terms( $id, $taxonomy, $return_single );
+}
+
+/**
+ * Generate an HTML sitemap
+ *
+ * @deprecated 1.5.5.4
+ * @deprecated use plugin WordPress SEO Premium
+ * @see WordPress SEO Premium
+ *
+ * @param array $atts The attributes passed to the shortcode.
+ *
+ * @return string
+ */
+function wpseo_sitemap_handler( $atts ) {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.5.4', 'Functionality has been discontinued after being in beta, it\'ll be available in the WordPress SEO Premium plugin soon.' );
+ return '';
+}
+
+add_shortcode( 'wpseo_sitemap', 'wpseo_sitemap_handler' );
--- /dev/null
+<?php
+/**
+ * @package Internals
+ */
+
+if ( ! defined( 'WPSEO_VERSION' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+
+/**
+ * Test whether force rewrite should be enabled or not.
+ */
+function wpseo_title_test() {
+ $options = get_option( 'wpseo_titles' );
+
+ $options['forcerewritetitle'] = false;
+ $options['title_test'] = 1;
+ update_option( 'wpseo_titles', $options );
+
+ // Setting title_test to > 0 forces the plugin to output the title below through a filter in class-frontend.php
+ $expected_title = 'This is a Yoast Test Title';
+
+ WPSEO_Options::clear_cache();
+
+
+ global $wp_version;
+ $args = array(
+ 'user-agent' => "WordPress/${wp_version}; " . get_site_url() . ' - Yoast',
+ );
+ $resp = wp_remote_get( get_bloginfo( 'url' ), $args );
+
+ // echo '<pre>'.$resp['body'].'</pre>';
+
+ if ( ( $resp && ! is_wp_error( $resp ) ) && ( 200 == $resp['response']['code'] && isset( $resp['body'] ) ) ) {
+ $res = preg_match( '`<title>([^<]+)</title>`im', $resp['body'], $matches );
+
+ if ( $res && strcmp( $matches[1], $expected_title ) !== 0 ) {
+ $options['forcerewritetitle'] = true;
+
+ $resp = wp_remote_get( get_bloginfo( 'url' ), $args );
+ $res = false;
+ if ( ( $resp && ! is_wp_error( $resp ) ) && ( 200 == $resp['response']['code'] && isset( $resp['body'] ) ) ) {
+ $res = preg_match( '`/<title>([^>]+)</title>`im', $resp['body'], $matches );
+ }
+ }
+
+ if ( ! $res || $matches[1] != $expected_title ) {
+ $options['forcerewritetitle'] = false;
+ }
+ } else {
+ // If that dies, let's make sure the titles are correct and force the output.
+ $options['forcerewritetitle'] = true;
+ }
+
+ $options['title_test'] = 0;
+ update_option( 'wpseo_titles', $options );
+}
+
+//add_filter( 'switch_theme', 'wpseo_title_test', 0 );
+
+
+/**
+ * Test whether the active theme contains a <meta> description tag.
+ *
+ * @since 1.4.14 Moved from dashboard.php and adjusted - see changelog
+ *
+ * @return void
+ */
+function wpseo_description_test() {
+ $options = get_option( 'wpseo' );
+
+ // Reset any related options - dirty way of getting the default to make sure it works on activation
+ $options['theme_has_description'] = WPSEO_Option_Wpseo::$desc_defaults['theme_has_description'];
+ $options['theme_description_found'] = WPSEO_Option_Wpseo::$desc_defaults['theme_description_found'];
+ /* @internal Should this be reset too ? Best to do so as test is done on re-activate and switch_theme
+ * as well and new warning would be warranted then. Only might give irritation on theme upgrade. */
+ $options['ignore_meta_description_warning'] = WPSEO_Option_Wpseo::$desc_defaults['ignore_meta_description_warning'];
+
+ $file = false;
+ if ( file_exists( get_stylesheet_directory() . '/header.php' ) ) {
+ // theme or child theme
+ $file = get_stylesheet_directory() . '/header.php';
+ } elseif ( file_exists( get_template_directory() . '/header.php' ) ) {
+ // parent theme in case of a child theme
+ $file = get_template_directory() . '/header.php';
+ }
+
+ if ( is_string( $file ) && $file !== '' ) {
+ $header_file = file_get_contents( $file );
+ $issue = preg_match_all( '#<\s*meta\s*(name|content)\s*=\s*("|\')(.*)("|\')\s*(name|content)\s*=\s*("|\')(.*)("|\')(\s+)?/?>#i', $header_file, $matches, PREG_SET_ORDER );
+ if ( $issue === false ) {
+ $options['theme_has_description'] = false;
+ } else {
+ foreach ( $matches as $meta ) {
+ if ( ( strtolower( $meta[1] ) == 'name' && strtolower( $meta[3] ) == 'description' ) || ( strtolower( $meta[5] ) == 'name' && strtolower( $meta[7] ) == 'description' ) ) {
+ $options['theme_description_found'] = $meta[0];
+ $options['ignore_meta_description_warning'] = false;
+ break; // no need to run through the rest of the meta's
+ }
+ }
+ if ( $options['theme_description_found'] !== '' ) {
+ $options['theme_has_description'] = true;
+ } else {
+ $options['theme_has_description'] = false;
+ }
+ }
+ }
+ update_option( 'wpseo', $options );
+}
+
+add_filter( 'after_switch_theme', 'wpseo_description_test', 0 );
+
+if ( version_compare( $GLOBALS['wp_version'], '3.6.99', '>' ) ) {
+ // Use the new and *sigh* adjusted action hook WP 3.7+
+ add_action( 'upgrader_process_complete', 'wpseo_upgrader_process_complete', 10, 2 );
+} elseif ( version_compare( $GLOBALS['wp_version'], '3.5.99', '>' ) ) {
+ // Use the new action hook WP 3.6+
+ add_action( 'upgrader_process_complete', 'wpseo_upgrader_process_complete', 10, 3 );
+} else {
+ // Abuse filters to do our action
+ add_filter( 'update_theme_complete_actions', 'wpseo_update_theme_complete_actions', 10, 2 );
+ add_filter( 'update_bulk_theme_complete_actions', 'wpseo_update_theme_complete_actions', 10, 2 );
+}
+
+
+/**
+ * Check if the current theme was updated and if so, test the updated theme
+ * for the title and meta description tag
+ *
+ * @since 1.4.14
+ *
+ * @param object $upgrader_object
+ * @param array $context_array
+ * @param mixed $themes
+ *
+ * @return void
+ */
+function wpseo_upgrader_process_complete( $upgrader_object, $context_array, $themes = null ) {
+ $options = get_option( 'wpseo' );
+
+ // Break if admin_notice already in place
+ if ( ( ( isset( $options['theme_has_description'] ) && $options['theme_has_description'] === true ) || $options['theme_description_found'] !== '' ) && $options['ignore_meta_description_warning'] !== true ) {
+ return;
+ }
+ // Break if this is not a theme update, not interested in installs as after_switch_theme would still be called
+ if ( ! isset( $context_array['type'] ) || $context_array['type'] !== 'theme' || ! isset( $context_array['action'] ) || $context_array['action'] !== 'update' ) {
+ return;
+ }
+
+ $theme = get_stylesheet();
+ if ( ! isset( $themes ) ) {
+ // WP 3.7+
+ $themes = array();
+ if ( isset( $context_array['themes'] ) && $context_array['themes'] !== array() ) {
+ $themes = $context_array['themes'];
+ } elseif ( isset( $context_array['theme'] ) && $context_array['theme'] !== '' ) {
+ $themes = $context_array['theme'];
+ }
+ }
+
+ if ( ( isset( $context_array['bulk'] ) && $context_array['bulk'] === true ) && ( is_array( $themes ) && count( $themes ) > 0 ) ) {
+
+ if ( in_array( $theme, $themes ) ) {
+// wpseo_title_test();
+ wpseo_description_test();
+ }
+ } elseif ( is_string( $themes ) && $themes === $theme ) {
+// wpseo_title_test();
+ wpseo_description_test();
+ }
+
+ return;
+}
+
+/**
+ * Abuse a filter to check if the current theme was updated and if so, test the updated theme
+ * for the title and meta description tag
+ *
+ * @since 1.4.14
+ *
+ * @param array $update_actions
+ * @param mixed $updated_theme
+ *
+ * @return array $update_actions Unchanged array
+ */
+function wpseo_update_theme_complete_actions( $update_actions, $updated_theme ) {
+ $options = get_option( 'wpseo' );
+
+ // Break if admin_notice already in place
+ if ( ( ( isset( $options['theme_has_description'] ) && $options['theme_has_description'] === true ) || $options['theme_description_found'] !== '' ) && $options['ignore_meta_description_warning'] !== true ) {
+ return $update_actions;
+ }
+
+ $theme = get_stylesheet();
+ if ( is_object( $updated_theme ) ) {
+ /* Bulk update and $updated_theme only contains info on which theme was last in the list
+ of updated themes, so go & test */
+// wpseo_title_test();
+ wpseo_description_test();
+ } elseif ( $updated_theme === $theme ) {
+ /* Single theme update for the active theme */
+// wpseo_title_test();
+ wpseo_description_test();
+ }
+
+ return $update_actions;
+}
+
+/**
+ * Translates a decimal analysis score into a textual one.
+ *
+ * @param int $val The decimal score to translate.
+ * @param bool $css_value Whether to return the i18n translated score or the CSS class value.
+ *
+ * @return string
+ */
+function wpseo_translate_score( $val, $css_value = true ) {
+ if ( $val > 10 ) {
+ $val = round( $val / 10 );
+ }
+ switch ( $val ) {
+ case 0:
+ $score = __( 'N/A', 'wordpress-seo' );
+ $css = 'na';
+ break;
+ case 4:
+ case 5:
+ $score = __( 'Poor', 'wordpress-seo' );
+ $css = 'poor';
+ break;
+ case 6:
+ case 7:
+ $score = __( 'OK', 'wordpress-seo' );
+ $css = 'ok';
+ break;
+ case 8:
+ case 9:
+ case 10:
+ $score = __( 'Good', 'wordpress-seo' );
+ $css = 'good';
+ break;
+ default:
+ $score = __( 'Bad', 'wordpress-seo' );
+ $css = 'bad';
+ }
+
+ if ( $css_value ) {
+ return $css;
+ } else {
+ return $score;
+ }
+}
+
+
+/**
+ * Check whether file editing is allowed for the .htaccess and robots.txt files
+ *
+ * @internal current_user_can() checks internally whether a user is on wp-ms and adjusts accordingly.
+ *
+ * @return bool
+ */
+function wpseo_allow_system_file_edit() {
+ $allowed = true;
+
+ if ( current_user_can( 'edit_files' ) === false ) {
+ $allowed = false;
+ }
+
+ /**
+ * Filter: 'wpseo_allow_system_file_edit' - Allow developers to change whether the editing of
+ * .htaccess and robots.txt is allowed
+ *
+ * @api bool $allowed Whether file editing is allowed
+ */
+
+ return apply_filters( 'wpseo_allow_system_file_edit', $allowed );
+}
+
+
+/**
+ * Adds an SEO admin bar menu with several options. If the current user is an admin he can also go straight to several settings menu's from here.
+ */
+function wpseo_admin_bar_menu() {
+ // If the current user can't write posts, this is all of no use, so let's not output an admin menu
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return;
+ }
+
+ global $wp_admin_bar, $wpseo_front, $post;
+
+ $url = '';
+ if ( is_object( $wpseo_front ) ) {
+ $url = $wpseo_front->canonical( false );
+ }
+
+ $focuskw = '';
+ $score = '';
+ $seo_url = get_admin_url( null, 'admin.php?page=wpseo_dashboard' );
+
+ if ( ( is_singular() || ( is_admin() && in_array( $GLOBALS['pagenow'], array(
+ 'post.php',
+ 'post-new.php',
+ ), true ) ) ) && isset( $post ) && is_object( $post ) && apply_filters( 'wpseo_use_page_analysis', true ) === true
+ ) {
+ $focuskw = WPSEO_Meta::get_value( 'focuskw', $post->ID );
+ $perc_score = WPSEO_Meta::get_value( 'linkdex', $post->ID );
+ $calc_score = wpseo_calc( $perc_score, '/', 10, true );
+ $txtscore = wpseo_translate_score( $calc_score );
+ $title = wpseo_translate_score( $calc_score, false );
+ $score = '<div title="' . esc_attr( $title ) . '" class="' . esc_attr( 'wpseo-score-icon ' . $txtscore . ' ' . $perc_score ) . '"></div>';
+
+ $seo_url = get_edit_post_link( $post->ID );
+ if ( $txtscore !== 'na' ) {
+ $seo_url .= '#wpseo_linkdex';
+ }
+ }
+
+ $wp_admin_bar->add_menu( array(
+ 'id' => 'wpseo-menu',
+ 'title' => __( 'SEO', 'wordpress-seo' ) . $score,
+ 'href' => $seo_url,
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-menu',
+ 'id' => 'wpseo-kwresearch',
+ 'title' => __( 'Keyword Research', 'wordpress-seo' ),
+ '#',
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-kwresearch',
+ 'id' => 'wpseo-adwordsexternal',
+ 'title' => __( 'AdWords External', 'wordpress-seo' ),
+ 'href' => 'http://adwords.google.com/keywordplanner',
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-kwresearch',
+ 'id' => 'wpseo-googleinsights',
+ 'title' => __( 'Google Insights', 'wordpress-seo' ),
+ 'href' => 'http://www.google.com/insights/search/#q=' . urlencode( $focuskw ) . '&cmpt=q',
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-kwresearch',
+ 'id' => 'wpseo-wordtracker',
+ 'title' => __( 'SEO Book', 'wordpress-seo' ),
+ 'href' => 'http://tools.seobook.com/keyword-tools/seobook/?keyword=' . urlencode( $focuskw ),
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+
+ if ( ! is_admin() ) {
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-menu',
+ 'id' => 'wpseo-analysis',
+ 'title' => __( 'Analyze this page', 'wordpress-seo' ),
+ '#',
+ ) );
+ if ( is_string( $url ) ) {
+ // @todo [JRF => whomever] check if this url shouldn't be encoded either with urlencode or with esc_url or something
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-analysis',
+ 'id' => 'wpseo-inlinks-ose',
+ 'title' => __( 'Check Inlinks (OSE)', 'wordpress-seo' ),
+ 'href' => 'http://www.opensiteexplorer.org/' . str_replace( '/', '%252F', preg_replace( '`^http[s]?://`', '', $url ) ) . '/a!links',
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-analysis',
+ 'id' => 'wpseo-kwdensity',
+ 'title' => __( 'Check Keyword Density', 'wordpress-seo' ),
+ 'href' => 'http://www.zippy.co.uk/keyworddensity/index.php?url=' . urlencode( $url ) . '&keyword=' . urlencode( $focuskw ),
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-analysis',
+ 'id' => 'wpseo-cache',
+ 'title' => __( 'Check Google Cache', 'wordpress-seo' ),
+ 'href' => 'http://webcache.googleusercontent.com/search?strip=1&q=cache:' . urlencode( $url ),
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-analysis',
+ 'id' => 'wpseo-header',
+ 'title' => __( 'Check Headers', 'wordpress-seo' ),
+ 'href' => 'http://quixapp.com/headers/?r=' . urlencode( $url ),
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-analysis',
+ 'id' => 'wpseo-richsnippets',
+ 'title' => __( 'Check Rich Snippets', 'wordpress-seo' ),
+ 'href' => 'http://www.google.com/webmasters/tools/richsnippets?q=' . urlencode( $url ),
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-analysis',
+ 'id' => 'wpseo-facebookdebug',
+ 'title' => __( 'Facebook Debugger', 'wordpress-seo' ),
+ 'href' => 'https://developers.facebook.com/tools/debug/og/object?q=' . urlencode( $url ),
+ 'meta' => array( 'target' => '_blank' )
+ ) );
+ }
+ }
+
+ $admin_menu = false;
+ if ( is_multisite() ) {
+ $options = get_site_option( 'wpseo_ms' );
+ if ( $options['access'] === 'superadmin' && is_super_admin() ) {
+ $admin_menu = true;
+ } elseif ( current_user_can( 'manage_options' ) ) {
+ $admin_menu = true;
+ }
+ } elseif ( current_user_can( 'manage_options' ) ) {
+ $admin_menu = true;
+ }
+
+ // @todo: add links to bulk title and bulk description edit pages
+ if ( $admin_menu ) {
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-menu',
+ 'id' => 'wpseo-settings',
+ 'title' => __( 'SEO Settings', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_titles' ),
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-titles',
+ 'title' => __( 'Titles & Metas', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_titles' ),
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-social',
+ 'title' => __( 'Social', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_social' ),
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-xml',
+ 'title' => __( 'XML Sitemaps', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_xml' ),
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-permalinks',
+ 'title' => __( 'Permalinks', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_permalinks' ),
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-internal-links',
+ 'title' => __( 'Internal Links', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_internal-links' ),
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-rss',
+ 'title' => __( 'RSS', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_rss' ),
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-import',
+ 'title' => __( 'Import & Export', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_import' ),
+ ) );
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo_bulk-editor',
+ 'title' => __( 'Bulk Editor', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_bulk-editor' ),
+ ) );
+
+ // Check where to add the edit files page
+ if ( wpseo_allow_system_file_edit() === true ) {
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-files',
+ 'title' => __( 'Edit Files', 'wordpress-seo' ),
+ 'href' => network_admin_url( 'admin.php?page=wpseo_files' ),
+ ) ); // will auto-use admin_url if not in multi-site
+ }
+
+ $wp_admin_bar->add_menu( array(
+ 'parent' => 'wpseo-settings',
+ 'id' => 'wpseo-licenses',
+ 'title' => __( 'Extensions', 'wordpress-seo' ),
+ 'href' => admin_url( 'admin.php?page=wpseo_licenses' ),
+ ) );
+ }
+}
+
+add_action( 'admin_bar_menu', 'wpseo_admin_bar_menu', 95 );
+
+/**
+ * Enqueue a tiny bit of CSS to show so the adminbar shows right.
+ */
+function wpseo_admin_bar_css() {
+ if ( is_admin_bar_showing() && is_singular() ) {
+ wp_enqueue_style( 'boxes', plugins_url( 'css/adminbar' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
+ }
+}
+
+add_action( 'wp_enqueue_scripts', 'wpseo_admin_bar_css' );
+
+/**
+ * Allows editing of the meta fields through weblog editors like Marsedit.
+ *
+ * @param array $allcaps Capabilities that must all be true to allow action.
+ * @param array $cap Array of capabilities to be checked, unused here.
+ * @param array $args List of arguments for the specific cap to be checked.
+ *
+ * @return array $allcaps
+ */
+function allow_custom_field_edits( $allcaps, $cap, $args ) {
+ // $args[0] holds the capability
+ // $args[2] holds the post ID
+ // $args[3] holds the custom field
+
+ // Make sure the request is to edit or add a post meta (this is usually also the second value in $cap,
+ // but this is safer to check).
+ if ( in_array( $args[0], array( 'edit_post_meta', 'add_post_meta' ) ) ) {
+ // Only allow editing rights for users who have the rights to edit this post and make sure
+ // the meta value starts with _yoast_wpseo (WPSEO_Meta::$meta_prefix).
+ if ( ( isset( $args[2] ) && current_user_can( 'edit_post', $args[2] ) ) && ( ( isset( $args[3] ) && $args[3] !== '' ) && strpos( $args[3], WPSEO_Meta::$meta_prefix ) === 0 ) ) {
+ $allcaps[ $args[0] ] = true;
+ }
+ }
+
+ return $allcaps;
+}
+
+add_filter( 'user_has_cap', 'allow_custom_field_edits', 0, 3 );
+
+/**
+ * Display an import message when robots-meta is active
+ *
+ * @since 1.5.0
+ */
+function wpseo_robots_meta_message() {
+ // check if robots meta is running
+ if ( ( ! isset( $_GET['page'] ) || 'wpseo_import' !== $_GET['page'] ) && is_plugin_active( 'robots-meta/robots-meta.php' ) ) {
+ add_action( 'admin_notices', 'wpseo_import_robots_meta_notice' );
+ }
+}
+
+add_action( 'admin_init', 'wpseo_robots_meta_message' );
+
+/**
+ * Handle deactivation Robots Meta
+ *
+ * @since 1.5.0
+ */
+function wpseo_disable_robots_meta() {
+ if ( isset( $_GET['deactivate_robots_meta'] ) && $_GET['deactivate_robots_meta'] === '1' && is_plugin_active( 'robots-meta/robots-meta.php' ) ) {
+ // Deactivate the plugin
+ deactivate_plugins( 'robots-meta/robots-meta.php' );
+
+ // show notice that robots meta has been deactivated
+ add_action( 'admin_notices', 'wpseo_deactivate_robots_meta_notice' );
+
+ // Clean up the referrer url for later use
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'deactivate_robots_meta' ), sanitize_text_field( $_SERVER['REQUEST_URI'] ) );
+ }
+ }
+}
+
+add_action( 'admin_init', 'wpseo_disable_robots_meta' );
+
+/**
+ * Handle deactivation & import of AIOSEO data
+ *
+ * @since 1.5.0
+ */
+function wpseo_aioseo_message() {
+ // check if aioseo is running
+ if ( ( ! isset( $_GET['page'] ) || 'wpseo_import' != $_GET['page'] ) && is_plugin_active( 'all-in-one-seo-pack/all_in_one_seo_pack.php' ) ) {
+ add_action( 'admin_notices', 'wpseo_import_aioseo_setting_notice' );
+ }
+}
+
+add_action( 'admin_init', 'wpseo_aioseo_message' );
+
+/**
+ * Handle deactivation AIOSEO
+ *
+ * @since 1.5.0
+ */
+function wpseo_disable_aioseo() {
+ if ( isset( $_GET['deactivate_aioseo'] ) && $_GET['deactivate_aioseo'] === '1' && is_plugin_active( 'all-in-one-seo-pack/all_in_one_seo_pack.php' ) ) {
+ // Deactivate AIO
+ deactivate_plugins( 'all-in-one-seo-pack/all_in_one_seo_pack.php' );
+
+ // show notice that aioseo has been deactivated
+ add_action( 'admin_notices', 'wpseo_deactivate_aioseo_notice' );
+
+ // Clean up the referrer url for later use
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'deactivate_aioseo' ), sanitize_text_field( $_SERVER['REQUEST_URI'] ) );
+ }
+ }
+}
+
+add_action( 'admin_init', 'wpseo_disable_aioseo' );
+
+/**
+ * Throw a notice to import AIOSEO.
+ *
+ * @since 1.4.8
+ */
+function wpseo_import_aioseo_setting_notice() {
+ echo '<div class="error"><p>' . sprintf( esc_html__( 'The plugin All-In-One-SEO has been detected. Do you want to %simport its settings%s.', 'wordpress-seo' ), '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_import&import=1&importaioseo=1&_wpnonce=' . wp_create_nonce( 'wpseo-import' ) ) ) . '">', '</a>' ) . '</p></div>';
+}
+
+/**
+ * Throw a notice to inform the user AIOSEO has been deactivated
+ *
+ * @since 1.4.8
+ */
+function wpseo_deactivate_aioseo_notice() {
+ echo '<div class="updated"><p>' . esc_html__( 'All-In-One-SEO has been deactivated', 'wordpress-seo' ) . '</p></div>';
+}
+
+/**
+ * Throw a notice to import Robots Meta.
+ *
+ * @since 1.4.8
+ */
+function wpseo_import_robots_meta_notice() {
+ echo '<div class="error"><p>' . sprintf( esc_html__( 'The plugin Robots-Meta has been detected. Do you want to %simport its settings%s.', 'wordpress-seo' ), '<a href="' . esc_url( admin_url( 'admin.php?page=wpseo_import&import=1&importrobotsmeta=1&_wpnonce=' . wp_create_nonce( 'wpseo-import' ) ) ) . '">', '</a>' ) . '</p></div>';
+}
+
+/**
+ * Throw a notice to inform the user Robots Meta has been deactivated
+ *
+ * @since 1.4.8
+ */
+function wpseo_deactivate_robots_meta_notice() {
+ echo '<div class="updated"><p>' . esc_html__( 'Robots-Meta has been deactivated', 'wordpress-seo' ) . '</p></div>';
+}
+
+/********************** DEPRECATED FUNCTIONS **********************/
+
+/**
+ * Set the default settings.
+ *
+ * @deprecated 1.5.0
+ * @deprecated use WPSEO_Options::initialize()
+ * @see WPSEO_Options::initialize()
+ */
+function wpseo_defaults() {
+ _deprecated_function( __FUNCTION__, 'WPSEO 1.5.0', 'WPSEO_Options::initialize()' );
+ WPSEO_Options::initialize();
+}
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+/*
+ * jquery.qtip. The jQuery tooltip plugin
+ *
+ * Copyright (c) 2009 Craig Thompson
+ * http://craigsworks.com
+ *
+ * Licensed under MIT
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Launch : February 2009
+ * Version : 1.0.0-rc3
+ * Released: Tuesday 12th May, 2009 - 00:00
+ * Debug: jquery.qtip.debug.js
+ */
+(function(f){f.fn.qtip=function(B,u){var y,t,A,s,x,w,v,z;if(typeof B=="string"){if(typeof f(this).data("qtip")!=="object"){f.fn.qtip.log.error.call(self,1,f.fn.qtip.constants.NO_TOOLTIP_PRESENT,false)}if(B=="api"){return f(this).data("qtip").interfaces[f(this).data("qtip").current]}else{if(B=="interfaces"){return f(this).data("qtip").interfaces}}}else{if(!B){B={}}if(typeof B.content!=="object"||(B.content.jquery&&B.content.length>0)){B.content={text:B.content}}if(typeof B.content.title!=="object"){B.content.title={text:B.content.title}}if(typeof B.position!=="object"){B.position={corner:B.position}}if(typeof B.position.corner!=="object"){B.position.corner={target:B.position.corner,tooltip:B.position.corner}}if(typeof B.show!=="object"){B.show={when:B.show}}if(typeof B.show.when!=="object"){B.show.when={event:B.show.when}}if(typeof B.show.effect!=="object"){B.show.effect={type:B.show.effect}}if(typeof B.hide!=="object"){B.hide={when:B.hide}}if(typeof B.hide.when!=="object"){B.hide.when={event:B.hide.when}}if(typeof B.hide.effect!=="object"){B.hide.effect={type:B.hide.effect}}if(typeof B.style!=="object"){B.style={name:B.style}}B.style=c(B.style);s=f.extend(true,{},f.fn.qtip.defaults,B);s.style=a.call({options:s},s.style);s.user=f.extend(true,{},B)}return f(this).each(function(){if(typeof B=="string"){w=B.toLowerCase();A=f(this).qtip("interfaces");if(typeof A=="object"){if(u===true&&w=="destroy"){while(A.length>0){A[A.length-1].destroy()}}else{if(u!==true){A=[f(this).qtip("api")]}for(y=0;y<A.length;y++){if(w=="destroy"){A[y].destroy()}else{if(A[y].status.rendered===true){if(w=="show"){A[y].show()}else{if(w=="hide"){A[y].hide()}else{if(w=="focus"){A[y].focus()}else{if(w=="disable"){A[y].disable(true)}else{if(w=="enable"){A[y].disable(false)}}}}}}}}}}}else{v=f.extend(true,{},s);v.hide.effect.length=s.hide.effect.length;v.show.effect.length=s.show.effect.length;if(v.position.container===false){v.position.container=f(document.body)}if(v.position.target===false){v.position.target=f(this)}if(v.show.when.target===false){v.show.when.target=f(this)}if(v.hide.when.target===false){v.hide.when.target=f(this)}t=f.fn.qtip.interfaces.length;for(y=0;y<t;y++){if(typeof f.fn.qtip.interfaces[y]=="undefined"){t=y;break}}x=new d(f(this),v,t);f.fn.qtip.interfaces[t]=x;if(typeof f(this).data("qtip")=="object"){if(typeof f(this).attr("qtip")==="undefined"){f(this).data("qtip").current=f(this).data("qtip").interfaces.length}f(this).data("qtip").interfaces.push(x)}else{f(this).data("qtip",{current:0,interfaces:[x]})}if(v.content.prerender===false&&v.show.when.event!==false&&v.show.ready!==true){v.show.when.target.bind(v.show.when.event+".qtip-"+t+"-create",{qtip:t},function(C){z=f.fn.qtip.interfaces[C.data.qtip];z.options.show.when.target.unbind(z.options.show.when.event+".qtip-"+C.data.qtip+"-create");z.cache.mouse={x:C.pageX,y:C.pageY};p.call(z);z.options.show.when.target.trigger(z.options.show.when.event)})}else{x.cache.mouse={x:v.show.when.target.offset().left,y:v.show.when.target.offset().top};p.call(x)}}})};function d(u,t,v){var s=this;s.id=v;s.options=t;s.status={animated:false,rendered:false,disabled:false,focused:false};s.elements={target:u.addClass(s.options.style.classes.target),tooltip:null,wrapper:null,content:null,contentWrapper:null,title:null,button:null,tip:null,bgiframe:null};s.cache={mouse:{},position:{},toggle:0};s.timers={};f.extend(s,s.options.api,{show:function(y){var x,z;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"show")}if(s.elements.tooltip.css("display")!=="none"){return s}s.elements.tooltip.stop(true,false);x=s.beforeShow.call(s,y);if(x===false){return s}function w(){if(s.options.position.type!=="static"){s.focus()}s.onShow.call(s,y);if(f.browser.msie){s.elements.tooltip.get(0).style.removeAttribute("filter")}}s.cache.toggle=1;if(s.options.position.type!=="static"){s.updatePosition(y,(s.options.show.effect.length>0))}if(typeof s.options.show.solo=="object"){z=f(s.options.show.solo)}else{if(s.options.show.solo===true){z=f("div.qtip").not(s.elements.tooltip)}}if(z){z.each(function(){if(f(this).qtip("api").status.rendered===true){f(this).qtip("api").hide()}})}if(typeof s.options.show.effect.type=="function"){s.options.show.effect.type.call(s.elements.tooltip,s.options.show.effect.length);s.elements.tooltip.queue(function(){w();f(this).dequeue()})}else{switch(s.options.show.effect.type.toLowerCase()){case"fade":s.elements.tooltip.fadeIn(s.options.show.effect.length,w);break;case"slide":s.elements.tooltip.slideDown(s.options.show.effect.length,function(){w();if(s.options.position.type!=="static"){s.updatePosition(y,true)}});break;case"grow":s.elements.tooltip.show(s.options.show.effect.length,w);break;default:s.elements.tooltip.show(null,w);break}s.elements.tooltip.addClass(s.options.style.classes.active)}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_SHOWN,"show")},hide:function(y){var x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"hide")}else{if(s.elements.tooltip.css("display")==="none"){return s}}clearTimeout(s.timers.show);s.elements.tooltip.stop(true,false);x=s.beforeHide.call(s,y);if(x===false){return s}function w(){s.onHide.call(s,y)}s.cache.toggle=0;if(typeof s.options.hide.effect.type=="function"){s.options.hide.effect.type.call(s.elements.tooltip,s.options.hide.effect.length);s.elements.tooltip.queue(function(){w();f(this).dequeue()})}else{switch(s.options.hide.effect.type.toLowerCase()){case"fade":s.elements.tooltip.fadeOut(s.options.hide.effect.length,w);break;case"slide":s.elements.tooltip.slideUp(s.options.hide.effect.length,w);break;case"grow":s.elements.tooltip.hide(s.options.hide.effect.length,w);break;default:s.elements.tooltip.hide(null,w);break}s.elements.tooltip.removeClass(s.options.style.classes.active)}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_HIDDEN,"hide")},updatePosition:function(w,x){var C,G,L,J,H,E,y,I,B,D,K,A,F,z;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updatePosition")}else{if(s.options.position.type=="static"){return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.CANNOT_POSITION_STATIC,"updatePosition")}}G={position:{left:0,top:0},dimensions:{height:0,width:0},corner:s.options.position.corner.target};L={position:s.getPosition(),dimensions:s.getDimensions(),corner:s.options.position.corner.tooltip};if(s.options.position.target!=="mouse"){if(s.options.position.target.get(0).nodeName.toLowerCase()=="area"){J=s.options.position.target.attr("coords").split(",");for(C=0;C<J.length;C++){J[C]=parseInt(J[C])}H=s.options.position.target.parent("map").attr("name");E=f('img[usemap="#'+H+'"]:first').offset();G.position={left:Math.floor(E.left+J[0]),top:Math.floor(E.top+J[1])};switch(s.options.position.target.attr("shape").toLowerCase()){case"rect":G.dimensions={width:Math.ceil(Math.abs(J[2]-J[0])),height:Math.ceil(Math.abs(J[3]-J[1]))};break;case"circle":G.dimensions={width:J[2]+1,height:J[2]+1};break;case"poly":G.dimensions={width:J[0],height:J[1]};for(C=0;C<J.length;C++){if(C%2==0){if(J[C]>G.dimensions.width){G.dimensions.width=J[C]}if(J[C]<J[0]){G.position.left=Math.floor(E.left+J[C])}}else{if(J[C]>G.dimensions.height){G.dimensions.height=J[C]}if(J[C]<J[1]){G.position.top=Math.floor(E.top+J[C])}}}G.dimensions.width=G.dimensions.width-(G.position.left-E.left);G.dimensions.height=G.dimensions.height-(G.position.top-E.top);break;default:return f.fn.qtip.log.error.call(s,4,f.fn.qtip.constants.INVALID_AREA_SHAPE,"updatePosition");break}G.dimensions.width-=2;G.dimensions.height-=2}else{if(s.options.position.target.add(document.body).length===1){G.position={left:f(document).scrollLeft(),top:f(document).scrollTop()};G.dimensions={height:f(window).height(),width:f(window).width()}}else{if(typeof s.options.position.target.attr("qtip")!=="undefined"){G.position=s.options.position.target.qtip("api").cache.position}else{G.position=s.options.position.target.offset()}G.dimensions={height:s.options.position.target.outerHeight(),width:s.options.position.target.outerWidth()}}}y=f.extend({},G.position);if(G.corner.search(/right/i)!==-1){y.left+=G.dimensions.width}if(G.corner.search(/bottom/i)!==-1){y.top+=G.dimensions.height}if(G.corner.search(/((top|bottom)Middle)|center/)!==-1){y.left+=(G.dimensions.width/2)}if(G.corner.search(/((left|right)Middle)|center/)!==-1){y.top+=(G.dimensions.height/2)}}else{G.position=y={left:s.cache.mouse.x,top:s.cache.mouse.y};G.dimensions={height:1,width:1}}if(L.corner.search(/right/i)!==-1){y.left-=L.dimensions.width}if(L.corner.search(/bottom/i)!==-1){y.top-=L.dimensions.height}if(L.corner.search(/((top|bottom)Middle)|center/)!==-1){y.left-=(L.dimensions.width/2)}if(L.corner.search(/((left|right)Middle)|center/)!==-1){y.top-=(L.dimensions.height/2)}I=(f.browser.msie)?1:0;B=(f.browser.msie&&parseInt(f.browser.version.charAt(0))===6)?1:0;if(s.options.style.border.radius>0){if(L.corner.search(/Left/)!==-1){y.left-=s.options.style.border.radius}else{if(L.corner.search(/Right/)!==-1){y.left+=s.options.style.border.radius}}if(L.corner.search(/Top/)!==-1){y.top-=s.options.style.border.radius}else{if(L.corner.search(/Bottom/)!==-1){y.top+=s.options.style.border.radius}}}if(I){if(L.corner.search(/top/)!==-1){y.top-=I}else{if(L.corner.search(/bottom/)!==-1){y.top+=I}}if(L.corner.search(/left/)!==-1){y.left-=I}else{if(L.corner.search(/right/)!==-1){y.left+=I}}if(L.corner.search(/leftMiddle|rightMiddle/)!==-1){y.top-=1}}if(s.options.position.adjust.screen===true){y=o.call(s,y,G,L)}if(s.options.position.target==="mouse"&&s.options.position.adjust.mouse===true){if(s.options.position.adjust.screen===true&&s.elements.tip){K=s.elements.tip.attr("rel")}else{K=s.options.position.corner.tooltip}y.left+=(K.search(/right/i)!==-1)?-6:6;y.top+=(K.search(/bottom/i)!==-1)?-6:6}if(!s.elements.bgiframe&&f.browser.msie&&parseInt(f.browser.version.charAt(0))==6){f("select, object").each(function(){A=f(this).offset();A.bottom=A.top+f(this).height();A.right=A.left+f(this).width();if(y.top+L.dimensions.height>=A.top&&y.left+L.dimensions.width>=A.left){k.call(s)}})}y.left+=s.options.position.adjust.x;y.top+=s.options.position.adjust.y;F=s.getPosition();if(y.left!=F.left||y.top!=F.top){z=s.beforePositionUpdate.call(s,w);if(z===false){return s}s.cache.position=y;if(x===true){s.status.animated=true;s.elements.tooltip.animate(y,200,"swing",function(){s.status.animated=false})}else{s.elements.tooltip.css(y)}s.onPositionUpdate.call(s,w);if(typeof w!=="undefined"&&w.type&&w.type!=="mousemove"){f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_POSITION_UPDATED,"updatePosition")}}return s},updateWidth:function(w){var x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateWidth")}else{if(w&&typeof w!=="number"){return f.fn.qtip.log.error.call(s,2,"newWidth must be of type number","updateWidth")}}x=s.elements.contentWrapper.siblings().add(s.elements.tip).add(s.elements.button);if(!w){if(typeof s.options.style.width.value=="number"){w=s.options.style.width.value}else{s.elements.tooltip.css({width:"auto"});x.hide();if(f.browser.msie){s.elements.wrapper.add(s.elements.contentWrapper.children()).css({zoom:"normal"})}w=s.getDimensions().width+1;if(!s.options.style.width.value){if(w>s.options.style.width.max){w=s.options.style.width.max}if(w<s.options.style.width.min){w=s.options.style.width.min}}}}if(w%2!==0){w-=1}s.elements.tooltip.width(w);x.show();if(s.options.style.border.radius){s.elements.tooltip.find(".qtip-betweenCorners").each(function(y){f(this).width(w-(s.options.style.border.radius*2))})}if(f.browser.msie){s.elements.wrapper.add(s.elements.contentWrapper.children()).css({zoom:"1"});s.elements.wrapper.width(w);if(s.elements.bgiframe){s.elements.bgiframe.width(w).height(s.getDimensions.height)}}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_WIDTH_UPDATED,"updateWidth")},updateStyle:function(w){var z,A,x,y,B;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateStyle")}else{if(typeof w!=="string"||!f.fn.qtip.styles[w]){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.STYLE_NOT_DEFINED,"updateStyle")}}s.options.style=a.call(s,f.fn.qtip.styles[w],s.options.user.style);s.elements.content.css(q(s.options.style));if(s.options.content.title.text!==false){s.elements.title.css(q(s.options.style.title,true))}s.elements.contentWrapper.css({borderColor:s.options.style.border.color});if(s.options.style.tip.corner!==false){if(f("<canvas>").get(0).getContext){z=s.elements.tooltip.find(".qtip-tip canvas:first");x=z.get(0).getContext("2d");x.clearRect(0,0,300,300);y=z.parent("div[rel]:first").attr("rel");B=b(y,s.options.style.tip.size.width,s.options.style.tip.size.height);h.call(s,z,B,s.options.style.tip.color||s.options.style.border.color)}else{if(f.browser.msie){z=s.elements.tooltip.find('.qtip-tip [nodeName="shape"]');z.attr("fillcolor",s.options.style.tip.color||s.options.style.border.color)}}}if(s.options.style.border.radius>0){s.elements.tooltip.find(".qtip-betweenCorners").css({backgroundColor:s.options.style.border.color});if(f("<canvas>").get(0).getContext){A=g(s.options.style.border.radius);s.elements.tooltip.find(".qtip-wrapper canvas").each(function(){x=f(this).get(0).getContext("2d");x.clearRect(0,0,300,300);y=f(this).parent("div[rel]:first").attr("rel");r.call(s,f(this),A[y],s.options.style.border.radius,s.options.style.border.color)})}else{if(f.browser.msie){s.elements.tooltip.find('.qtip-wrapper [nodeName="arc"]').each(function(){f(this).attr("fillcolor",s.options.style.border.color)})}}}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_STYLE_UPDATED,"updateStyle")},updateContent:function(A,y){var z,x,w;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateContent")}else{if(!A){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.NO_CONTENT_PROVIDED,"updateContent")}}z=s.beforeContentUpdate.call(s,A);if(typeof z=="string"){A=z}else{if(z===false){return}}if(f.browser.msie){s.elements.contentWrapper.children().css({zoom:"normal"})}if(A.jquery&&A.length>0){A.clone(true).appendTo(s.elements.content).show()}else{s.elements.content.html(A)}x=s.elements.content.find("img[complete=false]");if(x.length>0){w=0;x.each(function(C){f('<img src="'+f(this).attr("src")+'" />').load(function(){if(++w==x.length){B()}})})}else{B()}function B(){s.updateWidth();if(y!==false){if(s.options.position.type!=="static"){s.updatePosition(s.elements.tooltip.is(":visible"),true)}if(s.options.style.tip.corner!==false){n.call(s)}}}s.onContentUpdate.call(s);return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_CONTENT_UPDATED,"loadContent")},loadContent:function(w,z,A){var y;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"loadContent")}y=s.beforeContentLoad.call(s);if(y===false){return s}if(A=="post"){f.post(w,z,x)}else{f.get(w,z,x)}function x(B){s.onContentLoad.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_CONTENT_LOADED,"loadContent");s.updateContent(B)}return s},updateTitle:function(w){if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateTitle")}else{if(!w){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.NO_CONTENT_PROVIDED,"updateTitle")}}returned=s.beforeTitleUpdate.call(s);if(returned===false){return s}if(s.elements.button){s.elements.button=s.elements.button.clone(true)}s.elements.title.html(w);if(s.elements.button){s.elements.title.prepend(s.elements.button)}s.onTitleUpdate.call(s);return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_TITLE_UPDATED,"updateTitle")},focus:function(A){var y,x,w,z;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"focus")}else{if(s.options.position.type=="static"){return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.CANNOT_FOCUS_STATIC,"focus")}}y=parseInt(s.elements.tooltip.css("z-index"));x=6000+f("div.qtip[qtip]").length-1;if(!s.status.focused&&y!==x){z=s.beforeFocus.call(s,A);if(z===false){return s}f("div.qtip[qtip]").not(s.elements.tooltip).each(function(){if(f(this).qtip("api").status.rendered===true){w=parseInt(f(this).css("z-index"));if(typeof w=="number"&&w>-1){f(this).css({zIndex:parseInt(f(this).css("z-index"))-1})}f(this).qtip("api").status.focused=false}});s.elements.tooltip.css({zIndex:x});s.status.focused=true;s.onFocus.call(s,A);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_FOCUSED,"focus")}return s},disable:function(w){if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"disable")}if(w){if(!s.status.disabled){s.status.disabled=true;f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_DISABLED,"disable")}else{f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.TOOLTIP_ALREADY_DISABLED,"disable")}}else{if(s.status.disabled){s.status.disabled=false;f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_ENABLED,"disable")}else{f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.TOOLTIP_ALREADY_ENABLED,"disable")}}return s},destroy:function(){var w,x,y;x=s.beforeDestroy.call(s);if(x===false){return s}if(s.status.rendered){s.options.show.when.target.unbind("mousemove.qtip",s.updatePosition);s.options.show.when.target.unbind("mouseout.qtip",s.hide);s.options.show.when.target.unbind(s.options.show.when.event+".qtip");s.options.hide.when.target.unbind(s.options.hide.when.event+".qtip");s.elements.tooltip.unbind(s.options.hide.when.event+".qtip");s.elements.tooltip.unbind("mouseover.qtip",s.focus);s.elements.tooltip.remove()}else{s.options.show.when.target.unbind(s.options.show.when.event+".qtip-create")}if(typeof s.elements.target.data("qtip")=="object"){y=s.elements.target.data("qtip").interfaces;if(typeof y=="object"&&y.length>0){for(w=0;w<y.length-1;w++){if(y[w].id==s.id){y.splice(w,1)}}}}delete f.fn.qtip.interfaces[s.id];if(typeof y=="object"&&y.length>0){s.elements.target.data("qtip").current=y.length-1}else{s.elements.target.removeData("qtip")}s.onDestroy.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_DESTROYED,"destroy");return s.elements.target},getPosition:function(){var w,x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"getPosition")}w=(s.elements.tooltip.css("display")!=="none")?false:true;if(w){s.elements.tooltip.css({visiblity:"hidden"}).show()}x=s.elements.tooltip.offset();if(w){s.elements.tooltip.css({visiblity:"visible"}).hide()}return x},getDimensions:function(){var w,x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"getDimensions")}w=(!s.elements.tooltip.is(":visible"))?true:false;if(w){s.elements.tooltip.css({visiblity:"hidden"}).show()}x={height:s.elements.tooltip.outerHeight(),width:s.elements.tooltip.outerWidth()};if(w){s.elements.tooltip.css({visiblity:"visible"}).hide()}return x}})}function p(){var s,w,u,t,v,y,x;s=this;s.beforeRender.call(s);s.status.rendered=true;s.elements.tooltip='<div qtip="'+s.id+'" class="qtip '+(s.options.style.classes.tooltip||s.options.style)+'"style="display:none; -moz-border-radius:0; -webkit-border-radius:0; border-radius:0;position:'+s.options.position.type+';"> <div class="qtip-wrapper" style="position:relative; overflow:hidden; text-align:left;"> <div class="qtip-contentWrapper" style="overflow:hidden;"> <div class="qtip-content '+s.options.style.classes.content+'"></div></div></div></div>';s.elements.tooltip=f(s.elements.tooltip);s.elements.tooltip.appendTo(s.options.position.container);s.elements.tooltip.data("qtip",{current:0,interfaces:[s]});s.elements.wrapper=s.elements.tooltip.children("div:first");s.elements.contentWrapper=s.elements.wrapper.children("div:first").css({background:s.options.style.background});s.elements.content=s.elements.contentWrapper.children("div:first").css(q(s.options.style));if(f.browser.msie){s.elements.wrapper.add(s.elements.content).css({zoom:1})}if(s.options.hide.when.event=="unfocus"){s.elements.tooltip.attr("unfocus",true)}if(typeof s.options.style.width.value=="number"){s.updateWidth()}if(f("<canvas>").get(0).getContext||f.browser.msie){if(s.options.style.border.radius>0){m.call(s)}else{s.elements.contentWrapper.css({border:s.options.style.border.width+"px solid "+s.options.style.border.color})}if(s.options.style.tip.corner!==false){e.call(s)}}else{s.elements.contentWrapper.css({border:s.options.style.border.width+"px solid "+s.options.style.border.color});s.options.style.border.radius=0;s.options.style.tip.corner=false;f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.CANVAS_VML_NOT_SUPPORTED,"render")}if((typeof s.options.content.text=="string"&&s.options.content.text.length>0)||(s.options.content.text.jquery&&s.options.content.text.length>0)){u=s.options.content.text}else{if(typeof s.elements.target.attr("title")=="string"&&s.elements.target.attr("title").length>0){u=s.elements.target.attr("title").replace("\\n","<br />");s.elements.target.attr("title","")}else{if(typeof s.elements.target.attr("alt")=="string"&&s.elements.target.attr("alt").length>0){u=s.elements.target.attr("alt").replace("\\n","<br />");s.elements.target.attr("alt","")}else{u=" ";f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.NO_VALID_CONTENT,"render")}}}if(s.options.content.title.text!==false){j.call(s)}s.updateContent(u);l.call(s);if(s.options.show.ready===true){s.show()}if(s.options.content.url!==false){t=s.options.content.url;v=s.options.content.data;y=s.options.content.method||"get";s.loadContent(t,v,y)}s.onRender.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_RENDERED,"render")}function m(){var F,z,t,B,x,E,u,G,D,y,w,C,A,s,v;F=this;F.elements.wrapper.find(".qtip-borderBottom, .qtip-borderTop").remove();t=F.options.style.border.width;B=F.options.style.border.radius;x=F.options.style.border.color||F.options.style.tip.color;E=g(B);u={};for(z in E){u[z]='<div rel="'+z+'" style="'+((z.search(/Left/)!==-1)?"left":"right")+":0; position:absolute; height:"+B+"px; width:"+B+'px; overflow:hidden; line-height:0.1px; font-size:1px">';if(f("<canvas>").get(0).getContext){u[z]+='<canvas height="'+B+'" width="'+B+'" style="vertical-align: top"></canvas>'}else{if(f.browser.msie){G=B*2+3;u[z]+='<v:arc stroked="false" fillcolor="'+x+'" startangle="'+E[z][0]+'" endangle="'+E[z][1]+'" style="width:'+G+"px; height:"+G+"px; margin-top:"+((z.search(/bottom/)!==-1)?-2:-1)+"px; margin-left:"+((z.search(/Right/)!==-1)?E[z][2]-3.5:-1)+'px; vertical-align:top; display:inline-block; behavior:url(#default#VML)"></v:arc>'}}u[z]+="</div>"}D=F.getDimensions().width-(Math.max(t,B)*2);y='<div class="qtip-betweenCorners" style="height:'+B+"px; width:"+D+"px; overflow:hidden; background-color:"+x+'; line-height:0.1px; font-size:1px;">';w='<div class="qtip-borderTop" dir="ltr" style="height:'+B+"px; margin-left:"+B+'px; line-height:0.1px; font-size:1px; padding:0;">'+u.topLeft+u.topRight+y;F.elements.wrapper.prepend(w);C='<div class="qtip-borderBottom" dir="ltr" style="height:'+B+"px; margin-left:"+B+'px; line-height:0.1px; font-size:1px; padding:0;">'+u.bottomLeft+u.bottomRight+y;F.elements.wrapper.append(C);if(f("<canvas>").get(0).getContext){F.elements.wrapper.find("canvas").each(function(){A=E[f(this).parent("[rel]:first").attr("rel")];r.call(F,f(this),A,B,x)})}else{if(f.browser.msie){F.elements.tooltip.append('<v:image style="behavior:url(#default#VML);"></v:image>')}}s=Math.max(B,(B+(t-B)));v=Math.max(t-B,0);F.elements.contentWrapper.css({border:"0px solid "+x,borderWidth:v+"px "+s+"px"})}function r(u,w,s,t){var v=u.get(0).getContext("2d");v.fillStyle=t;v.beginPath();v.arc(w[0],w[1],s,0,Math.PI*2,false);v.fill()}function e(v){var t,s,x,u,w;t=this;if(t.elements.tip!==null){t.elements.tip.remove()}s=t.options.style.tip.color||t.options.style.border.color;if(t.options.style.tip.corner===false){return}else{if(!v){v=t.options.style.tip.corner}}x=b(v,t.options.style.tip.size.width,t.options.style.tip.size.height);t.elements.tip='<div class="'+t.options.style.classes.tip+'" dir="ltr" rel="'+v+'" style="position:absolute; height:'+t.options.style.tip.size.height+"px; width:"+t.options.style.tip.size.width+'px; margin:0 auto; line-height:0.1px; font-size:1px;">';if(f("<canvas>").get(0).getContext){t.elements.tip+='<canvas height="'+t.options.style.tip.size.height+'" width="'+t.options.style.tip.size.width+'"></canvas>'}else{if(f.browser.msie){u=t.options.style.tip.size.width+","+t.options.style.tip.size.height;w="m"+x[0][0]+","+x[0][1];w+=" l"+x[1][0]+","+x[1][1];w+=" "+x[2][0]+","+x[2][1];w+=" xe";t.elements.tip+='<v:shape fillcolor="'+s+'" stroked="false" filled="true" path="'+w+'" coordsize="'+u+'" style="width:'+t.options.style.tip.size.width+"px; height:"+t.options.style.tip.size.height+"px; line-height:0.1px; display:inline-block; behavior:url(#default#VML); vertical-align:"+((v.search(/top/)!==-1)?"bottom":"top")+'"></v:shape>';t.elements.tip+='<v:image style="behavior:url(#default#VML);"></v:image>';t.elements.contentWrapper.css("position","relative")}}t.elements.tooltip.prepend(t.elements.tip+"</div>");t.elements.tip=t.elements.tooltip.find("."+t.options.style.classes.tip).eq(0);if(f("<canvas>").get(0).getContext){h.call(t,t.elements.tip.find("canvas:first"),x,s)}if(v.search(/top/)!==-1&&f.browser.msie&&parseInt(f.browser.version.charAt(0))===6){t.elements.tip.css({marginTop:-4})}n.call(t,v)}function h(t,v,s){var u=t.get(0).getContext("2d");u.fillStyle=s;u.beginPath();u.moveTo(v[0][0],v[0][1]);u.lineTo(v[1][0],v[1][1]);u.lineTo(v[2][0],v[2][1]);u.fill()}function n(u){var t,w,s,x,v;t=this;if(t.options.style.tip.corner===false||!t.elements.tip){return}if(!u){u=t.elements.tip.attr("rel")}w=positionAdjust=(f.browser.msie)?1:0;t.elements.tip.css(u.match(/left|right|top|bottom/)[0],0);if(u.search(/top|bottom/)!==-1){if(f.browser.msie){if(parseInt(f.browser.version.charAt(0))===6){positionAdjust=(u.search(/top/)!==-1)?-3:1}else{positionAdjust=(u.search(/top/)!==-1)?1:2}}if(u.search(/Middle/)!==-1){t.elements.tip.css({left:"50%",marginLeft:-(t.options.style.tip.size.width/2)})}else{if(u.search(/Left/)!==-1){t.elements.tip.css({left:t.options.style.border.radius-w})}else{if(u.search(/Right/)!==-1){t.elements.tip.css({right:t.options.style.border.radius+w})}}}if(u.search(/top/)!==-1){t.elements.tip.css({top:-positionAdjust})}else{t.elements.tip.css({bottom:positionAdjust})}}else{if(u.search(/left|right/)!==-1){if(f.browser.msie){positionAdjust=(parseInt(f.browser.version.charAt(0))===6)?1:((u.search(/left/)!==-1)?1:2)}if(u.search(/Middle/)!==-1){t.elements.tip.css({top:"50%",marginTop:-(t.options.style.tip.size.height/2)})}else{if(u.search(/Top/)!==-1){t.elements.tip.css({top:t.options.style.border.radius-w})}else{if(u.search(/Bottom/)!==-1){t.elements.tip.css({bottom:t.options.style.border.radius+w})}}}if(u.search(/left/)!==-1){t.elements.tip.css({left:-positionAdjust})}else{t.elements.tip.css({right:positionAdjust})}}}s="padding-"+u.match(/left|right|top|bottom/)[0];x=t.options.style.tip.size[(s.search(/left|right/)!==-1)?"width":"height"];t.elements.tooltip.css("padding",0);t.elements.tooltip.css(s,x);if(f.browser.msie&&parseInt(f.browser.version.charAt(0))==6){v=parseInt(t.elements.tip.css("margin-top"))||0;v+=parseInt(t.elements.content.css("margin-top"))||0;t.elements.tip.css({marginTop:v})}}function j(){var s=this;if(s.elements.title!==null){s.elements.title.remove()}s.elements.title=f('<div class="'+s.options.style.classes.title+'">').css(q(s.options.style.title,true)).css({zoom:(f.browser.msie)?1:0}).prependTo(s.elements.contentWrapper);if(s.options.content.title.text){s.updateTitle.call(s,s.options.content.title.text)}if(s.options.content.title.button!==false&&typeof s.options.content.title.button=="string"){s.elements.button=f('<a class="'+s.options.style.classes.button+'" style="float:right; position: relative"></a>').css(q(s.options.style.button,true)).html(s.options.content.title.button).prependTo(s.elements.title).click(function(t){if(!s.status.disabled){s.hide(t)}})}}function l(){var t,v,u,s;t=this;v=t.options.show.when.target;u=t.options.hide.when.target;if(t.options.hide.fixed){u=u.add(t.elements.tooltip)}if(t.options.hide.when.event=="inactive"){s=["click","dblclick","mousedown","mouseup","mousemove","mouseout","mouseenter","mouseleave","mouseover"];function y(z){if(t.status.disabled===true){return}clearTimeout(t.timers.inactive);t.timers.inactive=setTimeout(function(){f(s).each(function(){u.unbind(this+".qtip-inactive");t.elements.content.unbind(this+".qtip-inactive")});t.hide(z)},t.options.hide.delay)}}else{if(t.options.hide.fixed===true){t.elements.tooltip.bind("mouseover.qtip",function(){if(t.status.disabled===true){return}clearTimeout(t.timers.hide)})}}function x(z){if(t.status.disabled===true){return}if(t.options.hide.when.event=="inactive"){f(s).each(function(){u.bind(this+".qtip-inactive",y);t.elements.content.bind(this+".qtip-inactive",y)});y()}clearTimeout(t.timers.show);clearTimeout(t.timers.hide);t.timers.show=setTimeout(function(){t.show(z)},t.options.show.delay)}function w(z){if(t.status.disabled===true){return}if(t.options.hide.fixed===true&&t.options.hide.when.event.search(/mouse(out|leave)/i)!==-1&&f(z.relatedTarget).parents("div.qtip[qtip]").length>0){z.stopPropagation();z.preventDefault();clearTimeout(t.timers.hide);return false}clearTimeout(t.timers.show);clearTimeout(t.timers.hide);t.elements.tooltip.stop(true,true);t.timers.hide=setTimeout(function(){t.hide(z)},t.options.hide.delay)}if((t.options.show.when.target.add(t.options.hide.when.target).length===1&&t.options.show.when.event==t.options.hide.when.event&&t.options.hide.when.event!=="inactive")||t.options.hide.when.event=="unfocus"){t.cache.toggle=0;v.bind(t.options.show.when.event+".qtip",function(z){if(t.cache.toggle==0){x(z)}else{w(z)}})}else{v.bind(t.options.show.when.event+".qtip",x);if(t.options.hide.when.event!=="inactive"){u.bind(t.options.hide.when.event+".qtip",w)}}if(t.options.position.type.search(/(fixed|absolute)/)!==-1){t.elements.tooltip.bind("mouseover.qtip",t.focus)}if(t.options.position.target==="mouse"&&t.options.position.type!=="static"){v.bind("mousemove.qtip",function(z){t.cache.mouse={x:z.pageX,y:z.pageY};if(t.status.disabled===false&&t.options.position.adjust.mouse===true&&t.options.position.type!=="static"&&t.elements.tooltip.css("display")!=="none"){t.updatePosition(z)}})}}function o(u,v,A){var z,s,x,y,t,w;z=this;if(A.corner=="center"){return v.position}s=f.extend({},u);y={x:false,y:false};t={left:(s.left<f.fn.qtip.cache.screen.scroll.left),right:(s.left+A.dimensions.width+2>=f.fn.qtip.cache.screen.width+f.fn.qtip.cache.screen.scroll.left),top:(s.top<f.fn.qtip.cache.screen.scroll.top),bottom:(s.top+A.dimensions.height+2>=f.fn.qtip.cache.screen.height+f.fn.qtip.cache.screen.scroll.top)};x={left:(t.left&&(A.corner.search(/right/i)!=-1||(A.corner.search(/right/i)==-1&&!t.right))),right:(t.right&&(A.corner.search(/left/i)!=-1||(A.corner.search(/left/i)==-1&&!t.left))),top:(t.top&&A.corner.search(/top/i)==-1),bottom:(t.bottom&&A.corner.search(/bottom/i)==-1)};if(x.left){if(z.options.position.target!=="mouse"){s.left=v.position.left+v.dimensions.width}else{s.left=z.cache.mouse.x}y.x="Left"}else{if(x.right){if(z.options.position.target!=="mouse"){s.left=v.position.left-A.dimensions.width}else{s.left=z.cache.mouse.x-A.dimensions.width}y.x="Right"}}if(x.top){if(z.options.position.target!=="mouse"){s.top=v.position.top+v.dimensions.height}else{s.top=z.cache.mouse.y}y.y="top"}else{if(x.bottom){if(z.options.position.target!=="mouse"){s.top=v.position.top-A.dimensions.height}else{s.top=z.cache.mouse.y-A.dimensions.height}y.y="bottom"}}if(s.left<0){s.left=u.left;y.x=false}if(s.top<0){s.top=u.top;y.y=false}if(z.options.style.tip.corner!==false){s.corner=new String(A.corner);if(y.x!==false){s.corner=s.corner.replace(/Left|Right|Middle/,y.x)}if(y.y!==false){s.corner=s.corner.replace(/top|bottom/,y.y)}if(s.corner!==z.elements.tip.attr("rel")){e.call(z,s.corner)}}return s}function q(u,t){var v,s;v=f.extend(true,{},u);for(s in v){if(t===true&&s.search(/(tip|classes)/i)!==-1){delete v[s]}else{if(!t&&s.search(/(width|border|tip|title|classes|user)/i)!==-1){delete v[s]}}}return v}function c(s){if(typeof s.tip!=="object"){s.tip={corner:s.tip}}if(typeof s.tip.size!=="object"){s.tip.size={width:s.tip.size,height:s.tip.size}}if(typeof s.border!=="object"){s.border={width:s.border}}if(typeof s.width!=="object"){s.width={value:s.width}}if(typeof s.width.max=="string"){s.width.max=parseInt(s.width.max.replace(/([0-9]+)/i,"$1"))}if(typeof s.width.min=="string"){s.width.min=parseInt(s.width.min.replace(/([0-9]+)/i,"$1"))}if(typeof s.tip.size.x=="number"){s.tip.size.width=s.tip.size.x;delete s.tip.size.x}if(typeof s.tip.size.y=="number"){s.tip.size.height=s.tip.size.y;delete s.tip.size.y}return s}function a(){var s,t,u,x,v,w;s=this;u=[true,{}];for(t=0;t<arguments.length;t++){u.push(arguments[t])}x=[f.extend.apply(f,u)];while(typeof x[0].name=="string"){x.unshift(c(f.fn.qtip.styles[x[0].name]))}x.unshift(true,{classes:{tooltip:"qtip-"+(arguments[0].name||"defaults")}},f.fn.qtip.styles.defaults);v=f.extend.apply(f,x);w=(f.browser.msie)?1:0;v.tip.size.width+=w;v.tip.size.height+=w;if(v.tip.size.width%2>0){v.tip.size.width+=1}if(v.tip.size.height%2>0){v.tip.size.height+=1}if(v.tip.corner===true){v.tip.corner=(s.options.position.corner.tooltip==="center")?false:s.options.position.corner.tooltip}return v}function b(v,u,t){var s={bottomRight:[[0,0],[u,t],[u,0]],bottomLeft:[[0,0],[u,0],[0,t]],topRight:[[0,t],[u,0],[u,t]],topLeft:[[0,0],[0,t],[u,t]],topMiddle:[[0,t],[u/2,0],[u,t]],bottomMiddle:[[0,0],[u,0],[u/2,t]],rightMiddle:[[0,0],[u,t/2],[0,t]],leftMiddle:[[u,0],[u,t],[0,t/2]]};s.leftTop=s.bottomRight;s.rightTop=s.bottomLeft;s.leftBottom=s.topRight;s.rightBottom=s.topLeft;return s[v]}function g(s){var t;if(f("<canvas>").get(0).getContext){t={topLeft:[s,s],topRight:[0,s],bottomLeft:[s,0],bottomRight:[0,0]}}else{if(f.browser.msie){t={topLeft:[-90,90,0],topRight:[-90,90,-s],bottomLeft:[90,270,0],bottomRight:[90,270,-s]}}}return t}function k(){var s,t,u;s=this;u=s.getDimensions();t='<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:false" style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=\'0\'); border: 1px solid red; height:'+u.height+"px; width:"+u.width+'px" />';s.elements.bgiframe=s.elements.wrapper.prepend(t).children(".qtip-bgiframe:first")}f(document).ready(function(){f.fn.qtip.cache={screen:{scroll:{left:f(window).scrollLeft(),top:f(window).scrollTop()},width:f(window).width(),height:f(window).height()}};var s;f(window).bind("resize scroll",function(t){clearTimeout(s);s=setTimeout(function(){if(t.type==="scroll"){f.fn.qtip.cache.screen.scroll={left:f(window).scrollLeft(),top:f(window).scrollTop()}}else{f.fn.qtip.cache.screen.width=f(window).width();f.fn.qtip.cache.screen.height=f(window).height()}for(i=0;i<f.fn.qtip.interfaces.length;i++){var u=f.fn.qtip.interfaces[i];if(u.status.rendered===true&&(u.options.position.type!=="static"||u.options.position.adjust.scroll&&t.type==="scroll"||u.options.position.adjust.resize&&t.type==="resize")){u.updatePosition(t,true)}}},100)});f(document).bind("mousedown.qtip",function(t){if(f(t.target).parents("div.qtip").length===0){f(".qtip[unfocus]").each(function(){var u=f(this).qtip("api");if(f(this).is(":visible")&&!u.status.disabled&&f(t.target).add(u.elements.target).length>1){u.hide(t)}})}})});f.fn.qtip.interfaces=[];f.fn.qtip.log={error:function(){return this}};f.fn.qtip.constants={};f.fn.qtip.defaults={content:{prerender:false,text:false,url:false,data:null,title:{text:false,button:false}},position:{target:false,corner:{target:"bottomRight",tooltip:"topLeft"},adjust:{x:0,y:0,mouse:true,screen:false,scroll:true,resize:true},type:"absolute",container:false},show:{when:{target:false,event:"mouseover"},effect:{type:"fade",length:100},delay:140,solo:false,ready:false},hide:{when:{target:false,event:"mouseout"},effect:{type:"fade",length:100},delay:0,fixed:false},api:{beforeRender:function(){},onRender:function(){},beforePositionUpdate:function(){},onPositionUpdate:function(){},beforeShow:function(){},onShow:function(){},beforeHide:function(){},onHide:function(){},beforeContentUpdate:function(){},onContentUpdate:function(){},beforeContentLoad:function(){},onContentLoad:function(){},beforeTitleUpdate:function(){},onTitleUpdate:function(){},beforeDestroy:function(){},onDestroy:function(){},beforeFocus:function(){},onFocus:function(){}}};f.fn.qtip.styles={defaults:{background:"white",color:"#111",overflow:"hidden",textAlign:"left",width:{min:0,max:250},padding:"5px 9px",border:{width:1,radius:0,color:"#d3d3d3"},tip:{corner:false,color:false,size:{width:13,height:13},opacity:1},title:{background:"#e1e1e1",fontWeight:"bold",padding:"7px 12px"},button:{cursor:"pointer"},classes:{target:"",tip:"qtip-tip",title:"qtip-title",button:"qtip-button",content:"qtip-content",active:"qtip-active"}},cream:{border:{width:3,radius:0,color:"#F9E98E"},title:{background:"#F0DE7D",color:"#A27D35"},background:"#FBF7AA",color:"#A27D35",classes:{tooltip:"qtip-cream"}},light:{border:{width:3,radius:0,color:"#E2E2E2"},title:{background:"#f1f1f1",color:"#454545"},background:"white",color:"#454545",classes:{tooltip:"qtip-light"}},dark:{border:{width:3,radius:0,color:"#303030"},title:{background:"#404040",color:"#f3f3f3"},background:"#505050",color:"#f3f3f3",classes:{tooltip:"qtip-dark"}},red:{border:{width:3,radius:0,color:"#CE6F6F"},title:{background:"#f28279",color:"#9C2F2F"},background:"#F79992",color:"#9C2F2F",classes:{tooltip:"qtip-red"}},green:{border:{width:3,radius:0,color:"#A9DB66"},title:{background:"#b9db8c",color:"#58792E"},background:"#CDE6AC",color:"#58792E",classes:{tooltip:"qtip-green"}},blue:{border:{width:3,radius:0,color:"#ADD9ED"},title:{background:"#D0E9F5",color:"#5E99BD"},background:"#E5F6FE",color:"#4D9FBF",classes:{tooltip:"qtip-blue"}}}})(jQuery);
+
+
+
+
+
+
--- /dev/null
+(function($){$.extend({tablesorter:new
+function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,rows,-1,i);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,rows,rowIndex,cellIndex){var l=parsers.length,node=false,nodeValue=false,keepLooking=true;while(nodeValue==''&&keepLooking){rowIndex++;if(rows[rowIndex]){node=getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex);nodeValue=trimAndGetNodeText(table.config,node);if(table.config.debug){log('Checking if value was empty on row:'+rowIndex);}}else{keepLooking=false;}}for(var i=1;i<l;i++){if(parsers[i].is(nodeValue,table,node)){return parsers[i];}}return parsers[0];}function getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex){return rows[rowIndex].cells[cellIndex];}function trimAndGetNodeText(config,node){return $.trim(getElementText(config,node));}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=$(table.tBodies[0].rows[i]),cols=[];if(c.hasClass(table.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue;}cache.row.push(c);for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c[0].cells[j]),table,c[0].cells[j]));}cols.push(cache.normalized.length);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){var text="";if(!node)return"";if(!config.supportsTextContent)config.supportsTextContent=node.textContent||false;if(config.textExtraction=="simple"){if(config.supportsTextContent){text=node.textContent;}else{if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){text=node.childNodes[0].innerHTML;}else{text=node.innerHTML;}}}else{if(typeof(config.textExtraction)=="function"){text=config.textExtraction(node);}else{text=$(node).text();}}return text;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){var pos=n[i][checkCell];rows.push(r[pos]);if(!table.config.appender){var l=r[pos].length;for(var j=0;j<l;j++){tableBody[0].appendChild(r[pos][j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false;var header_index=computeTableHeaderCellIndexes(table);$tableHeaders=$(table.config.selectorHeaders,table).each(function(index){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(table.config.sortInitialOrder);this.count=this.order;if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(checkHeaderOptionsSortingLocked(table,index))this.order=this.lockedOrder=checkHeaderOptionsSortingLocked(table,index);if(!this.sortDisabled){var $th=$(this).addClass(table.config.cssHeader);if(table.config.onRenderHeader)table.config.onRenderHeader.apply($th);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function computeTableHeaderCellIndexes(t){var matrix=[];var lookup={};var thead=t.getElementsByTagName('THEAD')[0];var trs=thead.getElementsByTagName('TR');for(var i=0;i<trs.length;i++){var cells=trs[i].cells;for(var j=0;j<cells.length;j++){var c=cells[j];var rowIndex=c.parentNode.rowIndex;var cellId=rowIndex+"-"+c.cellIndex;var rowSpan=c.rowSpan||1;var colSpan=c.colSpan||1
+var firstAvailCol;if(typeof(matrix[rowIndex])=="undefined"){matrix[rowIndex]=[];}for(var k=0;k<matrix[rowIndex].length+1;k++){if(typeof(matrix[rowIndex][k])=="undefined"){firstAvailCol=k;break;}}lookup[cellId]=firstAvailCol;for(var k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(matrix[k])=="undefined"){matrix[k]=[];}var matrixrow=matrix[k];for(var l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x";}}}}return lookup;}function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){return(v.toLowerCase()=="desc")?1:0;}else{return(v==1)?1:0;}}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(table.config.parsers[c].type=="text")?((order==0)?makeSortFunction("text","asc",c):makeSortFunction("text","desc",c)):((order==0)?makeSortFunction("numeric","asc",c):makeSortFunction("numeric","desc",c));var e="e"+i;dynamicExp+="var "+e+" = "+s;dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";if(table.config.debug){benchmark("Evaling expression:"+dynamicExp,new Date());}eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function makeSortFunction(type,direction,index){var a="a["+index+"]",b="b["+index+"]";if(type=='text'&&direction=='asc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+a+" < "+b+") ? -1 : 1 )));";}else if(type=='text'&&direction=='desc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+b+" < "+a+") ? -1 : 1 )));";}else if(type=='numeric'&&direction=='asc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+a+" - "+b+"));";}else if(type=='numeric'&&direction=='desc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+b+" - "+a+"));";}};function makeSortText(i){return"((a["+i+"] < b["+i+"]) ? -1 : ((a["+i+"] > b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){var me=this;setTimeout(function(){me.config.parsers=buildParserCache(me,$headers);cache=buildCache(me);},1);}).bind("updateCell",function(e,cell){var config=this.config;var pos=[(cell.parentNode.rowIndex-1),cell.cellIndex];cache.normalized[pos[0]][pos[1]]=config.parsers[pos[1]].format(getElementText(config,cell),cell);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){return/^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g,'')));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLocaleLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}var $tr,row=-1,odd;$("tr:visible",table.tBodies[0]).each(function(i){$tr=$(this);if(!$tr.hasClass(table.config.cssChildRow))row++;odd=(row%2==0);$tr.removeClass(table.config.widgetZebra.css[odd?0:1]).addClass(table.config.widgetZebra.css[odd?1:0])});if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);
\ No newline at end of file
--- /dev/null
+function wpseo_setIgnore( option, hide, nonce ) {
+ jQuery.post(ajaxurl, {
+ action: 'wpseo_set_ignore',
+ option: option,
+ _wpnonce: nonce
+ }, function(data) {
+ if (data) {
+ jQuery('#'+hide).hide();
+ jQuery('#hidden_ignore_'+option).val('ignore');
+ }
+ }
+ );
+}
\ No newline at end of file
--- /dev/null
+function wpseo_setIgnore(a,b,c){jQuery.post(ajaxurl,{action:"wpseo_set_ignore",option:a,_wpnonce:c},function(c){c&&(jQuery("#"+b).hide(),jQuery("#hidden_ignore_"+a).val("ignore"))})}
\ No newline at end of file
--- /dev/null
+// Taken and adapted from http://www.webmaster-source.com/2013/02/06/using-the-wordpress-3-5-media-uploader-in-your-plugin-or-theme/
+jQuery(document).ready(function ($) {
+ var wpseo_custom_uploader;
+ $('.wpseo_image_upload_button').click(function (e) {
+ wpseo_target_id = $(this).attr('id').replace(/_button$/,'');
+ e.preventDefault();
+ if (wpseo_custom_uploader) {
+ wpseo_custom_uploader.open();
+ return;
+ }
+ wpseo_custom_uploader = wp.media.frames.file_frame = wp.media({
+ title : wpseoMediaL10n.choose_image,
+ button : { text: wpseoMediaL10n.choose_image },
+ multiple: false
+ });
+ wpseo_custom_uploader.on('select', function () {
+ var attachment = wpseo_custom_uploader.state().get('selection').first().toJSON();
+ $('#'+wpseo_target_id).val(attachment.url);
+ });
+ wpseo_custom_uploader.open();
+ });
+});
--- /dev/null
+jQuery(document).ready(function(a){var b;a(".wpseo_image_upload_button").click(function(c){return wpseo_target_id=a(this).attr("id").replace(/_button$/,""),c.preventDefault(),b?void b.open():(b=wp.media.frames.file_frame=wp.media({title:wpseoMediaL10n.choose_image,button:{text:wpseoMediaL10n.choose_image},multiple:!1}),b.on("select",function(){var c=b.state().get("selection").first().toJSON();a("#"+wpseo_target_id).val(c.url)}),void b.open())})});
\ No newline at end of file
--- /dev/null
+jQuery(document).ready(function() {
+
+ /* Fix banner images overlapping help texts */
+ jQuery('.screen-meta-toggle a').click( function() {
+ jQuery("#sidebar-container").toggle();
+ });
+
+ // events
+ jQuery("#enablexmlsitemap").change(function() {
+ jQuery("#sitemapinfo").toggle(jQuery(this).is(':checked'));
+ }).change();
+
+ // events
+ jQuery("#disable_author_sitemap").change(function() {
+ jQuery("#xml_user_block").toggle(!jQuery(this).is(':checked'));
+ }).change();
+
+ jQuery("#cleanpermalinks").change(function() {
+ jQuery("#cleanpermalinksdiv").toggle(jQuery(this).is(':checked'));
+ }).change();
+
+ jQuery('#wpseo-tabs').find('a').click(function() {
+ jQuery('#wpseo-tabs').find('a').removeClass('nav-tab-active');
+ jQuery('.wpseotab').removeClass('active');
+
+ var id = jQuery(this).attr('id').replace('-tab','');
+ jQuery('#' + id).addClass('active');
+ jQuery(this).addClass('nav-tab-active');
+ });
+
+ // init
+ var active_tab = window.location.hash.replace('#top#','');
+
+ // default to first tab
+ if ( active_tab == '' || active_tab == '#_=_') {
+ active_tab = jQuery('.wpseotab').attr('id');
+ }
+
+ jQuery('#' + active_tab).addClass('active');
+ jQuery('#' + active_tab + '-tab').addClass('nav-tab-active');
+
+});
+
+// global functions
+function setWPOption( option, newval, hide, nonce ) {
+ jQuery.post(ajaxurl, {
+ action: 'wpseo_set_option',
+ option: option,
+ newval: newval,
+ _wpnonce: nonce
+ }, function(data) {
+ if (data)
+ jQuery('#'+hide).hide();
+ }
+ );
+}
+
+function wpseo_killBlockingFiles( nonce ) {
+ jQuery.post( ajaxurl, {
+ action: 'wpseo_kill_blocking_files',
+ _ajax_nonce: nonce
+ }, function(data) {
+ if (data == 'success')
+ jQuery('#blocking_files').hide();
+ else
+ jQuery('#block_files').html(data);
+ });
+}
+
+function copy_home_meta() {
+ jQuery('#og_frontpage_desc').val(jQuery('#meta_description').val());
+}
+
+/*jQuery(document).ready(function(){
+ // Collapsible debug information on the settings pages
+ jQuery('#wpseo-debug-info').accordion({
+ active: false,
+ collapsible: true,
+ icons: {
+ header: 'ui-icon-circle-triangle-e',
+ activeHeader: 'ui-icon-circle-triangle-s'
+ },
+ heightStyle: 'content'
+ });
+});*/
\ No newline at end of file
--- /dev/null
+function setWPOption(a,b,c,d){jQuery.post(ajaxurl,{action:"wpseo_set_option",option:a,newval:b,_wpnonce:d},function(a){a&&jQuery("#"+c).hide()})}function wpseo_killBlockingFiles(a){jQuery.post(ajaxurl,{action:"wpseo_kill_blocking_files",_ajax_nonce:a},function(a){"success"==a?jQuery("#blocking_files").hide():jQuery("#block_files").html(a)})}function copy_home_meta(){jQuery("#og_frontpage_desc").val(jQuery("#meta_description").val())}jQuery(document).ready(function(){jQuery(".screen-meta-toggle a").click(function(){jQuery("#sidebar-container").toggle()}),jQuery("#enablexmlsitemap").change(function(){jQuery("#sitemapinfo").toggle(jQuery(this).is(":checked"))}).change(),jQuery("#disable_author_sitemap").change(function(){jQuery("#xml_user_block").toggle(!jQuery(this).is(":checked"))}).change(),jQuery("#cleanpermalinks").change(function(){jQuery("#cleanpermalinksdiv").toggle(jQuery(this).is(":checked"))}).change(),jQuery("#wpseo-tabs").find("a").click(function(){jQuery("#wpseo-tabs").find("a").removeClass("nav-tab-active"),jQuery(".wpseotab").removeClass("active");var a=jQuery(this).attr("id").replace("-tab","");jQuery("#"+a).addClass("active"),jQuery(this).addClass("nav-tab-active")});var a=window.location.hash.replace("#top#","");(""==a||"#_=_"==a)&&(a=jQuery(".wpseotab").attr("id")),jQuery("#"+a).addClass("active"),jQuery("#"+a+"-tab").addClass("nav-tab-active")});
\ No newline at end of file
--- /dev/null
+jQuery(document).ready( function() {
+
+ var existing_title_id = "#wpseo-existing-title-";
+ var new_title_id = "#wpseo-new-title-";
+ var new_title_class = ".wpseo-new-title";
+
+ var existing_metadesc_id = "#wpseo-existing-metadesc-";
+ var new_metadesc_id = "#wpseo-new-metadesc-";
+ var new_metadesc_class = ".wpseo-new-metadesc";
+
+ var current_element = null;
+
+ var handle_response = function( response, status ) {
+ if (status != "success") { return; }
+
+ var resp = response;
+
+ if (typeof resp == "string") {
+ resp = JSON.parse( resp );
+ }
+
+ if ( resp instanceof Array ) {
+ jQuery.each( resp, function() {
+ handle_response( this, status );
+ });
+ }
+ else {
+ if ( resp.status == 'success' ) {
+ if ( jQuery(current_element).closest( '.wpseo_bulk_titles' ).length ) {
+ jQuery( existing_title_id + resp.post_id ).html( resp.new_title.replace(/\\(?!\\)/g, '') );
+ jQuery( new_title_id + resp.post_id ).val( '' ).focus();
+ }
+ else if ( jQuery(current_element).closest( '.wpseo_bulk_descriptions').length ) {
+ jQuery( existing_metadesc_id + resp.post_id ).html( resp.new_metadesc.replace(/\\(?!\\)/g, '') );
+ jQuery( new_metadesc_id + resp.post_id ).val( '' ).focus();
+ }
+ }
+ else {
+ alert( "Failure");
+ }
+ }
+ };
+
+ var handle_responses = function( responses ) {
+ var resps = jQuery.parseJSON( responses );
+ jQuery.each( resps, function() {
+ handle_response( this );
+ });
+ };
+
+ var submit_new = function( id , element) {
+
+ current_element = element;
+
+ if ( jQuery(current_element).closest( '.wpseo_bulk_titles' ).length == 1 ) {
+ submit_new_title( id );
+ }
+ else if ( jQuery(current_element).closest( '.wpseo_bulk_descriptions').length == 1 ) {
+ submit_new_metadesc( id );
+ }
+ };
+
+ var submit_new_title = function( id ) {
+ var data = {
+ 'action': 'wpseo_save_title',
+ '_ajax_nonce' : wpseo_bulk_editor_nonce,
+ 'wpseo_post_id': id,
+ 'new_title' : jQuery( new_title_id + id ).val(),
+ 'existing_title' : jQuery( existing_title_id + id ).html()
+ };
+
+ if ( data.new_title == data.existing_title ) {
+ jQuery( new_title_id + id ).val('').focus();
+ }
+ else {
+ if ( ( data.new_title == '' ) && !confirm( "Are you sure you want to remove the existing Yoast SEO Title?" ) ) {
+ jQuery( new_title_id + id ).focus();
+ return;
+ }
+ jQuery.post( ajaxurl, data, handle_response );
+ }
+ };
+
+ var submit_new_metadesc = function( id ) {
+ var data = {
+ 'action': 'wpseo_save_desc',
+ '_ajax_nonce' : wpseo_bulk_editor_nonce,
+ 'wpseo_post_id': id,
+ 'new_metadesc' : jQuery( new_metadesc_id + id ).val(),
+ 'existing_metadesc' : jQuery( existing_metadesc_id + id ).html()
+ };
+
+ if ( data.new_metadesc == data.existing_metadesc ) {
+ jQuery( new_metadesc_id + id ).val('').focus();
+ }
+ else {
+ if ( data.new_metadesc == '' && !confirm( "Are you sure you want to remove the existing Yoast SEO Description?" ) ) {
+ jQuery( data.new_metadesc + id ).focus();
+ return;
+ }
+ jQuery.post( ajaxurl, data, handle_response );
+ }
+ };
+
+ jQuery.each( jQuery( new_title_class + ', ' + new_metadesc_class ), function() {
+ jQuery(this).keypress( function(event) {
+ if ( event.which == 13 ) {
+ event.preventDefault();
+ var id = jQuery(this).data('id');
+ submit_new( id );
+ }
+ });
+ });
+
+ jQuery.each( jQuery('.wpseo-save'), function() {
+ jQuery(this).click( function() {
+ var id = jQuery(this).data('id');
+ submit_new( id, this );
+ });
+ });
+
+ jQuery.each( jQuery('.wpseo-save-all'), function() {
+ var save_all_titles = function(e) {
+ e.preventDefault();
+
+ current_element = this;
+
+ var data = {
+ 'action': 'wpseo_save_all_titles',
+ '_ajax_nonce' : wpseo_bulk_editor_nonce
+ };
+
+ data.send = false;
+ data.titles = {};
+ data.existing_titles = {};
+
+ jQuery.each( jQuery( new_title_class ), function() {
+ var id = jQuery(this).data('id');
+ var value = jQuery(this).val();
+ var existing_title = jQuery( existing_title_id + id ).html();
+
+ if ( value != '' ) {
+ if ( value == existing_title ) {
+ jQuery( new_title_id + id ).val('').focus();
+ }
+ else {
+ data.send = true;
+ data.titles[ id ] = value;
+ data.existing_titles[ id ] = existing_title;
+ }
+ }
+ });
+
+ if ( data.send ) {
+ jQuery.post( ajaxurl, data, handle_response );
+ }
+ };
+
+ var save_all_metadescs = function(e) {
+ e.preventDefault();
+
+ current_element = this;
+ var data = {
+ 'action': 'wpseo_save_all_descs',
+ '_ajax_nonce' : wpseo_bulk_editor_nonce
+ };
+
+ data.send = false;
+ data.metadescs = {};
+ data.existing_metadescs = {};
+
+ jQuery.each( jQuery( new_metadesc_class ), function() {
+ var id = jQuery(this).data('id');
+ var value = jQuery(this).val();
+ var existing_metadesc = jQuery( existing_metadesc_id + id ).html();
+
+ if ( value != '' ) {
+ if ( value == existing_metadesc ) {
+ jQuery( new_metadesc_id + id ).val('').focus();
+ }
+ else {
+ data.send = true;
+ data.metadescs[ id ] = value;
+ data.existing_metadescs[ id ] = existing_metadesc;
+ }
+ }
+ });
+
+ if ( data.send ) {
+ jQuery.post( ajaxurl, data, handle_response );
+ }
+ };
+
+ if ( jQuery(this).closest( '.wpseo_bulk_titles' ).length ) {
+ jQuery(this).on( 'click', save_all_titles );
+ }
+ else if ( jQuery(this).closest( '.wpseo_bulk_descriptions').length ) {
+ jQuery(this).on( 'click', save_all_metadescs );
+ }
+ });
+});
--- /dev/null
+jQuery(document).ready(function(){var a="#wpseo-existing-title-",b="#wpseo-new-title-",c=".wpseo-new-title",d="#wpseo-existing-metadesc-",e="#wpseo-new-metadesc-",f=".wpseo-new-metadesc",g=null,h=function(c,f){if("success"==f){var i=c;"string"==typeof i&&(i=JSON.parse(i)),i instanceof Array?jQuery.each(i,function(){h(this,f)}):"success"==i.status?jQuery(g).closest(".wpseo_bulk_titles").length?(jQuery(a+i.post_id).html(i.new_title.replace(/\\(?!\\)/g,"")),jQuery(b+i.post_id).val("").focus()):jQuery(g).closest(".wpseo_bulk_descriptions").length&&(jQuery(d+i.post_id).html(i.new_metadesc.replace(/\\(?!\\)/g,"")),jQuery(e+i.post_id).val("").focus()):alert("Failure")}},i=function(a,b){g=b,1==jQuery(g).closest(".wpseo_bulk_titles").length?j(a):1==jQuery(g).closest(".wpseo_bulk_descriptions").length&&k(a)},j=function(c){var d={action:"wpseo_save_title",_ajax_nonce:wpseo_bulk_editor_nonce,wpseo_post_id:c,new_title:jQuery(b+c).val(),existing_title:jQuery(a+c).html()};if(d.new_title==d.existing_title)jQuery(b+c).val("").focus();else{if(""==d.new_title&&!confirm("Are you sure you want to remove the existing Yoast SEO Title?"))return void jQuery(b+c).focus();jQuery.post(ajaxurl,d,h)}},k=function(a){var b={action:"wpseo_save_desc",_ajax_nonce:wpseo_bulk_editor_nonce,wpseo_post_id:a,new_metadesc:jQuery(e+a).val(),existing_metadesc:jQuery(d+a).html()};if(b.new_metadesc==b.existing_metadesc)jQuery(e+a).val("").focus();else{if(""==b.new_metadesc&&!confirm("Are you sure you want to remove the existing Yoast SEO Description?"))return void jQuery(b.new_metadesc+a).focus();jQuery.post(ajaxurl,b,h)}};jQuery.each(jQuery(c+", "+f),function(){jQuery(this).keypress(function(a){if(13==a.which){a.preventDefault();var b=jQuery(this).data("id");i(b)}})}),jQuery.each(jQuery(".wpseo-save"),function(){jQuery(this).click(function(){var a=jQuery(this).data("id");i(a,this)})}),jQuery.each(jQuery(".wpseo-save-all"),function(){var i=function(d){d.preventDefault(),g=this;var e={action:"wpseo_save_all_titles",_ajax_nonce:wpseo_bulk_editor_nonce};e.send=!1,e.titles={},e.existing_titles={},jQuery.each(jQuery(c),function(){var c=jQuery(this).data("id"),d=jQuery(this).val(),f=jQuery(a+c).html();""!=d&&(d==f?jQuery(b+c).val("").focus():(e.send=!0,e.titles[c]=d,e.existing_titles[c]=f))}),e.send&&jQuery.post(ajaxurl,e,h)},j=function(a){a.preventDefault(),g=this;var b={action:"wpseo_save_all_descs",_ajax_nonce:wpseo_bulk_editor_nonce};b.send=!1,b.metadescs={},b.existing_metadescs={},jQuery.each(jQuery(f),function(){var a=jQuery(this).data("id"),c=jQuery(this).val(),f=jQuery(d+a).html();""!=c&&(c==f?jQuery(e+a).val("").focus():(b.send=!0,b.metadescs[a]=c,b.existing_metadescs[a]=f))}),b.send&&jQuery.post(ajaxurl,b,h)};jQuery(this).closest(".wpseo_bulk_titles").length?jQuery(this).on("click",i):jQuery(this).closest(".wpseo_bulk_descriptions").length&&jQuery(this).on("click",j)})});
\ No newline at end of file
--- /dev/null
+function yst_clean(str) {
+ if (str == '' || str == undefined)
+ return '';
+
+ try {
+ str = jQuery('<div/>').html(str).text();
+ str = str.replace(/<\/?[^>]+>/gi, '');
+ str = str.replace(/\[(.+?)\](.+?\[\/\\1\])?/g, '');
+ } catch (e) {
+ }
+
+ return str;
+}
+
+function ptest(str, p) {
+ str = yst_clean(str);
+ str = str.toLowerCase();
+ var r = str.match(p);
+ if (r != null)
+ return '<span class="good">Yes (' + r.length + ')</span>';
+ else
+ return '<span class="wrong">No</span>';
+}
+
+function removeLowerCaseDiacritics(str) {
+ var defaultDiacriticsRemovalMap = [
+ {'base': 'a', 'letters': /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g},
+ {'base': 'aa', 'letters': /[\uA733]/g},
+ {'base': 'ae', 'letters': /[\u00E6\u01FD\u01E3]/g},
+ {'base': 'ao', 'letters': /[\uA735]/g},
+ {'base': 'au', 'letters': /[\uA737]/g},
+ {'base': 'av', 'letters': /[\uA739\uA73B]/g},
+ {'base': 'ay', 'letters': /[\uA73D]/g},
+ {'base': 'b', 'letters': /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g},
+ {'base': 'c', 'letters': /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g},
+ {'base': 'd', 'letters': /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g},
+ {'base': 'dz', 'letters': /[\u01F3\u01C6]/g},
+ {'base': 'e', 'letters': /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g},
+ {'base': 'f', 'letters': /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g},
+ {'base': 'g', 'letters': /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g},
+ {'base': 'h', 'letters': /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g},
+ {'base': 'hv', 'letters': /[\u0195]/g},
+ {'base': 'i', 'letters': /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g},
+ {'base': 'j', 'letters': /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g},
+ {'base': 'k', 'letters': /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g},
+ {'base': 'l', 'letters': /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g},
+ {'base': 'lj', 'letters': /[\u01C9]/g},
+ {'base': 'm', 'letters': /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g},
+ {'base': 'n', 'letters': /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g},
+ {'base': 'nj', 'letters': /[\u01CC]/g},
+ {'base': 'o', 'letters': /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g},
+ {'base': 'oi', 'letters': /[\u01A3]/g},
+ {'base': 'ou', 'letters': /[\u0223]/g},
+ {'base': 'oo', 'letters': /[\uA74F]/g},
+ {'base': 'p', 'letters': /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g},
+ {'base': 'q', 'letters': /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g},
+ {'base': 'r', 'letters': /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g},
+ {'base': 's', 'letters': /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g},
+ {'base': 't', 'letters': /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g},
+ {'base': 'tz', 'letters': /[\uA729]/g},
+ {'base': 'u', 'letters': /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g},
+ {'base': 'v', 'letters': /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g},
+ {'base': 'vy', 'letters': /[\uA761]/g},
+ {'base': 'w', 'letters': /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g},
+ {'base': 'x', 'letters': /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g},
+ {'base': 'y', 'letters': /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g},
+ {'base': 'z', 'letters': /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g}
+ ];
+ var changes;
+ if (!changes) {
+ changes = defaultDiacriticsRemovalMap;
+ }
+ for (var i = 0; i < changes.length; i++) {
+ str = str.replace(changes[i].letters, changes[i].base);
+ }
+ return str;
+}
+
+function yst_testFocusKw() {
+ // Retrieve focus keyword and trim
+ var focuskw = jQuery.trim(jQuery('#' + wpseoMetaboxL10n.field_prefix + 'focuskw').val());
+ focuskw = yst_escapeFocusKw(focuskw).toLowerCase();
+
+ if (jQuery('#editable-post-name-full').length) {
+ var postname = jQuery('#editable-post-name-full').text();
+ var url = wpseoMetaboxL10n.wpseo_permalink_template.replace('%postname%', postname).replace('http://', '');
+ }
+ var p = new RegExp("(^|[ \s\n\r\t\.,'\(\"\+;!?:\-])" + focuskw + "($|[ \s\n\r\t.,'\)\"\+!?:;\-])", 'gim');
+ //remove diacritics of a lower cased focuskw for url matching in foreign lang
+ var focuskwNoDiacritics = removeLowerCaseDiacritics(focuskw);
+ var p2 = new RegExp(focuskwNoDiacritics.replace(/\s+/g, "[-_\\\//]"), 'gim');
+
+ var focuskwresults = jQuery('#focuskwresults');
+ var metadesc = jQuery('#wpseosnippet').find('.desc span.content').text();
+
+ if (focuskw != '') {
+ var html = '<p>' + wpseoMetaboxL10n.keyword_header + '</p>';
+ html += '<ul>';
+ if (jQuery('#title').length) {
+ html += '<li>' + wpseoMetaboxL10n.article_header_text + ptest(jQuery('#title').val(), p) + '</li>';
+ }
+ html += '<li>' + wpseoMetaboxL10n.page_title_text + ptest(jQuery('#wpseosnippet_title').text(), p) + '</li>';
+ html += '<li>' + wpseoMetaboxL10n.page_url_text + ptest(url, p2) + '</li>';
+ if (jQuery('#content').length) {
+ html += '<li>' + wpseoMetaboxL10n.content_text + ptest(jQuery('#content').val(), p) + '</li>';
+ }
+ html += '<li>' + wpseoMetaboxL10n.meta_description_text + ptest(metadesc, p) + '</li>';
+ html += '</ul>';
+ focuskwresults.html(html);
+ } else {
+ focuskwresults.html('');
+ }
+}
+
+function yst_replaceVariables(str, callback) {
+ if (typeof str === "undefined") {
+ return '';
+ }
+ // title
+ if (jQuery('#title').length) {
+ str = str.replace(/%%title%%/g, jQuery('#title').val());
+ }
+
+ // These are added in the head for performance reasons.
+ str = str.replace(/%%sitedesc%%/g, wpseoMetaboxL10n.sitedesc);
+ str = str.replace(/%%sitename%%/g, wpseoMetaboxL10n.sitename);
+ str = str.replace(/%%sep%%/g, wpseoMetaboxL10n.sep);
+ str = str.replace(/%%date%%/g, wpseoMetaboxL10n.date);
+ str = str.replace(/%%id%%/g, wpseoMetaboxL10n.id);
+ str = str.replace(/%%page%%/g, wpseoMetaboxL10n.page);
+ str = str.replace(/%%currenttime%%/g, wpseoMetaboxL10n.currenttime);
+ str = str.replace(/%%currentdate%%/g, wpseoMetaboxL10n.currentdate);
+ str = str.replace(/%%currentday%%/g, wpseoMetaboxL10n.currentday);
+ str = str.replace(/%%currentmonth%%/g, wpseoMetaboxL10n.currentmonth);
+ str = str.replace(/%%currentyear%%/g, wpseoMetaboxL10n.currentyear);
+
+ str = str.replace(/%%focuskw%%/g, jQuery('#yoast_wpseo_focuskw').val() );
+ // excerpt
+ var excerpt = '';
+ if (jQuery('#excerpt').length) {
+ excerpt = yst_clean(jQuery("#excerpt").val());
+ str = str.replace(/%%excerpt_only%%/g, excerpt);
+ }
+ if ('' == excerpt && jQuery('#content').length) {
+ excerpt = jQuery('#content').val().replace(/(<([^>]+)>)/ig,"").substring(0,wpseoMetaboxL10n.wpseo_meta_desc_length-1);
+ }
+ str = str.replace(/%%excerpt%%/g, excerpt);
+
+ // parent page
+ if (jQuery('#parent_id').length && jQuery('#parent_id option:selected').text() != wpseoMetaboxL10n.no_parent_text ) {
+ str = str.replace(/%%parent_title%%/g, jQuery('#parent_id option:selected').text());
+ }
+
+ // remove double separators
+ var esc_sep = yst_escapeFocusKw(wpseoMetaboxL10n.sep);
+ var pattern = new RegExp(esc_sep + ' ' + esc_sep, 'g');
+ str = str.replace(pattern, wpseoMetaboxL10n.sep);
+
+ if (str.indexOf('%%') != -1 && str.match(/%%[a-z0-9_-]+%%/i) != null) {
+ regex = /%%[a-z0-9_-]+%%/gi;
+ matches = str.match(regex);
+ for (i = 0; i < matches.length; i++) {
+ if (replacedVars[matches[i]] != undefined) {
+ str = str.replace(matches[i], replacedVars[matches[i]]);
+ } else {
+ replaceableVar = matches[i];
+ // create the cache already, so we don't do the request twice.
+ replacedVars[replaceableVar] = '';
+ jQuery.post(ajaxurl, {
+ action : 'wpseo_replace_vars',
+ string : matches[i],
+ post_id : jQuery('#post_ID').val(),
+ _wpnonce: wpseoMetaboxL10n.wpseo_replace_vars_nonce
+ }, function (data) {
+ if (data) {
+ replacedVars[replaceableVar] = data;
+ yst_replaceVariables(str, callback);
+ } else {
+ yst_replaceVariables(str, callback);
+ }
+ }
+ );
+ }
+ }
+ }
+ callback(str);
+}
+
+function yst_strip_tags(string) {
+ return jQuery(jQuery('<div />')).html( string ).text();
+}
+
+function yst_updateTitle(force) {
+ var title = '';
+ var titleElm = jQuery('#' + wpseoMetaboxL10n.field_prefix + 'title');
+ var titleLengthError = jQuery('#' + wpseoMetaboxL10n.field_prefix + 'title-length-warning');
+ var divHtml = jQuery('<div />');
+ var snippetTitle = jQuery('#wpseosnippet_title');
+
+ if (titleElm.val()) {
+ title = titleElm.val();
+ } else {
+ title = wpseoMetaboxL10n.wpseo_title_template;
+ title = divHtml.html(title).text();
+ }
+ if (title == '') {
+ snippetTitle.html('');
+ titleLengthError.hide();
+ return;
+ }
+
+ title = yst_clean(title);
+ title = jQuery.trim(title);
+ title = divHtml.text(title).html();
+
+ if (force) {
+ titleElm.val(title);
+ }
+
+ title = yst_replaceVariables(title, function (title) {
+ // do the placeholder
+ var placeholder_title = divHtml.html(title).text();
+ titleElm.attr('placeholder', placeholder_title);
+
+ title = yst_strip_tags(title);
+
+ // and now the snippet preview title
+ title = yst_boldKeywords(title, false);
+
+ jQuery('#wpseosnippet_title').html(title);
+
+ var e = document.getElementById('wpseosnippet_title');
+ if (e != null) {
+ if (e.scrollWidth > e.clientWidth) {
+ titleLengthError.show();
+ } else {
+ titleLengthError.hide();
+ }
+ }
+
+ yst_testFocusKw();
+ });
+}
+
+function yst_updateDesc() {
+ var desc = jQuery.trim(yst_clean(jQuery('#' + wpseoMetaboxL10n.field_prefix + 'metadesc').val()));
+ var divHtml = jQuery('<div />');
+ var snippet = jQuery('#wpseosnippet');
+
+ if (desc == '' && wpseoMetaboxL10n.wpseo_metadesc_template != '') {
+ desc = wpseoMetaboxL10n.wpseo_metadesc_template;
+ }
+
+ if (desc != '') {
+ desc = yst_replaceVariables(desc, function (desc) {
+ desc = divHtml.text(desc).html();
+ desc = yst_clean(desc);
+
+
+ var len = -1;
+ len = wpseoMetaboxL10n.wpseo_meta_desc_length - desc.length;
+
+ if (len < 0)
+ len = '<span class="wrong">' + len + '</span>';
+ else
+ len = '<span class="good">' + len + '</span>';
+
+ jQuery('#' + wpseoMetaboxL10n.field_prefix + 'metadesc-length').html(len);
+
+ desc = yst_trimDesc(desc);
+ desc = yst_boldKeywords(desc, false);
+ // Clear the autogen description.
+ snippet.find('.desc span.autogen').html('');
+ // Set our new one.
+ snippet.find('.desc span.content').html(desc);
+
+ yst_testFocusKw();
+ });
+ } else {
+ // Clear the generated description
+ snippet.find('.desc span.content').html('');
+ yst_testFocusKw();
+
+ if (jQuery('#content').length) {
+ desc = jQuery('#content').val();
+ desc = yst_clean(desc);
+ }
+
+ var focuskw = yst_escapeFocusKw(jQuery.trim(jQuery('#' + wpseoMetaboxL10n.field_prefix + 'focuskw').val()));
+ if (focuskw != '') {
+ var descsearch = new RegExp(focuskw, 'gim');
+ if (desc.search(descsearch) != -1 && desc.length > wpseoMetaboxL10n.wpseo_meta_desc_length) {
+ desc = desc.substr(desc.search(descsearch), wpseoMetaboxL10n.wpseo_meta_desc_length);
+ } else {
+ desc = desc.substr(0, wpseoMetaboxL10n.wpseo_meta_desc_length);
+ }
+ } else {
+ desc = desc.substr(0, wpseoMetaboxL10n.wpseo_meta_desc_length);
+ }
+ desc = yst_boldKeywords(desc, false);
+ desc = yst_trimDesc(desc);
+ snippet.find('.desc span.autogen').html(desc);
+ }
+
+}
+
+function yst_trimDesc(desc) {
+ if (desc.length > wpseoMetaboxL10n.wpseo_meta_desc_length) {
+ var space;
+ if (desc.length > wpseoMetaboxL10n.wpseo_meta_desc_length)
+ space = desc.lastIndexOf(" ", ( wpseoMetaboxL10n.wpseo_meta_desc_length - 3 ));
+ else
+ space = wpseoMetaboxL10n.wpseo_meta_desc_length;
+ desc = desc.substring(0, space).concat(' ...');
+ }
+ return desc;
+}
+
+function yst_updateURL() {
+ if (jQuery('#editable-post-name-full').length) {
+ var name = jQuery('#editable-post-name-full').text();
+ var url = wpseoMetaboxL10n.wpseo_permalink_template.replace('%postname%', name).replace('http://', '');
+ }
+ url = yst_boldKeywords(url, true);
+ jQuery('#wpseosnippet').find('.url').html(url);
+ yst_testFocusKw();
+}
+
+function yst_boldKeywords(str, url) {
+ var focuskw = yst_escapeFocusKw(jQuery.trim(jQuery('#' + wpseoMetaboxL10n.field_prefix + 'focuskw').val()));
+ var keywords;
+
+ if (focuskw == '')
+ return str;
+
+ if (focuskw.search(' ') != -1) {
+ keywords = focuskw.split(' ');
+ } else {
+ keywords = new Array(focuskw);
+ }
+ for (var i = 0; i < keywords.length; i++) {
+ var kw = yst_clean(keywords[i]);
+ var kwregex = '';
+ if (url) {
+ kw = kw.replace(' ', '-').toLowerCase();
+ kwregex = new RegExp("([-/])(" + kw + ")([-/])?");
+ } else {
+ kwregex = new RegExp("(^|[ \s\n\r\t\.,'\(\"\+;!?:\-]+)(" + kw + ")($|[ \s\n\r\t\.,'\)\"\+;!?:\-]+)", 'gim');
+ }
+ if (str != undefined) {
+ str = str.replace(kwregex, "$1<strong>$2</strong>$3");
+ }
+ }
+ return str;
+}
+
+function yst_updateSnippet() {
+ yst_updateURL();
+ yst_updateTitle();
+ yst_updateDesc();
+}
+
+function yst_escapeFocusKw(str) {
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+}
+
+var delay = (function () {
+ var timer = 0;
+ return function (callback, ms) {
+ clearTimeout(timer);
+ timer = setTimeout(callback, ms);
+ };
+})();
+
+jQuery(document).ready(function () {
+ replacedVars = new Array();
+
+ if (jQuery('.wpseo-metabox-tabs-div').length > 0) {
+ var active_tab = window.location.hash;
+ if (active_tab == '' || active_tab.search('wpseo') == -1)
+ active_tab = 'general';
+ else
+ active_tab = active_tab.replace('#wpseo_', '');
+
+ jQuery('.' + active_tab).addClass('active');
+
+ var descElm = jQuery('#' + wpseoMetaboxL10n.field_prefix + 'metadesc');
+ var desc = jQuery.trim(yst_clean(descElm.val()));
+ desc = jQuery('<div />').html(desc).text();
+ descElm.val(desc);
+
+ jQuery('a.wpseo_tablink').click(function () {
+ jQuery('.wpseo-metabox-tabs li').removeClass('active');
+ jQuery('.wpseotab').removeClass('active');
+
+ var id = jQuery(this).attr('href').replace('#wpseo_', '');
+ jQuery('.' + id).addClass('active');
+ jQuery(this).parent().addClass('active');
+
+ if (jQuery(this).hasClass('scroll')) {
+ var scrollto = jQuery(this).attr('href').replace('wpseo_', '');
+ jQuery("html, body").animate({
+ scrollTop: jQuery(scrollto).offset().top
+ }, 500);
+ }
+ });
+ }
+
+ jQuery('.wpseo-heading').hide();
+ jQuery('.wpseo-metabox-tabs').show();
+ // End Tabs code
+
+ var cache = {}, lastXhr;
+
+ jQuery('#' + wpseoMetaboxL10n.field_prefix + 'focuskw').autocomplete({
+ minLength : 3,
+ formatResult: function (row) {
+ return jQuery('<div/>').html(row).html();
+ },
+ source : function (request, response) {
+ var term = request.term;
+ if (term in cache) {
+ response(cache[term]);
+ return;
+ }
+ request._ajax_nonce = wpseoMetaboxL10n.wpseo_keyword_suggest_nonce;
+ request.action = 'wpseo_get_suggest';
+
+ lastXhr = jQuery.getJSON(ajaxurl, request, function (data, status, xhr) {
+ cache[term] = data;
+ if (xhr === lastXhr) {
+ response(data);
+ }
+ return;
+ });
+ }
+ });
+
+ jQuery('#' + wpseoMetaboxL10n.field_prefix + 'title').keyup(function () {
+ yst_updateTitle();
+ });
+ jQuery('#title').keyup(function () {
+ yst_updateTitle();
+ yst_updateDesc();
+ });
+ jQuery('#parent_id').change(function () {
+ yst_updateTitle();
+ yst_updateDesc();
+ });
+ // DON'T 'optimize' this to use descElm! descElm might not be defined and will cause js errors (Soliloquy issue)
+ jQuery('#' + wpseoMetaboxL10n.field_prefix + 'metadesc').keyup(function () {
+ yst_updateDesc();
+ });
+ jQuery('#excerpt').keyup(function () {
+ yst_updateDesc();
+ });
+ jQuery('#content').focusout(function () {
+ yst_updateDesc();
+ });
+ var focuskwhelptriggered = false;
+ jQuery(document).on('change', '#' + wpseoMetaboxL10n.field_prefix + 'focuskw', function () {
+ var focuskwhelpElm = jQuery('#focuskwhelp');
+ if (jQuery('#' + wpseoMetaboxL10n.field_prefix + 'focuskw').val().search(',') != -1) {
+ focuskwhelpElm.click();
+ focuskwhelptriggered = true;
+ } else if (focuskwhelptriggered) {
+ focuskwhelpElm.qtip("hide");
+ focuskwhelptriggered = false;
+ }
+
+ yst_updateSnippet();
+ });
+
+
+ jQuery(".yoast_help").qtip({
+ position: {
+ corner: {
+ target : 'topMiddle',
+ tooltip: 'bottomLeft'
+ }
+ },
+ show : {
+ when: {
+ event: 'mouseover'
+ }
+ },
+ hide : {
+ fixed: true,
+ when : {
+ event: 'mouseout'
+ }
+ },
+ style : {
+ tip : 'bottomLeft',
+ name: 'blue'
+ }
+ });
+
+ yst_updateSnippet();
+
+});
--- /dev/null
+function yst_clean(a){if(""==a||void 0==a)return"";try{a=jQuery("<div/>").html(a).text(),a=a.replace(/<\/?[^>]+>/gi,""),a=a.replace(/\[(.+?)\](.+?\[\/\\1\])?/g,"")}catch(b){}return a}function ptest(a,b){a=yst_clean(a),a=a.toLowerCase();var c=a.match(b);return null!=c?'<span class="good">Yes ('+c.length+")</span>":'<span class="wrong">No</span>'}function removeLowerCaseDiacritics(a){var b,c=[{base:"a",letters:/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g},{base:"aa",letters:/[\uA733]/g},{base:"ae",letters:/[\u00E6\u01FD\u01E3]/g},{base:"ao",letters:/[\uA735]/g},{base:"au",letters:/[\uA737]/g},{base:"av",letters:/[\uA739\uA73B]/g},{base:"ay",letters:/[\uA73D]/g},{base:"b",letters:/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g},{base:"c",letters:/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g},{base:"d",letters:/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g},{base:"dz",letters:/[\u01F3\u01C6]/g},{base:"e",letters:/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g},{base:"f",letters:/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g},{base:"g",letters:/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g},{base:"h",letters:/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g},{base:"hv",letters:/[\u0195]/g},{base:"i",letters:/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g},{base:"j",letters:/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g},{base:"k",letters:/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g},{base:"l",letters:/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g},{base:"lj",letters:/[\u01C9]/g},{base:"m",letters:/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g},{base:"n",letters:/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g},{base:"nj",letters:/[\u01CC]/g},{base:"o",letters:/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g},{base:"oi",letters:/[\u01A3]/g},{base:"ou",letters:/[\u0223]/g},{base:"oo",letters:/[\uA74F]/g},{base:"p",letters:/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g},{base:"q",letters:/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g},{base:"r",letters:/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g},{base:"s",letters:/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g},{base:"t",letters:/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g},{base:"tz",letters:/[\uA729]/g},{base:"u",letters:/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g},{base:"v",letters:/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g},{base:"vy",letters:/[\uA761]/g},{base:"w",letters:/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g},{base:"x",letters:/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g},{base:"y",letters:/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g},{base:"z",letters:/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g}];b||(b=c);for(var d=0;d<b.length;d++)a=a.replace(b[d].letters,b[d].base);return a}function yst_testFocusKw(){var a=jQuery.trim(jQuery("#"+wpseoMetaboxL10n.field_prefix+"focuskw").val());if(a=yst_escapeFocusKw(a).toLowerCase(),jQuery("#editable-post-name-full").length)var b=jQuery("#editable-post-name-full").text(),c=wpseoMetaboxL10n.wpseo_permalink_template.replace("%postname%",b).replace("http://","");var d=new RegExp("(^|[ s\n\r .,'(\"+;!?:-])"+a+"($|[ s\n\r .,')\"+!?:;-])","gim"),e=removeLowerCaseDiacritics(a),f=new RegExp(e.replace(/\s+/g,"[-_\\//]"),"gim"),g=jQuery("#focuskwresults"),h=jQuery("#wpseosnippet").find(".desc span.content").text();if(""!=a){var i="<p>"+wpseoMetaboxL10n.keyword_header+"</p>";i+="<ul>",jQuery("#title").length&&(i+="<li>"+wpseoMetaboxL10n.article_header_text+ptest(jQuery("#title").val(),d)+"</li>"),i+="<li>"+wpseoMetaboxL10n.page_title_text+ptest(jQuery("#wpseosnippet_title").text(),d)+"</li>",i+="<li>"+wpseoMetaboxL10n.page_url_text+ptest(c,f)+"</li>",jQuery("#content").length&&(i+="<li>"+wpseoMetaboxL10n.content_text+ptest(jQuery("#content").val(),d)+"</li>"),i+="<li>"+wpseoMetaboxL10n.meta_description_text+ptest(h,d)+"</li>",i+="</ul>",g.html(i)}else g.html("")}function yst_replaceVariables(a,b){if("undefined"==typeof a)return"";jQuery("#title").length&&(a=a.replace(/%%title%%/g,jQuery("#title").val())),a=a.replace(/%%sitedesc%%/g,wpseoMetaboxL10n.sitedesc),a=a.replace(/%%sitename%%/g,wpseoMetaboxL10n.sitename),a=a.replace(/%%sep%%/g,wpseoMetaboxL10n.sep),a=a.replace(/%%date%%/g,wpseoMetaboxL10n.date),a=a.replace(/%%id%%/g,wpseoMetaboxL10n.id),a=a.replace(/%%page%%/g,wpseoMetaboxL10n.page),a=a.replace(/%%currenttime%%/g,wpseoMetaboxL10n.currenttime),a=a.replace(/%%currentdate%%/g,wpseoMetaboxL10n.currentdate),a=a.replace(/%%currentday%%/g,wpseoMetaboxL10n.currentday),a=a.replace(/%%currentmonth%%/g,wpseoMetaboxL10n.currentmonth),a=a.replace(/%%currentyear%%/g,wpseoMetaboxL10n.currentyear),a=a.replace(/%%focuskw%%/g,jQuery("#yoast_wpseo_focuskw").val());var c="";jQuery("#excerpt").length&&(c=yst_clean(jQuery("#excerpt").val()),a=a.replace(/%%excerpt_only%%/g,c)),""==c&&jQuery("#content").length&&(c=jQuery("#content").val().replace(/(<([^>]+)>)/gi,"").substring(0,wpseoMetaboxL10n.wpseo_meta_desc_length-1)),a=a.replace(/%%excerpt%%/g,c),jQuery("#parent_id").length&&jQuery("#parent_id option:selected").text()!=wpseoMetaboxL10n.no_parent_text&&(a=a.replace(/%%parent_title%%/g,jQuery("#parent_id option:selected").text()));var d=yst_escapeFocusKw(wpseoMetaboxL10n.sep),e=new RegExp(d+" "+d,"g");if(a=a.replace(e,wpseoMetaboxL10n.sep),-1!=a.indexOf("%%")&&null!=a.match(/%%[a-z0-9_-]+%%/i))for(regex=/%%[a-z0-9_-]+%%/gi,matches=a.match(regex),i=0;i<matches.length;i++)void 0!=replacedVars[matches[i]]?a=a.replace(matches[i],replacedVars[matches[i]]):(replaceableVar=matches[i],replacedVars[replaceableVar]="",jQuery.post(ajaxurl,{action:"wpseo_replace_vars",string:matches[i],post_id:jQuery("#post_ID").val(),_wpnonce:wpseoMetaboxL10n.wpseo_replace_vars_nonce},function(c){c?(replacedVars[replaceableVar]=c,yst_replaceVariables(a,b)):yst_replaceVariables(a,b)}));b(a)}function yst_strip_tags(a){return jQuery(jQuery("<div />")).html(a).text()}function yst_updateTitle(a){var b="",c=jQuery("#"+wpseoMetaboxL10n.field_prefix+"title"),d=jQuery("#"+wpseoMetaboxL10n.field_prefix+"title-length-warning"),e=jQuery("<div />"),f=jQuery("#wpseosnippet_title");return c.val()?b=c.val():(b=wpseoMetaboxL10n.wpseo_title_template,b=e.html(b).text()),""==b?(f.html(""),void d.hide()):(b=yst_clean(b),b=jQuery.trim(b),b=e.text(b).html(),a&&c.val(b),void(b=yst_replaceVariables(b,function(a){var b=e.html(a).text();c.attr("placeholder",b),a=yst_strip_tags(a),a=yst_boldKeywords(a,!1),jQuery("#wpseosnippet_title").html(a);var f=document.getElementById("wpseosnippet_title");null!=f&&(f.scrollWidth>f.clientWidth?d.show():d.hide()),yst_testFocusKw()})))}function yst_updateDesc(){var a=jQuery.trim(yst_clean(jQuery("#"+wpseoMetaboxL10n.field_prefix+"metadesc").val())),b=jQuery("<div />"),c=jQuery("#wpseosnippet");if(""==a&&""!=wpseoMetaboxL10n.wpseo_metadesc_template&&(a=wpseoMetaboxL10n.wpseo_metadesc_template),""!=a)a=yst_replaceVariables(a,function(a){a=b.text(a).html(),a=yst_clean(a);var d=-1;d=wpseoMetaboxL10n.wpseo_meta_desc_length-a.length,d=0>d?'<span class="wrong">'+d+"</span>":'<span class="good">'+d+"</span>",jQuery("#"+wpseoMetaboxL10n.field_prefix+"metadesc-length").html(d),a=yst_trimDesc(a),a=yst_boldKeywords(a,!1),c.find(".desc span.autogen").html(""),c.find(".desc span.content").html(a),yst_testFocusKw()});else{c.find(".desc span.content").html(""),yst_testFocusKw(),jQuery("#content").length&&(a=jQuery("#content").val(),a=yst_clean(a));var d=yst_escapeFocusKw(jQuery.trim(jQuery("#"+wpseoMetaboxL10n.field_prefix+"focuskw").val()));if(""!=d){var e=new RegExp(d,"gim");a=-1!=a.search(e)&&a.length>wpseoMetaboxL10n.wpseo_meta_desc_length?a.substr(a.search(e),wpseoMetaboxL10n.wpseo_meta_desc_length):a.substr(0,wpseoMetaboxL10n.wpseo_meta_desc_length)}else a=a.substr(0,wpseoMetaboxL10n.wpseo_meta_desc_length);a=yst_boldKeywords(a,!1),a=yst_trimDesc(a),c.find(".desc span.autogen").html(a)}}function yst_trimDesc(a){if(a.length>wpseoMetaboxL10n.wpseo_meta_desc_length){var b;b=a.length>wpseoMetaboxL10n.wpseo_meta_desc_length?a.lastIndexOf(" ",wpseoMetaboxL10n.wpseo_meta_desc_length-3):wpseoMetaboxL10n.wpseo_meta_desc_length,a=a.substring(0,b).concat(" ...")}return a}function yst_updateURL(){if(jQuery("#editable-post-name-full").length)var a=jQuery("#editable-post-name-full").text(),b=wpseoMetaboxL10n.wpseo_permalink_template.replace("%postname%",a).replace("http://","");b=yst_boldKeywords(b,!0),jQuery("#wpseosnippet").find(".url").html(b),yst_testFocusKw()}function yst_boldKeywords(a,b){var c,d=yst_escapeFocusKw(jQuery.trim(jQuery("#"+wpseoMetaboxL10n.field_prefix+"focuskw").val()));if(""==d)return a;c=-1!=d.search(" ")?d.split(" "):new Array(d);for(var e=0;e<c.length;e++){var f=yst_clean(c[e]),g="";b?(f=f.replace(" ","-").toLowerCase(),g=new RegExp("([-/])("+f+")([-/])?")):g=new RegExp("(^|[ s\n\r .,'(\"+;!?:-]+)("+f+")($|[ s\n\r .,')\"+;!?:-]+)","gim"),void 0!=a&&(a=a.replace(g,"$1<strong>$2</strong>$3"))}return a}function yst_updateSnippet(){yst_updateURL(),yst_updateTitle(),yst_updateDesc()}function yst_escapeFocusKw(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}var delay=function(){var a=0;return function(b,c){clearTimeout(a),a=setTimeout(b,c)}}();jQuery(document).ready(function(){if(replacedVars=new Array,jQuery(".wpseo-metabox-tabs-div").length>0){var a=window.location.hash;a=""==a||-1==a.search("wpseo")?"general":a.replace("#wpseo_",""),jQuery("."+a).addClass("active");var b=jQuery("#"+wpseoMetaboxL10n.field_prefix+"metadesc"),c=jQuery.trim(yst_clean(b.val()));c=jQuery("<div />").html(c).text(),b.val(c),jQuery("a.wpseo_tablink").click(function(){jQuery(".wpseo-metabox-tabs li").removeClass("active"),jQuery(".wpseotab").removeClass("active");var a=jQuery(this).attr("href").replace("#wpseo_","");if(jQuery("."+a).addClass("active"),jQuery(this).parent().addClass("active"),jQuery(this).hasClass("scroll")){var b=jQuery(this).attr("href").replace("wpseo_","");jQuery("html, body").animate({scrollTop:jQuery(b).offset().top},500)}})}jQuery(".wpseo-heading").hide(),jQuery(".wpseo-metabox-tabs").show();var d,e={};jQuery("#"+wpseoMetaboxL10n.field_prefix+"focuskw").autocomplete({minLength:3,formatResult:function(a){return jQuery("<div/>").html(a).html()},source:function(a,b){var c=a.term;return c in e?void b(e[c]):(a._ajax_nonce=wpseoMetaboxL10n.wpseo_keyword_suggest_nonce,a.action="wpseo_get_suggest",void(d=jQuery.getJSON(ajaxurl,a,function(a,f,g){e[c]=a,g===d&&b(a)})))}}),jQuery("#"+wpseoMetaboxL10n.field_prefix+"title").keyup(function(){yst_updateTitle()}),jQuery("#title").keyup(function(){yst_updateTitle(),yst_updateDesc()}),jQuery("#parent_id").change(function(){yst_updateTitle(),yst_updateDesc()}),jQuery("#"+wpseoMetaboxL10n.field_prefix+"metadesc").keyup(function(){yst_updateDesc()}),jQuery("#excerpt").keyup(function(){yst_updateDesc()}),jQuery("#content").focusout(function(){yst_updateDesc()});var f=!1;jQuery(document).on("change","#"+wpseoMetaboxL10n.field_prefix+"focuskw",function(){var a=jQuery("#focuskwhelp");-1!=jQuery("#"+wpseoMetaboxL10n.field_prefix+"focuskw").val().search(",")?(a.click(),f=!0):f&&(a.qtip("hide"),f=!1),yst_updateSnippet()}),jQuery(".yoast_help").qtip({position:{corner:{target:"topMiddle",tooltip:"bottomLeft"}},show:{when:{event:"mouseover"}},hide:{fixed:!0,when:{event:"mouseout"}},style:{tip:"bottomLeft",name:"blue"}}),yst_updateSnippet()});
\ No newline at end of file
--- /dev/null
+<?php
+//Nothing to see here
\ No newline at end of file
--- /dev/null
+# Copyright (C) 2014 Team Yoast
+# This file is distributed under the GPL v3.
+msgid ""
+msgstr ""
+"Project-Id-Version: WordPress SEO 1.6\n"
+"Report-Msgid-Bugs-To: https://github.com/yoast/wordpress-seo\n"
+"POT-Creation-Date: 2014-09-12 13:59:14+00:00\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2014-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Yoast Translate Team <translations@yoast.com>\n"
+"Language-Team: Yoast Translate <translations@yoast.com>\n"
+"X-Generator: grunt-wp-i18n 0.4.4\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Poedit-Basepath: .\n"
+"X-Poedit-Language: English\n"
+"X-Poedit-Country: UNITED STATES\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2; "
+"_nx:1,2,4c;_n_noop:1,2;_nx_noop:1,2,3c;esc_attr__; esc_html__;esc_attr_e; "
+"esc_html_e;esc_attr_x:1,2c; esc_html_x:1,2c;\n"
+"X-Poedit-Bookmarks: \n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Textdomain-Support: yes\n"
+
+#: admin/ajax.php:69
+msgid "Some files could not be removed. Please remove them via FTP."
+msgstr ""
+
+#: admin/ajax.php:177
+msgid "Post doesn't exist."
+msgstr ""
+
+#: admin/ajax.php:186
+msgid "Post has an invalid Post Type: %s."
+msgstr ""
+
+#: admin/ajax.php:194
+msgid "You can't edit %s."
+msgstr ""
+
+#: admin/ajax.php:202
+msgid "You can't edit %s that aren't yours."
+msgstr ""
+
+#: admin/ajax.php:210
+msgid "You have used HTML in your value which is not allowed."
+msgstr ""
+
+#: admin/class-admin.php:90 admin/class-admin.php:209 admin/class-admin.php:269
+#: admin/class-admin.php:275 admin/class-admin.php:282
+msgid "Yoast WordPress SEO:"
+msgstr ""
+
+#: admin/class-admin.php:90
+msgid "General Settings"
+msgstr ""
+
+#: admin/class-admin.php:90 admin/class-admin.php:269
+#: admin/class-metabox.php:813 inc/wpseo-non-ajax-functions.php:323
+msgid "SEO"
+msgstr ""
+
+#: admin/class-admin.php:107 inc/wpseo-non-ajax-functions.php:431
+msgid "Titles & Metas"
+msgstr ""
+
+#: admin/class-admin.php:116 admin/class-opengraph-admin.php:58
+#: admin/class-opengraph-admin.php:69 inc/wpseo-non-ajax-functions.php:437
+msgid "Social"
+msgstr ""
+
+#: admin/class-admin.php:125 admin/class-pointers.php:133
+#: inc/wpseo-non-ajax-functions.php:443
+msgid "XML Sitemaps"
+msgstr ""
+
+#: admin/class-admin.php:134 inc/wpseo-non-ajax-functions.php:449
+msgid "Permalinks"
+msgstr ""
+
+#: admin/class-admin.php:143 inc/wpseo-non-ajax-functions.php:455
+msgid "Internal Links"
+msgstr ""
+
+#: admin/class-admin.php:152 inc/wpseo-non-ajax-functions.php:461
+msgid "RSS"
+msgstr ""
+
+#: admin/class-admin.php:161 inc/wpseo-non-ajax-functions.php:467
+msgid "Import & Export"
+msgstr ""
+
+#: admin/class-admin.php:170 admin/class-pointers.php:173
+#: inc/wpseo-non-ajax-functions.php:473
+msgid "Bulk Editor"
+msgstr ""
+
+#: admin/class-admin.php:183 admin/class-admin.php:275
+#: inc/wpseo-non-ajax-functions.php:482
+msgid "Edit Files"
+msgstr ""
+
+#: admin/class-admin.php:194 admin/class-admin.php:282
+#: admin/class-pointers.php:188 admin/pages/licenses.php:21
+#: inc/wpseo-non-ajax-functions.php:490
+msgid "Extensions"
+msgstr ""
+
+#: admin/class-admin.php:222 admin/class-pointers.php:96
+msgid "Dashboard"
+msgstr ""
+
+#: admin/class-admin.php:233
+msgid "For more information:"
+msgstr ""
+
+#: admin/class-admin.php:234
+msgid "Title optimization"
+msgstr ""
+
+#: admin/class-admin.php:235
+msgid "Why Google won't display the right page title"
+msgstr ""
+
+#: admin/class-admin.php:241
+msgid "Template explanation"
+msgstr ""
+
+#: admin/class-admin.php:242
+msgid ""
+"The title & metas settings for WordPress SEO are made up of variables "
+"that are replaced by specific values from the page when the page is "
+"displayed. The tabs on the left explain the available variables."
+msgstr ""
+
+#: admin/class-admin.php:250 admin/class-admin.php:251
+msgid "Basic Variables"
+msgstr ""
+
+#: admin/class-admin.php:258 admin/class-admin.php:259
+msgid "Advanced Variables"
+msgstr ""
+
+#: admin/class-admin.php:269 admin/pages/network.php:120
+msgid "MultiSite Settings"
+msgstr ""
+
+#: admin/class-admin.php:360
+msgid "Posts"
+msgstr ""
+
+#: admin/class-admin.php:391
+msgid "Huge SEO Issue: You're blocking access to robots."
+msgstr ""
+
+#: admin/class-admin.php:391
+msgid ""
+"You must %sgo to your Reading Settings%s and uncheck the box for Search "
+"Engine Visibility."
+msgstr ""
+
+#: admin/class-admin.php:391 admin/class-admin.php:415
+msgid "I know, don't bug me."
+msgstr ""
+
+#: admin/class-admin.php:415
+msgid "SEO Issue:"
+msgstr ""
+
+#: admin/class-admin.php:415
+msgid ""
+"Your theme contains a meta description, which blocks WordPress SEO from "
+"working properly. Please visit the %sSEO Dashboard%s to fix this."
+msgstr ""
+
+#: admin/class-admin.php:430
+msgid "Settings"
+msgstr ""
+
+#: admin/class-admin.php:442
+msgid "Premium Support"
+msgstr ""
+
+#: admin/class-admin.php:446
+msgid "FAQ"
+msgstr ""
+
+#: admin/class-admin.php:488 admin/class-pointers.php:126
+#: admin/pages/social.php:174
+msgid "Google+"
+msgstr ""
+
+#: admin/class-admin.php:490
+msgid "Twitter username (without @)"
+msgstr ""
+
+#: admin/class-admin.php:492
+msgid "Facebook profile URL"
+msgstr ""
+
+#: admin/class-admin.php:512
+msgid "WordPress SEO settings"
+msgstr ""
+
+#: admin/class-admin.php:516
+msgid "Title to use for Author page"
+msgstr ""
+
+#: admin/class-admin.php:524
+msgid "Meta description to use for Author page"
+msgstr ""
+
+#: admin/class-admin.php:533
+msgid "Meta keywords to use for Author page"
+msgstr ""
+
+#: admin/class-admin.php:542
+msgid "Exclude user from Author-sitemap"
+msgstr ""
+
+#: admin/class-admin.php:593
+#. translators: this should be an array of stopwords for your language,
+#. separated by comma's.
+msgid ""
+"a,about,above,after,again,against,all,am,an,and,any,are,aren't,as,at,be,"
+"because,been,before,being,below,between,both,but,by,can't,cannot,could,"
+"couldn't,did,didn't,do,does,doesn't,doing,don't,down,during,each,few,for,"
+"from,further,had,hadn't,has,hasn't,have,haven't,having,he,he'd,he'll,he's,"
+"her,here,here's,hers,herself,him,himself,his,how,how's,i,i'd,i'll,i'm,i've,"
+"if,in,into,is,isn't,it,it's,its,itself,let's,me,more,most,mustn't,my,myself,"
+"no,nor,not,of,off,on,once,only,or,other,ought,our,ours,ourselves,out,over,"
+"own,same,shan't,she,she'd,she'll,she's,should,shouldn't,so,some,such,than,"
+"that,that's,the,their,theirs,them,themselves,then,there,there's,these,they,"
+"they'd,they'll,they're,they've,this,those,through,to,too,under,until,up,"
+"very,was,wasn't,we,we'd,we'll,we're,we've,were,weren't,what,what's,when,"
+"when's,where,where's,which,while,who,who's,whom,why,why's,with,won't,would,"
+"wouldn't,you,you'd,you'll,you're,you've,your,yours,yourself,yourselves"
+msgstr ""
+
+#: admin/class-bulk-description-editor-list-table.php:45
+#: admin/class-bulk-title-editor-list-table.php:45
+msgid "WP Page Title"
+msgstr ""
+
+#: admin/class-bulk-description-editor-list-table.php:46
+#: admin/class-bulk-title-editor-list-table.php:46
+msgid "Post Type"
+msgstr ""
+
+#: admin/class-bulk-description-editor-list-table.php:47
+#: admin/class-bulk-title-editor-list-table.php:47
+msgid "Post Status"
+msgstr ""
+
+#: admin/class-bulk-description-editor-list-table.php:48
+#: admin/class-bulk-title-editor-list-table.php:48
+msgid "Publication date"
+msgstr ""
+
+#: admin/class-bulk-description-editor-list-table.php:49
+#: admin/class-bulk-title-editor-list-table.php:49
+msgid "Page URL/Slug"
+msgstr ""
+
+#: admin/class-bulk-description-editor-list-table.php:50
+msgid "Existing Yoast Meta Description"
+msgstr ""
+
+#: admin/class-bulk-description-editor-list-table.php:51
+msgid "New Yoast Meta Description"
+msgstr ""
+
+#: admin/class-bulk-description-editor-list-table.php:52
+#: admin/class-bulk-title-editor-list-table.php:52
+msgid "Action"
+msgstr ""
+
+#: admin/class-bulk-editor-list-table.php:320
+msgid "Filter"
+msgstr ""
+
+#: admin/class-bulk-editor-list-table.php:520
+msgid "Edit this item"
+msgstr ""
+
+#: admin/class-bulk-editor-list-table.php:520
+msgid "Edit"
+msgstr ""
+
+#: admin/class-bulk-editor-list-table.php:526
+msgid "Preview “%s”"
+msgstr ""
+
+#: admin/class-bulk-editor-list-table.php:526
+msgid "Preview"
+msgstr ""
+
+#: admin/class-bulk-editor-list-table.php:529
+msgid "View “%s”"
+msgstr ""
+
+#: admin/class-bulk-editor-list-table.php:529
+msgid "View"
+msgstr ""
+
+#: admin/class-bulk-title-editor-list-table.php:50
+msgid "Existing Yoast SEO Title"
+msgstr ""
+
+#: admin/class-bulk-title-editor-list-table.php:51
+msgid "New Yoast SEO Title"
+msgstr ""
+
+#: admin/class-config.php:150
+msgid "Remove these ads?"
+msgstr ""
+
+#: admin/class-config.php:151
+msgid "Upgrade to WordPress SEO Premium »"
+msgstr ""
+
+#: admin/class-config.php:231
+msgid "Debug Information"
+msgstr ""
+
+#: admin/class-config.php:233
+msgid "Current option:"
+msgstr ""
+
+#: admin/class-config.php:267
+msgid "This is a settings export file for the WordPress SEO plugin by Yoast.com"
+msgstr ""
+
+#: admin/class-config.php:373 admin/class-metabox.php:375
+#: admin/class-metabox.php:764
+msgid "Use Image"
+msgstr ""
+
+#: admin/class-metabox.php:48
+msgid "Snippet Preview"
+msgstr ""
+
+#: admin/class-metabox.php:49
+msgid ""
+"This is a rendering of what this post might look like in Google's search "
+"results.<br/><br/>Read %sthis post%s for more info."
+msgstr ""
+
+#: admin/class-metabox.php:51
+msgid "Focus Keyword"
+msgstr ""
+
+#: admin/class-metabox.php:52
+msgid ""
+"Pick the main keyword or keyphrase that this post/page is "
+"about.<br/><br/>Read %sthis post%s for more info."
+msgstr ""
+
+#: admin/class-metabox.php:54 admin/class-metabox.php:814
+#: admin/class-taxonomy.php:177
+msgid "SEO Title"
+msgstr ""
+
+#: admin/class-metabox.php:55
+msgid "Warning:"
+msgstr ""
+
+#: admin/class-metabox.php:55
+msgid "Title display in Google is limited to a fixed width, yours is too long."
+msgstr ""
+
+#: admin/class-metabox.php:56
+msgid ""
+"The SEO Title defaults to what is generated based on this sites title "
+"template for this posttype."
+msgstr ""
+
+#: admin/class-metabox.php:58
+msgid "Meta Description"
+msgstr ""
+
+#: admin/class-metabox.php:59
+msgid ""
+"The <code>meta</code> description will be limited to %s chars%s, %s chars "
+"left."
+msgstr ""
+
+#: admin/class-metabox.php:60
+msgid ""
+"The meta description is often shown as the black text under the title in a "
+"search result. For this to work it has to contain the keyword that was "
+"searched for.<br/><br/>Read %sthis post%s for more info."
+msgstr ""
+
+#: admin/class-metabox.php:62 admin/class-taxonomy.php:181
+msgid "Meta Keywords"
+msgstr ""
+
+#: admin/class-metabox.php:63
+msgid ""
+"If you type something above it will override your %smeta keywords "
+"template%s."
+msgstr ""
+
+#: admin/class-metabox.php:66
+msgid "Meta Robots Index"
+msgstr ""
+
+#: admin/class-metabox.php:68 admin/class-taxonomy.php:200
+msgid ""
+"Warning: even though you can set the meta robots setting here, the entire "
+"site is set to noindex in the sitewide privacy settings, so these settings "
+"won't have an effect."
+msgstr ""
+
+#: admin/class-metabox.php:70
+msgid "Default for post type, currently: %s"
+msgstr ""
+
+#: admin/class-metabox.php:71
+msgid "index"
+msgstr ""
+
+#: admin/class-metabox.php:72
+msgid "noindex"
+msgstr ""
+
+#: admin/class-metabox.php:74
+msgid "Meta Robots Follow"
+msgstr ""
+
+#: admin/class-metabox.php:75
+msgid "Follow"
+msgstr ""
+
+#: admin/class-metabox.php:76
+msgid "Nofollow"
+msgstr ""
+
+#: admin/class-metabox.php:78
+msgid "Meta Robots Advanced"
+msgstr ""
+
+#: admin/class-metabox.php:79
+msgid "Advanced <code>meta</code> robots settings for this page."
+msgstr ""
+
+#: admin/class-metabox.php:80
+msgid "Site-wide default: %s"
+msgstr ""
+
+#: admin/class-metabox.php:81 admin/pages/internal-links.php:37
+#: admin/pages/internal-links.php:55 admin/pages/network.php:52
+#: inc/class-wpseo-meta.php:431
+msgid "None"
+msgstr ""
+
+#: admin/class-metabox.php:82
+msgid "NO ODP"
+msgstr ""
+
+#: admin/class-metabox.php:83
+msgid "NO YDIR"
+msgstr ""
+
+#: admin/class-metabox.php:84
+msgid "No Image Index"
+msgstr ""
+
+#: admin/class-metabox.php:85
+msgid "No Archive"
+msgstr ""
+
+#: admin/class-metabox.php:86
+msgid "No Snippet"
+msgstr ""
+
+#: admin/class-metabox.php:88
+msgid "Breadcrumbs title"
+msgstr ""
+
+#: admin/class-metabox.php:89
+msgid "Title to use for this page in breadcrumb paths"
+msgstr ""
+
+#: admin/class-metabox.php:91
+msgid "Include in Sitemap"
+msgstr ""
+
+#: admin/class-metabox.php:92
+msgid ""
+"Should this page be in the XML Sitemap at all times, regardless of Robots "
+"Meta settings?"
+msgstr ""
+
+#: admin/class-metabox.php:93 admin/class-taxonomy.php:65
+msgid "Auto detect"
+msgstr ""
+
+#: admin/class-metabox.php:94 admin/class-taxonomy.php:66
+msgid "Always include"
+msgstr ""
+
+#: admin/class-metabox.php:95 admin/class-taxonomy.php:67
+msgid "Never include"
+msgstr ""
+
+#: admin/class-metabox.php:97
+msgid "Sitemap Priority"
+msgstr ""
+
+#: admin/class-metabox.php:98
+msgid "The priority given to this page in the XML sitemap."
+msgstr ""
+
+#: admin/class-metabox.php:99
+msgid "Automatic prioritization"
+msgstr ""
+
+#: admin/class-metabox.php:100
+msgid "1 - Highest priority"
+msgstr ""
+
+#: admin/class-metabox.php:101
+msgid "Default for first tier pages"
+msgstr ""
+
+#: admin/class-metabox.php:102
+msgid "Default for second tier pages and posts"
+msgstr ""
+
+#: admin/class-metabox.php:103
+msgid "Medium priority"
+msgstr ""
+
+#: admin/class-metabox.php:104
+msgid "Lowest priority"
+msgstr ""
+
+#: admin/class-metabox.php:106
+msgid "Canonical URL"
+msgstr ""
+
+#: admin/class-metabox.php:107
+msgid ""
+"The canonical URL that this page should point to, leave empty to default to "
+"permalink. %sCross domain canonical%s supported too."
+msgstr ""
+
+#: admin/class-metabox.php:109
+msgid "301 Redirect"
+msgstr ""
+
+#: admin/class-metabox.php:110
+msgid "The URL that this page should redirect to."
+msgstr ""
+
+#: admin/class-metabox.php:252 admin/class-metabox.php:835
+msgid "Post is set to noindex."
+msgstr ""
+
+#: admin/class-metabox.php:265
+msgid "No focus keyword set."
+msgstr ""
+
+#: admin/class-metabox.php:278
+msgid "SEO: "
+msgstr ""
+
+#: admin/class-metabox.php:280
+msgid "Check"
+msgstr ""
+
+#: admin/class-metabox.php:294
+msgid "WordPress SEO by Yoast"
+msgstr ""
+
+#: admin/class-metabox.php:322
+msgid " (because of date display)"
+msgstr ""
+
+#: admin/class-metabox.php:369
+msgid "Your focus keyword was found in:"
+msgstr ""
+
+#: admin/class-metabox.php:370
+msgid "Article Heading: "
+msgstr ""
+
+#: admin/class-metabox.php:371
+msgid "Page title: "
+msgstr ""
+
+#: admin/class-metabox.php:372
+msgid "Page URL: "
+msgstr ""
+
+#: admin/class-metabox.php:373
+msgid "Content: "
+msgstr ""
+
+#: admin/class-metabox.php:374
+msgid "Meta description: "
+msgstr ""
+
+#: admin/class-metabox.php:382
+msgid "(no parent)"
+msgstr ""
+
+#: admin/class-metabox.php:415 admin/class-metabox.php:433
+#: admin/pages/dashboard.php:144 admin/pages/metas.php:20
+msgid "General"
+msgstr ""
+
+#: admin/class-metabox.php:417 admin/class-metabox.php:435
+msgid "Page Analysis"
+msgstr ""
+
+#: admin/class-metabox.php:421 admin/class-metabox.php:442
+msgid "Advanced"
+msgstr ""
+
+#: admin/class-metabox.php:780
+msgid "SEO: No Focus Keyword"
+msgstr ""
+
+#: admin/class-metabox.php:781
+msgid "SEO: Bad"
+msgstr ""
+
+#: admin/class-metabox.php:782
+msgid "SEO: Poor"
+msgstr ""
+
+#: admin/class-metabox.php:783
+msgid "SEO: OK"
+msgstr ""
+
+#: admin/class-metabox.php:784
+msgid "SEO: Good"
+msgstr ""
+
+#: admin/class-metabox.php:785
+msgid "SEO: Post Noindexed"
+msgstr ""
+
+#: admin/class-metabox.php:789
+msgid "All SEO Scores"
+msgstr ""
+
+#: admin/class-metabox.php:815
+msgid "Meta Desc."
+msgstr ""
+
+#: admin/class-metabox.php:816
+msgid "Focus KW"
+msgstr ""
+
+#: admin/class-metabox.php:847
+msgid "Focus keyword not set."
+msgstr ""
+
+#: admin/class-metabox.php:1117
+msgid ""
+"To update this page analysis, save as draft or update and check this tab "
+"again"
+msgstr ""
+
+#: admin/class-metabox.php:1141
+msgid "Your hosting environment does not support PHP's %sDocument Object Model%s."
+msgstr ""
+
+#: admin/class-metabox.php:1141
+msgid ""
+"To enjoy all the benefits of the page analysis feature, you'll need to (get "
+"your host to) install it."
+msgstr ""
+
+#: admin/class-metabox.php:1147
+msgid "No post content to analyse."
+msgstr ""
+
+#: admin/class-metabox.php:1151
+msgid ""
+"No focus keyword was set for this %s. If you do not set a focus keyword, no "
+"score can be calculated."
+msgstr ""
+
+#: admin/class-metabox.php:1157
+msgid "Page Analysis has been disabled."
+msgstr ""
+
+#: admin/class-metabox.php:1389
+msgid "You've never used this focus keyword before, very good."
+msgstr ""
+
+#: admin/class-metabox.php:1391
+msgid ""
+"You've used this focus keyword %1$sonce before%2$s, be sure to make very "
+"clear which URL on your site is the most important for this keyword."
+msgstr ""
+
+#: admin/class-metabox.php:1396
+msgid ""
+"You've used this focus keyword %3$s%4$d times before%2$s, it's probably a "
+"good idea to read %1$sthis post on cornerstone content%2$s and improve your "
+"keyword strategy."
+msgstr ""
+
+#: admin/class-metabox.php:1409
+msgid ""
+"The keyword for this page contains one or more %sstop words%s, consider "
+"removing them. Found '%s'."
+msgstr ""
+
+#: admin/class-metabox.php:1425
+msgid "The keyword / phrase appears in the URL for this page."
+msgstr ""
+
+#: admin/class-metabox.php:1426
+msgid ""
+"The keyword / phrase does not appear in the URL for this page. If you "
+"decide to rename the URL be sure to check the old URL 301 redirects to the "
+"new one!"
+msgstr ""
+
+#: admin/class-metabox.php:1427
+msgid ""
+"The slug for this page contains one or more <a "
+"href=\"http://en.wikipedia.org/wiki/Stop_words\">stop words</a>, consider "
+"removing them."
+msgstr ""
+
+#: admin/class-metabox.php:1428
+msgid "The slug for this page is a bit long, consider shortening it."
+msgstr ""
+
+#: admin/class-metabox.php:1462
+msgid "Please create a page title."
+msgstr ""
+
+#: admin/class-metabox.php:1463
+msgid ""
+"The page title is more than 40 characters and less than the recommended 70 "
+"character limit."
+msgstr ""
+
+#: admin/class-metabox.php:1464
+msgid ""
+"The page title contains %d characters, which is less than the recommended "
+"minimum of 40 characters. Use the space to add keyword variations or create "
+"compelling call-to-action copy."
+msgstr ""
+
+#: admin/class-metabox.php:1465
+msgid ""
+"The page title contains %d characters, which is more than the viewable "
+"limit of 70 characters; some words will not be visible to users in your "
+"listing."
+msgstr ""
+
+#: admin/class-metabox.php:1466
+msgid "The keyword / phrase %s does not appear in the page title."
+msgstr ""
+
+#: admin/class-metabox.php:1467
+msgid ""
+"The page title contains keyword / phrase, at the beginning which is "
+"considered to improve rankings."
+msgstr ""
+
+#: admin/class-metabox.php:1468
+msgid ""
+"The page title contains keyword / phrase, but it does not appear at the "
+"beginning; try and move it to the beginning."
+msgstr ""
+
+#: admin/class-metabox.php:1508
+msgid "No outbound links appear in this page, consider adding some as appropriate."
+msgstr ""
+
+#: admin/class-metabox.php:1509
+msgid ""
+"You're linking to another page with the keyword you want this page to rank "
+"for, consider changing that if you truly want this page to rank."
+msgstr ""
+
+#: admin/class-metabox.php:1510
+msgid "This page has %s outbound link(s)."
+msgstr ""
+
+#: admin/class-metabox.php:1511
+msgid "This page has %s outbound link(s), all nofollowed."
+msgstr ""
+
+#: admin/class-metabox.php:1512
+msgid "This page has %s nofollowed link(s) and %s normal outbound link(s)."
+msgstr ""
+
+#: admin/class-metabox.php:1621
+msgid "No images appear in this page, consider adding some as appropriate."
+msgstr ""
+
+#: admin/class-metabox.php:1622
+msgid "The images on this page are missing alt tags."
+msgstr ""
+
+#: admin/class-metabox.php:1623
+msgid "The images on this page contain alt tags with the target keyword / phrase."
+msgstr ""
+
+#: admin/class-metabox.php:1624
+msgid ""
+"The images on this page do not have alt tags containing your keyword / "
+"phrase."
+msgstr ""
+
+#: admin/class-metabox.php:1698
+msgid "No subheading tags (like an H2) appear in the copy."
+msgstr ""
+
+#: admin/class-metabox.php:1699
+msgid ""
+"Keyword / keyphrase appears in %s (out of %s) subheadings in the copy. "
+"While not a major ranking factor, this is beneficial."
+msgstr ""
+
+#: admin/class-metabox.php:1700
+msgid ""
+"You have not used your keyword / keyphrase in any subheading (such as an "
+"H2) in your copy."
+msgstr ""
+
+#: admin/class-metabox.php:1756
+msgid ""
+"In the specified meta description, consider: How does it compare to the "
+"competition? Could it be made more appealing?"
+msgstr ""
+
+#: admin/class-metabox.php:1757
+msgid ""
+"The meta description is under 120 characters, however up to %s characters "
+"are available. %s"
+msgstr ""
+
+#: admin/class-metabox.php:1758
+msgid ""
+"The specified meta description is over %s characters, reducing it will "
+"ensure the entire description is visible. %s"
+msgstr ""
+
+#: admin/class-metabox.php:1759
+msgid ""
+"No meta description has been specified, search engines will display copy "
+"from the page instead."
+msgstr ""
+
+#: admin/class-metabox.php:1760
+msgid "The meta description contains the primary keyword / phrase."
+msgstr ""
+
+#: admin/class-metabox.php:1761
+msgid ""
+"A meta description has been specified, but it does not contain the target "
+"keyword / phrase."
+msgstr ""
+
+#: admin/class-metabox.php:1765
+msgid ""
+"The available space is shorter than the usual 155 characters because Google "
+"will also include the publication date in the snippet."
+msgstr ""
+
+#: admin/class-metabox.php:1809
+msgid ""
+"There are %d words contained in the body copy, this is more than the %d "
+"word recommended minimum."
+msgstr ""
+
+#: admin/class-metabox.php:1810
+msgid ""
+"There are %d words contained in the body copy, this is below the %d word "
+"recommended minimum. Add more useful content on this topic for readers."
+msgstr ""
+
+#: admin/class-metabox.php:1811
+msgid ""
+"There are %d words contained in the body copy, this is slightly below the "
+"%d word recommended minimum, add a bit more copy."
+msgstr ""
+
+#: admin/class-metabox.php:1812
+msgid ""
+"There are %d words contained in the body copy. This is far too low and "
+"should be increased."
+msgstr ""
+
+#: admin/class-metabox.php:1814
+msgid ""
+"The keyword density is %s%%, which is a bit low, the keyword was found %s "
+"times."
+msgstr ""
+
+#: admin/class-metabox.php:1815
+msgid ""
+"The keyword density is %s%%, which is over the advised 4.5%% maximum, the "
+"keyword was found %s times."
+msgstr ""
+
+#: admin/class-metabox.php:1816
+msgid "The keyword density is %s%%, which is great, the keyword was found %s times."
+msgstr ""
+
+#: admin/class-metabox.php:1818
+msgid ""
+"The keyword doesn't appear in the first paragraph of the copy, make sure "
+"the topic is clear immediately."
+msgstr ""
+
+#: admin/class-metabox.php:1819
+msgid "The keyword appears in the first paragraph of the copy."
+msgstr ""
+
+#: admin/class-metabox.php:1821
+msgid "Flesch Reading Ease"
+msgstr ""
+
+#: admin/class-metabox.php:1822
+msgid "The copy scores %s in the %s test, which is considered %s to read. %s"
+msgstr ""
+
+#: admin/class-metabox.php:1849
+msgid ""
+"Your keyphrase is over 10 words, a keyphrase should be shorter and there "
+"can be only one keyphrase."
+msgstr ""
+
+#: admin/class-metabox.php:1888
+msgid "very easy"
+msgstr ""
+
+#: admin/class-metabox.php:1891
+msgid "easy"
+msgstr ""
+
+#: admin/class-metabox.php:1894
+msgid "fairly easy"
+msgstr ""
+
+#: admin/class-metabox.php:1897 inc/wpseo-non-ajax-functions.php:236
+msgid "OK"
+msgstr ""
+
+#: admin/class-metabox.php:1900
+msgid "fairly difficult"
+msgstr ""
+
+#: admin/class-metabox.php:1901
+msgid "Try to make shorter sentences to improve readability."
+msgstr ""
+
+#: admin/class-metabox.php:1904
+msgid "difficult"
+msgstr ""
+
+#: admin/class-metabox.php:1905 admin/class-metabox.php:1909
+msgid ""
+"Try to make shorter sentences, using less difficult words to improve "
+"readability."
+msgstr ""
+
+#: admin/class-metabox.php:1908
+msgid "very difficult"
+msgstr ""
+
+#: admin/class-opengraph-admin.php:35
+msgid "Facebook Title"
+msgstr ""
+
+#: admin/class-opengraph-admin.php:36
+msgid ""
+"If you don't want to use the post title for sharing the post on Facebook "
+"but instead want another title there, write it here."
+msgstr ""
+
+#: admin/class-opengraph-admin.php:38
+msgid "Facebook Description"
+msgstr ""
+
+#: admin/class-opengraph-admin.php:39
+msgid ""
+"If you don't want to use the meta description for sharing the post on "
+"Facebook but want another description there, write it here."
+msgstr ""
+
+#: admin/class-opengraph-admin.php:41
+msgid "Facebook Image"
+msgstr ""
+
+#: admin/class-opengraph-admin.php:42
+msgid ""
+"If you want to override the Facebook image for this post, upload / choose "
+"an image or add the URL here."
+msgstr ""
+
+#: admin/class-opengraph-admin.php:44
+msgid "Google+ Title"
+msgstr ""
+
+#: admin/class-opengraph-admin.php:45
+msgid ""
+"If you don't want to use the post title for sharing the post on Google+ but "
+"instead want another title there, write it here."
+msgstr ""
+
+#: admin/class-opengraph-admin.php:47
+msgid "Google+ Description"
+msgstr ""
+
+#: admin/class-opengraph-admin.php:48
+msgid ""
+"If you don't want to use the meta description for sharing the post on "
+"Google+ but want another description there, write it here."
+msgstr ""
+
+#: admin/class-opengraph-admin.php:50
+msgid "Google+ Image"
+msgstr ""
+
+#: admin/class-opengraph-admin.php:51
+msgid ""
+"If you want to override the image for this post that Google+ will use, "
+"upload / choose an image or add the URL here. Note that it will otherwise "
+"default to the Facebook one above."
+msgstr ""
+
+#: admin/class-pointers.php:67
+msgid "Help improve WordPress SEO"
+msgstr ""
+
+#: admin/class-pointers.php:68
+msgid ""
+"You've just installed WordPress SEO by Yoast. Please helps us improve it by "
+"allowing us to gather anonymous usage stats so we know which "
+"configurations, plugins and themes to test with."
+msgstr ""
+
+#: admin/class-pointers.php:75
+msgid "Do not allow tracking"
+msgstr ""
+
+#: admin/class-pointers.php:79
+msgid "Allow tracking"
+msgstr ""
+
+#: admin/class-pointers.php:96
+msgid ""
+"This is the WordPress SEO Dashboard, here you can restart this tour or "
+"revert the WP SEO settings to default."
+msgstr ""
+
+#: admin/class-pointers.php:97
+msgid "More WordPress SEO"
+msgstr ""
+
+#: admin/class-pointers.php:97
+msgid ""
+"There's more to learn about WordPress & SEO than just using this "
+"plugin. A great start is our article %1$sthe definitive guide to WordPress "
+"SEO%2$s."
+msgstr ""
+
+#: admin/class-pointers.php:98 admin/pages/dashboard.php:154
+msgid "Tracking"
+msgstr ""
+
+#: admin/class-pointers.php:98
+msgid ""
+"To provide you with the best experience possible, we need your help. Please "
+"enable tracking to help us gather anonymous usage data."
+msgstr ""
+
+#: admin/class-pointers.php:99 admin/pages/dashboard.php:162
+msgid "Webmaster Tools"
+msgstr ""
+
+#: admin/class-pointers.php:99
+msgid ""
+"You can also add the verification codes for the different Webmaster Tools "
+"programs here, we highly encourage you to check out both Google and Bing's "
+"Webmaster Tools."
+msgstr ""
+
+#: admin/class-pointers.php:100
+msgid "WordPress SEO Tour"
+msgstr ""
+
+#: admin/class-pointers.php:100
+msgid ""
+"This tour will show you around in the plugin, to give you a general "
+"overview of the plugin."
+msgstr ""
+
+#: admin/class-pointers.php:101
+msgid "Newsletter"
+msgstr ""
+
+#: admin/class-pointers.php:102
+msgid ""
+"If you would like to us to keep you up-to-date regarding WordPress SEO and "
+"other plugins by Yoast, subscribe to our newsletter:"
+msgstr ""
+
+#: admin/class-pointers.php:105
+msgid "Email"
+msgstr ""
+
+#: admin/class-pointers.php:107
+msgid "Subscribe"
+msgstr ""
+
+#: admin/class-pointers.php:109 admin/class-pointers.php:117
+#: admin/class-pointers.php:127 admin/class-pointers.php:136
+#: admin/class-pointers.php:143 admin/class-pointers.php:150
+#: admin/class-pointers.php:157 admin/class-pointers.php:167
+#: admin/class-pointers.php:174 admin/class-pointers.php:181
+msgid "Next"
+msgstr ""
+
+#: admin/class-pointers.php:114
+msgid "Title & Metas settings"
+msgstr ""
+
+#: admin/class-pointers.php:114
+msgid ""
+"This is where you set the titles and meta-information for all your post "
+"types, taxonomies, archives, special pages and for your homepage. The page "
+"is divided into different tabs., make sure you check 'm all out!"
+msgstr ""
+
+#: admin/class-pointers.php:115
+msgid "Sitewide settings"
+msgstr ""
+
+#: admin/class-pointers.php:115
+msgid ""
+"The first tab will show you site-wide settings. You can also set some "
+"settings for the entire site here to add specific meta tags or to remove "
+"some unneeded cruft."
+msgstr ""
+
+#: admin/class-pointers.php:116
+msgid "Templates and settings"
+msgstr ""
+
+#: admin/class-pointers.php:116
+msgid "Now click on the '%1$sPost Types%2$s'-tab, as this will be our example."
+msgstr ""
+
+#: admin/class-pointers.php:116
+msgid ""
+"The templates are built using variables. You can find all these variables "
+"in the help tab (in the top-right corner of the page). The settings allow "
+"you to set specific behavior for the post types."
+msgstr ""
+
+#: admin/class-pointers.php:119 admin/class-pointers.php:129
+#: admin/class-pointers.php:138 admin/class-pointers.php:145
+#: admin/class-pointers.php:152 admin/class-pointers.php:159
+#: admin/class-pointers.php:169 admin/class-pointers.php:176
+#: admin/class-pointers.php:183 admin/class-pointers.php:192
+msgid "Previous"
+msgstr ""
+
+#: admin/class-pointers.php:123
+msgid "Social settings"
+msgstr ""
+
+#: admin/class-pointers.php:124 admin/pages/social.php:172
+msgid "Facebook"
+msgstr ""
+
+#: admin/class-pointers.php:124
+msgid ""
+"On this tab you can enable the %1$sFacebook Open Graph%2$s functionality "
+"from this plugin, as well as assign a Facebook user or Application to be "
+"the admin of your site, so you can view the Facebook insights."
+msgstr ""
+
+#: admin/class-pointers.php:124
+msgid ""
+"The frontpage settings allow you to set meta-data for your homepage, "
+"whereas the default settings allow you to set a fallback for all "
+"posts/pages without images. "
+msgstr ""
+
+#: admin/class-pointers.php:125 admin/pages/social.php:173
+msgid "Twitter"
+msgstr ""
+
+#: admin/class-pointers.php:125
+msgid ""
+"With %1$sTwitter Cards%2$s, you can attach rich photos, videos and media "
+"experience to tweets that drive traffic to your website. Simply check the "
+"box, sign up for the service, and users who Tweet links to your content "
+"will have a \"Card\" added to the tweet that's visible to all of their "
+"followers."
+msgstr ""
+
+#: admin/class-pointers.php:126
+msgid ""
+"This tab allows you to add specific post meta data for Google+. And if you "
+"have a Google+ page for your business, add that URL here and link it on "
+"your %1$sGoogle+%2$s page's about page."
+msgstr ""
+
+#: admin/class-pointers.php:134
+msgid "What are XML sitemaps?"
+msgstr ""
+
+#: admin/class-pointers.php:134
+msgid ""
+"A Sitemap is an XML file that lists the URLs for a site. It allows "
+"webmasters to include additional information about each URL: when it was "
+"last updated, how often it changes, and how important it is in relation to "
+"other URLs in the site. This allows search engines to crawl the site more "
+"intelligently."
+msgstr ""
+
+#: admin/class-pointers.php:135
+msgid "What does the plugin do with XML Sitemaps?"
+msgstr ""
+
+#: admin/class-pointers.php:135
+msgid ""
+"This plugin adds XML sitemaps to your site. The sitemaps are automatically "
+"updated when you publish a new post, page or custom post and Google and "
+"Bing will be automatically notified. You can also have the plugin "
+"automatically notify Yahoo! and Ask.com."
+msgstr ""
+
+#: admin/class-pointers.php:135
+msgid ""
+"If you want to exclude certain post types and/or taxonomies, you can also "
+"set that on this page."
+msgstr ""
+
+#: admin/class-pointers.php:135
+msgid ""
+"Is your webserver low on memory? Decrease the entries per sitemap (default: "
+"1000) to reduce load."
+msgstr ""
+
+#: admin/class-pointers.php:142 admin/pages/permalinks.php:38
+msgid "Permalink Settings"
+msgstr ""
+
+#: admin/class-pointers.php:142
+msgid ""
+"All of the options here are for advanced users only, if you don't know "
+"whether you should check any, don't touch them."
+msgstr ""
+
+#: admin/class-pointers.php:149 admin/pages/internal-links.php:81
+msgid "Breadcrumbs Settings"
+msgstr ""
+
+#: admin/class-pointers.php:149
+msgid ""
+"If your theme supports my breadcrumbs, as all Genesis and WooThemes themes "
+"as well as a couple of other ones do, you can change the settings for those "
+"here. If you want to modify your theme to support them, %sfollow these "
+"instructions%s."
+msgstr ""
+
+#: admin/class-pointers.php:156
+msgid "RSS Settings"
+msgstr ""
+
+#: admin/class-pointers.php:156
+msgid ""
+"This incredibly powerful function allows you to add content to the "
+"beginning and end of your posts in your RSS feed. This helps you gain links "
+"from people who steal your content!"
+msgstr ""
+
+#: admin/class-pointers.php:163
+msgid "Import & Export"
+msgstr ""
+
+#: admin/class-pointers.php:164
+msgid "Import from other (SEO) plugins"
+msgstr ""
+
+#: admin/class-pointers.php:164
+msgid ""
+"We can imagine that you switch from another SEO plugin to WordPress SEO. If "
+"you just did, you can use these options to transfer your SEO-data. If you "
+"were using one of my older plugins like Robots Meta & RSS Footer, you "
+"can import the settings here too."
+msgstr ""
+
+#: admin/class-pointers.php:165
+msgid "Other imports"
+msgstr ""
+
+#: admin/class-pointers.php:165
+msgid ""
+"If you're using one of our premium plugins, such as %1$sLocal SEO%2$s, you "
+"can also find specific import-options for that plugin here."
+msgstr ""
+
+#: admin/class-pointers.php:166 admin/pages/import.php:358
+msgid "Export"
+msgstr ""
+
+#: admin/class-pointers.php:166
+msgid ""
+"If you have multiple blogs and you're happy with how you've configured this "
+"blog, you can export the settings and import them on another blog so you "
+"don't have to go through this process twice!"
+msgstr ""
+
+#: admin/class-pointers.php:173
+msgid ""
+"This page lets you view and edit the titles and meta descriptions of all "
+"posts and pages on your site. This allows you to edit the title or meta "
+"description of all your pages in one place, rather than having to edit each "
+"individual page."
+msgstr ""
+
+#: admin/class-pointers.php:180
+msgid "File Editor"
+msgstr ""
+
+#: admin/class-pointers.php:180
+msgid ""
+"Here you can edit the .htaccess and robots.txt files, two of the most "
+"powerful files in your WordPress install, if your WordPress installation "
+"has write-access to the files. But please, only touch these files if you "
+"know what you're doing!"
+msgstr ""
+
+#: admin/class-pointers.php:187
+msgid "Extensions and Licenses"
+msgstr ""
+
+#: admin/class-pointers.php:188
+msgid ""
+"The powerful functions of WordPress SEO can be extended with %1$sYoast "
+"premium plugins%2$s. These premium plugins require the installation of "
+"WordPress SEO or WordPress SEO Premium and add specific functionality. You "
+"can read all about the Yoast Premium Plugins on "
+"%1$shttp://yoast.com/wordpress/plugins/%2$s."
+msgstr ""
+
+#: admin/class-pointers.php:189 admin/pages/licenses.php:22
+msgid "Licenses"
+msgstr ""
+
+#: admin/class-pointers.php:189
+msgid ""
+"Once you've purchased WordPress SEO Premium or any other premium Yoast "
+"plugin, you'll have to enter a license key. You can do so on the "
+"Licenses-tab. Once you've activated your premium plugin, you can use all "
+"its powerful features."
+msgstr ""
+
+#: admin/class-pointers.php:190
+msgid "Like this plugin?"
+msgstr ""
+
+#: admin/class-pointers.php:190
+msgid ""
+"So, we've come to the end of the tour. If you like the plugin, please "
+"%srate it 5 stars on WordPress.org%s!"
+msgstr ""
+
+#: admin/class-pointers.php:191
+msgid ""
+"Thank you for using my plugin and good luck with your "
+"SEO!<br/><br/>Best,<br/>Team Yoast - %1$sYoast.com%2$s"
+msgstr ""
+
+#: admin/class-pointers.php:210
+msgid "Close"
+msgstr ""
+
+#: admin/class-pointers.php:218
+msgid "Congratulations!"
+msgstr ""
+
+#: admin/class-pointers.php:219
+msgid ""
+"You've just installed WordPress SEO by Yoast! Click \"Start Tour\" to view "
+"a quick introduction of this plugins core functionality."
+msgstr ""
+
+#: admin/class-pointers.php:224 admin/pages/dashboard.php:147
+msgid "Start Tour"
+msgstr ""
+
+#: admin/class-taxonomy.php:61
+msgid "Use %s default (Currently: %s)"
+msgstr ""
+
+#: admin/class-taxonomy.php:62
+msgid "Always index"
+msgstr ""
+
+#: admin/class-taxonomy.php:63
+msgid "Always noindex"
+msgstr ""
+
+#: admin/class-taxonomy.php:174
+msgid "Yoast WordPress SEO Settings"
+msgstr ""
+
+#: admin/class-taxonomy.php:177
+msgid "The SEO title is used on the archive page for this term."
+msgstr ""
+
+#: admin/class-taxonomy.php:178
+msgid "SEO Description"
+msgstr ""
+
+#: admin/class-taxonomy.php:178
+msgid ""
+"The SEO description is used for the meta description on the archive page "
+"for this term."
+msgstr ""
+
+#: admin/class-taxonomy.php:181
+msgid "Meta keywords used on the archive page for this term."
+msgstr ""
+
+#: admin/class-taxonomy.php:184
+msgid "Canonical"
+msgstr ""
+
+#: admin/class-taxonomy.php:184
+msgid "The canonical link is shown on the archive page for this term."
+msgstr ""
+
+#: admin/class-taxonomy.php:187 admin/pages/metas.php:143
+msgid "Breadcrumbs Title"
+msgstr ""
+
+#: admin/class-taxonomy.php:187
+msgid "The Breadcrumbs title is used in the breadcrumbs where this %s appears."
+msgstr ""
+
+#: admin/class-taxonomy.php:198
+msgid ""
+"This %s follows the indexation rules set under Metas and Titles, you can "
+"override it here."
+msgstr ""
+
+#: admin/class-taxonomy.php:203
+msgid "Noindex this %s"
+msgstr ""
+
+#: admin/class-taxonomy.php:207
+msgid "Include in sitemap?"
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:137
+msgid ""
+"<b>Warning!</b> You're blocking external requests which means you won't be "
+"able to get %s updates. Please add %s to %s."
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:180
+msgid "Your %s license has been activated. You have an unlimited license. "
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:182
+msgid "Your %s license has been activated. You have used %d/%d activations. "
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:187
+msgid "<a href=\"%s\">Did you know you can upgrade your license?</a>"
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:191
+msgid ""
+"<a href=\"%s\">Your license is expiring in %d days, would you like to "
+"extend it?</a>"
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:200
+msgid ""
+"You've reached your activation limit. You must <a href=\"%s\">upgrade your "
+"license</a> to use it on this site."
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:203
+msgid ""
+"Your license has expired. You must <a href=\"%s\">extend your license</a> "
+"in order to use it again."
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:206
+msgid "Failed to activate your license, your license key seems to be invalid."
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:230
+msgid "Your %s license has been deactivated."
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:232
+msgid "Failed to deactivate your %s license."
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:267
+msgid "Request error: \"%s\" (%scommon license notices%s)"
+msgstr ""
+
+#: admin/license-manager/class-license-manager.php:423
+msgid "%s: License Settings"
+msgstr ""
+
+#: admin/license-manager/class-plugin-license-manager.php:73
+msgid ""
+"%s is network activated, you can manage your license in the <a "
+"href=\"%s\">network admin license page</a>."
+msgstr ""
+
+#: admin/license-manager/class-plugin-license-manager.php:75
+msgid ""
+"%s is network activated, please contact your site administrator to manage "
+"the license."
+msgstr ""
+
+#: admin/license-manager/class-theme-license-manager.php:34
+#: admin/license-manager/samples/sample-plugin.php:53
+msgid "%s License"
+msgstr ""
+
+#: admin/license-manager/class-theme-license-manager.php:34
+msgid "Theme License"
+msgstr ""
+
+#: admin/license-manager/class-theme-update-manager.php:96
+msgid ""
+"Updating this theme will lose any customizations you have made. 'Cancel' to "
+"stop, 'OK' to update."
+msgstr ""
+
+#: admin/license-manager/class-theme-update-manager.php:101
+msgid ""
+"<strong>%s version %s</strong> is available. <a href=\"%s\" "
+"class=\"thickbox\" title=\"%s\">Check out what's new</a> or <a href=\"%s\" "
+"%s>update now</a>."
+msgstr ""
+
+#: admin/license-manager/class-update-manager.php:83
+msgid "%s failed to check for updates because of the following error: <em>%s</em>"
+msgstr ""
+
+#: admin/license-manager/class-update-manager.php:153
+msgid ""
+"This site has not been activated properly on yoast.com and thus cannot "
+"check for future updates. Please activate your site with a valid license "
+"key."
+msgstr ""
+
+#: admin/license-manager/views/form.php:23
+msgid "License status"
+msgstr ""
+
+#: admin/license-manager/views/form.php:33
+msgid "Toggle license status"
+msgstr ""
+
+#: admin/license-manager/views/form.php:37
+msgid "Deactivate License"
+msgstr ""
+
+#: admin/license-manager/views/form.php:38
+msgid "(deactivate your license so you can activate it on another WordPress site)"
+msgstr ""
+
+#: admin/license-manager/views/form.php:42
+msgid "Activate License"
+msgstr ""
+
+#: admin/license-manager/views/form.php:44
+msgid "Please enter a license key in the field below first."
+msgstr ""
+
+#: admin/license-manager/views/form.php:52
+msgid "License Key"
+msgstr ""
+
+#: admin/license-manager/views/form.php:54
+msgid "Paste your %s license key here.."
+msgstr ""
+
+#: admin/license-manager/views/form.php:56
+msgid "You defined your license key using the %s PHP constant."
+msgstr ""
+
+#: admin/license-manager/views/form.php:73
+msgid "Your %s license will expire on %s."
+msgstr ""
+
+#: admin/license-manager/views/form.php:76
+msgid "%sRenew your license now%s."
+msgstr ""
+
+#: admin/pages/bulk-editor.php:36 admin/pages/metas.php:137
+#: admin/pages/social.php:188
+msgid "Title"
+msgstr ""
+
+#: admin/pages/bulk-editor.php:37 admin/pages/social.php:189
+msgid "Description"
+msgstr ""
+
+#: admin/pages/dashboard.php:64
+msgid "Removed hardcoded meta description."
+msgstr ""
+
+#: admin/pages/dashboard.php:70
+msgid "Failed to remove hardcoded meta description."
+msgstr ""
+
+#: admin/pages/dashboard.php:77
+msgid ""
+"Earlier found meta description was not found in file. Renewed the "
+"description test data."
+msgstr ""
+
+#: admin/pages/dashboard.php:110 admin/pages/dashboard.php:122
+#: admin/pages/dashboard.php:132 admin/pages/dashboard.php:139
+msgid "Fix it."
+msgstr ""
+
+#: admin/pages/dashboard.php:111
+msgid ""
+"The following file(s) is/are blocking your XML sitemaps from working "
+"properly:"
+msgstr ""
+
+#: admin/pages/dashboard.php:115
+msgid ""
+"Either delete them (this can be done with the \"Fix it\" button) or disable "
+"WP SEO XML sitemaps."
+msgstr ""
+
+#: admin/pages/dashboard.php:123
+msgid "Re-check theme."
+msgstr ""
+
+#: admin/pages/dashboard.php:124
+msgid ""
+"Your theme contains a meta description, which blocks WordPress SEO from "
+"working properly, please delete the following line, or press fix it:"
+msgstr ""
+
+#: admin/pages/dashboard.php:133 admin/pages/dashboard.php:140
+msgid "Ignore."
+msgstr ""
+
+#: admin/pages/dashboard.php:134
+msgid ""
+"You do not have your postname in the URL of your posts and pages, it is "
+"highly recommended that you do. Consider setting your permalink structure "
+"to <strong>/%postname%/</strong>."
+msgstr ""
+
+#: admin/pages/dashboard.php:141
+msgid ""
+"Paging comments is enabled, this is not needed in 999 out of 1000 cases, so "
+"the suggestion is to disable it, to do that, simply uncheck the box before "
+"\"Break comments into pages...\""
+msgstr ""
+
+#: admin/pages/dashboard.php:147
+msgid "Introduction Tour:"
+msgstr ""
+
+#: admin/pages/dashboard.php:148
+msgid "Take this tour to quickly learn about the use of this plugin."
+msgstr ""
+
+#: admin/pages/dashboard.php:151
+msgid "Default Settings:"
+msgstr ""
+
+#: admin/pages/dashboard.php:151
+msgid "Are you sure you want to reset your SEO settings?"
+msgstr ""
+
+#: admin/pages/dashboard.php:151
+msgid "Reset Default Settings"
+msgstr ""
+
+#: admin/pages/dashboard.php:152
+msgid ""
+"If you want to restore a site to the default WordPress SEO settings, press "
+"this button."
+msgstr ""
+
+#: admin/pages/dashboard.php:155
+msgid "Allow tracking of this WordPress install's anonymous data."
+msgstr ""
+
+#: admin/pages/dashboard.php:156
+msgid ""
+"To maintain a plugin as big as WordPress SEO, we need to know what we're "
+"dealing with: what kinds of other plugins our users are using, what themes, "
+"etc. Please allow us to track that data from your install. It will not "
+"track <em>any</em> user details, so your security and privacy are safe with "
+"us."
+msgstr ""
+
+#: admin/pages/dashboard.php:158
+msgid "Security"
+msgstr ""
+
+#: admin/pages/dashboard.php:159
+msgid "Disable the Advanced part of the WordPress SEO meta box"
+msgstr ""
+
+#: admin/pages/dashboard.php:160
+msgid ""
+"Unchecking this box allows authors and editors to redirect posts, noindex "
+"them and do other things you might not want if you don't trust your authors."
+msgstr ""
+
+#: admin/pages/dashboard.php:163
+msgid ""
+"You can use the boxes below to verify with the different Webmaster Tools, "
+"if your site is already verified, you can just forget about these. Enter "
+"the verify meta values for:"
+msgstr ""
+
+#: admin/pages/dashboard.php:164
+msgid "Alexa Verification ID"
+msgstr ""
+
+#: admin/pages/dashboard.php:165
+msgid "Bing Webmaster Tools"
+msgstr ""
+
+#: admin/pages/dashboard.php:166
+msgid "Google Webmaster Tools"
+msgstr ""
+
+#: admin/pages/dashboard.php:167
+msgid "Pinterest"
+msgstr ""
+
+#: admin/pages/dashboard.php:168
+msgid "Yandex Webmaster Tools"
+msgstr ""
+
+#: admin/pages/files.php:19
+msgid "You cannot create a robots.txt file."
+msgstr ""
+
+#: admin/pages/files.php:35
+msgid "You cannot edit the robots.txt file."
+msgstr ""
+
+#: admin/pages/files.php:46
+msgid "Updated Robots.txt"
+msgstr ""
+
+#: admin/pages/files.php:53
+msgid "You cannot edit the .htaccess file."
+msgstr ""
+
+#: admin/pages/files.php:79
+msgid "You don't have a robots.txt file, create one here:"
+msgstr ""
+
+#: admin/pages/files.php:80
+msgid "Create robots.txt file"
+msgstr ""
+
+#: admin/pages/files.php:84
+msgid ""
+"If you had a robots.txt file and it was editable, you could edit it from "
+"here."
+msgstr ""
+
+#: admin/pages/files.php:97
+msgid "If your robots.txt were writable, you could edit it from here."
+msgstr ""
+
+#: admin/pages/files.php:102
+msgid "Edit the content of your robots.txt:"
+msgstr ""
+
+#: admin/pages/files.php:104
+msgid "Save changes to Robots.txt"
+msgstr ""
+
+#: admin/pages/files.php:109
+msgid "Robots.txt"
+msgstr ""
+
+#: admin/pages/files.php:121
+msgid "If your .htaccess were writable, you could edit it from here."
+msgstr ""
+
+#: admin/pages/files.php:126
+msgid "Edit the content of your .htaccess:"
+msgstr ""
+
+#: admin/pages/files.php:128
+msgid "Save changes to .htaccess"
+msgstr ""
+
+#: admin/pages/files.php:131 admin/pages/files.php:134
+msgid ".htaccess file"
+msgstr ""
+
+#: admin/pages/files.php:133
+msgid ""
+"If you had a .htaccess file and it was editable, you could edit it from "
+"here."
+msgstr ""
+
+#: admin/pages/import.php:164
+msgid "WooThemes SEO framework settings & data successfully imported."
+msgstr ""
+
+#: admin/pages/import.php:206
+msgid "HeadSpace2 data successfully imported"
+msgstr ""
+
+#: admin/pages/import.php:221
+msgid "All in One SEO (Old version) data successfully imported."
+msgstr ""
+
+#: admin/pages/import.php:269
+msgid "RSS Footer options imported successfully."
+msgstr ""
+
+#: admin/pages/import.php:287
+msgid "Yoast Breadcrumbs options imported successfully."
+msgstr ""
+
+#: admin/pages/import.php:290
+msgid "Yoast Breadcrumbs options could not be found"
+msgstr ""
+
+#: admin/pages/import.php:306
+msgid ", and old data deleted."
+msgstr ""
+
+#: admin/pages/import.php:309
+msgid ", and meta keywords data deleted."
+msgstr ""
+
+#: admin/pages/import.php:319
+msgid ""
+"No doubt you've used an SEO plugin before if this site isn't new. Let's "
+"make it easy on you, you can import the data below. If you want, you can "
+"import first, check if it was imported correctly, and then import & "
+"delete. No duplicate data will be imported."
+msgstr ""
+
+#: admin/pages/import.php:320
+msgid ""
+"If you've used another SEO plugin, try the %sSEO Data Transporter%s plugin "
+"to move your data into this plugin, it rocks!"
+msgstr ""
+
+#: admin/pages/import.php:324
+msgid "Import from HeadSpace2?"
+msgstr ""
+
+#: admin/pages/import.php:325
+msgid "Import from All-in-One SEO?"
+msgstr ""
+
+#: admin/pages/import.php:326
+msgid "Import from OLD All-in-One SEO?"
+msgstr ""
+
+#: admin/pages/import.php:327
+msgid "Import from WooThemes SEO framework?"
+msgstr ""
+
+#: admin/pages/import.php:329
+msgid "Delete the old data after import? (recommended)"
+msgstr ""
+
+#: admin/pages/import.php:331 admin/pages/import.php:346
+#: admin/pages/import.php:349 admin/pages/import.php:384
+msgid "Import"
+msgstr ""
+
+#: admin/pages/import.php:334
+msgid "Import settings from other plugins"
+msgstr ""
+
+#: admin/pages/import.php:335
+msgid "Import from Robots Meta (by Yoast)?"
+msgstr ""
+
+#: admin/pages/import.php:336
+msgid "Import from RSS Footer (by Yoast)?"
+msgstr ""
+
+#: admin/pages/import.php:337
+msgid "Import from Yoast Breadcrumbs?"
+msgstr ""
+
+#: admin/pages/import.php:361
+msgid ""
+"Export your WordPress SEO settings here, to import them again later or to "
+"import them on another site."
+msgstr ""
+
+#: admin/pages/import.php:362
+msgid "Include Taxonomy Metadata"
+msgstr ""
+
+#: admin/pages/import.php:363
+msgid "Export settings"
+msgstr ""
+
+#: admin/pages/import.php:386
+msgid "Import settings by locating <em>settings.zip</em> and clicking"
+msgstr ""
+
+#: admin/pages/import.php:386 admin/pages/import.php:392
+msgid "Import settings"
+msgstr ""
+
+#: admin/pages/import.php:433
+msgid "Setting \"%s\" is no longer used and has been discarded."
+msgstr ""
+
+#: admin/pages/import.php:437
+msgid "Settings successfully imported."
+msgstr ""
+
+#: admin/pages/import.php:440 admin/pages/import.php:445
+#: admin/pages/import.php:451 admin/pages/import.php:458
+#: admin/pages/import.php:461
+msgid "Settings could not be imported:"
+msgstr ""
+
+#: admin/pages/import.php:440
+msgid "No settings found in file."
+msgstr ""
+
+#: admin/pages/import.php:445
+msgid "Unzipping failed - file settings.ini not found."
+msgstr ""
+
+#: admin/pages/import.php:451
+msgid "Unzipping failed with error \"%s\"."
+msgstr ""
+
+#: admin/pages/import.php:461
+msgid "Upload failed."
+msgstr ""
+
+#: admin/pages/import.php:465
+msgid "Export & Import SEO Settings"
+msgstr ""
+
+#: admin/pages/internal-links.php:16
+msgid "Enable Breadcrumbs"
+msgstr ""
+
+#: admin/pages/internal-links.php:18
+msgid "Separator between breadcrumbs"
+msgstr ""
+
+#: admin/pages/internal-links.php:19
+msgid "Anchor text for the Homepage"
+msgstr ""
+
+#: admin/pages/internal-links.php:20
+msgid "Prefix for the breadcrumb path"
+msgstr ""
+
+#: admin/pages/internal-links.php:21
+msgid "Prefix for Archive breadcrumbs"
+msgstr ""
+
+#: admin/pages/internal-links.php:22
+msgid "Prefix for Search Page breadcrumbs"
+msgstr ""
+
+#: admin/pages/internal-links.php:23
+msgid "Breadcrumb for 404 Page"
+msgstr ""
+
+#: admin/pages/internal-links.php:25
+msgid "Remove Blog page from Breadcrumbs"
+msgstr ""
+
+#: admin/pages/internal-links.php:27
+msgid "Bold the last page in the breadcrumb"
+msgstr ""
+
+#: admin/pages/internal-links.php:33
+msgid "Taxonomy to show in breadcrumbs for:"
+msgstr ""
+
+#: admin/pages/internal-links.php:53
+msgid "Post type archive to show in breadcrumbs for:"
+msgstr ""
+
+#: admin/pages/internal-links.php:57
+msgid "Blog"
+msgstr ""
+
+#: admin/pages/internal-links.php:76
+msgid "How to insert breadcrumbs in your theme"
+msgstr ""
+
+#: admin/pages/internal-links.php:77
+msgid ""
+"Usage of this breadcrumbs feature is explained <a "
+"href=\"https://yoast.com/wordpress/plugins/breadcrumbs/\">here</a>. For the "
+"more code savvy, insert this in your theme:"
+msgstr ""
+
+#: admin/pages/licenses.php:33
+msgid "WordPress SEO Premium"
+msgstr ""
+
+#: admin/pages/licenses.php:34
+msgid "The premium version of WordPress SEO with more features & support."
+msgstr ""
+
+#: admin/pages/licenses.php:40
+msgid "Video SEO"
+msgstr ""
+
+#: admin/pages/licenses.php:41
+msgid "Optimize your videos to show them off in search results and get more clicks!"
+msgstr ""
+
+#: admin/pages/licenses.php:47
+msgid "News SEO"
+msgstr ""
+
+#: admin/pages/licenses.php:48
+msgid ""
+"Are you in Google News? Increase your traffic from Google News by "
+"optimizing for it!"
+msgstr ""
+
+#: admin/pages/licenses.php:54
+msgid "Local SEO"
+msgstr ""
+
+#: admin/pages/licenses.php:55
+msgid "Rank better locally and in Google Maps, without breaking a sweat!"
+msgstr ""
+
+#: admin/pages/licenses.php:61
+msgid "Yoast WooCommerce SEO"
+msgstr ""
+
+#: admin/pages/licenses.php:62
+msgid "Seamlessly integrate WooCommerce with WordPress SEO and get extra features!"
+msgstr ""
+
+#: admin/pages/licenses.php:75
+msgid "Get this extension"
+msgstr ""
+
+#: admin/pages/metas.php:21 inc/class-wpseo-options.php:2073
+msgid "Home"
+msgstr ""
+
+#: admin/pages/metas.php:22
+msgid "Post Types"
+msgstr ""
+
+#: admin/pages/metas.php:23
+msgid "Taxonomies"
+msgstr ""
+
+#: admin/pages/metas.php:24
+msgid "Other"
+msgstr ""
+
+#: admin/pages/metas.php:30
+msgid "Title settings"
+msgstr ""
+
+#: admin/pages/metas.php:31
+msgid "Force rewrite titles"
+msgstr ""
+
+#: admin/pages/metas.php:32
+msgid ""
+"WordPress SEO has auto-detected whether it needs to force rewrite the "
+"titles for your pages, if you think it's wrong and you know what you're "
+"doing, you can change the setting here."
+msgstr ""
+
+#: admin/pages/metas.php:34
+msgid "Title Separator"
+msgstr ""
+
+#: admin/pages/metas.php:36
+msgid ""
+"Choose the symbol to use as your title separator. This will display, for "
+"instance, between your post title and site name."
+msgstr ""
+
+#: admin/pages/metas.php:36
+msgid "Symbols are shown in the size they'll appear in in search results."
+msgstr ""
+
+#: admin/pages/metas.php:38
+msgid "Sitewide <code>meta</code> settings"
+msgstr ""
+
+#: admin/pages/metas.php:39
+msgid "Noindex subpages of archives"
+msgstr ""
+
+#: admin/pages/metas.php:40
+msgid ""
+"If you want to prevent /page/2/ and further of any archive to show up in "
+"the search results, enable this."
+msgstr ""
+
+#: admin/pages/metas.php:42
+msgid "Use <code>meta</code> keywords tag?"
+msgstr ""
+
+#: admin/pages/metas.php:43
+msgid ""
+"I don't know why you'd want to use meta keywords, but if you want to, check "
+"this box."
+msgstr ""
+
+#: admin/pages/metas.php:45
+msgid "Add <code>noodp</code> meta robots tag sitewide"
+msgstr ""
+
+#: admin/pages/metas.php:46
+msgid ""
+"Prevents search engines from using the DMOZ description for pages from this "
+"site in the search results."
+msgstr ""
+
+#: admin/pages/metas.php:48
+msgid "Add <code>noydir</code> meta robots tag sitewide"
+msgstr ""
+
+#: admin/pages/metas.php:49
+msgid ""
+"Prevents search engines from using the Yahoo! directory description for "
+"pages from this site in the search results."
+msgstr ""
+
+#: admin/pages/metas.php:51
+msgid "Clean up the <code><head></code>"
+msgstr ""
+
+#: admin/pages/metas.php:52
+msgid "Hide RSD Links"
+msgstr ""
+
+#: admin/pages/metas.php:53
+msgid "Hide WLW Manifest Links"
+msgstr ""
+
+#: admin/pages/metas.php:54
+msgid "Hide Shortlink for posts"
+msgstr ""
+
+#: admin/pages/metas.php:55
+msgid "Hide RSS Links"
+msgstr ""
+
+#: admin/pages/metas.php:61
+msgid "Homepage"
+msgstr ""
+
+#: admin/pages/metas.php:62 admin/pages/metas.php:97 admin/pages/metas.php:159
+#: admin/pages/metas.php:177 admin/pages/metas.php:187
+#: admin/pages/metas.php:197 admin/pages/metas.php:199
+msgid "Title template"
+msgstr ""
+
+#: admin/pages/metas.php:63 admin/pages/metas.php:98 admin/pages/metas.php:160
+#: admin/pages/metas.php:178 admin/pages/metas.php:188
+msgid "Meta description template"
+msgstr ""
+
+#: admin/pages/metas.php:65 admin/pages/metas.php:100 admin/pages/metas.php:162
+#: admin/pages/metas.php:180
+msgid "Meta keywords template"
+msgstr ""
+
+#: admin/pages/metas.php:69
+msgid "Homepage & Front page"
+msgstr ""
+
+#: admin/pages/metas.php:70
+msgid ""
+"You can determine the title and description for the front page by %sediting "
+"the front page itself »%s"
+msgstr ""
+
+#: admin/pages/metas.php:72
+msgid ""
+"You can determine the title and description for the blog page by %sediting "
+"the blog page itself »%s"
+msgstr ""
+
+#: admin/pages/metas.php:91
+msgid "Take note:"
+msgstr ""
+
+#: admin/pages/metas.php:93
+msgid ""
+"As you are redirecting attachment URLs to parent post URLs, these settings "
+"will currently only have an effect on <strong>unattached</strong> media "
+"items!"
+msgstr ""
+
+#: admin/pages/metas.php:94
+msgid ""
+"So remember: If you change the %sattachment redirection setting%s in the "
+"future, the below settings will take effect for *all* media items."
+msgstr ""
+
+#: admin/pages/metas.php:102 admin/pages/metas.php:145
+#: admin/pages/metas.php:164 admin/pages/metas.php:182
+#: admin/pages/metas.php:190
+msgid "Meta Robots"
+msgstr ""
+
+#: admin/pages/metas.php:103
+msgid "Show date in snippet preview?"
+msgstr ""
+
+#: admin/pages/metas.php:103
+msgid "Date in Snippet Preview"
+msgstr ""
+
+#: admin/pages/metas.php:104 admin/pages/metas.php:165
+msgid "Hide"
+msgstr ""
+
+#: admin/pages/metas.php:104 admin/pages/metas.php:165
+msgid "WordPress SEO Meta Box"
+msgstr ""
+
+#: admin/pages/metas.php:126
+msgid "Custom Post Type Archives"
+msgstr ""
+
+#: admin/pages/metas.php:127
+msgid ""
+"Note: instead of templates these are the actual titles and meta "
+"descriptions for these custom post type archive pages."
+msgstr ""
+
+#: admin/pages/metas.php:138
+msgid "Meta description"
+msgstr ""
+
+#: admin/pages/metas.php:140
+msgid "Meta keywords"
+msgstr ""
+
+#: admin/pages/metas.php:176
+msgid "Author Archives"
+msgstr ""
+
+#: admin/pages/metas.php:183
+msgid "Disable the author archives"
+msgstr ""
+
+#: admin/pages/metas.php:184
+msgid ""
+"If you're running a one author blog, the author archive will always look "
+"exactly the same as your homepage. And even though you may not link to it, "
+"others might, to do you harm. Disabling them here will make sure any link "
+"to those archives will be 301 redirected to the homepage."
+msgstr ""
+
+#: admin/pages/metas.php:186
+msgid "Date Archives"
+msgstr ""
+
+#: admin/pages/metas.php:191
+msgid "Disable the date-based archives"
+msgstr ""
+
+#: admin/pages/metas.php:192
+msgid ""
+"For the date based archives, the same applies: they probably look a lot "
+"like your homepage, and could thus be seen as duplicate content."
+msgstr ""
+
+#: admin/pages/metas.php:194
+msgid "Special Pages"
+msgstr ""
+
+#: admin/pages/metas.php:195
+msgid ""
+"These pages will be noindex, followed by default, so they will never show "
+"up in search results."
+msgstr ""
+
+#: admin/pages/metas.php:196
+msgid "Search pages"
+msgstr ""
+
+#: admin/pages/metas.php:198
+msgid "404 pages"
+msgstr ""
+
+#: admin/pages/metas.php:206
+msgid "Variables"
+msgstr ""
+
+#: admin/pages/network.php:23
+msgid "Settings Updated."
+msgstr ""
+
+#: admin/pages/network.php:34
+msgid "%s restored to default SEO settings."
+msgstr ""
+
+#: admin/pages/network.php:37
+msgid "Blog %s not found."
+msgstr ""
+
+#: admin/pages/network.php:60
+msgid "public"
+msgstr ""
+
+#: admin/pages/network.php:63
+msgid "archived"
+msgstr ""
+
+#: admin/pages/network.php:66
+msgid "mature"
+msgstr ""
+
+#: admin/pages/network.php:69
+msgid "spam"
+msgstr ""
+
+#: admin/pages/network.php:93
+msgid "Who should have access to the WordPress SEO settings"
+msgstr ""
+
+#: admin/pages/network.php:95
+msgid "Site Admins (default)"
+msgstr ""
+
+#: admin/pages/network.php:96
+msgid "Super Admins only"
+msgstr ""
+
+#: admin/pages/network.php:104
+msgid "New sites in the network inherit their SEO settings from this site"
+msgstr ""
+
+#: admin/pages/network.php:108
+msgid ""
+"Choose the site whose settings you want to use as default for all sites "
+"that are added to your network. If you choose 'None', the normal plugin "
+"defaults will be used."
+msgstr ""
+
+#: admin/pages/network.php:111
+msgid "New sites in the network get the SEO settings from this site"
+msgstr ""
+
+#: admin/pages/network.php:112
+msgid ""
+"Enter the %sSite ID%s for the site whose settings you want to use as "
+"default for all sites that are added to your network. Leave empty for none "
+"(i.e. the normal plugin defaults will be used)."
+msgstr ""
+
+#: admin/pages/network.php:114
+msgid "Take note :"
+msgstr ""
+
+#: admin/pages/network.php:114
+msgid ""
+"Privacy sensitive (FB admins and such), theme specific (title rewrite) and "
+"a few very site specific settings will not be imported to new blogs."
+msgstr ""
+
+#: admin/pages/network.php:117
+msgid "Save MultiSite Settings"
+msgstr ""
+
+#: admin/pages/network.php:125
+msgid "Using this form you can reset a site to the default SEO settings."
+msgstr ""
+
+#: admin/pages/network.php:131
+msgid "Site ID"
+msgstr ""
+
+#: admin/pages/network.php:137
+msgid "Blog ID"
+msgstr ""
+
+#: admin/pages/network.php:140
+msgid "Restore site to defaults"
+msgstr ""
+
+#: admin/pages/network.php:143
+msgid "Restore site to default settings"
+msgstr ""
+
+#: admin/pages/permalinks.php:20
+msgid ""
+"Strip the category base (usually <code>/category/</code>) from the category "
+"URL."
+msgstr ""
+
+#: admin/pages/permalinks.php:21
+msgid ""
+"We suggest using %1$sFV Top Level Categories%2$s, if you insist on keeping "
+"this but do know that the feature is very error prone and not <em>that</em> "
+"important for your SEO."
+msgstr ""
+
+#: admin/pages/permalinks.php:23
+msgid "Enforce a trailing slash on all category and tag URL's"
+msgstr ""
+
+#: admin/pages/permalinks.php:24
+msgid ""
+"If you choose a permalink for your posts with <code>.html</code>, or "
+"anything else but a / on the end, this will force WordPress to add a "
+"trailing slash to non-post pages nonetheless."
+msgstr ""
+
+#: admin/pages/permalinks.php:26
+msgid "Remove stop words from slugs."
+msgstr ""
+
+#: admin/pages/permalinks.php:27
+msgid ""
+"This helps you to create cleaner URLs by automatically removing the "
+"stopwords from them."
+msgstr ""
+
+#: admin/pages/permalinks.php:29
+msgid "Redirect attachment URL's to parent post URL."
+msgstr ""
+
+#: admin/pages/permalinks.php:30
+msgid ""
+"Attachments to posts are stored in the database as posts, this means "
+"they're accessible under their own URL's if you do not redirect them, "
+"enabling this will redirect them to the post they were attached to."
+msgstr ""
+
+#: admin/pages/permalinks.php:32
+msgid "Remove the <code>?replytocom</code> variables."
+msgstr ""
+
+#: admin/pages/permalinks.php:33
+msgid ""
+"This prevents threaded replies from working when the user has JavaScript "
+"disabled, but on a large site can mean a <em>huge</em> improvement in crawl "
+"efficiency for search engines when you have a lot of comments."
+msgstr ""
+
+#: admin/pages/permalinks.php:35
+msgid "Redirect ugly URL's to clean permalinks. (Not recommended in many cases!)"
+msgstr ""
+
+#: admin/pages/permalinks.php:36
+msgid ""
+"People make mistakes in their links towards you sometimes, or unwanted "
+"parameters are added to the end of your URLs, this allows you to redirect "
+"them all away. Please note that while this is a feature that is actively "
+"maintained, it is known to break several plugins, and should for that "
+"reason be the first feature you disable when you encounter issues after "
+"installing this plugin."
+msgstr ""
+
+#: admin/pages/permalinks.php:41
+msgid "Force Transport"
+msgstr ""
+
+#: admin/pages/permalinks.php:41
+msgid "Leave default"
+msgstr ""
+
+#: admin/pages/permalinks.php:41
+msgid "Force http"
+msgstr ""
+
+#: admin/pages/permalinks.php:41
+msgid "Force https"
+msgstr ""
+
+#: admin/pages/permalinks.php:42
+msgid "Force the canonical to either http or https, when your blog runs under both."
+msgstr ""
+
+#: admin/pages/permalinks.php:44
+msgid "Canonical Settings"
+msgstr ""
+
+#: admin/pages/permalinks.php:47
+msgid "Prevent cleaning out Google Site Search URL's."
+msgstr ""
+
+#: admin/pages/permalinks.php:48
+msgid ""
+"Google Site Search URL's look weird, and ugly, but if you're using Google "
+"Site Search, you probably do not want them cleaned out."
+msgstr ""
+
+#: admin/pages/permalinks.php:50
+msgid "Prevent cleaning out Google Analytics Campaign & Google AdWords Parameters."
+msgstr ""
+
+#: admin/pages/permalinks.php:51
+msgid ""
+"If you use Google Analytics campaign parameters starting with "
+"<code>?utm_</code>, check this box. You shouldn't use these btw, you should "
+"instead use the hash tagged version instead."
+msgstr ""
+
+#: admin/pages/permalinks.php:53
+msgid "Other variables not to clean"
+msgstr ""
+
+#: admin/pages/permalinks.php:54
+msgid ""
+"You might have extra variables you want to prevent from cleaning out, add "
+"them here, comma separated."
+msgstr ""
+
+#: admin/pages/permalinks.php:56
+msgid "Clean Permalink Settings"
+msgstr ""
+
+#: admin/pages/rss.php:17
+msgid ""
+"This feature is used to automatically add content to your RSS, more "
+"specifically, it's meant to add links back to your blog and your blog "
+"posts, so dumb scrapers will automatically add these links too, helping "
+"search engines identify you as the original source of the content."
+msgstr ""
+
+#: admin/pages/rss.php:22
+msgid "Content to put before each post in the feed"
+msgstr ""
+
+#: admin/pages/rss.php:23 admin/pages/rss.php:29
+msgid "(HTML allowed)"
+msgstr ""
+
+#: admin/pages/rss.php:28
+msgid "Content to put after each post"
+msgstr ""
+
+#: admin/pages/rss.php:33
+msgid "Explanation"
+msgstr ""
+
+#: admin/pages/rss.php:34
+msgid ""
+"You can use the following variables within the content, they will be "
+"replaced by the value on the right."
+msgstr ""
+
+#: admin/pages/rss.php:36
+msgid ""
+"A link to the archive for the post author, with the authors name as anchor "
+"text."
+msgstr ""
+
+#: admin/pages/rss.php:37
+msgid "A link to the post, with the title as anchor text."
+msgstr ""
+
+#: admin/pages/rss.php:38
+msgid "A link to your site, with your site's name as anchor text."
+msgstr ""
+
+#: admin/pages/rss.php:39
+msgid "A link to your site, with your site's name and description as anchor text."
+msgstr ""
+
+#: admin/pages/rss.php:42
+msgid "Content of your RSS Feed"
+msgstr ""
+
+#: admin/pages/social.php:15
+msgid "Facebook Insights and Admins"
+msgstr ""
+
+#: admin/pages/social.php:16
+msgid ""
+"To be able to access your %sFacebook Insights%s for your site, you need to "
+"specify a Facebook Admin. This can be a user, but if you have an app for "
+"your site, you could use that. For most people a user will be \"good "
+"enough\" though."
+msgstr ""
+
+#: admin/pages/social.php:33
+msgid "Successfully removed admin %s"
+msgstr ""
+
+#: admin/pages/social.php:53
+msgid "Successfully cleared all Facebook Data"
+msgstr ""
+
+#: admin/pages/social.php:69
+msgid "Successfully added %s as a Facebook Admin!"
+msgstr ""
+
+#: admin/pages/social.php:72
+msgid "%s already exists as a Facebook Admin."
+msgstr ""
+
+#: admin/pages/social.php:79
+msgid "Do not use a Facebook App as Admin"
+msgstr ""
+
+#: admin/pages/social.php:84
+msgid ""
+"Successfully retrieved your apps from Facebook, now select an app to use as "
+"admin."
+msgstr ""
+
+#: admin/pages/social.php:87
+msgid "Failed to retrieve your apps from Facebook."
+msgstr ""
+
+#: admin/pages/social.php:110
+msgid "Use a Facebook App as Admin"
+msgstr ""
+
+#: admin/pages/social.php:114
+msgid "Select an app to use as Facebook admin:"
+msgstr ""
+
+#: admin/pages/social.php:125
+msgid "Update Facebook Apps"
+msgstr ""
+
+#: admin/pages/social.php:129
+msgid "Add Facebook Admin"
+msgstr ""
+
+#: admin/pages/social.php:133
+msgid "Currently connected Facebook admins:"
+msgstr ""
+
+#: admin/pages/social.php:144
+msgid "Add Another Facebook Admin"
+msgstr ""
+
+#: admin/pages/social.php:160
+msgid "Clear all Facebook Data"
+msgstr ""
+
+#: admin/pages/social.php:180
+msgid "Add Open Graph meta data"
+msgstr ""
+
+#: admin/pages/social.php:182
+msgid ""
+"Add Open Graph meta data to your site's <code><head></code> section. "
+"You can specify some of the ID's that are sometimes needed below:"
+msgstr ""
+
+#: admin/pages/social.php:184
+msgid "Facebook Page URL"
+msgstr ""
+
+#: admin/pages/social.php:186
+msgid "Frontpage settings"
+msgstr ""
+
+#: admin/pages/social.php:187 admin/pages/social.php:199
+msgid "Image URL"
+msgstr ""
+
+#: admin/pages/social.php:194
+msgid "Copy home meta description"
+msgstr ""
+
+#: admin/pages/social.php:196
+msgid ""
+"These are the title, description and image used in the Open Graph meta tags "
+"on the front page of your site."
+msgstr ""
+
+#: admin/pages/social.php:198
+msgid "Default settings"
+msgstr ""
+
+#: admin/pages/social.php:200
+msgid ""
+"This image is used if the post/page being shared does not contain any "
+"images."
+msgstr ""
+
+#: admin/pages/social.php:208
+msgid ""
+"Note that for the Twitter Cards to work, you have to check the box below "
+"and then validate your Twitter Cards through the %1$sTwitter Card "
+"Validator%2$s."
+msgstr ""
+
+#: admin/pages/social.php:211
+msgid "Add Twitter card meta data"
+msgstr ""
+
+#: admin/pages/social.php:213
+msgid "Add Twitter card meta data to your site's <code><head></code> section."
+msgstr ""
+
+#: admin/pages/social.php:214
+msgid "Site Twitter Username"
+msgstr ""
+
+#: admin/pages/social.php:215
+msgid "The default card type to use"
+msgstr ""
+
+#: admin/pages/social.php:223
+msgid "Add Google+ specific post meta data"
+msgstr ""
+
+#: admin/pages/social.php:226
+msgid "Google Publisher Page"
+msgstr ""
+
+#: admin/pages/social.php:227
+msgid ""
+"If you have a Google+ page for your business, add that URL here and link it "
+"on your Google+ page's about page."
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:29
+msgid "Check this box to enable XML sitemap functionality."
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:33
+msgid "As you're on NGINX, you'll need the following rewrites:"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:40
+msgid "You can find your XML Sitemap here: %sXML Sitemap%s"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:40
+msgid ""
+"You do <strong>not</strong> need to generate the XML sitemap, nor will it "
+"take up time to generate after publishing a post."
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:42
+msgid "Save your settings to activate XML Sitemaps."
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:46
+msgid "User sitemap"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:47
+msgid "Disable author/user sitemap"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:50
+msgid "Exclude users without posts"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:51
+msgid "Disable all users with zero posts"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:55
+msgid "Exclude userroles"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:56
+msgid ""
+"Please check the appropriate box below if there's a user role that you do "
+"<strong>NOT</strong> want to include in your sitemap:"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:64
+msgid "General settings"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:65
+msgid ""
+"After content publication, the plugin automatically pings Google and Bing, "
+"do you need it to ping other search engines too? If so, check the box:"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:66
+msgid "Ping Yahoo!"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:67
+msgid "Ping Ask.com"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:72
+msgid "Exclude post types"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:73
+msgid ""
+"Please check the appropriate box below if there's a post type that you do "
+"<strong>NOT</strong> want to include in your sitemap:"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:82
+msgid "Exclude taxonomies"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:83
+msgid ""
+"Please check the appropriate box below if there's a taxonomy that you do "
+"<strong>NOT</strong> want to include in your sitemap:"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:93
+msgid "Entries per page"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:94
+msgid ""
+"Please enter the maximum number of entries per sitemap page (defaults to "
+"%s, you might want to lower this to prevent memory issues on some installs):"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:95
+msgid "Max entries per sitemap page"
+msgstr ""
+
+#: admin/pages/xml-sitemaps.php:100
+msgid "XML Sitemap"
+msgstr ""
+
+#: frontend/class-frontend.php:368
+msgid "Search for \"%s\""
+msgstr ""
+
+#: frontend/class-frontend.php:420 frontend/class-frontend.php:422
+#: frontend/class-frontend.php:424 frontend/class-frontend.php:437
+#: frontend/class-frontend.php:439 frontend/class-frontend.php:441
+#: inc/class-wpseo-options.php:1588
+msgid "%s Archives"
+msgstr ""
+
+#: frontend/class-frontend.php:426 frontend/class-frontend.php:443
+msgid "Archives"
+msgstr ""
+
+#: frontend/class-frontend.php:452
+msgid "Page not found"
+msgstr ""
+
+#: frontend/class-frontend.php:1185
+msgid ""
+"Admin only notice: this page doesn't show a meta description because it "
+"doesn't have one, either write it for this page specifically or go into the "
+"SEO -> Titles menu and set up a template."
+msgstr ""
+
+#: inc/class-wpseo-options.php:1065
+msgid "%s does not seem to be a valid %s verification string. Please correct."
+msgstr ""
+
+#: inc/class-wpseo-options.php:1304
+msgid ""
+"Invalid transport mode set for the canonical settings. Value reset to "
+"default."
+msgstr ""
+
+#: inc/class-wpseo-options.php:1538
+msgid "%s, Author at %s"
+msgstr ""
+
+#: inc/class-wpseo-options.php:1539
+msgid "You searched for %s"
+msgstr ""
+
+#: inc/class-wpseo-options.php:1540
+msgid "Page Not Found"
+msgstr ""
+
+#: inc/class-wpseo-options.php:1577
+msgid "%s Archive"
+msgstr ""
+
+#: inc/class-wpseo-options.php:1979
+msgid "The post %s appeared first on %s."
+msgstr ""
+
+#: inc/class-wpseo-options.php:2071
+msgid "Error 404: Page not found"
+msgstr ""
+
+#: inc/class-wpseo-options.php:2072
+msgid "Archives for"
+msgstr ""
+
+#: inc/class-wpseo-options.php:2074
+msgid "You searched for"
+msgstr ""
+
+#: inc/class-wpseo-options.php:2163
+msgid "Please select a valid taxonomy for post type \"%s\""
+msgstr ""
+
+#: inc/class-wpseo-options.php:2196
+msgid "Please select a valid post type for taxonomy \"%s\""
+msgstr ""
+
+#: inc/class-wpseo-options.php:2503
+msgid ""
+"\"Max entries per sitemap page\" should be a positive number, which %s is "
+"not. Please correct."
+msgstr ""
+
+#: inc/class-wpseo-options.php:2666
+msgid "Summary"
+msgstr ""
+
+#: inc/class-wpseo-options.php:2667
+msgid "Summary with large image"
+msgstr ""
+
+#: inc/class-wpseo-options.php:2791
+msgid "%s does not seem to be a valid url. Please correct."
+msgstr ""
+
+#: inc/class-wpseo-options.php:2827
+msgid "%s does not seem to be a valid Twitter user-id. Please correct."
+msgstr ""
+
+#: inc/class-wpseo-options.php:3072
+msgid ""
+"%s is not a valid choice for who should be allowed access to the WP SEO "
+"settings. Value reset to the default."
+msgstr ""
+
+#: inc/class-wpseo-options.php:3091 inc/class-wpseo-options.php:3100
+msgid ""
+"The default blog setting must be the numeric blog id of the blog you want "
+"to use as default."
+msgstr ""
+
+#: inc/class-wpseo-options.php:3091
+msgid ""
+"This must be an existing blog. Blog %s does not exist or has been marked as "
+"deleted."
+msgstr ""
+
+#: inc/class-wpseo-options.php:3100
+msgid "No numeric value was received."
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:108
+msgid ""
+"A replacement variable can only contain alphanumeric characters, an "
+"underscore or a dash. Try renaming your variable."
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:110
+msgid ""
+"A replacement variable can not start with \"%%cf_\" or \"%%ct_\" as these "
+"are reserved for the WPSEO standard variable variables for custom fields "
+"and custom taxonomies. Try making your variable name unique."
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:117
+msgid ""
+"A replacement variable with the same name has already been registered. Try "
+"making your variable name more unique."
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:120
+msgid ""
+"You cannot overrule a WPSEO standard variable replacement by registering a "
+"variable with the same name. Use the \"wpseo_replacements\" filter instead "
+"to adjust the replacement value."
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:886
+msgid "Page %d of %d"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1069
+msgid "Replaced with the date of the post/page"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1070
+msgid "Replaced with the title of the post/page"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1071
+msgid "Replaced with the title of the parent page of the current page"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1072
+msgid "The site's name"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1073
+msgid "The site's tag line / description"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1074
+msgid "Replaced with the post/page excerpt (or auto-generated if it does not exist)"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1075
+msgid "Replaced with the post/page excerpt (without auto-generation)"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1076
+msgid "Replaced with the current tag/tags"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1077
+msgid "Replaced with the post categories (comma separated)"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1078
+msgid "Replaced with the category description"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1079
+msgid "Replaced with the tag description"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1080
+msgid "Replaced with the term description"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1081
+msgid "Replaced with the term name"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1082
+msgid "Replaced with the current search phrase"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1083
+msgid "The separator defined in your theme's <code>wp_title()</code> tag."
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1092
+msgid "Replaced with the post type single label"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1093
+msgid "Replaced with the post type plural label"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1094
+msgid "Replaced with the post/page modified time"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1095
+msgid "Replaced with the post/page ID"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1096
+msgid "Replaced with the post/page author's 'nicename'"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1097
+msgid "Replaced with the post/page author's 'Biographical Info'"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1098
+msgid "Replaced with the post/page author's userid"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1099
+msgid "Replaced with the current time"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1100
+msgid "Replaced with the current date"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1101
+msgid "Replaced with the current day"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1102
+msgid "Replaced with the current month"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1103
+msgid "Replaced with the current year"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1104
+msgid "Replaced with the current page number with context (i.e. page 2 of 4)"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1105
+msgid "Replaced with the current page total"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1106
+msgid "Replaced with the current page number"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1107
+msgid "Attachment caption"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1108
+msgid "Replaced with the posts focus keyword"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1109
+msgid "Replaced with the slug which caused the 404"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1110
+msgid "Replaced with a posts custom field value"
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1111
+msgid "Replaced with a posts custom taxonomies, comma separated."
+msgstr ""
+
+#: inc/class-wpseo-replace-vars.php:1112
+msgid "Replaced with a custom taxonomies description"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:226
+msgid "N/A"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:231
+msgid "Poor"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:242
+msgid "Good"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:246
+msgid "Bad"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:329
+msgid "Keyword Research"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:335
+msgid "AdWords External"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:342
+msgid "Google Insights"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:349
+msgid "SEO Book"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:358
+msgid "Analyze this page"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:366
+msgid "Check Inlinks (OSE)"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:373
+msgid "Check Keyword Density"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:380
+msgid "Check Google Cache"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:387
+msgid "Check Headers"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:394
+msgid "Check Rich Snippets"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:401
+msgid "Facebook Debugger"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:425
+msgid "SEO Settings"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:616
+msgid ""
+"The plugin All-In-One-SEO has been detected. Do you want to %simport its "
+"settings%s."
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:625
+msgid "All-In-One-SEO has been deactivated"
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:634
+msgid ""
+"The plugin Robots-Meta has been detected. Do you want to %simport its "
+"settings%s."
+msgstr ""
+
+#: inc/wpseo-non-ajax-functions.php:643
+msgid "Robots-Meta has been deactivated"
+msgstr ""
+
+#: wp-seo-main.php:420
+msgid ""
+"The Standard PHP Library (SPL) extension seem to be unavailable. Please ask "
+"your web host to enable it."
+msgstr ""
+
+#. Plugin Name of the plugin/theme
+msgid "WordPress SEO"
+msgstr ""
+
+#. Plugin URI of the plugin/theme
+msgid ""
+"https://yoast.com/wordpress/plugins/seo/#utm_source=wpadmin&utm_medium="
+"plugin&utm_campaign=wpseoplugin"
+msgstr ""
+
+#. Description of the plugin/theme
+msgid ""
+"The first true all-in-one SEO solution for WordPress, including on-page "
+"content analysis, XML sitemaps and much more."
+msgstr ""
+
+#. Author of the plugin/theme
+msgid "Team Yoast"
+msgstr ""
+
+#. Author URI of the plugin/theme
+msgid "https://yoast.com/"
+msgstr ""
+
+#: admin/class-bulk-editor-list-table.php:231
+msgctxt "posts"
+msgid "All <span class=\"count\">(%s)</span>"
+msgid_plural "All <span class=\"count\">(%s)</span>"
+msgstr[0] ""
+msgstr[1] ""
+
+#: admin/class-bulk-editor-list-table.php:271
+msgctxt "posts"
+msgid "Trash <span class=\"count\">(%s)</span>"
+msgid_plural "Trash <span class=\"count\">(%s)</span>"
+msgstr[0] ""
+msgstr[1] ""
\ No newline at end of file
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
--- /dev/null
+=== WordPress SEO by Yoast ===
+Contributors: joostdevalk
+Donate link: https://yoast.com/
+License: GPLv3
+License URI: http://www.gnu.org/licenses/gpl.html
+Tags: seo, SEO, Yoast SEO, google, meta, meta description, search engine optimization, xml sitemap, xml sitemaps, google sitemap, sitemap, sitemaps, robots meta, rss, rss footer, yahoo, bing, news sitemaps, XML News Sitemaps, WordPress SEO, WordPress SEO by Yoast, yoast, multisite, canonical, nofollow, noindex, keywords, meta keywords, description, webmaster tools, google webmaster tools, seo pack
+Requires at least: 3.8
+Tested up to: 4.0
+Stable tag: 1.6.1
+
+Improve your WordPress SEO: Write better content and have a fully optimized WordPress site using Yoast's WordPress SEO plugin.
+
+== Description ==
+
+WordPress out of the box is already technically quite a good platform for SEO, this was true when I wrote my original [WordPress SEO](https://yoast.com/articles/wordpress-seo/) article in 2008 (and updated every few months) and it's still true today, but that doesn't mean you can't improve it further! This plugin is written from the ground up by Joost de Valk and his team at [Yoast](https://yoast.com/) to improve your site's SEO on *all* needed aspects. While this [WordPress SEO plugin](https://yoast.com/wordpress/plugins/seo/) goes the extra mile to take care of all the technical optimization, more on that below, it first and foremost helps you write better content. WordPress SEO forces you to choose a focus keyword when you're writing your articles, and then makes sure you use that focus keyword everywhere.
+
+> <strong>Premium Support</strong><br>
+> The Yoast team does not provide support for the WordPress SEO plugin on the WordPress.org forums. One on one email support is available to people who bought the [Premium WordPress SEO plugin](https://yoast.com/wordpress/plugins/seo-premium/) only.
+> Note that the Premium SEO plugin has several extra features too so it might be well worth your investment!
+>
+> You should also check out the [Local SEO](https://yoast.com/wordpress/plugins/local-seo/), [News SEO](https://yoast.com/wordpress/plugins/news-seo/) and [Video SEO](https://yoast.com/wordpress/plugins/video-seo/) extensions to WordPress SEO, these of course come with support too.
+
+> <strong>Bug Reports</strong><br>
+> Bug reports for WordPress SEO are [welcomed on GitHub](https://github.com/Yoast/wordpress-seo). Please note GitHub is _not_ a support forum and issues that aren't properly qualified as bugs will be closed.
+
+= Write better content with WordPress SEO =
+Using the snippet preview you can see a rendering of what your post or page will look like in the search results, whether your title is too long or too short and your meta description makes sense in the context of a search result. This way the plugin will help you not only increase rankings but also increase the click through for organic search results.
+
+= Page Analysis =
+The WordPress SEO plugins [Page Analysis](https://yoast.com/content-seo-wordpress-linkdex/) functionality checks simple things you're bound to forget. It checks, for instance, if you have images in your post and whether they have an alt tag containing the focus keyword for that post. It also checks whether your posts are long enough, if you've written a meta description and if that meta description contains your focus keyword, if you've used any subheadings within your post, etc. etc.
+
+The plugin also allows you to write meta titles and descriptions for all your category, tag and custom taxonomy archives, giving you the option to further optimize those pages.
+
+Combined, this plugin makes sure that your content is the type of content search engines will love!
+
+= Technical WordPress Search Engine Optimization =
+While out of the box WordPress is pretty good for SEO, it needs some tweaks here and there. This WordPress SEO plugin guides you through some of the settings needed, for instance by reminding you to enable pretty permalinks. But it also goes beyond that, by automatically optimizing and inserting the meta tags and link elements that Google and other search engines like so much:
+
+= Meta & Link Elements =
+With the WordPress SEO plugin you can control which pages Google shows in its search results and which pages it doesn't show. By default, it will tell search engines to index all of your pages, including category and tag archives, but only show the first pages in the search results. It's not very useful for a user to end up on the third page of your "personal" category, right?
+
+WordPress itself only shows canonical link elements on single pages, WordPress SEO makes it output canonical link elements everywhere. Google has recently announced they would also use `rel="next"` and `rel="prev"` link elements in the `head` section of your paginated archives, this plugin adds those automatically, see [this post](https://yoast.com/rel-next-prev-paginated-archives/ title="rel=next & rel=prev for paginated archives") for more info.
+
+= XML Sitemaps =
+Yoast's WordPress SEO plugin has the most advanced XML Sitemaps functionality in any WordPress plugin. Once you check the box, it automatically creates XML sitemaps and notifies Google & Bing of the sitemaps existence. These XML sitemaps include the images in your posts & pages too, so that your images may be found better in the search engines too.
+
+These XML Sitemaps will even work on large sites, because of how they're created, using one index sitemap that links to sub-sitemaps for each 1,000 posts. They will also work with custom post types and custom taxonomies automatically, while giving you the option to remove those from the XML sitemap should you wish to.
+
+Because of using [XSL stylesheets for these XML Sitemaps](https://yoast.com/xsl-stylesheet-xml-sitemap/), the XML sitemaps are easily readable for the human eye too, so you can spot things that shouldn't be in there.
+
+= RSS Optimization =
+Are you being outranked by scrapers? Instead of cursing at them, use them to your advantage! By automatically adding a link to your RSS feed pointing back to the original article, you're telling the search engine where they should be looking for the original. This way, the WordPress SEO plugin increases your own chance of ranking for your chosen keywords and gets rid of scrapers in one go!
+
+= Breadcrumbs =
+If your theme is compatible, and themes based on Genesis or by WooThemes for instance often are, you can use the built-in Breadcrumbs functionality. This allows you to create an easy navigation that is great for both users and search engines and will support the search engines in understanding the structure of your site.
+
+Making your theme compatible isn't hard either, check [these instructions](https://yoast.com/wordpress/plugins/breadcrumbs/).
+
+= Edit your .htaccess and robots.txt file =
+Using the built-in file editor you can edit your WordPress blogs .htaccess and robots.txt file, giving you direct access to the two most powerful files, from an SEO perspective, in your WordPress install.
+
+= Social Integration =
+SEO and Social Media are heavily intertwined, that's why this plugin also comes with a Facebook OpenGraph implementation and will soon also support Google+ sharing tags.
+
+= Multi-Site Compatible =
+The Yoast SEO plugin, unlike some others, is fully Multi-Site compatible. The XML Sitemaps work fine in all setups and you even have the option, in the Network settings, to copy the settings from one blog to another, or make blogs default to the settings for a specific blog.
+
+= Import & Export functionality =
+If you have multiple blogs, setting up plugins like this one on all of them might seem like a daunting task. Except that it's not, because what you can do is simple: you set up the plugin once. You then export your settings and simply import them on all your other sites. It's that simple!
+
+= Import functionality for other WordPress SEO plugins =
+If you've used All In One SEO Pack or HeadSpace2 before using this plugin, you might want to import all your old titles and descriptions. You can do that easily using the built-in import functionality. There's also import functionality for some of the older Yoast plugins like Robots Meta and RSS footer.
+
+Should you have a need to import from another SEO plugin to Yoast SEO or from a theme like Genesis or Thesis, you can use the [SEO Data Transporter](http://wordpress.org/extend/plugins/seo-data-transporter/) plugin, that'll easily convert your SEO meta data from and to a whole set of plugins like Platinum SEO, SEO Ultimate, Greg's High Performance SEO and themes like Headway, Hybrid, WooFramework, Catalyst etc.
+
+Read [this migration guide](https://yoast.com/all-in-one-seo-pack-migration/) if you still have questions about migrating from another SEO plugin to WordPress SEO.
+
+= WordPress SEO Plugin in your Language! =
+Currently a huge translation project is underway, translating WordPress SEO in as much as 24 languages. So far, the translations for French and Dutch are complete, but we still need help on a lot of other languages, so if you're good at translating, please join us at [translate.yoast.com](http://translate.yoast.com).
+
+= News SEO =
+Be sure to also check out the premium [News SEO module](https://yoast.com/wordpress/plugins/news-seo/) if you need Google News Sitemaps. It tightly integrates with WordPress SEO to give you the combined power of News Sitemaps and full Search Engine Optimization.
+
+= Further Reading =
+For more info, check out the following articles:
+
+* The [WordPress SEO Knowledgebase](http://kb.yoast.com/category/42-wordpress-seo).
+* [WordPress SEO - The definitive Guide by Yoast](https://yoast.com/articles/wordpress-seo/).
+* Once you have great SEO, you'll need the [best WordPress Hosting](https://yoast.com/articles/wordpress-hosting/).
+* The [WordPress SEO Plugin](https://yoast.com/wordpress/plugins/seo/) official homepage.
+* Other [WordPress Plugins](https://yoast.com/wordpress/plugins/) by the same author.
+* Follow Yoast on [Facebook](https://facebook.com/yoast) & [Twitter](http://twitter.com/yoast).
+
+= Tags =
+seo, SEO, Yoast SEO, google, meta, meta description, search engine optimization, xml sitemap, xml sitemaps, google sitemap, sitemap, sitemaps, robots meta, rss, rss footer, yahoo, bing, news sitemaps, XML News Sitemaps, WordPress SEO, WordPress SEO by Yoast, yoast, multisite, canonical, nofollow, noindex, keywords, meta keywords, description, webmaster tools, google webmaster tools, seo pack
+
+== Installation ==
+
+1. Upload the `wordpress-seo` folder to the `/wp-content/plugins/` directory
+1. Activate the WordPress SEO plugin through the 'Plugins' menu in WordPress
+1. Configure the plugin by going to the `SEO` menu that appears in your admin menu
+
+== Frequently Asked Questions ==
+
+You'll find the [FAQ on Yoast.com](https://yoast.com/wordpress/plugins/seo/faq/).
+
+== Screenshots ==
+
+1. The WordPress SEO plugin general meta box. You'll see this on edit post pages, for posts, pages and custom post types.
+2. Some of the sites using this WordPress SEO plugin.
+3. The WordPress SEO settings for a taxonomy.
+4. The fully configurable XML sitemap for WordPress SEO.
+5. Easily import SEO data from All In One SEO pack and HeadSpace2 SEO.
+6. Example of the Page Analysis functionality.
+7. The advanced section of the WordPress SEO meta box.
+
+== Changelog ==
+
+= 1.6.1 =
+
+* Bugfixes:
+ * Remove tags from title and description for snippet preview.
+ * Fix several notices.
+ * Improve escaping of values in the bulk editor before saving.
+
+* Enhancements:
+ * New admin icon using SVG, which uses proper color.
+ * Introduced a filter for the XML Sitemap base URL, `wpseo_sitemaps_base_url`
+ * Introduced a filter for the JSON+LD output: `wpseo_json_ld_search_output`
+
+* For developers: the [GitHub version](https://github.com/Yoast/wordpress-seo) now contains a full Grunt implementation for many actions.
+
+= 1.6 =
+
+This update removes more code than it adds, because Google stopped support for rel=author. It adds the new json+ld code for search in sitelinks though, so could have some cool results!
+
+* Bugfixes:
+ * Removed leftover code for the deleted HTML sitemap functionality.
+ * Fix [a bug](https://github.com/Yoast/wordpress-seo/pull/1520) where the wrong `$post` info would be used for the metabox, props [mgmartel](https://github.com/mgmartel).
+ * Fix the way we [replace whitespace](https://github.com/Yoast/wordpress-seo/pull/1542) to be more compatible with different encoding, props [Jrf](http://profiles.wordpress.org/jrf).
+
+* Enhancements:
+ * Implement new [sitelinks search box json+ld code](https://developers.google.com/webmasters/richsnippets/sitelinkssearch). Enabled by default, to disable use the new `disable_wpseo_json_ld_search` filter. To change the URL being put out use the `wpseo_json_ld_search_url` filter.
+ * Improved the onboarding tour to be more in line with the current status of the plugin.
+
+* Other:
+ * Removed all code to do with `rel=author` as Google has stopped that "experiment", see [this blog post](https://yoast.com/ten-blue-links/) for more info.
+
+* i18n
+ * Updated da_DK, fa_IR, fr_FR, hr, hu_HU, nl_NL, pt_BR and tr_RK
+
+= 1.5.6 =
+
+* Bugfixes:
+ * Fixed a dot without explanation on the page analysis tab.
+ * Fix save all feature bug in Bulk Editor as reported (and fixed) by [vdwijngaert](https://github.com/vdwijngaert) [here](https://github.com/Yoast/wordpress-seo/issues/1485).
+ * Fix bug where meta description based on a template wouldn't show up on author archive pages.
+ * Fix bug where shortlink HTTP header wouldn't be removed when checking the remove shortlink function as [reported here](https://github.com/Yoast/wordpress-seo/issues/1397).
+ * Fix a bug where force title setting would be reset on upgrade / update.
+ * Fix warning being thrown in breadcrumbs code.
+
+* Enhancements:
+ * Removing sitemap transients when updating the plugin, to make sure XML sitemaps always use latest code.
+ * Styling of metaboxes is more in line with WordPress core.
+ * Add new `%%user_description%%` replacement tag.
+ * Add option to remove users with zero posts from the XML sitemap.
+ * Move SEO data on term edit pages to lower on the page, to not interfere with themes.
+ * Code: use WP time constants as introduced in WP 3.5.
+
+* Other:
+ * Removing html-sitemap shortcode, it'll reappear in WordPress SEO Premium when it actually works.
+
+= 1.5.5.3 =
+Release Date: August 14th, 2014
+
+* Bugfixes:
+ * Prevent dying on edit post page for new posts / pages without focus keyword.
+ * Fix replacement of `%%excerpt%%` in snippet preview.
+
+= 1.5.5.2 =
+Release Date: August 14th, 2014
+
+* Bugfixes:
+ * Fix wrong SEO Analysis value icon, regression from 1.5.5.1
+* Enhancements:
+ * Add role specific removal from XML Author sitemap
+ * Add option to exclude user from XML Author sitemap on user profile page
+
+= 1.5.5.1 =
+Release Date: August 14th, 2014
+
+* Bugfixes:
+ * Fixed a potential error with `$canonical` not being a string after being filtered.
+ * Fixed more bugs with first paragraph keyword detection.
+ * Fixed bug in saving new opengraph title and images variables in the social settings.
+ * Fixed bug where SEO score incorrectly reported as 'Bad' when no focus keyword set, props [smerriman](https://github.com/smerriman) for finding, props [Jrf](http://profiles.wordpress.org/jrf) for the fix.
+ * Override `woo_title()` output harder than before to remove need for force rewrite with WooThemes themes.
+
+* Enhancements:
+ * Replace `%%parent_title%%` variable client side through JS.
+
+* i18n
+ * updated ar, cs_CZ, fr_FR, hr, pl_PL, pt_BR and ru_RU
+ * new .pot file based off of the 1.5.5 version
+
+= 1.5.5 =
+Release Date: August 12th, 2014
+
+* Bugfixes:
+ * WP Shortlinks weren't always removed when user did choose to remove them as reported in [issue #1397](https://github.com/Yoast/wordpress-seo/issues/1397), props [Firebird75](https://github.com/Firebird75).
+ * Fixed the way we prevent Jetpack from outputting OpenGraph tags. Props [jeherve](https://github.com/jeherve).
+ * Symlinking the plugin should now work. Props [crewstyle](https://github.com/crewstyle) and [dannyvankooten](https://github.com/dannyvankooten).
+ * Fix warnings on new site creation multisite as reported in [issue #1368](https://github.com/Yoast/wordpress-seo/issues/1368), props [jrfnl](https://github.com/jrfnl) and [jennybeaumont](https://github.com/jennybeaumont).
+ * Fixed redirect loop which occurred on multi-word search or when search query contained special characters and the 'redirect ugly URL's' option was on, as reported by [inventurblogger](https://github.com/inventurblogger) in [issue #1340](https://github.com/Yoast/wordpress-seo/issues/1340).
+ * Fixed double separators in snippet preview as reported by [GermanKiwi](https://github.com/GermanKiwi) in [issue #1321](https://github.com/Yoast/wordpress-seo/issues/1321), props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed slashes in title in snippet preview as reported by [fittedwebdesign](https://github.com/fittedwebdesign) in [issue #1333](https://github.com/Yoast/wordpress-seo/issues/1333), props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed re-introduced js compatibility issue with Soliloquy slider as reported by [ajsonnick](https://github.com/ajsonnick) in [issue #1343](https://github.com/Yoast/wordpress-seo/issues/1343), props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed a bug where we could do a query in XML Sitemaps even when there were no posts to query for.
+ * If the sitemap is empty, add the homepage URL to make sure it doesn't throw errors in GWT.
+ * Change how we set 404's for non existing sitemap files, as reported in [#1383](https://github.com/Yoast/wordpress-seo/issues/1383) props [Dinglebat](https://github.com/Dinglebat).
+ * Fix issues with conflicting expectations being plugins/theme of the user meta twitter field - url vs twitter id, props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix how the first paragraph test for the keyword is done after a solid bug report by [squelchdesign](squelchdesign).
+ * Fix how we're handling protocol relative image URLs in the XML sitemap.
+ * Fix page analysis slug test for keywords with special characters.
+ * Properly set "No score" result for posts that have no focus keyword.
+
+* Enhancements:
+ * Drastically improved performance of snippet preview rendering.
+ * Added Facebook / OpenGraph title input and Google+ title input and image upload field to Social tab.
+ * Added Facebook / OpenGraph title input for the homepage on SEO -> Social settings page.
+ * Changed Facebook / OpenGraph default image and homepage image input fields to use the media uploader.
+ * Added a new title separator feature on the Titles admin page.
+ * Merged the bulk editor pages for titles and descriptions into one menu item "bulk editor".
+ * Added `noimageindex` option to advanced meta robots options.
+ * Bulk editor rights are no longer added for contributors, only for editors and up.
+ * If an archives meta description template has `%%page` variables, show it on page 2 and onwards of archives too.
+ * Add a confirm dialog when resetting setting to default.
+ * Add sorting by publication date in bulk editor as [requested by krogsgard here](https://github.com/Yoast/wordpress-seo/issues/1269).
+
+* Other:
+ * Remove references to deprecated Video Manual plugin.
+
+= 1.5.4.2 =
+Release Date: July 16th, 2014
+
+* Bugfixes:
+ * Fixed several notices for undefined variables.
+ * Properly trim meta description to its desired size again, regression caused in 1.5.4.
+ * Fix empty last modified date for term sitemaps in sitemap index.
+ * Fix bug where `wpseo_sitemap_exclude_empty_terms` filter wouldn't work for index sitemap.
+
+* Enhancements:
+ * Improve nonce checking in bulk title & description editor.
+ * Prevent direct access to XSL file.
+ * Improve code styling to match WordPress code standard even more strictly, props [Jrf](http://profiles.wordpress.org/jrf).
+ * Add button to copy home meta description to home OpenGraph description.
+
+= 1.5.4.1 =
+Release Date: July 15th, 2014
+
+* Bugfixes:
+ * Properly minified the metabox JS file, fixing snippet preview, props [Jrf](http://profiles.wordpress.org/jrf).
+ * Format unix timestamp to string in sitemap, fixes possible fatal error in XML sitemap.
+
+= 1.5.4 =
+Release Date: July 15th, 2014
+
+* Bugfixes
+ * Refactored the variable replacement function for better and faster results and more stability. This should fix most if not all problems users where having with variables not being replaced in the title, meta description, snippet preview etc - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: `wpseo_replacements` filter was being run before all replacements were known.
+ * Fixed: `%%pt_single%%` and `%%pt_plural%%` didn't work in preview mode.
+ * Fixed: `%%page_total%%` would sometimes be one short.
+ * Fixed: `%%term404%%` would sometimes be empty while the pagename causing the 404 was known.
+ * Fixed: empty taxonomy sitemap could still be shown, while it shouldn't, as reported by [allasai](https://github.com/allasai) in [issue #1004](https://github.com/Yoast/wordpress-seo/issues/1004) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: if first result of a search is a post, the blog page was incorrectly added to the breadcrumb, as reported in [issue #1248](https://github.com/Yoast/wordpress-seo/issues/1248) by [Nikoya](https://github.com/Nikoya) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: ensure that all our options exist always, fixes rare case in which this wouldn't be so. As reported by [bonny](https://github.com/bonny) in [issue #1245](https://github.com/Yoast/wordpress-seo/issues/1245) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: Media title and meta settings could not be set when 'attachment URLs redirect to parent post' was selected which let to issues for attachments without a parent, as reported by [Firebird75](https://github.com/Firebird75) in [issue #1243](https://github.com/Yoast/wordpress-seo/issues/1243) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Improved and more consistent check for whether to show the admin 'Edit files' screen, [issue #1197](https://github.com/Yoast/wordpress-seo/issues/1197) - props [hostliketoast](https://github.com/hostliketoast) and [Jrf](http://profiles.wordpress.org/jrf).
+ * Restore robots meta box per taxonomy to its former glory, it now shows even when blog is not set to public, as reported by [Lumieredelune](https://github.com/Lumieredelune) in [issue #1158](https://github.com/Yoast/wordpress-seo/issues/1158) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: Multisite issues, as reported by [GaryJones](https://github.com/GaryJones) and [chrisfromthelc](https://github.com/chrisfromthelc) in [issue #935](https://github.com/Yoast/wordpress-seo/issues/935) - props [Jrf](http://profiles.wordpress.org/jrf).
+ - saving of settings on the multisite settings page was not working.
+ - restoring site to default settings from multisite settings page was not working.
+ - initializing new blogs with settings from a chosen default blog was not working (might still not be completely stable for WP multisite with WPSEO in must-use plugins directory, stable in all other cases).
+ - wrong option debug information shown on multisite settings page
+ * Fixed: an issue with sitemap transient caching for plugins not using paginated sitemaps (like news seo).
+ * Check if get_queried_object_id is not 0 before enqueueing wp_enqueue_media.
+ * Set rssafter to empty string on test_embed_rss() test.
+ * Fixed: Bing URL - props [GodThor](https://github.com/GodThor).
+ * Prevent from loading if WP is installing - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: Incorrect timezone in the root sitemap.
+ * Fixed: Multiselect fields are now properly saved in wpseo meta boxes.
+ * Force canonical links to be absolute, relative is NOT an option.
+ * Fixed: Breadcrumb on search pages.
+ * Added CDATA in sitemap image captions and titles.
+ * Various sitemap fixes and improvements - props [Rarst] (https://github.com/Rarst).
+
+* Enhancements
+ * Heavily reduce query load for XML sitemaps by caching XML sitemaps in transients.
+ * New `wpseo_register_extra_replacements` action hook which lets plugin/theme builders add new `%%...%%` replacement variables - including relevant help texts -. See [function documentation](https://github.com/Yoast/wordpress-seo/blob/master/inc/wpseo-functions.php) for an example of how to use this new functionality.
+ * If the final string - after replacement - would contain two separators with nothing between them, this extra separator will be removed.
+ * All remaining not replaced replacement vars are now stripped from the strings (without breaking the snippet preview).
+ * New filter `wpseo_replacements_filter_sep` which can be used to change the seperator character passed by the theme.
+ * When using the 'Reset default settings' button on a blog in a network while another blog has been chosen to be used as a basis for the settings for all new blogs, the reset will respect that setting and reset the blog to the settings from the chosen blog.
+ * For small networks ( < 100 sites ), the network page user interface has been improved, by offering drop-down lists of the blogs for blog selection fields. For larger networks, the interface remains the same.
+ * Added an action to allow adding content to the Post Type tab on the meta admin page.
+ * Removing the extra blog name added to the title by woo_title().
+ * More optimization improvements to snippet preview.
+ * Add filter to allow other plugins to interact with our metaboxes outside of the standard pages - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Replace variables through an AJAX call, which makes them work in the post editor too and allows for more variables to be replaced in the title.
+ * Added priority filters for XML sitemaps.
+
+* Other enhancements
+ * Security improvement: As the .htaccess / robots.txt files are site-wide files, on a multi-site WP installation they will no longer be available for editing to individual site owners. For super-admins, the 'SEO -> Edit Files' admin page will now be accessible through the Network Admin.
+ * We've added server specific info to our tracking class. Most notably, we're tracking whether a number of PHP extensions are enabled for our users now.
+
+= 1.5.3.3 =
+Release Date: June 2nd, 2014
+
+* Enhancements
+ * We've added some options and some host specific info to our tracking class. Most notably, we're tracking the PHP version for our users now, so we can see whether we, at some point, might drop PHP 5.2 support before WordPress does.
+ * Auto-deactivate plugin in the rare case that the SPL (Standard PHP Library) extension is not available.
+ * Switch from inline `xmlns` to inline use of the `prefix` attribute for breadcrumbs as that makes validation work. Fixes [Issue 1186]((https://github.com/Yoast/wordpress-seo/issues/1186).
+
+* Bugfixes
+ * Check whether snippet preview is shown on page before hiding / showing errors, deducted from [#1178](https://github.com/Yoast/wordpress-seo/issues/1178)
+ * Fixed incorrect sitemap last modified date as reported in [issue 1136](https://github.com/Yoast/wordpress-seo/issues/1136) - props [rscs](https://github.com/rscs).
+ * Specify post ID when using `wp_enqueue_media()` to set up correctly for the post being edited. [Pull #1165](https://github.com/Yoast/wordpress-seo/pull/1165), props [benhuson](https://github.com/benhuson).
+ * Fixed unreachable filter `wpseo_sitemap_[post_type]_content` as reported in [pull #1163](https://github.com/Yoast/wordpress-seo/pull/1163), also fixes unreachable filter `wpseo_sitemap_author_content`. Props [jakub-klapka](https://github.com/jakub-klapka).
+ * Fixed PHP notice as reported by [maxiwheat](https://github.com/maxiwheat) in [issue #1160](https://github.com/Yoast/wordpress-seo/issues/1160).
+ * Backed out pagination overflow redirect as it's causing too many issues.
+
+* i18n
+ * Make sure extensions menu is fully i18n compatible.
+
+= 1.5.3.2 =
+Release Date: May 16th, 2014
+
+* Bugfixes
+ * Backing out earlier change, as this breaks the snippet preview.
+
+* Enhancement
+ * Reintroduced the 'Strip the category base (usually /category/) from the category URL.' option.
+
+= 1.5.3.1 =
+Release Date: May 15th, 2014
+
+* Bugfixes
+ * Fix regression issue - non-replacement of %%name%% variable as reported in [issue #1104](https://github.com/Yoast/wordpress-seo/issues/1104) by [firstinflight](https://github.com/firstinflight) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed an issue where %%category%% was not replaced on certain pages.
+ * Added support for %%tag%% even if the ID is empty.
+ * All remaining not replaced title vars are now stripped from the title.
+ * Added a fallback to post_date in the sitemap 'mod' property for when a post is lacking the post_date_gmt value.
+
+= 1.5.3 =
+
+* Bugfixes
+ * Don't ping search engines if the blog is set to 'discourage search engines from indexing this site' - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix error in sitemap_index.xml if post type does not contain any posts as reported by [sebastiaandegeus](https://github.com/sebastiaandegeus).
+ * Use the correct HTTP protocol for responses - props [Fab1en](https://github.com/Fab1en).
+ * Better OG locale handling - props [maiis](https://github.com/maiis).
+ * Fixed: 'breadcrumb_last' class was missing on homepage, as reported by [uprise10](https://github.com/uprise10) in [issue #1045](https://github.com/Yoast/wordpress-seo/issues/1045) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix empty post id notice, [issue #1080](https://github.com/Yoast/wordpress-seo/issues/1080) as reported by [sosada](https://github.com/sosada).
+ * Localize dates where appropriate as suggested by [allankronmark](https://github.com/allankronmark) in [issue #1073](https://github.com/Yoast/wordpress-seo/issues/1073).
+ * Fix for escaping str literals in JS regexes - props [MarventusWP](https://github.com/MarventusWP).
+
+* Enhancement
+ * Redirect paginated archive pages with a pagination number that doesn't exist to the first page of that archive.
+ * Update score circle icon to look great on HiDPI displays, as well as fitting better with WordPress 3.8+ design - props [paulwilde](https://github.com/paulwilde).
+ * Only show article publication time for posts, not for pages or other post types, introduce a new filter to _do_ allow them when needed.
+ * Load of improvements to banners and licenses page.
+ * Update snippet preview to use latest Google design changes - props [paulwilde](https://github.com/paulwilde).
+
+= 1.5.2.8 =
+
+* Bugfixes
+ * Added some missing textdomains.
+ * Fixed a license manager request bug.
+ * Work-around for fatal error caused by other plugins doing front-end post updates without loading all the required WP files, such as the WP Google Forms plugin - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed incorrect link to Issues in CONTRIBUTING.md - props [GaryJones](https://github.com/GaryJones).
+ * Fixed a fatal error caused by not checking if Google Suggest request reponse is valid - props [jeremyfelt](https://github.com/jeremyfelt).
+ * Fixed a screen option bug in bulk edit options - props [designerken](https://github.com/designerken).
+ * Fixed warnings on edit files section - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed a warning when post_type is an array - props [unr](https://github.com/unr).
+
+* i18n
+ * Updated el_GR, hu_HU, nl_NL and pl_PL
+
+= 1.5.2.7 =
+
+* Bugfixes
+ * Fixed a WordPress Network license bug.
+
+* i18n
+ * Updated el_GR, fa_IR, hu, it_IT, pt_PT, ru_RU, tr_TK and zh_CN
+ * Added Malay
+
+= 1.5.2.6 =
+
+* Bugfixes
+ * Fixed Open Graph Facebook Debubber Tags/Categories Issue, tags/categories are now grouped into one metatag - props [lgrandicelli](https://github.com/lgrandicelli).
+ * Fixed: %%cf_<custom-field-name>%% and %%parent_title%% not being resolved in the preview snippet as reported by [Glark](https://github.com/Glark) in [issue #916](https://github.com/Yoast/wordpress-seo/issues/916) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Options are no longer deleted on plugin uninstall.
+ * Fixed a bug that caused the 'Plugins activated' message to be removed by the robots error message - props [andyexeter](https://github.com/andyexeter).
+ * Fix white screen/blog down issues caused by some webhosts actively disabling the PHP ctype extension - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixes Metabox Social tab media uploader not working on custom post types which don't use media as reported by [Drethic](https://github.com/Drethic) in [issue #911](https://github.com/Yoast/wordpress-seo/issues/911) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed vars not being replaced in OG description tag.
+
+* Enhancement
+ * Fix PHP warnings when post_type is an array.
+
+= 1.5.2.5 =
+
+* Bugfixes
+ * Fixed: Premium support link was being added to all plugins, not just ours ;-)
+ * Only show the breadcrumbs-blog-remove option if user uses page_for_posts as it's not applicable otherwise and can cause confusion.
+ * Clean up url query vars after use in our settings page to avoid actions being executed twice - props [Jrf](http://profiles.wordpress.org/jrf).
+
+= 1.5.2.4 =
+
+* Bugfixes
+ * Changed 'wpseo_frontend_head_init' hook to 'template_redirect' to prevent incorrect canonical redirect.
+ * Improved upgrade routine for breadcrumbs maintax/pt option as reported by [benfreke](https://github.com/benfreke) in [issue #849](https://github.com/Yoast/wordpress-seo/issues/849) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed a bug where the banners overlapped WordPress notices/errors.
+ * Fixed: Slashes in Taxonomy text inputs as reported by [chuckreynolds](https://github.com/chuckreynolds) in [issue #868](https://github.com/Yoast/wordpress-seo/issues/868) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Increased priority (decreased priority int) on the template_redirect for the sitemap redirect hook.
+ * Fixed: `current_user_can` was being called too early as reported by [satrya](https://github.com/satrya) in [issue #881](https://github.com/Yoast/wordpress-seo/issues/881) - props [Jrf](http://profiles.wordpress.org/jrf).
+
+* Enhancement
+ * Enhanced validation of webmaster verification keys to prevent invalidating incorrect input which does contain a key as reported by [TheZoker](https://github.com/TheZoker) in [issue #864](https://github.com/Yoast/wordpress-seo/issues/864) - props [Jrf](http://profiles.wordpress.org/jrf).
+
+= 1.5.2.3 =
+
+** Note: if you already upgraded to v1.5+, you will need to retrieve your Facebook Apps again and please also check your Google+ URL. We had some bugs with both being escaped too aggressively. Sorry about that. **
+
+* Bugfixes
+ * Added missing settings menu pages to wp admin bar.
+ * Replaced old AdWords keyword tool link.
+ * Fix wp admin bar keyword density check link
+ * Taxonomy sitemap will now also show if empty.
+ * Prevent infinite loop triggered by `sitemap_close()`, fixes [#600](https://github.com/Yoast/wordpress-seo/issues/) as reported and fixed by [pbogdan](https://github.com/pbogdan).
+ * Fixed a link count Page Analysis bug.
+ * Fixed a keyword density problem in the Page Analysis
+ * Fixed OpenGraph/GooglePlus/Twitter tags not showing in a select few themes, [issue #750](https://github.com/Yoast/wordpress-seo/issues/750) as reported by [Jovian](https://github.com/Jovian) and [wwdboer](https://github.com/wwdboer) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed Facebook Apps not being saved/ "Failed to retrieve your apps from Facebook" as reported by [kevinlisota](https://github.com/kevinlisota) in [issue #812](https://github.com/Yoast/wordpress-seo/issues/812) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed duplicate feedback messages on WPSEO -> Social pages as reported by [steverep](https://github.com/steverep) in [issue #743](https://github.com/Yoast/wordpress-seo/issues/743) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Flush our force title rewrite buffer earlier in `wp_footer` so it can be used by other plugins in `wp_footer`. Props [Gabriel Pérez Salazar](http://www.guero.net/).
+ * Start the force rewrite buffer late (at 999) in `template_redirect` instead of `get_header` because of several themes not using `get_header`, issue [#817](https://github.com/Yoast/wordpress-seo/issues/817) as reported by [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed 'Page %d of %d' / %%page%% variable not being replaced when on pages, as reported by [SGr33n](https://github.com/SGr33n) in [issue #801](https://github.com/Yoast/wordpress-seo/issues/801) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Restore robots meta box per post to its former glory, it now shows even when blog is not set to public.
+ * Fixed individual page robots settings not being respected when using a page as blog as reported by [wintersolutions](https://github.com/wintersolutions) in [issue #813](https://github.com/Yoast/wordpress-seo/issues/813) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: Too aggressive html escaping of the breadcrumbs.
+ * Fixed: Last breadcrumb wasn't always determined correctly resulting in crumbs not being linked when they should have been.
+ * Fixed: Breadcrumbs were sometimes missing separators and default texts since v1.5.0.
+ * Fixed: 404 date based breadcrumb and title creation could cause corruption of the `$post` object.
+ * Fixed: Filtering posts based on SEO score via the dropdown at the top of a post/page overview page no longer worked. Fixed. As reported by [gmuehl](https://github.com/gmuehl) in [issue #838](https://github.com/Yoast/wordpress-seo/issues/838) - props [Jrf](http://profiles.wordpress.org/jrf).
+
+* Enhancements
+ * Added filters for the change frequencies of different URLs added to the sitemap. Props to [haroldkyle](https://github.com/haroldkyle) for the idea.
+ * Added filter `wpseo_sitemap_exclude_empty_terms` to allow including empty terms in the XML sitemap.
+ * Private posts now default to noindex (even though they technically probably couldn't be indexed anyway).
+ * Show a warning message underneath a post's robots meta settings when site is set to noindex sitewide in WP core.
+ * Updated licensing class to show a notice when requests to yoast.com are blocked because of `WP_HTTP_BLOCK_EXTERNALS`.
+
+* Other
+ * Refactored the breadcrumb class - props [Jrf](http://profiles.wordpress.org/jrf).
+
+= 1.5.2.2 =
+
+* Bugfixes
+ * Fix for issue with Soliloquy image slider was not applied to minified js file.
+ * Fixed some PHP 'undefined index' notices.
+ * Fix banner images overlapping text in help tabs.
+ * Fixed meta description tag not showing for taxonomy (category/tag/etc) pages as reported in [issue #737](https://github.com/Yoast/wordpress-seo/issues/737) and [#780](https://github.com/Yoast/wordpress-seo/issues/780) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Prevent a fatal error if `wp_remote_get()` fails while testing whether the title needs to be force rewritten as reported by [homeispv](http://wordpress.org/support/profile/homeispv) - props [Jrf](http://profiles.wordpress.org/jrf).
+
+* Enhancements
+ * Added composer support - props [codekipple](https://github.com/codekipple) and [Rarst](https://github.com/Rarst).
+
+= 1.5.2.1 =
+
+* Bugfixes
+ * Fix white screen/blog down issues caused by some (bloody stupid) webhosts actively disabling the filter extension - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix for some PHP notices, [issue #747](https://github.com/Yoast/wordpress-seo/issues/747) as reported by [benfreke](https://github.com/benfreke) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed: GooglePlus vanity urls were saved without the `+` as reported by [ronimarin](https://github.com/ronimarin) in [issue #730](https://github.com/Yoast/wordpress-seo/issues/730) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix WP Admin menu items no longer clickable when on WPSEO pages as reported in [issue #733](https://github.com/Yoast/wordpress-seo/issues/733) and [#738](https://github.com/Yoast/wordpress-seo/issues/738) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix strict warning for W3TC, [issue 721](https://github.com/Yoast/wordpress-seo/issues/721).
+ * Fix RSS text strings on options page being double escaped, [issue #731](https://github.com/Yoast/wordpress-seo/issues/731) as reported by [namaserajesh](https://github.com/namaserajesh) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Avoid potential confusion over Facebook OpenGraph front page usage, [issue #570](https://github.com/Yoast/wordpress-seo/issues/570) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Potentially fix [issue 565](https://github.com/Yoast/wordpress-seo/issues/565) bbpress warning message. Thanks [inetbiz](https://github.com/inetbiz) for reporting and [tobylewis](https://github.com/tobylewis) for finding the likely cause.
+ * Filter 'wpseo_pre_analysis_post_content' output is now only loaded in DOM object if not empty. - props [mgmartel](https://github.com/mgmartel).
+ * $post_content is now unset after loading in DOM object. - props [mgmartel](https://github.com/mgmartel).
+ * Fix Alexa ID string validation, as reported by [kyasajin](https://github.com/kyasajin) and [Bubichka](https://github.com/Bubichka) in [issue 736](https://github.com/Yoast/wordpress-seo/issues/736) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix issue with Soliloquy image query, as reported by [osalcedo](https://github.com/osalcedo) and [mattisherwood](https://github.com/mattisherwood) in [issue #733](https://github.com/Yoast/wordpress-seo/issues/733) - props [Jrf](http://profiles.wordpress.org/jrf).
+
+* Enhancements
+ * Twitter metatag key is now filterable by 'wpseo_twitter_metatag_key'.
+ * Added a filter called "wpseo_replacements" in wpseo_replace_vars to allow customization of the replacements before they are applied - props [martinernst](https://github.com/martinernst).
+ * Added useful links for page analysis - props [bhubbard](https://github.com/bhubbard).
+
+* i18n Updates
+ * Updated nl_NL, id_ID, it_IT, fr_FR and de_DE
+ * Added ko
+ * Updated .pot file.
+
+= 1.5.2 =
+
+* Bugfix:
+ * If `mbstring` extension isn't loaded, fatal error was thrown.
+
+= 1.5.0 =
+
+This release contains tons and tons of bugfixes and security improvements. Credits for this release largely go to Juliette Reinders Folmer aka [Jrf](http://profiles.wordpress.org/jrf) / [jrfnl](https://github.com/jrfnl).
+
+Also a heartfelt thanks go out to the beta testers who tested all the changes. Special mentions for testers [Woyto](https://github.com/Woyto), [Bnpositive](https://github.com/Bnpositive), [Surbma](https://github.com/Surbma), [DavidCH1](https://github.com/DavidCH1), [TheITJuggler](https://github.com/TheITJuggler), [kletskater](https://github.com/kletskater) who caught a number of bugs and provided us with actionable information to fix these.
+
+This version also incorporates the [SEO Extended](http://wordpress.org/plugins/seo-extended/) plugin functionality into WP SEO with graceful thanks to [Faison](http://profiles.wordpress.org/faison/) and [Scott Offord](http://profiles.wordpress.org/scottofford/) for their great work on this plugin.
+
+**This version contains a lot of changes under the hood which will break backward compatibility, i.e. once you've upgraded, downgrading will break things.** So make sure you make a backup of your settings/database before upgrading.
+
+
+* Bugfixes
+ * Major overhaul of the way the plugin deals with options. This should fix a truck-load of bugs and provides improved security.
+ * Major overhaul of the way the plugin deals with post meta values. This should fix a truck-load of bugs and provides improved security.
+ * Major overhaul of the way the plugin deals with taxonomy meta values. This should fix a truck-load of bugs and provides improved security.
+
+ * Fixed: Renamed a number of options as they ran the risk of being overwritten by post type/taxonomy options which could get the same name. This may fix some issues where options did not seem to get saved correctly.
+
+ * Fixed: if page specific keywords were set for a static homepage, they would never be shown.
+ * Fixed: if only one FB admin was selected, the tag would not be added.
+ * Fixed: bug where taxonomies which were on an individual level set to noindex and sitemap include 'auto-detect' would still be shown in the sitemap
+ * Fixed: bug in canonical urls where an essential part of the logic was skipped for singular posts/pages
+ * Fixed: category rewrite rules could have errors for categories without parent categories.
+ * Fixed: bug in delete_sitemaps() - wrong retrieval of needed options.
+ * Fixed: HTML sitemaps would sometimes display headers without a link list.
+ * Fixed: Breadcrumbs could potentially output an empty element as part of the chain, resulting in two separators in a row.
+ * Fixed: Breadcrumbs: even when removing the prefix texts from the admin page, they would sometimes still be included.
+ * Improved fixed for possible caching issue when `title_test` option remained set, issue [#627](https://github.com/Yoast/wordpress-seo/issues/627).
+ * Fixed bug in `title_test_helper` where it would pass the wrong information to `update_option()`, related to issue [#627](https://github.com/Yoast/wordpress-seo/issues/627).
+ * Fixed: shortcodes should be removed from ogdesc.
+
+ * Fixed: Admin -> Dashboard -> Failed removal of the meta description from a theme file would still change the relevant internal option as if it had succeeded.
+ * Fixed: Admin -> Dashboard -> bug where message about files blocking the sitemap from working would not be removed when it should.
+ * Fixed: Admin -> Titles & Meta's -> Post types would show attachments even when attachment redirection to post was enabled.
+ * Fixed: Admin -> Import -> Fixed partially broken import functionality for WooThemes SEO framework
+ * Fixed: Admin -> Import -> Importing settings from file would not always work due to file/directory permission issues.
+ * Fixed: Admin -> Export -> Some values were exported in a way that they couldn't be imported properly again.
+ * Fixed: Admin -> Import/Export -> On export, the part of the admin page after export would not be loaded.
+ * Fixed: Admin -> Various -> Removed some superfluous hidden fields which could cause issues.
+ * Fixed: Admin -> Social -> The same fb user can no longer be added twice as Facebook admin.
+
+ * Admin -> Multi-site -> Added error message when user tries to restore to defaults a non-existent blog (only applies to multi-site installations).
+
+ * Bow out early from displaying the post/taxonomy metabox if the post/taxonomy is not public (no use adding meta data which will never be displayed).
+ * Prevent the SEO score filter from displaying on a post type overview page if the metabox has been hidden for the post type as suggested by [coreyworrell](https://github.com/coreyworrell) in issue [#601](https://github.com/Yoast/wordpress-seo/issues/601).
+
+ * Improved: post meta -> the keyword density calculation for non-latin, non-ideograph languages - i.e. cyrillic, hebrew etc - has been improved. Related issues [#703](https://github.com/Yoast/wordpress-seo/issues/703), [#681](https://github.com/Yoast/wordpress-seo/issues/681), [#349](https://github.com/Yoast/wordpress-seo/issues/349) and [#264](https://github.com/Yoast/wordpress-seo/issues/264). The keyword density calculation for ideograph based languages such as Japanese and Chinese will not work yet, issue [#145](https://github.com/Yoast/wordpress-seo/issues/145) remains open.
+ * Fixed: post meta -> SEO score indicator -> wpseo_translate_score() would never return score, but always the css value.
+ * Fixed: post meta -> SEO score indicator -> wpseo_translate_score() calls were passing unintended wrong parameters
+ * Fixed: post meta -> page analysis -> text analysis did not respect the blog character encoding. This may or may not solve a number of related bugs.
+ * Fixed: post meta -> often wrong meta value was shown for meta robots follow and meta robots index in post meta box so it appeared as if the chosen value was not saved correctly.
+ * Fixed: post meta -> meta robots advanced entry could have strange (invalid) values.
+ * Fixed: post meta -> since v1.4.22 importing from other plugins would import data even when the post already had WP SEO values, effectively overwritting (empty by choice) WPSEO fields.
+ * Fixed: post meta -> A few of the meta values could contain line breaks where those aren't allowed.
+
+ * Fixed: taxonomy meta -> breadcrumb title entry field would show for taxonomy even when breadcrumbs were not enabled
+ * Fixed: taxonomy meta -> bug where W3TC cache for taxonomy meta data wouldn't always be refreshed when it should and sometimes would when it shouldn't
+
+ * Fixed: some things should work better now for must-use installations.
+ * Added sanitation/improved validation to $_GET and $_POST variables if/when they are used in a manner which could cause security issues.
+ * Fixed: wrong file was loaded for the get_plugin_data() function.
+ * Fixed: several bug-sensitive code constructs. This will probably get rid of a number of hard to figure out bugs.
+ * Fixed: several html validation issues.
+ * Prevent error when theme does not support featured images, issue [#639](https://github.com/Yoast/wordpress-seo/issues/639) as reported by [kuzudecoletaje](https://github.com/kuzudecoletaje).
+
+
+* Enhancements
+ * The [SEO Extended](http://wordpress.org/plugins/seo-extended/) plugin functionality has now been integrated into WP SEO.
+ * Added ability to add Pininterest and Yandex site verification tags. You can enter this info on the WPSEO Dashboard and it will auto-generate the relevant meta tags for your webpage headers.
+ * New `[wpseo_breadcrumb]` shortcode.
+ * Post meta -> Don't show robots index/no-index choice in advanced meta box if there is a blog-wide override in place, i.e. the Settings -> Reading -> Block search engines checkbox is checked.
+ * Post meta -> Added 'Site-wide default' option to meta robots advanced field in advanced meta box.
+ * Post meta -> Added an option to decide whether to include/exclude `rel="author"` on a per post base as suggested by [GaryJones](https://github.com/GaryJones). (Added to the advanced meta box).
+ * Taxonomy meta -> Don't show robots index/no-index choice in taxonomy meta box if there is a blog-wide override in place, i.e. the Settings -> Reading -> Block search engines checkbox is checked.
+ * Admin -> If WP_DEBUG is on or if you have set the special constant WPSEO_DEBUG, a block with the currently saved options will be shown on the settings pages.
+ * Admin -> Dashboard -> Added error message for when meta description tag removal from theme file fails.
+ * Admin -> Titles & Meta -> Added option to add meta keywords to post type archives.
+ * Admin -> Social -> Facebook -> Added error messages for if communication with Facebook failed.
+ * Admin -> Import -> WPSEO settings -> Better error messages for when importing the settings fails and better clean up after itself.
+ * Adminbar -> Keyword research links now also search for the set the keyword when editing a post in the back-end.
+ * [Usability] Proper field labels for user profile form fields.
+ * The New Relic daemon (not the W3TC New Relic PHP module) realtime user monitoring will be turned off for xml/xsl files by default to prevent breaking the sitemaps as suggested by [szepeviktor](https://github.com/szepeviktor) in [issue #603](https://github.com/Yoast/wordpress-seo/issues/603)
+ * General jQuery efficiency improvements.
+ * Improved lazy loading of plugin files using autoload.
+ * Made the Google+ and Facebook post descriptions translatable by WPML.
+ * Better calculation precision for SEO score
+ * Improved 403 headers for illegal file requests as suggested by [cfoellmann](https://github.com/cfoellmann)
+ * Synchronized TextStatistics class with changes from the original, this should provide somewhat better results for non-latin languages.
+ * CSS and JS files are now minified
+ * Rewrote query logic for XML sitemaps
+ * Changed default settings for rel="author"
+ * Added option to switch to summary card with image for Twitter cards
+ * Made several changes to Open Graph logic
+ * Implemented new Yoast License framework
+ * Added possibility to create a robots.txt file directly on the server
+
+* Other:
+ * Removed some backward compatibility with WP < 3.5 as minimum requirement for WP SEO is now 3.5
+ * Removed some old (commented out) code
+ * Deprecated category rewrite functionality
+
+
+
+= 1.4.25 =
+
+* Bugfixes
+ * Do not include external URLs in XML sitemap (Issue #528) - props [tivnet](https://github.com/tivnet).
+ * Get home_url out of the sitemap loop - props [tivnet](https://github.com/tivnet).
+ * Add support for html entities - props [julienmeyer](https://github.com/julienmeyer).
+ * Fixed wrong use of `__FILE__`.
+
+* Enhancement
+ * WPSEO_FILE now has a 'defined' check.
+ * Removed unneeded `dirname` calls.
+
+* i18n
+ * Updated cs_CZ, de_DE, fr_FR & tr_TK
+
+= 1.4.24 =
+
+* Bugfixes
+ * Removed screen_icon() calls.
+ * Fixed a bug in robots meta tag on singular items.
+ * Fix double robots header, WP native settings will be respected - props [Jrf](http://profiles.wordpress.org/jrf).
+ * When post published data is newer than last modified date, use that in XML sitemap, props [mindingdata](https://github.com/mindingdata).
+ * Check if tab hash is correct after being redirected from Facebook API, props [dannyvankooten](https://github.com/dannyvankooten).
+ * Fix 404 in category rewrites when `pagination_base` was changed, props [raugfer](https://github.com/raugfer).
+ * Make the metabox tabs jQuery only work for WPSEO tabs, props [imageinabox](https://github.com/imageinabox).
+ * Sitemap shortcode sql had hard-coded table name which could easily cause the shortcode display to fail. Fixed. - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix issue with user capability authorisation check as reported by [scienceandpoetry](https://github.com/scienceandpoetry) in issue [#492](https://github.com/Yoast/wordpress-seo/issues/492) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed canonical rel links was causing an error when given an invalid taxonomy, issue [#306](https://github.com/Yoast/wordpress-seo/issues/306) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Removed add_meta_box() function duplication - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix issue "Flesch Reading Ease should only be a positive number". This also fixes the message being unclear. Thanks [eugenmihailescu](https://github.com/eugenmihailescu) for reporting - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed issue with page analysis not taking feature image into account - props [darrarski](https://github.com/darrarski).
+
+* Enhancement
+ * Shortcode now also available to ajax requests - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Added gitignores to prevent incorrect commits (Cross platform collab) - props [cfoellmann](https://github.com/cfoellmann).
+ * Adding filters to individual sitemap url entries - props [mboynes](https://github.com/mboynes).
+
+= 1.4.23 =
+
+* Bugfixes
+ * Fix for serious sitemap issue which caused all pages of a split sitemap to be the same (show the first 1000 urls) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed a bug in the WPSEO tour in WP Network installs
+ * clean_permalink 301 redirect issue when using https - props [pirategaspard](https://github.com/pirategaspard)
+
+* i18n
+ * Updated cs_CZ, fa_IR, fr_FR, hu, hu_HU, pl_PL, ru_RU & zh_CN
+
+
+= 1.4.22 =
+
+* Bugfixes
+ * Reverted change to XML sitemaps stylesheet URL as that was giving issues on multisite installs.
+ * Reverted change to XML sitemap loading as we were no longer exposing some variables that other plugins relied upon.
+ * Fix bug with author sitemap showing for everyone.
+
+* Enhancement
+ * No longer save empty meta post variables, issue [#463](https://github.com/Yoast/wordpress-seo/issues/463). Clean up of DB is coming in future release, if you want to clean your DB now, see that issue for SQL queries.
+
+= 1.4.21 =
+
+* Bugfixes
+ * Fix notice for `ICL_LANGUAGE_CODE` not being defined.
+ * Fix missing function in install by adding a require.
+
+= 1.4.20 =
+
+* Bugfixes
+ * Fixed bug where posts set to _always_ index would not end up in XML sitemap.
+ * Fix _Invalid argument supplied for foreach()_ notice for WPML as reported by [pbearne](https://github.com/pbearne) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Yoast tracking cron job will now unschedule on disallowing of tracking, on deactivation and on uninstall, inspired by [Bluebird Blvd.](http://wordpress.org/support/topic/found-active-tracking-device-after-deleting-wp-seo-months-ago) - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fix issue [#453](https://github.com/Yoast/wordpress-seo/issues/435): setting shop as homepage caused a notice and wrong title with WooCommerce.
+ * Fixed a bug [#449](https://github.com/Yoast/wordpress-seo/issues/449) where a canonical, when manually set for a category, tag or term, could get pagination added to it on paginated pages, when it shouldn't.
+ * Fixed a bug where manually set canonicals would end up in `rel="next"` and `rel="prev"` tags.
+ * Fixed a bug [#450](https://github.com/Yoast/wordpress-seo/issues/450) where noindexed pages would appear in the HTML sitemap.
+ * Fixed a bug where non-public taxonomies would appear in the HTML sitemap.
+ * Fixed quotes not working in meta title and description for terms, issue [#405](https://github.com/Yoast/wordpress-seo/issues/405).
+ * Make sure author sitemap works when they should.
+ * Fix some notices in author sitemap, issue [#402](https://github.com/Yoast/wordpress-seo/issues/402).
+ * Fix breadcrumbs being broken on empty post type archives, issue [#443](https://github.com/Yoast/wordpress-seo/issues/443).
+ * Fixed a possible caching issue when `title_test` option remained set, issue [#419](https://github.com/Yoast/wordpress-seo/issues/419).
+ * Make sure og:description is shown on homepage when it's left empty in settings, fixes [#441](https://github.com/Yoast/wordpress-seo/issues/441).
+ * Make sure there are no WPML leftovers in our title, issue [#383](https://github.com/Yoast/wordpress-seo/issues/383).
+ * Fix padding on fix it buttons with 3.8 design, issue [#400](https://github.com/Yoast/wordpress-seo/issues/400).
+ * Hide SEO columns in responsive admin ( in 3.8 admin design ), issue [#445](https://github.com/Yoast/wordpress-seo/issues/445).
+
+* Misc
+ * Switch back to MailChimp for newsletter subscribe.
+ * Default to nofollowing links in RSS feed footers.
+
+* i18n
+ * Updated es_ES, pt_BR & ru_RU
+ * Added sk_SK
+
+= 1.4.19 =
+
+* Enhancements
+ * Added the option to upload a separate image for Facebook in the Social tab.
+ * Added published time, last modified time, tags and categories to OpenGraph output, to work with Pinterests new article pin.
+ * Added a filter for post length requirements in the Analysis tab.
+ * If there is a term description, use it in the OpenGraph description for a term archive page.
+ * Applied a number of settings form best practices - props [Jrf](http://profiles.wordpress.org/jrf).
+ * File inclusion best practices applied - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Breadcrumbs for Custom Post Types now take the CPT->label instead of CPT->labels->menu_name as text parameter, as suggested by [katart17](http://wordpress.org/support/profile/katart17) and [Robbert V](http://wordpress.org/support/profile/robbert-v) - props [Jrf](http://profiles.wordpress.org/jrf).
+
+* Bugfixes
+ * Move all rewrite flushing to shutdown, so it doesn't break other plugins who add their rewrites late.
+ * Fixed the wrong naming of the L10n JS object, props [Otto](http://profiles.wordpress.org/otto42).
+ * Improved form support for UTF-8 - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Corrected faulty multisite option registration - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed appropriate use of plugins_url() to avoid breaking hooked in filters - props [Jrf](http://profiles.wordpress.org/jrf).
+ * (Temporary) fix for metabox styling for users using the MP6 plugin - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Minor fix in localization loading - props [Jrf](http://profiles.wordpress.org/jrf).
+ * Fixed [Missing argument 3 for wpseo_upgrader_process_complete](https://github.com/Yoast/wordpress-seo/issues/327) notice for WP 3.7+, thanks [vickyindo](https://github.com/vickyindo), [Wendyhihi](https://github.com/Wendihihi) and [Theressa1](https://github.com/Theressa1) for reporting - props [Jrf](http://profiles.wordpress.org/jrf).
+
+* i18n
+ * Updated ru_RU, tr_TK and Hr
+
+= 1.4.18 =
+
+* Unhooking 'shutdown' (part of the NGG fix in 1.4.16) caused caching plugins to break, fixed while preserving NGG fix.
+* These changes were pushed in later but were deemed not important enough to force an update:
+ * Updated newsletter subscription form to reflect new newsletter system.
+ * Documentation
+ * Updated readme.txt to reflect support changes.
+ * Moved old sections of changelog to external file.
+ * i18n
+ * Updated pt_PT
+
+= Earlier versions =
+
+For the changelog of earlier versions, please refer to the separate changelog.txt file.
+
+
+== Upgrade Notice ==
+
+= 1.5.0 =
+* Major overhaul of the way the plugin deals with option. Upgrade highly recommended. Please do verify your settings after the upgrade.
--- /dev/null
+<?php
+
+/**
+ * @package Main
+ */
+
+if ( ! function_exists( 'add_filter' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+/**
+ * @internal Nobody should be able to overrule the real version number as this can cause serious issues
+ * with the options, so no if ( ! defined() )
+ */
+define( 'WPSEO_VERSION', '1.6.1' );
+
+if ( ! defined( 'WPSEO_PATH' ) ) {
+ define( 'WPSEO_PATH', plugin_dir_path( WPSEO_FILE ) );
+}
+
+if ( ! defined( 'WPSEO_BASENAME' ) ) {
+ define( 'WPSEO_BASENAME', plugin_basename( WPSEO_FILE ) );
+}
+
+if ( ! defined( 'WPSEO_CSSJS_SUFFIX' ) ) {
+ define( 'WPSEO_CSSJS_SUFFIX', ( ( defined( 'SCRIPT_DEBUG' ) && true === SCRIPT_DEBUG ) ? '' : '.min' ) );
+}
+
+
+/* ***************************** CLASS AUTOLOADING *************************** */
+
+/**
+ * Auto load our class files
+ *
+ * @param string $class Class name
+ * @return void
+ */
+function wpseo_auto_load( $class ) {
+ static $classes = null;
+
+ if ( $classes === null ) {
+ $classes = array(
+ 'wpseo_admin' => WPSEO_PATH . 'admin/class-admin.php',
+ 'wpseo_bulk_title_editor_list_table' => WPSEO_PATH . 'admin/class-bulk-title-editor-list-table.php',
+ 'wpseo_bulk_description_list_table' => WPSEO_PATH . 'admin/class-bulk-description-editor-list-table.php',
+ 'wpseo_bulk_list_table' => WPSEO_PATH . 'admin/class-bulk-editor-list-table.php',
+ 'wpseo_admin_pages' => WPSEO_PATH . 'admin/class-config.php',
+ 'wpseo_metabox' => WPSEO_PATH . 'admin/class-metabox.php',
+ 'wpseo_social_admin' => WPSEO_PATH . 'admin/class-opengraph-admin.php',
+ 'wpseo_pointers' => WPSEO_PATH . 'admin/class-pointers.php',
+ 'wpseo_sitemaps_admin' => WPSEO_PATH . 'admin/class-sitemaps-admin.php',
+ 'wpseo_taxonomy' => WPSEO_PATH . 'admin/class-taxonomy.php',
+ 'yoast_tracking' => WPSEO_PATH . 'admin/class-tracking.php',
+ 'yoast_textstatistics' => WPSEO_PATH . 'admin/TextStatistics.php',
+ 'wpseo_breadcrumbs' => WPSEO_PATH . 'frontend/class-breadcrumbs.php',
+ 'wpseo_frontend' => WPSEO_PATH . 'frontend/class-frontend.php',
+ 'wpseo_opengraph' => WPSEO_PATH . 'frontend/class-opengraph.php',
+ 'wpseo_twitter' => WPSEO_PATH . 'frontend/class-twitter.php',
+ 'wpseo_googleplus' => WPSEO_PATH . 'frontend/class-googleplus.php',
+ 'wpseo_rewrite' => WPSEO_PATH . 'inc/class-rewrite.php',
+ 'wpseo_sitemaps' => WPSEO_PATH . 'inc/class-sitemaps.php',
+ 'wpseo_options' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option_wpseo' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option_permalinks' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option_titles' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option_social' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option_rss' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option_internallinks' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option_xml' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_option_ms' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_taxonomy_meta' => WPSEO_PATH . 'inc/class-wpseo-options.php',
+ 'wpseo_meta' => WPSEO_PATH . 'inc/class-wpseo-meta.php',
+ 'wpseo_replace_vars' => WPSEO_PATH . 'inc/class-wpseo-replace-vars.php',
+
+ 'yoast_license_manager' => WPSEO_PATH . 'admin/license-manager/class-license-manager.php',
+ 'yoast_plugin_license_manager' => WPSEO_PATH . 'admin/license-manager/class-plugin-license-manager.php',
+ 'yoast_product' => WPSEO_PATH . 'admin/license-manager/class-product.php',
+
+ 'yoast_notification_center' => WPSEO_PATH . 'admin/class-yoast-notification-center.php',
+ 'yoast_notification' => WPSEO_PATH . 'admin/class-yoast-notification.php',
+
+ 'wp_list_table' => ABSPATH . 'wp-admin/includes/class-wp-list-table.php',
+ 'walker_category' => ABSPATH . 'wp-includes/category-template.php',
+ 'pclzip' => ABSPATH . 'wp-admin/includes/class-pclzip.php',
+ );
+ }
+
+ $cn = strtolower( $class );
+
+ if ( isset( $classes[ $cn ] ) ) {
+ require_once( $classes[ $cn ] );
+ }
+}
+if ( function_exists( 'spl_autoload_register' ) ) {
+ spl_autoload_register( 'wpseo_auto_load' );
+}
+
+
+
+/* ***************************** PLUGIN (DE-)ACTIVATION *************************** */
+
+/**
+ * Run single site / network-wide activation of the plugin.
+ *
+ * @param bool $networkwide Whether the plugin is being activated network-wide
+ */
+function wpseo_activate( $networkwide = false ) {
+ if ( ! is_multisite() || ! $networkwide ) {
+ _wpseo_activate();
+ }
+ else {
+ /* Multi-site network activation - activate the plugin for all blogs */
+ wpseo_network_activate_deactivate( true );
+ }
+}
+
+/**
+ * Run single site / network-wide de-activation of the plugin.
+ *
+ * @param bool $networkwide Whether the plugin is being de-activated network-wide
+ */
+function wpseo_deactivate( $networkwide = false ) {
+ if ( ! is_multisite() || ! $networkwide ) {
+ _wpseo_deactivate();
+ }
+ else {
+ /* Multi-site network activation - de-activate the plugin for all blogs */
+ wpseo_network_activate_deactivate( false );
+ }
+}
+
+/**
+ * Run network-wide (de-)activation of the plugin
+ *
+ * @param bool $activate True for plugin activation, false for de-activation
+ */
+function wpseo_network_activate_deactivate( $activate = true ) {
+ global $wpdb;
+
+ $original_blog_id = get_current_blog_id(); // alternatively use: $wpdb->blogid
+ $all_blogs = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
+
+ if ( is_array( $all_blogs ) && $all_blogs !== array() ) {
+ foreach ( $all_blogs as $blog_id ) {
+ switch_to_blog( $blog_id );
+
+ if ( $activate === true ) {
+ _wpseo_activate();
+ }
+ else {
+ _wpseo_deactivate();
+ }
+ }
+ // Restore back to original blog
+ switch_to_blog( $original_blog_id );
+ }
+}
+
+/**
+ * Runs on activation of the plugin.
+ */
+function _wpseo_activate() {
+ require_once( WPSEO_PATH . 'inc/wpseo-functions.php' );
+
+ wpseo_load_textdomain(); // Make sure we have our translations available for the defaults
+ WPSEO_Options::get_instance();
+ if ( ! is_multisite() ) {
+ WPSEO_Options::initialize();
+ }
+ else {
+ WPSEO_Options::maybe_set_multisite_defaults( true );
+ }
+ WPSEO_Options::ensure_options_exist();
+
+ flush_rewrite_rules();
+
+ wpseo_add_capabilities();
+
+ WPSEO_Options::schedule_yoast_tracking( null, get_option( 'wpseo' ) );
+
+ // Clear cache so the changes are obvious.
+ WPSEO_Options::clear_cache();
+
+ do_action( 'wpseo_activate' );
+}
+
+/**
+ * On deactivation, flush the rewrite rules so XML sitemaps stop working.
+ */
+function _wpseo_deactivate() {
+ require_once( WPSEO_PATH . 'inc/wpseo-functions.php' );
+
+ flush_rewrite_rules();
+
+ wpseo_remove_capabilities();
+
+ // Force unschedule
+ WPSEO_Options::schedule_yoast_tracking( null, get_option( 'wpseo' ), true );
+
+ // Clear cache so the changes are obvious.
+ WPSEO_Options::clear_cache();
+
+ do_action( 'wpseo_deactivate' );
+}
+
+/**
+ * Run wpseo activation routine on creation / activation of a multisite blog if WPSEO is activated
+ * network-wide.
+ *
+ * Will only be called by multisite actions.
+ * @internal Unfortunately will fail if the plugin is in the must-use directory
+ * @see https://core.trac.wordpress.org/ticket/24205
+ */
+function wpseo_on_activate_blog( $blog_id ) {
+ if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
+ require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
+ }
+
+ if ( is_plugin_active_for_network( plugin_basename( WPSEO_FILE ) ) ) {
+ switch_to_blog( $blog_id );
+ wpseo_activate( false );
+ restore_current_blog();
+ }
+}
+
+
+
+/* ***************************** PLUGIN LOADING *************************** */
+
+/**
+ * Load translations
+ */
+function wpseo_load_textdomain() {
+ load_plugin_textdomain( 'wordpress-seo', false, dirname( plugin_basename( WPSEO_FILE ) ) . '/languages/' );
+}
+add_action( 'init', 'wpseo_load_textdomain', 1 );
+
+
+
+/**
+ * On plugins_loaded: load the minimum amount of essential files for this plugin
+ */
+function wpseo_init() {
+ require_once( WPSEO_PATH . 'inc/wpseo-functions.php' );
+
+ // Make sure our option and meta value validation routines and default values are always registered and available
+ WPSEO_Options::get_instance();
+ WPSEO_Meta::init();
+
+ $option_wpseo = get_option( 'wpseo' );
+ if ( version_compare( $option_wpseo['version'], WPSEO_VERSION, '<' ) ) {
+ wpseo_do_upgrade( $option_wpseo['version'] );
+ }
+
+ $options = WPSEO_Options::get_all();
+
+ if ( $options['stripcategorybase'] === true ) {
+ $GLOBALS['wpseo_rewrite'] = new WPSEO_Rewrite;
+ }
+
+ if ( $options['enablexmlsitemap'] === true ) {
+ $GLOBALS['wpseo_sitemaps'] = new WPSEO_Sitemaps;
+ }
+
+ if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) {
+ require_once( WPSEO_PATH . 'inc/wpseo-non-ajax-functions.php' );
+ }
+}
+
+/**
+ * Used to load the required files on the plugins_loaded hook, instead of immediately.
+ */
+function wpseo_frontend_init() {
+ add_action( 'init', 'initialize_wpseo_front' );
+
+ $options = WPSEO_Options::get_all();
+ if ( $options['breadcrumbs-enable'] === true ) {
+ /**
+ * If breadcrumbs are active (which they supposedly are if the users has enabled this settings,
+ * there's no reason to have bbPress breadcrumbs as well.
+ *
+ * @internal The class itself is only loaded when the template tag is encountered via
+ * the template tag function in the wpseo-functions.php file
+ */
+ add_filter( 'bbp_get_breadcrumb', '__return_false' );
+ }
+
+ add_action( 'template_redirect', 'wpseo_frontend_head_init', 999 );
+}
+
+/**
+ * Instantiate the different social classes on the frontend
+ */
+function wpseo_frontend_head_init() {
+ $options = WPSEO_Options::get_all();
+ if ( $options['twitter'] === true && is_singular() ) {
+ add_action( 'wpseo_head', array( 'WPSEO_Twitter', 'get_instance' ), 40 );
+ }
+
+ if ( $options['opengraph'] === true ) {
+ $GLOBALS['wpseo_og'] = new WPSEO_OpenGraph;
+ }
+
+ if ( $options['googleplus'] === true && is_singular() ) {
+ add_action( 'wpseo_head', array( 'WPSEO_GooglePlus', 'get_instance' ), 35 );
+ }
+}
+
+/**
+ * Used to load the required files on the plugins_loaded hook, instead of immediately.
+ */
+function wpseo_admin_init() {
+ global $pagenow;
+
+ $GLOBALS['wpseo_admin'] = new WPSEO_Admin;
+
+ $options = WPSEO_Options::get_all();
+ if ( isset( $_GET['wpseo_restart_tour'] ) ) {
+ $options['ignore_tour'] = false;
+ update_option( 'wpseo', $options );
+ }
+
+ if ( $options['yoast_tracking'] === true ) {
+ /**
+ * @internal this is not a proper lean loading implementation (method_exist will autoload the class),
+ * but it can't be helped as there are other plugins out there which also use versions
+ * of the Yoast Tracking class and we need to take that into account unfortunately
+ */
+ if ( method_exists( 'Yoast_Tracking', 'get_instance' ) ) {
+ add_action( 'yoast_tracking', array( 'Yoast_Tracking', 'get_instance' ) );
+ }
+ else {
+ $GLOBALS['yoast_tracking'] = new Yoast_Tracking;
+ }
+ }
+
+ /**
+ * Filter: 'wpseo_always_register_metaboxes_on_admint' - Allow developers to change whether
+ * the WPSEO metaboxes are only registered on the typical pages (lean loading) or always
+ * registered when in admin.
+ *
+ * @api bool Whether to always register the metaboxes or not. Defaults to false.
+ */
+ if ( in_array( $pagenow, array( 'edit.php', 'post.php', 'post-new.php' ) ) || apply_filters( 'wpseo_always_register_metaboxes_on_admin', false ) ) {
+ $GLOBALS['wpseo_metabox'] = new WPSEO_Metabox;
+ if ( $options['opengraph'] === true ) {
+ $GLOBALS['wpseo_social'] = new WPSEO_Social_Admin;
+ }
+ }
+
+ if ( in_array( $pagenow, array( 'edit-tags.php' ) ) ) {
+ $GLOBALS['wpseo_taxonomy'] = new WPSEO_Taxonomy;
+ }
+
+ if ( in_array( $pagenow, array( 'admin.php' ) ) ) {
+ // @todo [JRF => whomever] Can we load this more selectively ? like only when $_GET['page'] is one of ours ?
+ $GLOBALS['wpseo_admin_pages'] = new WPSEO_Admin_Pages;
+ }
+
+ if ( $options['tracking_popup_done'] === false || $options['ignore_tour'] === false ) {
+ add_action( 'admin_enqueue_scripts', array( 'WPSEO_Pointers', 'get_instance' ) );
+ }
+
+ if ( $options['enablexmlsitemap'] === true ) {
+ $GLOBALS['wpseo_sitemaps_admin'] = new WPSEO_Sitemaps_Admin;
+ }
+}
+
+
+
+/* ***************************** BOOTSTRAP / HOOK INTO WP *************************** */
+
+if ( ! function_exists( 'spl_autoload_register' ) ) {
+ add_action( 'admin_init', 'yoast_wpseo_self_deactivate', 1 );
+}
+else if ( ! defined( 'WP_INSTALLING' ) || WP_INSTALLING === false ) {
+ add_action( 'plugins_loaded', 'wpseo_init', 14 );
+
+ if ( is_admin() ) {
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+ require_once( WPSEO_PATH . 'admin/ajax.php' );
+ }
+ else {
+ add_action( 'plugins_loaded', 'wpseo_admin_init', 15 );
+ }
+ }
+ else {
+ add_action( 'plugins_loaded', 'wpseo_frontend_init', 15 );
+ }
+
+ add_action( 'admin_init', 'load_yoast_notifications' );
+}
+
+// Activation and deactivation hook
+register_activation_hook( WPSEO_FILE, 'wpseo_activate' );
+register_deactivation_hook( WPSEO_FILE, 'wpseo_deactivate' );
+add_action( 'wpmu_new_blog', 'wpseo_on_activate_blog' );
+add_action( 'activate_blog', 'wpseo_on_activate_blog' );
+
+
+function load_yoast_notifications() {
+ // Init Yoast_Notification_Center class
+ Yoast_Notification_Center::get();
+}
+
+
+/**
+ * Throw an error if the PHP SPL extension is disabled (prevent white screens) and self-deactivate plugin
+ *
+ * @since 1.5.4
+ *
+ * @param string Error message
+ * @return void
+ */
+function yoast_wpseo_self_deactivate() {
+ if ( is_admin() ) {
+ $message = esc_html__( 'The Standard PHP Library (SPL) extension seem to be unavailable. Please ask your web host to enable it.', 'wordpress-seo' );
+ add_action( 'admin_notices', create_function( $message, 'echo \'<div class="error"><p>\' . __( \'Activation failed:\', \'wordpress-seo\' ) . \' \' . $message . \'</p></div>\';' ) );
+
+ deactivate_plugins( plugin_basename( WPSEO_FILE ) );
+ if ( isset( $_GET['activate'] ) ) {
+ unset( $_GET['activate'] );
+ }
+ }
+}
--- /dev/null
+<?php
+/*
+Plugin Name: WordPress SEO
+Version: 1.6.1
+Plugin URI: https://yoast.com/wordpress/plugins/seo/#utm_source=wpadmin&utm_medium=plugin&utm_campaign=wpseoplugin
+Description: The first true all-in-one SEO solution for WordPress, including on-page content analysis, XML sitemaps and much more.
+Author: Team Yoast
+Author URI: https://yoast.com/
+Text Domain: wordpress-seo
+Domain Path: /languages/
+License: GPL v3
+
+WordPress SEO Plugin
+Copyright (C) 2008-2014, Yoast BV - support@yoast.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+if ( ! function_exists( 'add_filter' ) ) {
+ header( 'Status: 403 Forbidden' );
+ header( 'HTTP/1.1 403 Forbidden' );
+ exit();
+}
+
+if ( ! defined( 'WPSEO_FILE' ) ) {
+ define( 'WPSEO_FILE', __FILE__ );
+}
+
+// Load the WordPress SEO plugin
+require_once( dirname( __FILE__ ) . '/wp-seo-main.php' );
\ No newline at end of file
--- /dev/null
+<wpml-config>
+ <custom-fields>
+ <custom-field action="translate">_yoast_wpseo_title</custom-field>
+ <custom-field action="translate">_yoast_wpseo_bctitle</custom-field>
+ <custom-field action="translate">_yoast_wpseo_metadesc</custom-field>
+ <custom-field action="translate">_yoast_wpseo_metakeywords</custom-field>
+ <custom-field action="translate">_yoast_wpseo_focuskw</custom-field>
+ <custom-field action="copy">_yoast_wpseo_meta-robots-noindex</custom-field>
+ <custom-field action="copy">_yoast_wpseo_meta-robots-nofollow</custom-field>
+ <custom-field action="copy">_yoast_wpseo_meta-robots-adv</custom-field>
+ <custom-field action="ignore">_yoast_wpseo_canonical</custom-field>
+ <custom-field action="ignore">_yoast_wpseo_redirect</custom-field>
+ <custom-field action="translate">_yoast_wpseo_opengraph-description</custom-field>
+ <custom-field action="translate">_yoast_wpseo_google-plus-description</custom-field>
+ </custom-fields>
+ <admin-texts>
+ <key name="wpseo_titles">
+ <key name="title-home-wpseo" />
+ <key name="metadesc-home-wpseo" />
+ <key name="metakey-home-wpseo" />
+ <key name="title-post" />
+ <key name="metadesc-post" />
+ <key name="metakey-post" />
+ <key name="title-page" />
+ <key name="metadesc-page" />
+ <key name="metakey-page" />
+ <key name="title-attachment" />
+ <key name="metadesc-attachment" />
+ <key name="metakey-attachment" />
+ <key name="title-category" />
+ <key name="metadesc-category" />
+ <key name="metakey-category" />
+ <key name="title-post_tag" />
+ <key name="metadesc-post_tag" />
+ <key name="metakey-post_tag" />
+ <key name="title-author-wpseo" />
+ <key name="metadesc-author-wpseo" />
+ <key name="metakey-author-wpseo" />
+ <key name="title-archive-wpseo" />
+ <key name="metadesc-archive-wpseo" />
+ <key name="title-search-wpseo" />
+ <key name="title-404-wpseo" />
+ </key>
+ <key name="wpseo_internallinks">
+ <key name="breadcrumbs-sep" />
+ <key name="breadcrumbs-home" />
+ <key name="breadcrumbs-prefix" />
+ <key name="breadcrumbs-archiveprefix" />
+ <key name="breadcrumbs-searchprefix" />
+ <key name="breadcrumbs-404crumb" />
+ </key>
+ <key name="wpseo_rss">
+ <key name="rssbefore" />
+ <key name="rssafter" />
+ </key>
+ </admin-texts>
+</wpml-config>
\ No newline at end of file
*/
get_header(); ?>
-<header id="homepage-hero" role="banner">
- <div class="row">
- <div class="small-12 medium-7 columns">
- <h1><a href="<?php bloginfo('url'); ?>" title="<?php bloginfo('name'); ?>"><?php bloginfo('name'); ?></a></h1>
- <h4 class="subheader"><?php bloginfo('description'); ?></h4>
- </div>
-
- <div class="medium-6 columns end">
- <a role="button" class="download large button hide-for-small" href="https://github.com/olefredrik/foundationpress">Download FoundationPress</a>
- </div>
-
- <div class="floatingyeti show-for-medium-up">
- <img data-cfsrc="http://foundation.zurb.com/assets/img/homepage/hero-image.svg" alt="Foundation Yeti" src="http://foundation.zurb.com/assets/img/homepage/hero-image.svg">
- </div>
- </div>
-
-</header>
-
<div class="row">
<div class="small-12 large-8 columns" role="main">
<aside class="left-off-canvas-menu" aria-hidden="true">
<?php foundationPress_mobile_off_canvas(); ?>
-</aside>
\ No newline at end of file
+</aside>
-<aside id="sidebar" class="small-12 large-4 columns large-pull-8">
+<aside id="sidebar" class="small-12 columns">
<?php do_action('foundationPress_before_sidebar'); ?>
<?php dynamic_sidebar("sidebar-widgets"); ?>
<?php do_action('foundationPress_after_sidebar'); ?>