PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/htdocs/wp-content/plugins/wordpress-seo/admin/class-metabox.php

https://github.com/Fishgate/privatecollectionswp
PHP | 2037 lines | 1385 code | 273 blank | 379 comment | 287 complexity | 1c3ac0b600889e78fa4bcacb329790c1 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * @package Admin
  4. *
  5. * This code generates the metabox on the edit post / page as well as contains all page analysis functionality.
  6. */
  7. if ( ! defined( 'WPSEO_VERSION' ) ) {
  8. header( 'Status: 403 Forbidden' );
  9. header( 'HTTP/1.1 403 Forbidden' );
  10. exit();
  11. }
  12. if ( ! class_exists( 'WPSEO_Metabox' ) ) {
  13. /**
  14. * class WPSEO_Metabox
  15. *
  16. * The class that generates the metabox on the edit post / page as well as contains all page analysis functionality.
  17. */
  18. class WPSEO_Metabox extends WPSEO_Meta {
  19. /**
  20. * Property holding the Text statistics object
  21. */
  22. public $statistics;
  23. /**
  24. * Class constructor
  25. */
  26. function __construct() {
  27. add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
  28. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
  29. add_action( 'wp_insert_post', array( $this, 'save_postdata' ) );
  30. add_action( 'edit_attachment', array( $this, 'save_postdata' ) );
  31. add_action( 'add_attachment', array( $this, 'save_postdata' ) );
  32. add_action( 'admin_init', array( $this, 'setup_page_analysis' ) );
  33. add_action( 'admin_init', array( $this, 'translate_meta_boxes' ) );
  34. }
  35. /**
  36. * Translate text strings for use in the meta box
  37. *
  38. * IMPORTANT: if you want to add a new string (option) somewhere, make sure you add that array key to
  39. * the main meta box definition array in the class WPSEO_Meta() as well!!!!
  40. *
  41. */
  42. public static function translate_meta_boxes() {
  43. self::$meta_fields['general']['snippetpreview']['title'] = __( 'Snippet Preview', 'wordpress-seo' );
  44. 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&amp;utm_medium=inline-help&amp;utm_campaign=snippet-preview">', '</a>' );
  45. self::$meta_fields['general']['focuskw']['title'] = __( 'Focus Keyword', 'wordpress-seo' );
  46. 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&amp;utm_medium=inline-help&amp;utm_campaign=focus-keyword">', '</a>' );
  47. self::$meta_fields['general']['title']['title'] = __( 'SEO Title', 'wordpress-seo' );
  48. 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>';
  49. 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' );
  50. self::$meta_fields['general']['metadesc']['title'] = __( 'Meta Description', 'wordpress-seo' );
  51. 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>';
  52. 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&amp;utm_medium=inline-help&amp;utm_campaign=focus-keyword">', '</a>' );
  53. self::$meta_fields['general']['metakeywords']['title'] = __( 'Meta Keywords', 'wordpress-seo' );
  54. self::$meta_fields['general']['metakeywords']['description'] = __( 'If you type something above it will override your %smeta keywords template%s.', 'wordpress-seo' );
  55. self::$meta_fields['advanced']['meta-robots-noindex']['title'] = __( 'Meta Robots Index', 'wordpress-seo' );
  56. if ( '0' == get_option( 'blog_public' ) ) {
  57. 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>';
  58. }
  59. self::$meta_fields['advanced']['meta-robots-noindex']['options']['0'] = __( 'Default for post type, currently: %s', 'wordpress-seo' );
  60. self::$meta_fields['advanced']['meta-robots-noindex']['options']['2'] = __( 'index', 'wordpress-seo' );
  61. self::$meta_fields['advanced']['meta-robots-noindex']['options']['1'] = __( 'noindex', 'wordpress-seo' );
  62. self::$meta_fields['advanced']['meta-robots-nofollow']['title'] = __( 'Meta Robots Follow', 'wordpress-seo' );
  63. self::$meta_fields['advanced']['meta-robots-nofollow']['options']['0'] = __( 'Follow', 'wordpress-seo' );
  64. self::$meta_fields['advanced']['meta-robots-nofollow']['options']['1'] = __( 'Nofollow', 'wordpress-seo' );
  65. self::$meta_fields['advanced']['meta-robots-adv']['title'] = __( 'Meta Robots Advanced', 'wordpress-seo' );
  66. self::$meta_fields['advanced']['meta-robots-adv']['description'] = __( 'Advanced <code>meta</code> robots settings for this page.', 'wordpress-seo' );
  67. self::$meta_fields['advanced']['meta-robots-adv']['options']['-'] = __( 'Site-wide default: %s', 'wordpress-seo' );
  68. self::$meta_fields['advanced']['meta-robots-adv']['options']['none'] = __( 'None', 'wordpress-seo' );
  69. self::$meta_fields['advanced']['meta-robots-adv']['options']['noodp'] = __( 'NO ODP', 'wordpress-seo' );
  70. self::$meta_fields['advanced']['meta-robots-adv']['options']['noydir'] = __( 'NO YDIR', 'wordpress-seo' );
  71. self::$meta_fields['advanced']['meta-robots-adv']['options']['noimageindex'] = __( 'No Image Index', 'wordpress-seo' );
  72. self::$meta_fields['advanced']['meta-robots-adv']['options']['noarchive'] = __( 'No Archive', 'wordpress-seo' );
  73. self::$meta_fields['advanced']['meta-robots-adv']['options']['nosnippet'] = __( 'No Snippet', 'wordpress-seo' );
  74. self::$meta_fields['advanced']['bctitle']['title'] = __( 'Breadcrumbs title', 'wordpress-seo' );
  75. self::$meta_fields['advanced']['bctitle']['description'] = __( 'Title to use for this page in breadcrumb paths', 'wordpress-seo' );
  76. self::$meta_fields['advanced']['sitemap-include']['title'] = __( 'Include in Sitemap', 'wordpress-seo' );
  77. 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' );
  78. self::$meta_fields['advanced']['sitemap-include']['options']['-'] = __( 'Auto detect', 'wordpress-seo' );
  79. self::$meta_fields['advanced']['sitemap-include']['options']['always'] = __( 'Always include', 'wordpress-seo' );
  80. self::$meta_fields['advanced']['sitemap-include']['options']['never'] = __( 'Never include', 'wordpress-seo' );
  81. self::$meta_fields['advanced']['sitemap-prio']['title'] = __( 'Sitemap Priority', 'wordpress-seo' );
  82. self::$meta_fields['advanced']['sitemap-prio']['description'] = __( 'The priority given to this page in the XML sitemap.', 'wordpress-seo' );
  83. self::$meta_fields['advanced']['sitemap-prio']['options']['-'] = __( 'Automatic prioritization', 'wordpress-seo' );
  84. self::$meta_fields['advanced']['sitemap-prio']['options']['1'] = __( '1 - Highest priority', 'wordpress-seo' );
  85. self::$meta_fields['advanced']['sitemap-prio']['options']['0.8'] .= __( 'Default for first tier pages', 'wordpress-seo' );
  86. self::$meta_fields['advanced']['sitemap-prio']['options']['0.6'] .= __( 'Default for second tier pages and posts', 'wordpress-seo' );
  87. self::$meta_fields['advanced']['sitemap-prio']['options']['0.5'] .= __( 'Medium priority', 'wordpress-seo' );
  88. self::$meta_fields['advanced']['sitemap-prio']['options']['0.1'] .= __( 'Lowest priority', 'wordpress-seo' );
  89. self::$meta_fields['advanced']['sitemap-html-include']['title'] = __( 'Include in HTML Sitemap', 'wordpress-seo' );
  90. self::$meta_fields['advanced']['sitemap-html-include']['description'] = __( 'Should this page be in the HTML Sitemap at all times, regardless of Robots Meta settings?', 'wordpress-seo' );
  91. self::$meta_fields['advanced']['sitemap-html-include']['options']['-'] = __( 'Auto detect', 'wordpress-seo' );
  92. self::$meta_fields['advanced']['sitemap-html-include']['options']['always'] = __( 'Always include', 'wordpress-seo' );
  93. self::$meta_fields['advanced']['sitemap-html-include']['options']['never'] = __( 'Never include', 'wordpress-seo' );
  94. self::$meta_fields['advanced']['authorship']['title'] = __( 'Authorship', 'wordpress-seo' );
  95. self::$meta_fields['advanced']['authorship']['description'] = __( 'Show <code>rel="author"</code> on this page?', 'wordpress-seo' );
  96. self::$meta_fields['advanced']['authorship']['options']['-'] = __( 'Default for post type, currently: %s', 'wordpress-seo' );
  97. self::$meta_fields['advanced']['authorship']['options']['always'] = __( 'Always show', 'wordpress-seo' );
  98. self::$meta_fields['advanced']['authorship']['options']['never'] = __( 'Never show', 'wordpress-seo' );
  99. self::$meta_fields['advanced']['canonical']['title'] = __( 'Canonical URL', 'wordpress-seo' );
  100. 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>' );
  101. self::$meta_fields['advanced']['redirect']['title'] = __( '301 Redirect', 'wordpress-seo' );
  102. self::$meta_fields['advanced']['redirect']['description'] = __( 'The URL that this page should redirect to.', 'wordpress-seo' );
  103. do_action( 'wpseo_tab_translate' );
  104. }
  105. /**
  106. * Test whether the metabox should be hidden either by choice of the admin or because
  107. * the post type is not a public post type
  108. *
  109. * @since 1.5.0
  110. *
  111. * @param string $post_type (optional) The post type to test, defaults to the current post post_type
  112. *
  113. * @return bool Whether or not the meta box (and associated columns etc) should be hidden
  114. */
  115. function is_metabox_hidden( $post_type = null ) {
  116. if ( ! isset( $post_type ) ) {
  117. if ( isset( $GLOBALS['post'] ) && ( is_object( $GLOBALS['post'] ) && isset( $GLOBALS['post']->post_type ) ) ) {
  118. $post_type = $GLOBALS['post']->post_type;
  119. } elseif ( isset( $_GET['post_type'] ) && $_GET['post_type'] !== '' ) {
  120. $post_type = sanitize_text_field( $_GET['post_type'] );
  121. }
  122. }
  123. if ( isset( $post_type ) ) {
  124. // Don't make static as post_types may still be added during the run
  125. $cpts = get_post_types( array( 'public' => true ), 'names' );
  126. $options = get_option( 'wpseo_titles' );
  127. return ( ( isset( $options[ 'hideeditbox-' . $post_type ] ) && $options[ 'hideeditbox-' . $post_type ] === true ) || in_array( $post_type, $cpts ) === false );
  128. } else {
  129. return false;
  130. }
  131. }
  132. /**
  133. * Sets up all the functionality related to the prominence of the page analysis functionality.
  134. */
  135. public function setup_page_analysis() {
  136. if ( apply_filters( 'wpseo_use_page_analysis', true ) === true ) {
  137. $post_types = get_post_types( array( 'public' => true ), 'names' );
  138. if ( is_array( $post_types ) && $post_types !== array() ) {
  139. foreach ( $post_types as $pt ) {
  140. if ( $this->is_metabox_hidden( $pt ) === false ) {
  141. add_filter( 'manage_' . $pt . '_posts_columns', array( $this, 'column_heading' ), 10, 1 );
  142. add_action( 'manage_' . $pt . '_posts_custom_column', array(
  143. $this,
  144. 'column_content',
  145. ), 10, 2 );
  146. add_action( 'manage_edit-' . $pt . '_sortable_columns', array(
  147. $this,
  148. 'column_sort',
  149. ), 10, 2 );
  150. }
  151. }
  152. }
  153. add_action( 'restrict_manage_posts', array( $this, 'posts_filter_dropdown' ) );
  154. add_filter( 'request', array( $this, 'column_sort_orderby' ) );
  155. add_action( 'post_submitbox_misc_actions', array( $this, 'publish_box' ) );
  156. }
  157. }
  158. /**
  159. * Get an instance of the text statistics class
  160. *
  161. * @return Yoast_TextStatistics
  162. */
  163. private function statistics() {
  164. if ( ! isset( $this->statistics ) ) {
  165. $this->statistics = new Yoast_TextStatistics( get_bloginfo( 'charset' ) );
  166. }
  167. return $this->statistics;
  168. }
  169. /**
  170. * Lowercase a sentence while preserving "weird" characters.
  171. *
  172. * This should work with Greek, Russian, Polish & French amongst other languages...
  173. *
  174. * @param string $string String to lowercase
  175. *
  176. * @return string
  177. */
  178. public function strtolower_utf8( $string ) {
  179. // Prevent comparison between utf8 characters and html entities (é vs &eacute;)
  180. $string = html_entity_decode( $string );
  181. $convert_to = array(
  182. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
  183. 'v', 'w', 'x', 'y', 'z', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï',
  184. 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж',
  185. 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы',
  186. 'ь', 'э', 'ю', 'я', 'ą', 'ć', 'ę', 'ł', 'ń', 'ó', 'ś', 'ź', 'ż',
  187. );
  188. $convert_from = array(
  189. 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
  190. 'V', 'W', 'X', 'Y', 'Z', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï',
  191. 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж',
  192. 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ъ',
  193. 'Ь', 'Э', 'Ю', 'Я', 'Ą', 'Ć', 'Ę', 'Ł', 'Ń', 'Ó', 'Ś', 'Ź', 'Ż',
  194. );
  195. return str_replace( $convert_from, $convert_to, $string );
  196. }
  197. /**
  198. * Outputs the page analysis score in the Publish Box.
  199. *
  200. */
  201. public function publish_box() {
  202. if ( $this->is_metabox_hidden() === true ) {
  203. return;
  204. }
  205. echo '<div class="misc-pub-section misc-yoast misc-pub-section-last">';
  206. if ( self::get_value( 'meta-robots-noindex' ) === '1' ) {
  207. $score_label = 'noindex';
  208. $title = __( 'Post is set to noindex.', 'wordpress-seo' );
  209. $score_title = $title;
  210. } else {
  211. if ( isset( $_GET['post'] ) ) {
  212. $post_id = (int) WPSEO_Option::validate_int( $_GET['post'] );
  213. $post = get_post( $post_id );
  214. } else {
  215. global $post;
  216. }
  217. $score = '';
  218. $results = $this->calculate_results( $post );
  219. if ( ! is_wp_error( $results ) && isset( $results['total'] ) ) {
  220. $score = $results['total'];
  221. unset( $results );
  222. }
  223. if ( $score === '' ) {
  224. $score_label = 'na';
  225. $title = __( 'No focus keyword set.', 'wordpress-seo' );
  226. } else {
  227. $score_label = wpseo_translate_score( $score );
  228. }
  229. $score_title = wpseo_translate_score( $score, false );
  230. if ( ! isset( $title ) ) {
  231. $title = $score_title;
  232. }
  233. }
  234. echo '<div title="' . esc_attr( $title ) . '" class="' . esc_attr( 'wpseo-score-icon ' . $score_label ) . '"></div>';
  235. echo __( 'SEO: ', 'wordpress-seo' ) . '<span class="wpseo-score-title">' . $score_title . '</span>';
  236. echo ' <a class="wpseo_tablink scroll" href="#wpseo_linkdex">' . __( 'Check', 'wordpress-seo' ) . '</a>';
  237. echo '</div>';
  238. }
  239. /**
  240. * Adds the WordPress SEO meta box to the edit boxes in the edit post / page / cpt pages.
  241. */
  242. public function add_meta_box() {
  243. $post_types = get_post_types( array( 'public' => true ) );
  244. if ( is_array( $post_types ) && $post_types !== array() ) {
  245. foreach ( $post_types as $post_type ) {
  246. if ( $this->is_metabox_hidden( $post_type ) === false ) {
  247. add_meta_box( 'wpseo_meta', __( 'WordPress SEO by Yoast', 'wordpress-seo' ), array(
  248. $this,
  249. 'meta_box',
  250. ), $post_type, 'normal', apply_filters( 'wpseo_metabox_prio', 'high' ) );
  251. }
  252. }
  253. }
  254. }
  255. /**
  256. * Pass some variables to js for the edit / post page overview, snippet preview, etc.
  257. *
  258. * @return array
  259. */
  260. public function localize_script() {
  261. if ( isset( $_GET['post'] ) ) {
  262. $post_id = (int) WPSEO_Option::validate_int( $_GET['post'] );
  263. $post = get_post( $post_id );
  264. } else {
  265. global $post;
  266. }
  267. if ( ( ! is_object( $post ) || ! isset( $post->post_type ) ) || $this->is_metabox_hidden( $post->post_type ) === true ) {
  268. return array();
  269. }
  270. $options = get_option( 'wpseo_titles' );
  271. $date = '';
  272. if ( isset( $options[ 'showdate-' . $post->post_type ] ) && $options[ 'showdate-' . $post->post_type ] === true ) {
  273. $date = $this->get_post_date( $post );
  274. self::$meta_length = self::$meta_length - ( strlen( $date ) + 5 );
  275. self::$meta_length_reason = __( ' (because of date display)', 'wordpress-seo' );
  276. }
  277. self::$meta_length_reason = apply_filters( 'wpseo_metadesc_length_reason', self::$meta_length_reason, $post );
  278. self::$meta_length = apply_filters( 'wpseo_metadesc_length', self::$meta_length, $post );
  279. unset( $date );
  280. $title_template = '';
  281. if ( isset( $options[ 'title-' . $post->post_type ] ) && $options[ 'title-' . $post->post_type ] !== '' ) {
  282. $title_template = $options[ 'title-' . $post->post_type ];
  283. }
  284. // If there's no title template set, use the default, otherwise title preview won't work.
  285. if ( $title_template == '' ) {
  286. $title_template = '%%title%% - %%sitename%%';
  287. }
  288. $metadesc_template = '';
  289. if ( isset( $options[ 'metadesc-' . $post->post_type ] ) && $options[ 'metadesc-' . $post->post_type ] !== '' ) {
  290. $metadesc_template = $options[ 'metadesc-' . $post->post_type ];
  291. }
  292. $sample_permalink = get_sample_permalink( $post->ID );
  293. $sample_permalink = str_replace( '%page', '%post', $sample_permalink[0] );
  294. $cached_replacement_vars = array();
  295. foreach (
  296. array(
  297. 'date',
  298. 'id',
  299. 'sitename',
  300. 'sitedesc',
  301. 'sep',
  302. 'page',
  303. 'currenttime',
  304. 'currentdate',
  305. 'currentday',
  306. 'currentmonth',
  307. 'currentyear',
  308. ) as $var
  309. ) {
  310. $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $post );
  311. }
  312. return array_merge( $cached_replacement_vars, array(
  313. 'field_prefix' => self::$form_prefix,
  314. 'keyword_header' => __( 'Your focus keyword was found in:', 'wordpress-seo' ),
  315. 'article_header_text' => __( 'Article Heading: ', 'wordpress-seo' ),
  316. 'page_title_text' => __( 'Page title: ', 'wordpress-seo' ),
  317. 'page_url_text' => __( 'Page URL: ', 'wordpress-seo' ),
  318. 'content_text' => __( 'Content: ', 'wordpress-seo' ),
  319. 'meta_description_text' => __( 'Meta description: ', 'wordpress-seo' ),
  320. 'choose_image' => __( 'Use Image', 'wordpress-seo' ),
  321. 'wpseo_meta_desc_length' => self::$meta_length,
  322. 'wpseo_title_template' => $title_template,
  323. 'wpseo_metadesc_template' => $metadesc_template,
  324. 'wpseo_permalink_template' => $sample_permalink,
  325. 'wpseo_keyword_suggest_nonce' => wp_create_nonce( 'wpseo-get-suggest' ),
  326. 'wpseo_replace_vars_nonce' => wp_create_nonce( 'wpseo-replace-vars' ),
  327. 'no_parent_text' => __( '(no parent)' ),
  328. ) );
  329. }
  330. /**
  331. * Output a tab in the WP SEO Metabox
  332. *
  333. * @param string $id CSS ID of the tab.
  334. * @param string $heading Heading for the tab.
  335. * @param string $content Content of the tab. This content should be escaped.
  336. */
  337. public function do_tab( $id, $heading, $content ) {
  338. ?>
  339. <div class="wpseotab <?php echo esc_attr( $id ) ?>">
  340. <h4 class="wpseo-heading"><?php echo esc_html( $heading ); ?></h4>
  341. <table class="form-table">
  342. <?php echo $content ?>
  343. </table>
  344. </div>
  345. <?php
  346. }
  347. /**
  348. * Output the meta box
  349. */
  350. function meta_box() {
  351. if ( isset( $_GET['post'] ) ) {
  352. $post_id = (int) WPSEO_Option::validate_int( $_GET['post'] );
  353. $post = get_post( $post_id );
  354. } else {
  355. global $post;
  356. }
  357. $options = WPSEO_Options::get_all();
  358. ?>
  359. <div class="wpseo-metabox-tabs-div">
  360. <ul class="wpseo-metabox-tabs" id="wpseo-metabox-tabs">
  361. <li class="general">
  362. <a class="wpseo_tablink" href="#wpseo_general"><?php _e( 'General', 'wordpress-seo' ); ?></a></li>
  363. <li id="linkdex" class="linkdex">
  364. <a class="wpseo_tablink" href="#wpseo_linkdex"><?php _e( 'Page Analysis', 'wordpress-seo' ); ?></a>
  365. </li>
  366. <?php if ( current_user_can( 'manage_options' ) || $options['disableadvanced_meta'] === false ): ?>
  367. <li class="advanced">
  368. <a class="wpseo_tablink" href="#wpseo_advanced"><?php _e( 'Advanced', 'wordpress-seo' ); ?></a>
  369. </li>
  370. <?php endif; ?>
  371. <?php do_action( 'wpseo_tab_header' ); ?>
  372. </ul>
  373. <?php
  374. $content = '';
  375. if ( is_object( $post ) && isset( $post->post_type ) ) {
  376. foreach ( $this->get_meta_field_defs( 'general', $post->post_type ) as $key => $meta_field ) {
  377. $content .= $this->do_meta_box( $meta_field, $key );
  378. }
  379. }
  380. $this->do_tab( 'general', __( 'General', 'wordpress-seo' ), $content );
  381. $this->do_tab( 'linkdex', __( 'Page Analysis', 'wordpress-seo' ), $this->linkdex_output( $post ) );
  382. if ( current_user_can( 'manage_options' ) || $options['disableadvanced_meta'] === false ) {
  383. $content = '';
  384. foreach ( $this->get_meta_field_defs( 'advanced' ) as $key => $meta_field ) {
  385. $content .= $this->do_meta_box( $meta_field, $key );
  386. }
  387. $this->do_tab( 'advanced', __( 'Advanced', 'wordpress-seo' ), $content );
  388. }
  389. do_action( 'wpseo_tab_content' );
  390. echo '</div>';
  391. }
  392. /**
  393. * Adds a line in the meta box
  394. *
  395. * @todo [JRF] check if $class is added appropriately everywhere
  396. *
  397. * @param array $meta_field_def Contains the vars based on which output is generated.
  398. * @param string $key Internal key (without prefix)
  399. *
  400. * @return string
  401. */
  402. function do_meta_box( $meta_field_def, $key = '' ) {
  403. $content = '';
  404. $esc_form_key = esc_attr( self::$form_prefix . $key );
  405. $meta_value = self::get_value( $key );
  406. $class = '';
  407. if ( isset( $meta_field_def['class'] ) && $meta_field_def['class'] !== '' ) {
  408. $class = ' ' . $meta_field_def['class'];
  409. }
  410. $placeholder = '';
  411. if ( isset( $meta_field_def['placeholder'] ) && $meta_field_def['placeholder'] !== '' ) {
  412. $placeholder = $meta_field_def['placeholder'];
  413. }
  414. switch ( $meta_field_def['type'] ) {
  415. case 'snippetpreview':
  416. $content .= $this->snippet();
  417. break;
  418. case 'text':
  419. $ac = '';
  420. if ( isset( $meta_field_def['autocomplete'] ) && $meta_field_def['autocomplete'] === false ) {
  421. $ac = 'autocomplete="off" ';
  422. }
  423. if ( $placeholder !== '' ) {
  424. $placeholder = ' placeholder="' . esc_attr( $placeholder ) . '"';
  425. }
  426. $content .= '<input type="text"' . $placeholder . '" id="' . $esc_form_key . '" ' . $ac . 'name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '" class="large-text' . $class . '"/><br />';
  427. break;
  428. case 'textarea':
  429. $rows = 3;
  430. if ( isset( $meta_field_def['rows'] ) && $meta_field_def['rows'] > 0 ) {
  431. $rows = $meta_field_def['rows'];
  432. }
  433. $content .= '<textarea class="large-text' . $class . '" rows="' . esc_attr( $rows ) . '" id="' . $esc_form_key . '" name="' . $esc_form_key . '">' . esc_textarea( $meta_value ) . '</textarea>';
  434. break;
  435. case 'select':
  436. if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== array() ) {
  437. $content .= '<select name="' . $esc_form_key . '" id="' . $esc_form_key . '" class="yoast' . $class . '">';
  438. foreach ( $meta_field_def['options'] as $val => $option ) {
  439. $selected = selected( $meta_value, $val, false );
  440. $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>';
  441. }
  442. $content .= '</select>';
  443. }
  444. break;
  445. case 'multiselect':
  446. if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== array() ) {
  447. // Set $meta_value as $selectedarr
  448. $selected_arr = $meta_value;
  449. // If the multiselect field is 'meta-robots-adv' we should explode on ,
  450. if ( 'meta-robots-adv' === $key ) {
  451. $selected_arr = explode( ',', $meta_value );
  452. }
  453. if ( ! is_array( $selected_arr ) ) {
  454. $selected_arr = (array) $selected_arr;
  455. }
  456. $options_count = count( $meta_field_def['options'] );
  457. // @todo [JRF => whomever] verify height calculation for older WP versions, was 16x, for WP3.8 20x is more appropriate
  458. $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 . '">';
  459. foreach ( $meta_field_def['options'] as $val => $option ) {
  460. $selected = '';
  461. if ( in_array( $val, $selected_arr ) ) {
  462. $selected = ' selected="selected"';
  463. }
  464. $content .= '<option ' . $selected . ' value="' . esc_attr( $val ) . '">' . esc_html( $option ) . '</option>';
  465. }
  466. $content .= '</select>';
  467. }
  468. break;
  469. case 'checkbox':
  470. $checked = checked( $meta_value, 'on', false );
  471. $expl = ( isset( $meta_field_def['expl'] ) ) ? esc_html( $meta_field_def['expl'] ) : '';
  472. $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 />';
  473. break;
  474. case 'radio':
  475. if ( isset( $meta_field_def['options'] ) && is_array( $meta_field_def['options'] ) && $meta_field_def['options'] !== array() ) {
  476. foreach ( $meta_field_def['options'] as $val => $option ) {
  477. $checked = checked( $meta_value, $val, false );
  478. $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> ';
  479. }
  480. }
  481. break;
  482. case 'upload':
  483. $content .= '<input id="' . $esc_form_key . '" type="text" size="36" name="' . $esc_form_key . '" value="' . esc_attr( $meta_value ) . '" />';
  484. $content .= '<input id="' . $esc_form_key . '_button" class="wpseo_image_upload_button button" type="button" value="Upload Image" />';
  485. break;
  486. }
  487. $html = '';
  488. if ( $content === '' ) {
  489. $content = apply_filters( 'wpseo_do_meta_box_field_' . $key, $content, $meta_value, $esc_form_key, $meta_field_def, $key );
  490. }
  491. if ( $content !== '' ) {
  492. $label = esc_html( $meta_field_def['title'] );
  493. if ( in_array( $meta_field_def['type'], array(
  494. 'snippetpreview',
  495. 'radio',
  496. 'checkbox',
  497. ), true ) === false
  498. ) {
  499. $label = '<label for="' . $esc_form_key . '">' . $label . ':</label>';
  500. }
  501. $help = '';
  502. if ( isset( $meta_field_def['help'] ) && $meta_field_def['help'] !== '' ) {
  503. $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'] ) . '" />';
  504. }
  505. $html = '
  506. <tr>
  507. <th scope="row">' . $label . $help . '</th>
  508. <td>';
  509. $html .= $content;
  510. if ( isset( $meta_field_def['description'] ) ) {
  511. $html .= '<div>' . $meta_field_def['description'] . '</div>';
  512. }
  513. $html .= '
  514. </td>
  515. </tr>';
  516. }
  517. return $html;
  518. }
  519. /**
  520. * Retrieve a post date when post is published, or return current date when it's not.
  521. *
  522. * @param object $post Post to retrieve the date for.
  523. *
  524. * @return string
  525. */
  526. function get_post_date( $post ) {
  527. if ( isset( $post->post_date ) && $post->post_status == 'publish' ) {
  528. $date = date_i18n( 'j M Y', strtotime( $post->post_date ) );
  529. } else {
  530. $date = date_i18n( 'j M Y' );
  531. }
  532. return (string) $date;
  533. }
  534. /**
  535. * Generate a snippet preview.
  536. *
  537. * @return string
  538. */
  539. function snippet() {
  540. if ( isset( $_GET['post'] ) ) {
  541. $post_id = (int) WPSEO_Option::validate_int( $_GET['post'] );
  542. $post = get_post( $post_id );
  543. } else {
  544. global $post;
  545. }
  546. $options = WPSEO_Options::get_all();
  547. $date = '';
  548. if ( is_object( $post ) && isset( $options[ 'showdate-' . $post->post_type ] ) && $options[ 'showdate-' . $post->post_type ] === true ) {
  549. $date = $this->get_post_date( $post );
  550. }
  551. $title = self::get_value( 'title' );
  552. $desc = self::get_value( 'metadesc' );
  553. $slug = ( is_object( $post ) && isset( $post->post_name ) ) ? $post->post_name : '';
  554. if ( $slug !== '' ) {
  555. $slug = sanitize_title( $title );
  556. }
  557. if ( is_string( $date ) && $date !== '' ) {
  558. $datestr = '<span class="date">' . $date . ' - </span>';
  559. } else {
  560. $datestr = '';
  561. }
  562. $content = '<div id="wpseosnippet">
  563. <a class="title" id="wpseosnippet_title" href="#">' . esc_html( $title ) . '</a>';
  564. $content .= '<span class="url">' . str_replace( 'http://', '', get_bloginfo( 'url' ) ) . '/' . esc_html( $slug ) . '/</span>';
  565. $content .= '<p class="desc">' . $datestr . '<span class="autogen"></span><span class="content">' . esc_html( $desc ) . '</span></p>';
  566. $content .= '</div>';
  567. $content = apply_filters( 'wpseo_snippet', $content, $post, compact( 'title', 'desc', 'date', 'slug' ) );
  568. return $content;
  569. }
  570. /**
  571. * Save the WP SEO metadata for posts.
  572. *
  573. * @internal $_POST parameters are validated via sanitize_post_meta()
  574. *
  575. * @param int $post_id
  576. *
  577. * @return bool|void Boolean false if invalid save post request
  578. */
  579. function save_postdata( $post_id ) {
  580. if ( $post_id === null ) {
  581. return false;
  582. }
  583. if ( wp_is_post_revision( $post_id ) ) {
  584. $post_id = wp_is_post_revision( $post_id );
  585. }
  586. clean_post_cache( $post_id );
  587. $post = get_post( $post_id );
  588. if ( ! is_object( $post ) ) {
  589. // non-existent post
  590. return false;
  591. }
  592. $meta_boxes = apply_filters( 'wpseo_save_metaboxes', array() );
  593. $meta_boxes = array_merge( $meta_boxes, $this->get_meta_field_defs( 'general', $post->post_type ), $this->get_meta_field_defs( 'advanced' ) );
  594. foreach ( $meta_boxes as $key => $meta_box ) {
  595. $data = null;
  596. if ( 'checkbox' === $meta_box['type'] ) {
  597. $data = isset( $_POST[ self::$form_prefix . $key ] ) ? 'on' : 'off';
  598. } else {
  599. if ( isset( $_POST[ self::$form_prefix . $key ] ) ) {
  600. $data = $_POST[ self::$form_prefix . $key ];
  601. }
  602. }
  603. if ( isset( $data ) ) {
  604. self::set_value( $key, $data, $post_id );
  605. }
  606. }
  607. do_action( 'wpseo_saved_postdata' );
  608. }
  609. /**
  610. * Enqueues all the needed JS and CSS.
  611. * @todo [JRF => whomever] create css/metabox-mp6.css file and add it to the below allowed colors array when done
  612. */
  613. public function enqueue() {
  614. global $pagenow;
  615. if ( ! in_array( $pagenow, array(
  616. 'post-new.php',
  617. 'post.php',
  618. 'edit.php',
  619. ), true ) || $this->is_metabox_hidden() === true
  620. ) {
  621. return;
  622. }
  623. $color = get_user_meta( get_current_user_id(), 'admin_color', true );
  624. if ( '' == $color || in_array( $color, array( 'classic', 'fresh' ), true ) === false ) {
  625. $color = 'fresh';
  626. }
  627. if ( $pagenow == 'edit.php' ) {
  628. wp_enqueue_style( 'edit-page', plugins_url( 'css/edit-page' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
  629. } else {
  630. if ( 0 != get_queried_object_id() ) {
  631. wp_enqueue_media( array( 'post' => get_queried_object_id() ) ); // enqueue files needed for upload functionality
  632. }
  633. wp_enqueue_style( 'metabox-tabs', plugins_url( 'css/metabox-tabs' . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
  634. wp_enqueue_style( "metabox-$color", plugins_url( 'css/metabox-' . esc_attr( $color ) . WPSEO_CSSJS_SUFFIX . '.css', WPSEO_FILE ), array(), WPSEO_VERSION );
  635. wp_enqueue_script( 'jquery-ui-autocomplete' );
  636. wp_enqueue_script( 'jquery-qtip', plugins_url( 'js/jquery.qtip.min.js', WPSEO_FILE ), array( 'jquery' ), '1.0.0-RC3', true );
  637. wp_enqueue_script( 'wp-seo-metabox', plugins_url( 'js/wp-seo-metabox' . WPSEO_CSSJS_SUFFIX . '.js', WPSEO_FILE ), array(
  638. 'jquery',
  639. 'jquery-ui-core',
  640. 'jquery-ui-autocomplete',
  641. ), WPSEO_VERSION, true );
  642. 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 );
  643. wp_localize_script( 'wpseo-admin-media', 'wpseoMediaL10n', $this->localize_media_script() );
  644. // Text strings to pass to metabox for keyword analysis
  645. wp_localize_script( 'wp-seo-metabox', 'wpseoMetaboxL10n', $this->localize_script() );
  646. }
  647. }
  648. /**
  649. * Pass some variables to js for upload module.
  650. *
  651. * @return array
  652. */
  653. public function localize_media_script() {
  654. return array(
  655. 'choose_image' => __( 'Use Image', 'wordpress-seo' ),
  656. );
  657. }
  658. /**
  659. * Adds a dropdown that allows filtering on the posts SEO Quality.
  660. *
  661. * @return bool
  662. */
  663. function posts_filter_dropdown() {
  664. global $pagenow;
  665. if ( $pagenow === 'upload.php' || $this->is_metabox_hidden() === true ) {
  666. return;
  667. }
  668. $scores_array = array(
  669. 'na' => __( 'SEO: No Focus Keyword', 'wordpress-seo' ),
  670. 'bad' => __( 'SEO: Bad', 'wordpress-seo' ),
  671. 'poor' => __( 'SEO: Poor', 'wordpress-seo' ),
  672. 'ok' => __( 'SEO: OK', 'wordpress-seo' ),
  673. 'good' => __( 'SEO: Good', 'wordpress-seo' ),
  674. 'noindex' => __( 'SEO: Post Noindexed', 'wordpress-seo' )
  675. );
  676. echo '<select name="seo_filter">';
  677. echo '<option value="">' . __( 'All SEO Scores', 'wordpress-seo' ) . '</option>';
  678. foreach ( $scores_array as $val => $text ) {
  679. $sel = '';
  680. if ( isset( $_GET['seo_filter'] ) ) {
  681. $sel = selected( $_GET['seo_filter'], $val, false );
  682. }
  683. echo '<option ' . $sel . 'value="' . $val . '">' . $text . '</option>';
  684. }
  685. echo '</select>';
  686. }
  687. /**
  688. * Adds the column headings for the SEO plugin for edit posts / pages overview
  689. *
  690. * @param array $columns Already existing columns.
  691. *
  692. * @return array
  693. */
  694. function column_heading( $columns ) {
  695. if ( $this->is_metabox_hidden() === true ) {
  696. return $columns;
  697. }
  698. return array_merge( $columns, array(
  699. 'wpseo-score' => __( 'SEO', 'wordpress-seo' ),
  700. 'wpseo-title' => __( 'SEO Title', 'wordpress-seo' ),
  701. 'wpseo-metadesc' => __( 'Meta Desc.', 'wordpress-seo' ),
  702. 'wpseo-focuskw' => __( 'Focus KW', 'wordpress-seo' )
  703. ) );
  704. }
  705. /**
  706. * Display the column content for the given column
  707. *
  708. * @param string $column_name Column to display the content for.
  709. * @param int $post_id Post to display the column content for.
  710. */
  711. function column_content( $column_name, $post_id ) {
  712. if ( $this->is_metabox_hidden() === true ) {
  713. return;
  714. }
  715. if ( $column_name === 'wpseo-score' ) {
  716. $score = self::get_value( 'linkdex', $post_id );
  717. if ( self::get_value( 'meta-robots-noindex', $post_id ) === '1' ) {
  718. $score_label = 'noindex';
  719. $title = __( 'Post is set to noindex.', 'wordpress-seo' );
  720. self::set_value( 'linkdex', 0, $post_id );
  721. } elseif ( $score !== '' ) {
  722. $nr = wpseo_calc( $score, '/', 10, true );
  723. $score_label = wpseo_translate_score( $nr );
  724. $title = wpseo_translate_score( $nr, false );
  725. unset( $nr );
  726. } else {
  727. $this->calculate_results( get_post( $post_id ) );
  728. $score = self::get_value( 'linkdex', $post_id );
  729. if ( $score === '' ) {
  730. $score_label = 'na';
  731. $title = __( 'Focus keyword not set.', 'wordpress-seo' );
  732. } else {
  733. $score_label = wpseo_translate_score( $score );
  734. $title = wpseo_translate_score( $score, false );
  735. }
  736. }
  737. echo '<div title="' . esc_attr( $title ) . '" class="wpseo-score-icon ' . esc_attr( $score_label ) . '"></div>';
  738. }
  739. if ( $column_name === 'wpseo-title' ) {
  740. echo esc_html( apply_filters( 'wpseo_title', wpseo_replace_vars( $this->page_title( $post_id ), get_post( $post_id, ARRAY_A ) ) ) );
  741. }
  742. if ( $column_name === 'wpseo-metadesc' ) {
  743. echo esc_html( apply_filters( 'wpseo_metadesc', wpseo_replace_vars( self::get_value( 'metadesc', $post_id ), get_post( $post_id, ARRAY_A ) ) ) );
  744. }
  745. if ( $column_name === 'wpseo-focuskw' ) {
  746. $focuskw = self::get_value( 'focuskw', $post_id );
  747. echo esc_html( $focuskw );
  748. }
  749. }
  750. /**
  751. * Indicate which of the SEO columns are sortable.
  752. *
  753. * @param array $columns appended with their orderby variable.
  754. *
  755. * @return array
  756. */
  757. function column_sort( $columns ) {
  758. if ( $this->is_metabox_hidden() === true ) {
  759. return $columns;
  760. }
  761. $columns['wpseo-score'] = 'wpseo-score';
  762. $columns['wpseo-metadesc'] = 'wpseo-metadesc';
  763. $columns['wpseo-focuskw'] = 'wpseo-focuskw';
  764. return $columns;
  765. }
  766. /**
  767. * Modify the query based on the seo_filter variable in $_GET
  768. *
  769. * @param array $vars Query variables.
  770. *
  771. * @return array
  772. */
  773. function column_sort_orderby( $vars ) {
  774. if ( isset( $_GET['seo_filter'] ) ) {
  775. $noindex = false;
  776. $high = false;
  777. switch ( $_GET['seo_filter'] ) {
  778. case 'noindex':
  779. $low = false;
  780. $noindex = true;
  781. break;
  782. case 'na':
  783. $low = 0;
  784. $high = 0;
  785. break;
  786. case 'bad':
  787. $low = 1;
  788. $high = 34;
  789. break;
  790. case 'poor':
  791. $low = 35;
  792. $high = 54;
  793. break;
  794. case 'ok':
  795. $low = 55;
  796. $high = 74;
  797. break;
  798. case 'good':
  799. $low = 75;
  800. $high = 100;
  801. break;
  802. default:
  803. $low = false;
  804. $high = false;
  805. $noindex = false;
  806. break;
  807. }
  808. if ( $low !== false ) {
  809. /* @internal DON'T touch the order of these without double-checking/adjusting
  810. * the seo_score_posts_where() method below! */
  811. $vars = array_merge(
  812. $vars,
  813. array(
  814. 'meta_query' => array(
  815. 'relation' => 'AND',
  816. array(
  817. 'key' => self::$meta_prefix . 'linkdex',
  818. 'value' => array( $low, $high ),
  819. 'type' => 'numeric',
  820. 'compare' => 'BETWEEN',
  821. ),
  822. array(
  823. 'key' => self::$meta_prefix . 'meta-robots-noindex',
  824. 'value' => 'needs-a-value-anyway',
  825. 'compare' => 'NOT EXISTS',
  826. ),
  827. array(
  828. 'key' => self::$meta_prefix . 'meta-robots-noindex',
  829. 'value' => '1',
  830. 'compare' => '!=',
  831. ),
  832. )
  833. )
  834. );
  835. add_filter( 'posts_where', array( $this, 'seo_score_posts_where' ) );
  836. } elseif ( $noindex ) {
  837. $vars = array_merge(
  838. $vars,
  839. array(
  840. 'meta_query' => array(
  841. array(
  842. 'key' => self::$meta_prefix . 'meta-robots-noindex',
  843. 'value' => '1',
  844. 'compare' => '=',
  845. ),
  846. )
  847. )
  848. );
  849. }
  850. }
  851. if ( isset( $_GET['seo_kw_filter'] ) && $_GET['seo_kw_filter'] !== '' ) {
  852. $vars = array_merge(
  853. $vars, array(
  854. 'post_type' => 'any',
  855. 'meta_key' => self::$meta_prefix . 'focuskw',
  856. 'meta_value' => sanitize_text_field( $_GET['seo_kw_filter'] ),
  857. )
  858. );
  859. }
  860. if ( isset( $vars['orderby'] ) && 'wpseo-score' === $vars['orderby'] ) {
  861. $vars = array_merge(
  862. $vars, array(
  863. 'meta_key' => self::$meta_prefix . 'linkdex',
  864. 'orderby' => 'meta_value_num',
  865. )
  866. );
  867. }
  868. if ( isset( $vars['orderby'] ) && 'wpseo-metadesc' === $vars['orderby'] ) {
  869. $vars = array_merge(
  870. $vars, array(
  871. 'meta_key' => self::$meta_prefix . 'metadesc',
  872. 'orderby' => 'meta_value',
  873. )
  874. );
  875. }
  876. if ( isset( $vars['orderby'] ) && 'wpseo-focuskw' === $vars['orderby'] ) {
  877. $vars = array_merge(
  878. $vars, array(
  879. 'meta_key' => self::$meta_prefix . 'focuskw',
  880. 'orderby' => 'meta_value',
  881. )
  882. );
  883. }
  884. return $vars;
  885. }
  886. /**
  887. * Hacky way to get round the limitation that you can only have AND *or* OR relationship between
  888. * meta key clauses and not a combination - which is what we need.
  889. *
  890. * @param string $where
  891. *
  892. * @return string
  893. */
  894. function seo_score_posts_where( $where ) {
  895. global $wpdb;
  896. /* Find the two mutually exclusive noindex clauses which should be changed from AND to OR relation */
  897. $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\([^\)]+\)[^\)]+\))`';
  898. $replace = '$1( $2OR$3 )';
  899. $new_where = preg_replace( $find, $replace, $where );
  900. if ( $new_where ) {
  901. return $new_where;
  902. } else {
  903. return $where;
  904. }
  905. }
  906. /**
  907. * Retrieve the page title.
  908. *
  909. * @param int $post_id Post to retrieve the title for.
  910. *
  911. * @return string
  912. */
  913. function page_title( $post_id ) {
  914. $fixed_title = self::get_value( 'title', $post_id );
  915. if ( $fixed_title !== '' ) {
  916. return $fixed_title;
  917. } else {
  918. $post = get_post( $post_id );
  919. $options = WPSEO_Options::get_all();
  920. if ( is_object( $post ) && ( isset( $options[ 'title-' . $post->post_type ] ) && $options[ 'title-' . $post->post_type ] !== '' ) ) {
  921. $title_template = $options[ 'title-' . $post->post_type ];
  922. $title_template = str_replace( ' %%page%% ', ' ', $title_template );
  923. return wpseo_replace_vars( $title_template, $post );
  924. } else {
  925. return wpseo_replace_vars( '%%title%%', $post );
  926. }
  927. }
  928. }
  929. /**
  930. * Sort an array by a given key.
  931. *
  932. * @param array $array Array to sort, array is returned sorted.
  933. * @param string $key Key to sort array by.
  934. */
  935. function aasort( &$array, $key ) {
  936. $sorter = array();
  937. $ret = array();
  938. reset( $array );
  939. foreach ( $array as $ii => $va ) {
  940. $sorter[ $ii ] = $va[ $key ];
  941. }
  942. asort( $sorter );
  943. foreach ( $sorter as $ii => $va ) {
  944. $ret[ $ii ] = $array[ $ii ];
  945. }
  946. $array = $ret;
  947. }
  948. /**
  949. * Output the page analysis results.
  950. *
  951. * @param object $post Post to output the page analysis results for.
  952. *
  953. * @return string
  954. */
  955. function linkdex_output( $post ) {
  956. $results = $this->calculate_results( $post );
  957. if ( is_wp_error( $results ) ) {
  958. $error = $results->get_error_messages();
  959. return '<tr><td><div class="wpseo_msg"><p><strong>' . esc_html( $error[0] ) . '</strong></p></div></td></tr>';
  960. }
  961. $output = '';
  962. if ( is_array( $results ) && $results !== array() ) {
  963. $output = '<table class="wpseoanalysis">';
  964. $perc_score = absint( $results['total'] );
  965. unset( $results['total'] ); // unset to prevent echoing it.
  966. foreach ( $results as $result ) {
  967. if ( is_array( $result ) ) {
  968. $score = wpseo_translate_score( $result['val'] );
  969. $output .= '<tr><td class="score"><div class="' . esc_attr( 'wpseo-score-icon ' . $score ) . '"></div></td><td>' . $result['msg'] . '</td></tr>';
  970. }
  971. }
  972. $output .= '</table>';
  973. if ( WP_DEBUG === true || ( defined( 'WPSEO_DEBUG' ) && WPSEO_DEBUG === true ) ) {
  974. $output .= '<p><small>(' . $perc_score . '%)</small></p>';
  975. }
  976. }
  977. $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;
  978. unset( $results );
  979. return $output;
  980. }
  981. /**
  982. * Calculate the page analysis results for post.
  983. *
  984. * @todo [JRF => whomever] check whether the results of this method are always checked with is_wp_error()
  985. * @todo [JRF => whomever] check the usage of this method as it's quite intense/heavy, see if it's only
  986. * used when really necessary
  987. * @todo [JRF => whomever] see if we can get rid of the passing by reference of $results as it makes
  988. * the code obfuscated
  989. *
  990. * @param object $post Post to calculate the results for.
  991. *
  992. * @return array|WP_Error
  993. */
  994. function calculate_results( $post ) {
  995. $options = WPSEO_Options::get_all();
  996. if ( ! class_exists( 'DOMDocument' ) ) {
  997. $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' ) );
  998. return $result;
  999. }
  1000. if ( ! is_array( $post ) && ! is_object( $post ) ) {
  1001. $result = new WP_Error( 'no-post', __( 'No post content to analyse.', 'wordpress-seo' ) );
  1002. return $result;
  1003. } elseif ( self::get_value( 'focuskw', $post->ID ) === '' ) {
  1004. $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 ) );
  1005. self::set_value( 'linkdex', 0, $post->ID );
  1006. return $result;
  1007. } elseif ( apply_filters( 'wpseo_use_page_analysis', true ) !== true ) {
  1008. $result = new WP_Error( 'page-analysis-disabled', sprintf( __( 'Page Analysis has been disabled.', 'wordpress-seo' ), $post->post_type ) );
  1009. return $result;
  1010. }
  1011. $results = array();
  1012. $job = array();
  1013. $sampleurl = $this->get_sample_permalink( $post );
  1014. $job['pageUrl'] = preg_replace( '`%(?:post|page)name%`', $sampleurl[1], $sampleurl[0] );
  1015. $job['pageSlug'] = urldecode( $post->post_name );
  1016. $job['keyword'] = self::get_value( 'focuskw' );
  1017. $job['keyword_folded'] = $this->strip_separators_and_fold( $job['keyword'] );
  1018. $job['post_id'] = $post->ID;
  1019. $job['post_type'] = $post->post_type;
  1020. $dom = new domDocument;
  1021. $dom->strictErrorChecking = false;
  1022. $dom->preserveWhiteSpace = false;
  1023. /**
  1024. * Filter: 'wpseo_pre_analysis_post_content' - Make the post content filterable before calculating the page analysis
  1025. *
  1026. * @api string $post_content The post content
  1027. *
  1028. * @param object $post The post
  1029. */
  1030. $post_content = apply_filters( 'wpseo_pre_analysis_post_content', $post->post_content, $post );
  1031. // Check if the post content is not empty
  1032. if ( ! empty( $post_content ) ) {
  1033. @$dom->loadHTML( $post_content );
  1034. }
  1035. unset( $post_content );
  1036. $xpath = new DOMXPath( $dom );
  1037. // Check if this focus keyword has been used already.
  1038. $this->check_double_focus_keyword( $job, $results );
  1039. // Keyword
  1040. $this->score_keyword( $job['keyword'], $results );
  1041. // Title
  1042. $title = self::get_value( 'title' );
  1043. if ( $title !== '' ) {
  1044. $job['title'] = $title;
  1045. } else {
  1046. if ( isset( $options[ 'title-' . $post->post_type ] ) && $options[ 'title-' . $post->post_type ] !== '' ) {
  1047. $title_template = $options[ 'title-' . $post->post_type ];
  1048. } else {
  1049. $title_template = '%%title%% - %%sitename%%';
  1050. }
  1051. $job['title'] = wpseo_replace_vars( $title_template, $post );
  1052. }
  1053. unset( $title );
  1054. $this->score_title( $job, $results );
  1055. // Meta description
  1056. $description = '';
  1057. $desc_meta = self::get_value( 'metadesc' );
  1058. if ( $desc_meta !== '' ) {
  1059. $description = $desc_meta;
  1060. } elseif ( isset( $options[ 'metadesc-' . $post->post_type ] ) && $options[ 'metadesc-' . $post->post_type ] !== '' ) {
  1061. $description = wpseo_replace_vars( $options[ 'metadesc-' . $post->post_type ], $post );
  1062. }
  1063. unset( $desc_meta );
  1064. self::$meta_length = apply_filters( 'wpseo_metadesc_length', self::$meta_length, $post );
  1065. $this->score_description( $job, $results, $description, self::$meta_length );
  1066. unset( $description );
  1067. // Body
  1068. $body = $this->get_body( $post );
  1069. $firstp = $this->get_first_paragraph( $body );
  1070. $this->score_body( $job, $results, $body, $firstp );
  1071. unset( $firstp );
  1072. // URL
  1073. $this->score_url( $job, $results );
  1074. // Headings
  1075. $headings = $this->get_headings( $body );
  1076. $this->score_headings( $job, $results, $headings );
  1077. unset( $headings );
  1078. // Images
  1079. $imgs = array();
  1080. $imgs['count'] = substr_count( $body, '<img' );
  1081. $imgs = $this->get_images_alt_text( $post->ID, $body, $imgs );
  1082. // Check featured image
  1083. if ( function_exists( 'has_post_thumbnail' ) && has_post_thumbnail() ) {
  1084. $imgs['count'] += 1;
  1085. if ( empty( $imgs['alts'] ) ) {
  1086. $imgs['alts'] = array();
  1087. }
  1088. $imgs['alts'][] = $this->strtolower_utf8( get_post_meta( get_post_thumbnail_id( $post->ID ), '_wp_attachment_image_alt', true ) );
  1089. }
  1090. $this->score_images_alt_text( $job, $results, $imgs );
  1091. unset( $imgs );
  1092. unset( $body );
  1093. // Anchors
  1094. $anchors = $this->get_anchor_texts( $xpath );
  1095. $count = $this->get_anchor_count( $xpath );
  1096. $this->score_anchor_texts( $job, $results, $anchors, $count );
  1097. unset( $anchors, $count, $dom );
  1098. $results = apply_filters( 'wpseo_linkdex_results', $results, $job, $post );
  1099. $this->aasort( $results, 'val' );
  1100. $overall = 0;
  1101. $overall_max = 0;
  1102. foreach ( $results as $result ) {
  1103. $overall += $result['val'];
  1104. $overall_

Large files files are truncated, but you can click here to view the full file