WPSEO_Meta

This class implements defaults and value validation for all WPSEO Post Meta values.

Defined (1)

The class is defined in the following location(s).

/inc/class-wpseo-meta.php  
  1. class WPSEO_Meta { 
  2.  
  3. /** 
  4. * @var string Prefix for all WPSEO meta values in the database 
  5. * @static 
  6. * @internal if at any point this would change, quite apart from an upgrade routine, this also will need to 
  7. * be changed in the wpml-config.xml file. 
  8. */ 
  9. public static $meta_prefix = '_yoast_wpseo_'; 
  10.  
  11.  
  12. /** 
  13. * @var string Prefix for all WPSEO meta value form field names and ids 
  14. * @static 
  15. */ 
  16. public static $form_prefix = 'yoast_wpseo_'; 
  17.  
  18.  
  19. /** 
  20. * @var int Allowed length of the meta description. 
  21. * @static 
  22. */ 
  23. public static $meta_length = 156; 
  24.  
  25.  
  26. /** 
  27. * @var string Reason the meta description is not the default length. 
  28. * @static 
  29. */ 
  30. public static $meta_length_reason = ''; 
  31.  
  32.  
  33. /** 
  34. * @var array $meta_fields Meta box field definitions for the meta box form 
  35. * Array format: 
  36. * (required) 'type' => (string) field type. i.e. text / textarea / checkbox / 
  37. * radio / select / multiselect / upload / snippetpreview etc 
  38. * (required) 'title' => (string) table row title 
  39. * (recommended) 'default_value' => (string|array) default value for the field 
  40. * IMPORTANT: 
  41. * - if the field has options, the default has to be the 
  42. * key of one of the options 
  43. * - if the field is a text field, the default **has** to be 
  44. * an empty string as otherwise the user can't save 
  45. * an empty value/delete the meta value 
  46. * - if the field is a checkbox, the only valid values 
  47. * are 'on' or 'off' 
  48. * (semi-required) 'options' => (array) options for used with (multi-)select and radio 
  49. * fields, required if that's the field type 
  50. * key = (string) value which will be saved to db 
  51. * value = (string) text label for the option 
  52. * (optional) 'autocomplete' => (bool) whether autocomplete is on for text fields,  
  53. * defaults to true 
  54. * (optional) 'class' => (string) classname(s) to add to the actual <input> tag 
  55. * (optional) 'description' => (string) description to show underneath the field 
  56. * (optional) 'expl' => (string) label for a checkbox 
  57. * (optional) 'help' => (string) help text to show on mouse over ? image 
  58. * (optional) 'rows' => (int) number of rows for a textarea, defaults to 3 
  59. * (optional) 'placeholder' => (string) Currently only used by add-on plugins 
  60. * (optional) 'serialized' => (bool) whether the value is expected to be serialized,  
  61. * i.e. an array or object, defaults to false 
  62. * Currently only used by add-on plugins 
  63. * @static 
  64. * @internal 
  65. * - Titles, help texts, description text and option labels are added via a translate_meta_boxes() method 
  66. * in the relevant child classes (WPSEO_Metabox and WPSEO_Social_admin) as they are only needed there. 
  67. * - Beware: even though the meta keys are divided into subsets, they still have to be uniquely named! 
  68. */ 
  69. public static $meta_fields = array( 
  70. 'general' => array( 
  71. 'snippetpreview' => array( 
  72. 'type' => 'snippetpreview',  
  73. 'title' => '', // Translation added later. 
  74. 'help' => '', // Translation added later. 
  75. 'help-button' => '', // Translation added later. 
  76. ),  
  77. 'focuskw_text_input' => array( 
  78. 'type' => 'focuskeyword',  
  79. 'title' => '', // Translation added later. 
  80. 'default_value' => '',  
  81. 'autocomplete' => false,  
  82. 'help' => '', // Translation added later. 
  83. 'description' => '<div id="focuskwresults"></div>',  
  84. 'help-button' => '', // Translation added later. 
  85. ),  
  86. 'focuskw' => array( 
  87. 'type' => 'hidden',  
  88. 'title' => '',  
  89. ),  
  90. 'title' => array( 
  91. 'type' => 'hidden',  
  92. 'title' => '', // Translation added later. 
  93. 'default_value' => '',  
  94. 'description' => '', // Translation added later. 
  95. 'help' => '', // Translation added later. 
  96. ),  
  97. 'metadesc' => array( 
  98. 'type' => 'hidden',  
  99. 'title' => '', // Translation added later. 
  100. 'default_value' => '',  
  101. 'class' => 'metadesc',  
  102. 'rows' => 2,  
  103. 'description' => '', // Translation added later. 
  104. 'help' => '', // Translation added later. 
  105. ),  
  106. 'linkdex' => array( 
  107. 'type' => 'hidden',  
  108. 'title' => 'linkdex',  
  109. 'default_value' => '0',  
  110. 'description' => '',  
  111. ),  
  112. 'content_score' => array( 
  113. 'type' => 'hidden',  
  114. 'title' => 'content_score',  
  115. 'default_value' => '0',  
  116. 'description' => '',  
  117. ),  
  118. 'metakeywords' => array( 
  119. 'type' => 'metakeywords',  
  120. 'title' => '', // Translation added later. 
  121. 'default_value' => '',  
  122. 'class' => 'metakeywords',  
  123. 'description' => '', // Translation added later. 
  124. ),  
  125. 'pageanalysis' => array( 
  126. 'type' => 'pageanalysis',  
  127. 'title' => '', // Translation added later. 
  128. 'help' => '', // Translation added later. 
  129. 'help-button' => '', // Translation added later. 
  130. ),  
  131. ),  
  132. 'advanced' => array( 
  133. 'meta-robots-noindex' => array( 
  134. 'type' => 'select',  
  135. 'title' => '', // Translation added later. 
  136. 'default_value' => '0', // = post-type default. 
  137. 'options' => array( 
  138. '0' => '', // Post type default - translation added later. 
  139. '2' => '', // Index - translation added later. 
  140. '1' => '', // No-index - translation added later. 
  141. ),  
  142. ),  
  143. 'meta-robots-nofollow' => array( 
  144. 'type' => 'radio',  
  145. 'title' => '', // Translation added later. 
  146. 'default_value' => '0', // = follow. 
  147. 'options' => array( 
  148. '0' => '', // Follow - translation added later. 
  149. '1' => '', // No-follow - translation added later. 
  150. ),  
  151. ),  
  152. 'meta-robots-adv' => array( 
  153. 'type' => 'multiselect',  
  154. 'title' => '', // Translation added later. 
  155. 'default_value' => '-', // = site-wide default. 
  156. 'description' => '', // Translation added later. 
  157. 'options' => array( 
  158. '-' => '', // Site-wide default - translation added later. 
  159. 'none' => '', // Translation added later. 
  160. 'noodp' => '', // Translation added later. 
  161. 'noimageindex' => '', // Translation added later. 
  162. 'noarchive' => '', // Translation added later. 
  163. 'nosnippet' => '', // Translation added later. 
  164. ),  
  165. ),  
  166. 'bctitle' => array( 
  167. 'type' => 'text',  
  168. 'title' => '', // Translation added later. 
  169. 'default_value' => '',  
  170. 'description' => '', // Translation added later. 
  171. ),  
  172. 'canonical' => array( 
  173. 'type' => 'text',  
  174. 'title' => '', // Translation added later. 
  175. 'default_value' => '',  
  176. 'description' => '', // Translation added later. 
  177. ),  
  178. 'redirect' => array( 
  179. 'type' => 'text',  
  180. 'title' => '', // Translation added later. 
  181. 'default_value' => '',  
  182. 'description' => '', // Translation added later. 
  183. ),  
  184. ),  
  185. 'social' => array(),  
  186. /** Fields we should validate & save, but not show on any form */ 
  187. 'non_form' => array( 
  188. 'linkdex' => array( 
  189. 'type' => null,  
  190. 'default_value' => '0',  
  191. ),  
  192. ),  
  193. ); 
  194.  
  195.  
  196. /** 
  197. * @var array Helper property - reverse index of the definition array 
  198. * Format: [full meta key including prefix] => array 
  199. * ['subset'] => (string) primary index 
  200. * ['key'] => (string) internal key 
  201. * @static 
  202. */ 
  203. public static $fields_index = array(); 
  204.  
  205.  
  206. /** 
  207. * @var array Helper property - array containing only the defaults in the format: 
  208. * [full meta key including prefix] => (string) default value 
  209. * @static 
  210. */ 
  211. public static $defaults = array(); 
  212.  
  213. /** 
  214. * @var array Helper property to define the social network meta field definitions - networks 
  215. * @static 
  216. */ 
  217. private static $social_networks = array( 
  218. 'opengraph' => 'opengraph',  
  219. 'twitter' => 'twitter',  
  220. ); 
  221.  
  222. /** 
  223. * @var array Helper property to define the social network meta field definitions - fields and their type 
  224. * @static 
  225. */ 
  226. private static $social_fields = array( 
  227. 'title' => 'text',  
  228. 'description' => 'textarea',  
  229. 'image' => 'upload',  
  230. ); 
  231.  
  232.  
  233. /** 
  234. * Register our actions and filters 
  235. * @static 
  236. * @return void 
  237. */ 
  238. public static function init() { 
  239.  
  240. $options = WPSEO_Options::get_option( 'wpseo_social' ); 
  241. foreach ( self::$social_networks as $option => $network ) { 
  242. if ( true === $options[ $option ] ) { 
  243. foreach ( self::$social_fields as $box => $type ) { 
  244. self::$meta_fields['social'][ $network . '-' . $box ] = array( 
  245. 'type' => $type,  
  246. 'title' => '', // Translation added later. 
  247. 'default_value' => '',  
  248. 'description' => '', // Translation added later. 
  249. ); 
  250. unset( $options, $option, $network, $box, $type ); 
  251.  
  252. /** 
  253. * Allow add-on plugins to register their meta fields for management by this class 
  254. * add_filter() calls must be made before plugins_loaded prio 14 
  255. */ 
  256. $extra_fields = apply_filters( 'add_extra_wpseo_meta_fields', array() ); 
  257. if ( is_array( $extra_fields ) ) { 
  258. self::$meta_fields = self::array_merge_recursive_distinct( $extra_fields, self::$meta_fields ); 
  259. unset( $extra_fields ); 
  260.  
  261. $register = function_exists( 'register_meta' ); 
  262.  
  263. foreach ( self::$meta_fields as $subset => $field_group ) { 
  264. foreach ( $field_group as $key => $field_def ) { 
  265. if ( $field_def['type'] !== 'snippetpreview' ) { 
  266. /** 
  267. * Function register_meta() is undocumented and not used by WP internally, wrapped in 
  268. * function_exists as a precaution in case they remove it. 
  269. */ 
  270. if ( $register === true ) { 
  271. register_meta( 'post', self::$meta_prefix . $key, array( __CLASS__, 'sanitize_post_meta' ) ); 
  272. else { 
  273. add_filter( 'sanitize_post_meta_' . self::$meta_prefix . $key, array( __CLASS__, 'sanitize_post_meta' ), 10, 2 ); 
  274.  
  275. // Set the $fields_index property for efficiency. 
  276. self::$fields_index[ self::$meta_prefix . $key ] = array( 
  277. 'subset' => $subset,  
  278. 'key' => $key,  
  279. ); 
  280.  
  281. // Set the $defaults property for efficiency. 
  282. if ( isset( $field_def['default_value'] ) ) { 
  283. self::$defaults[ self::$meta_prefix . $key ] = $field_def['default_value']; 
  284. else { 
  285. // Meta will always be a string, so let's make the meta meta default also a string. 
  286. self::$defaults[ self::$meta_prefix . $key ] = ''; 
  287. unset( $subset, $field_group, $key, $field_def, $register ); 
  288.  
  289. add_filter( 'update_post_metadata', array( __CLASS__, 'remove_meta_if_default' ), 10, 5 ); 
  290. add_filter( 'add_post_metadata', array( __CLASS__, 'dont_save_meta_if_default' ), 10, 4 ); 
  291.  
  292.  
  293. /** 
  294. * Retrieve the meta box form field definitions for the given tab and post type. 
  295. * @static 
  296. * @param string $tab Tab for which to retrieve the field definitions. 
  297. * @param string $post_type Post type of the current post. 
  298. * @return array Array containing the meta box field definitions 
  299. */ 
  300. public static function get_meta_field_defs( $tab, $post_type = 'post' ) { 
  301. if ( ! isset( self::$meta_fields[ $tab ] ) ) { 
  302. return array(); 
  303.  
  304. $field_defs = self::$meta_fields[ $tab ]; 
  305.  
  306. switch ( $tab ) { 
  307. case 'non-form': 
  308. // Prevent non-form fields from being passed to forms. 
  309. $field_defs = array(); 
  310. break; 
  311.  
  312.  
  313. case 'general': 
  314. $options = WPSEO_Options::get_option( 'wpseo_titles' ); 
  315. if ( $options['usemetakeywords'] === true ) { 
  316. /** Adjust the link in the keywords description text string based on the post type */ 
  317. $field_defs['metakeywords']['description'] = sprintf( $field_defs['metakeywords']['description'], '<a target="_blank" href="' . esc_url( admin_url( 'admin.php?page=wpseo_titles#top#post_types' ) ) . '">', '</a>' ); 
  318. else { 
  319. /** Don't show the keywords field if keywords aren't enabled */ 
  320. unset( $field_defs['metakeywords'] ); 
  321. /** 
  322. * Filter the WPSEO metabox form field definitions for the general tab, backward compatibility 
  323. * @deprecated 1.5.0 
  324. * @deprecated use the 'wpseo_metabox_entries_general' filter instead 
  325. * @see WPSEO_Meta::get_meta_field_defs() 
  326. * @param array $field_defs Metabox orm definitions. 
  327. * @return array 
  328. */ 
  329. $field_defs = apply_filters( 'wpseo_metabox_entries', $field_defs ); 
  330. break; 
  331.  
  332.  
  333. case 'advanced': 
  334. global $post; 
  335.  
  336. $options = WPSEO_Options::get_options( array( 'wpseo', 'wpseo_titles', 'wpseo_internallinks' ) ); 
  337.  
  338. if ( ! current_user_can( 'manage_options' ) && $options['disableadvanced_meta'] ) { 
  339. return array(); 
  340.  
  341. $post_type = ''; 
  342. if ( isset( $post->post_type ) ) { 
  343. $post_type = $post->post_type; 
  344. elseif ( ! isset( $post->post_type ) && isset( $_GET['post_type'] ) ) { 
  345. $post_type = sanitize_text_field( $_GET['post_type'] ); 
  346.  
  347. /** Adjust the no-index 'default for post type' text string based on the post type */ 
  348. $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' ) ); 
  349.  
  350. /** Adjust the robots advanced 'site-wide default' text string based on those settings */ 
  351. if ( $options['noodp'] !== false ) { 
  352. $robots_adv = array(); 
  353. if ( $options['noodp'] === true ) { 
  354. // Use translation from field def options - mind that $options and $field_def['options'] keys should be the same! 
  355. $robots_adv[] = $field_defs['meta-robots-adv']['options']['noodp']; 
  356. $robots_adv = implode( ', ', $robots_adv ); 
  357. else { 
  358. $robots_adv = __( 'None', 'wordpress-seo' ); 
  359. $field_defs['meta-robots-adv']['options']['-'] = sprintf( $field_defs['meta-robots-adv']['options']['-'], $robots_adv ); 
  360. unset( $robots_adv ); 
  361.  
  362.  
  363. /** Don't show the breadcrumb title field if breadcrumbs aren't enabled */ 
  364. if ( $options['breadcrumbs-enable'] !== true && ! current_theme_supports( 'yoast-seo-breadcrumbs' ) ) { 
  365. unset( $field_defs['bctitle'] ); 
  366.  
  367. global $post; 
  368.  
  369. if ( empty( $post->ID ) || ( ! empty( $post->ID ) && self::get_value( 'redirect', $post->ID ) === '' ) ) { 
  370. unset( $field_defs['redirect'] ); 
  371. break; 
  372.  
  373. /** 
  374. * Filter the WPSEO metabox form field definitions for a tab 
  375. * {tab} can be 'general', 'advanced' or 'social' 
  376. * @param array $field_defs Metabox form definitions. 
  377. * @param string $post_type Post type of the post the metabox is for, defaults to 'post'. 
  378. * @return array 
  379. */ 
  380.  
  381. return apply_filters( 'wpseo_metabox_entries_' . $tab, $field_defs, $post_type ); 
  382.  
  383.  
  384. /** 
  385. * Validate the post meta values 
  386. * @static 
  387. * @param mixed $meta_value The new value. 
  388. * @param string $meta_key The full meta key (including prefix). 
  389. * @return string Validated meta value 
  390. */ 
  391. public static function sanitize_post_meta( $meta_value, $meta_key ) { 
  392. $field_def = self::$meta_fields[ self::$fields_index[ $meta_key ]['subset'] ][ self::$fields_index[ $meta_key ]['key'] ]; 
  393. $clean = self::$defaults[ $meta_key ]; 
  394.  
  395. switch ( true ) { 
  396. case ( $meta_key === self::$meta_prefix . 'linkdex' ): 
  397. $int = WPSEO_Utils::validate_int( $meta_value ); 
  398. if ( $int !== false && $int >= 0 ) { 
  399. $clean = strval( $int ); // Convert to string to make sure default check works. 
  400. break; 
  401.  
  402. case ( $field_def['type'] === 'checkbox' ): 
  403. // Only allow value if it's one of the predefined options. 
  404. if ( in_array( $meta_value, array( 'on', 'off' ), true ) ) { 
  405. $clean = $meta_value; 
  406. break; 
  407.  
  408.  
  409. case ( $field_def['type'] === 'select' || $field_def['type'] === 'radio' ): 
  410. // Only allow value if it's one of the predefined options. 
  411. if ( isset( $field_def['options'][ $meta_value ] ) ) { 
  412. $clean = $meta_value; 
  413. break; 
  414.  
  415.  
  416. case ( $field_def['type'] === 'multiselect' && $meta_key === self::$meta_prefix . 'meta-robots-adv' ): 
  417. $clean = self::validate_meta_robots_adv( $meta_value ); 
  418. break; 
  419.  
  420.  
  421. case ( $field_def['type'] === 'text' && $meta_key === self::$meta_prefix . 'canonical' ): 
  422. case ( $field_def['type'] === 'text' && $meta_key === self::$meta_prefix . 'redirect' ): 
  423. // Validate as url(-part). 
  424. $url = WPSEO_Utils::sanitize_url( $meta_value ); 
  425. if ( $url !== '' ) { 
  426. $clean = $url; 
  427. break; 
  428.  
  429.  
  430. case ( $field_def['type'] === 'upload' && $meta_key === self::$meta_prefix . 'opengraph-image' ): 
  431. // Validate as url. 
  432. $url = WPSEO_Utils::sanitize_url( $meta_value, array( 'http', 'https', 'ftp', 'ftps' ) ); 
  433. if ( $url !== '' ) { 
  434. $clean = $url; 
  435. break; 
  436.  
  437.  
  438. case ( $field_def['type'] === 'textarea' ): 
  439. if ( is_string( $meta_value ) ) { 
  440. // Remove line breaks and tabs. 
  441. // @todo [JRF => Yoast] verify that line breaks and the likes aren't allowed/recommended in meta header fields. 
  442. $meta_value = str_replace( array( "\n", "\r", "\t", ' ' ), ' ', $meta_value ); 
  443. $clean = WPSEO_Utils::sanitize_text_field( trim( $meta_value ) ); 
  444. break; 
  445.  
  446. case ( 'multiselect' === $field_def['type'] ): 
  447. $clean = $meta_value; 
  448. break; 
  449.  
  450.  
  451. case ( $field_def['type'] === 'text' ): 
  452. default: 
  453. if ( is_string( $meta_value ) ) { 
  454. $clean = WPSEO_Utils::sanitize_text_field( trim( $meta_value ) ); 
  455.  
  456. if ( $meta_key === self::$meta_prefix . 'focuskw' ) { 
  457. $clean = str_replace( array( 
  458. '<',  
  459. '>',  
  460. '"',  
  461. '`',  
  462. '<',  
  463. '>',  
  464. '"',  
  465. '`',  
  466. ), '', $clean ); 
  467. break; 
  468.  
  469. $clean = apply_filters( 'wpseo_sanitize_post_meta_' . $meta_key, $clean, $meta_value, $field_def, $meta_key ); 
  470.  
  471. return $clean; 
  472.  
  473.  
  474. /** 
  475. * Validate a meta-robots-adv meta value 
  476. * @todo [JRF => Yoast] Verify that this logic for the prioritisation is correct 
  477. * @static 
  478. * @param array|string $meta_value The value to validate. 
  479. * @return string Clean value 
  480. */ 
  481. public static function validate_meta_robots_adv( $meta_value ) { 
  482. $clean = self::$meta_fields['advanced']['meta-robots-adv']['default_value']; 
  483. $options = self::$meta_fields['advanced']['meta-robots-adv']['options']; 
  484.  
  485. if ( is_string( $meta_value ) ) { 
  486. $meta_value = explode( ', ', $meta_value ); 
  487.  
  488. if ( is_array( $meta_value ) && $meta_value !== array() ) { 
  489. $meta_value = array_map( 'trim', $meta_value ); 
  490.  
  491. if ( in_array( 'none', $meta_value, true ) ) { 
  492. // None is one of the selected values, takes priority over everything else. 
  493. $clean = 'none'; 
  494. elseif ( in_array( '-', $meta_value, true ) ) { 
  495. // Site-wide defaults is one of the selected values, takes priority over individual selected entries. 
  496. $clean = '-'; 
  497. else { 
  498. // Individual selected entries. 
  499. $cleaning = array(); 
  500. foreach ( $meta_value as $value ) { 
  501. if ( isset( $options[ $value ] ) ) { 
  502. $cleaning[] = $value; 
  503.  
  504. if ( $cleaning !== array() ) { 
  505. $clean = implode( ', ', $cleaning ); 
  506. unset( $cleaning, $value ); 
  507.  
  508. return $clean; 
  509.  
  510.  
  511. /** 
  512. * Prevent saving of default values and remove potential old value from the database if replaced by a default 
  513. * @static 
  514. * @param null $null Old, disregard. 
  515. * @param int $object_id ID of the current object for which the meta is being updated. 
  516. * @param string $meta_key The full meta key (including prefix). 
  517. * @param string $meta_value New meta value. 
  518. * @param string $prev_value The old meta value. 
  519. * @return null|bool true = stop saving, null = continue saving 
  520. */ 
  521. public static function remove_meta_if_default( $null, $object_id, $meta_key, $meta_value, $prev_value = '' ) { 
  522. /** If it's one of our meta fields, check against default */ 
  523. if ( isset( self::$fields_index[ $meta_key ] ) && self::meta_value_is_default( $meta_key, $meta_value ) === true ) { 
  524. if ( $prev_value !== '' ) { 
  525. delete_post_meta( $object_id, $meta_key, $prev_value ); 
  526. else { 
  527. delete_post_meta( $object_id, $meta_key ); 
  528.  
  529. return true; // Stop saving the value. 
  530.  
  531. return null; // Go on with the normal execution (update) in meta.php. 
  532.  
  533.  
  534. /** 
  535. * Prevent adding of default values to the database 
  536. * @static 
  537. * @param null $null Old, disregard. 
  538. * @param int $object_id ID of the current object for which the meta is being added. 
  539. * @param string $meta_key The full meta key (including prefix). 
  540. * @param string $meta_value New meta value. 
  541. * @return null|bool true = stop saving, null = continue saving 
  542. */ 
  543. public static function dont_save_meta_if_default( $null, $object_id, $meta_key, $meta_value ) { 
  544. /** If it's one of our meta fields, check against default */ 
  545. if ( isset( self::$fields_index[ $meta_key ] ) && self::meta_value_is_default( $meta_key, $meta_value ) === true ) { 
  546. return true; // Stop saving the value. 
  547.  
  548. return null; // Go on with the normal execution (add) in meta.php. 
  549.  
  550.  
  551. /** 
  552. * Is the given meta value the same as the default value ? 
  553. * @static 
  554. * @param string $meta_key The full meta key (including prefix). 
  555. * @param mixed $meta_value The value to check. 
  556. * @return bool 
  557. */ 
  558. public static function meta_value_is_default( $meta_key, $meta_value ) { 
  559. return ( isset( self::$defaults[ $meta_key ] ) && $meta_value === self::$defaults[ $meta_key ] ); 
  560.  
  561.  
  562. /** 
  563. * Get a custom post meta value 
  564. * Returns the default value if the meta value has not been set 
  565. * @internal Unfortunately there isn't a filter available to hook into before returning the results 
  566. * for get_post_meta(), get_post_custom() and the likes. That would have been the preferred solution. 
  567. * @static 
  568. * @param string $key Internal key of the value to get (without prefix). 
  569. * @param int $postid Post ID of the post to get the value for. 
  570. * @return string All 'normal' values returned from get_post_meta() are strings. 
  571. * Objects and arrays are possible, but not used by this plugin 
  572. * and therefore discarted (except when the special 'serialized' field def 
  573. * value is set to true - only used by add-on plugins for now). 
  574. * Will return the default value if no value was found.. 
  575. * Will return empty string if no default was found (not one of our keys) or 
  576. * if the post does not exist. 
  577. */ 
  578. public static function get_value( $key, $postid = 0 ) { 
  579. global $post; 
  580.  
  581. $postid = absint( $postid ); 
  582. if ( $postid === 0 ) { 
  583. if ( ( isset( $post ) && is_object( $post ) ) && ( isset( $post->post_status ) && $post->post_status !== 'auto-draft' ) ) { 
  584. $postid = $post->ID; 
  585. else { 
  586. return ''; 
  587.  
  588. $custom = get_post_custom( $postid ); // Array of strings or empty array. 
  589.  
  590. if ( isset( $custom[ self::$meta_prefix . $key ][0] ) ) { 
  591. $unserialized = maybe_unserialize( $custom[ self::$meta_prefix . $key ][0] ); 
  592. if ( $custom[ self::$meta_prefix . $key ][0] === $unserialized ) { 
  593. return $custom[ self::$meta_prefix . $key ][0]; 
  594. else { 
  595. $field_def = self::$meta_fields[ self::$fields_index[ self::$meta_prefix . $key ]['subset'] ][ self::$fields_index[ self::$meta_prefix . $key ]['key'] ]; 
  596. if ( isset( $field_def['serialized'] ) && $field_def['serialized'] === true ) { 
  597. // Ok, serialize value expected/allowed. 
  598. return $unserialized; 
  599.  
  600. // Meta was either not found or found, but object/array while not allowed to be. 
  601. if ( isset( self::$defaults[ self::$meta_prefix . $key ] ) ) { 
  602. return self::$defaults[ self::$meta_prefix . $key ]; 
  603. else { 
  604. /** 
  605. Shouldn't ever happen, means not one of our keys as there will always be a default available 
  606. for all our keys 
  607. */ 
  608. return ''; 
  609.  
  610.  
  611. /** 
  612. * Update a meta value for a post 
  613. * @static 
  614. * @param string $key The internal key of the meta value to change (without prefix). 
  615. * @param mixed $meta_value The value to set the meta to. 
  616. * @param int $post_id The ID of the post to change the meta for. 
  617. * @return bool whether the value was changed 
  618. */ 
  619. public static function set_value( $key, $meta_value, $post_id ) { 
  620. return update_post_meta( $post_id, self::$meta_prefix . $key, $meta_value ); 
  621.  
  622. /** 
  623. * Deletes a meta value for a post 
  624. * @static 
  625. * @param string $key The internal key of the meta value to change (without prefix). 
  626. * @param int $post_id The ID of the post to change the meta for. 
  627. * @return bool Whether the value was changed 
  628. */ 
  629. public static function delete( $key, $post_id ) { 
  630. return delete_post_meta( $post_id, self::$meta_prefix . $key ); 
  631.  
  632. /** 
  633. * Used for imports, this functions imports the value of $old_metakey into $new_metakey for those post 
  634. * where no WPSEO meta data has been set. 
  635. * Optionally deletes the $old_metakey values. 
  636. * @static 
  637. * @param string $old_metakey The old key of the meta value. 
  638. * @param string $new_metakey The new key, usually the WPSEO meta key (including prefix). 
  639. * @param bool $delete_old Whether to delete the old meta key/value-sets. 
  640. * @return void 
  641. */ 
  642. public static function replace_meta( $old_metakey, $new_metakey, $delete_old = false ) { 
  643. global $wpdb; 
  644.  
  645. /** 
  646. Get only those rows where no wpseo meta values exist for the same post 
  647. (with the exception of linkdex as that will be set independently of whether the post has been edited) 
  648. @internal Query is pretty well optimized this way 
  649. */ 
  650. $query = $wpdb->prepare( 
  651. SELECT `a`.* 
  652. FROM {$wpdb->postmeta} AS a 
  653. WHERE `a`.`meta_key` = %s 
  654. AND NOT EXISTS ( 
  655. SELECT DISTINCT `post_id` , count( `meta_id` ) AS count 
  656. FROM {$wpdb->postmeta} AS b 
  657. WHERE `a`.`post_id` = `b`.`post_id` 
  658. AND `meta_key` LIKE %s 
  659. AND `meta_key` <> %s 
  660. GROUP BY `post_id` 
  661. ;",  
  662. $old_metakey,  
  663. $wpdb->esc_like( self::$meta_prefix . '%' ),  
  664. self::$meta_prefix . 'linkdex' 
  665. ); 
  666. $oldies = $wpdb->get_results( $query ); 
  667.  
  668. if ( is_array( $oldies ) && $oldies !== array() ) { 
  669. foreach ( $oldies as $old ) { 
  670. update_post_meta( $old->post_id, $new_metakey, $old->meta_value ); 
  671.  
  672. // Delete old keys. 
  673. if ( $delete_old === true ) { 
  674. delete_post_meta_by_key( $old_metakey ); 
  675.  
  676.  
  677. /** 
  678. * General clean-up of the saved meta values 
  679. * - Remove potentially lingering old meta keys 
  680. * - Remove all default and invalid values 
  681. * @static 
  682. * @return void 
  683. */ 
  684. public static function clean_up() { 
  685. global $wpdb; 
  686.  
  687. /** 
  688. * Clean up '_yoast_wpseo_meta-robots' 
  689. * Retrieve all '_yoast_wpseo_meta-robots' meta values and convert if no new values found 
  690. * @internal Query is pretty well optimized this way 
  691. * @todo [JRF => Yoast] find out all possible values which the old '_yoast_wpseo_meta-robots' could contain 
  692. * to convert the data correctly 
  693. */ 
  694. $query = $wpdb->prepare( 
  695. SELECT `a`.* 
  696. FROM {$wpdb->postmeta} AS a 
  697. WHERE `a`.`meta_key` = %s 
  698. AND NOT EXISTS ( 
  699. SELECT DISTINCT `post_id` , count( `meta_id` ) AS count 
  700. FROM {$wpdb->postmeta} AS b 
  701. WHERE `a`.`post_id` = `b`.`post_id` 
  702. AND ( `meta_key` = %s 
  703. OR `meta_key` = %s ) 
  704. GROUP BY `post_id` 
  705. ;",  
  706. self::$meta_prefix . 'meta-robots',  
  707. self::$meta_prefix . 'meta-robots-noindex',  
  708. self::$meta_prefix . 'meta-robots-nofollow' 
  709. ); 
  710. $oldies = $wpdb->get_results( $query ); 
  711.  
  712. if ( is_array( $oldies ) && $oldies !== array() ) { 
  713. foreach ( $oldies as $old ) { 
  714. $old_values = explode( ', ', $old->meta_value ); 
  715. foreach ( $old_values as $value ) { 
  716. if ( $value === 'noindex' ) { 
  717. update_post_meta( $old->post_id, self::$meta_prefix . 'meta-robots-noindex', 1 ); 
  718. elseif ( $value === 'nofollow' ) { 
  719. update_post_meta( $old->post_id, self::$meta_prefix . 'meta-robots-nofollow', 1 ); 
  720. unset( $query, $oldies, $old, $old_values, $value ); 
  721.  
  722. // Delete old keys. 
  723. delete_post_meta_by_key( self::$meta_prefix . 'meta-robots' ); 
  724.  
  725.  
  726. /** 
  727. * Remove all default values and (most) invalid option values 
  728. * Invalid option values for the multiselect (meta-robots-adv) field will be dealt with seperately 
  729. * @internal some of the defaults have changed in v1.5, but as the defaults will be removed and 
  730. * new defaults will now automatically be passed when no data found, this update is automatic 
  731. * (as long as we remove the old values which we do in the below routine) 
  732. * @internal unfortunately we can't use the normal delete_meta() with key/value combination as '' 
  733. * (empty string) values will be ignored and would result in all metas with that key being deleted,  
  734. * not just the empty fields. 
  735. * Still, the below implementation is largely based on the delete_meta() function 
  736. */ 
  737. $query = array(); 
  738.  
  739. foreach ( self::$meta_fields as $subset => $field_group ) { 
  740. foreach ( $field_group as $key => $field_def ) { 
  741. if ( $field_def['type'] === 'snippetpreview' || ! isset( $field_def['default_value'] ) ) { 
  742. continue; 
  743.  
  744. if ( $key === 'meta-robots-adv' ) { 
  745. $query[] = $wpdb->prepare( 
  746. "( meta_key = %s AND ( meta_value = 'none' OR meta_value = '-' ) )",  
  747. self::$meta_prefix . $key 
  748. ); 
  749. elseif ( isset( $field_def['options'] ) && is_array( $field_def['options'] ) && $field_def['options'] !== array() ) { 
  750. $valid = $field_def['options']; 
  751. // Remove the default value from the valid options. 
  752. unset( $valid[ $field_def['default_value'] ] ); 
  753. $valid = array_keys( $valid ); 
  754.  
  755. $query[] = $wpdb->prepare( 
  756. "( meta_key = %s AND meta_value NOT IN ( '" . implode( "', '", esc_sql( $valid ) ) . "' ) )",  
  757. self::$meta_prefix . $key 
  758. ); 
  759. unset( $valid ); 
  760. elseif ( is_string( $field_def['default_value'] ) && $field_def['default_value'] !== '' ) { 
  761. $query[] = $wpdb->prepare( 
  762. '( meta_key = %s AND meta_value = %s )',  
  763. self::$meta_prefix . $key,  
  764. $field_def['default_value'] 
  765. ); 
  766. else { 
  767. $query[] = $wpdb->prepare( 
  768. "( meta_key = %s AND meta_value = '' )",  
  769. self::$meta_prefix . $key 
  770. ); 
  771. unset( $subset, $field_group, $key, $field_def ); 
  772.  
  773. $query = "SELECT meta_id FROM {$wpdb->postmeta} WHERE " . implode( ' OR ', $query ) . ';'; 
  774. $meta_ids = $wpdb->get_col( $query ); 
  775.  
  776. if ( is_array( $meta_ids ) && $meta_ids !== array() ) { 
  777. // WP native action. 
  778. do_action( 'delete_post_meta', $meta_ids, null, null, null ); 
  779.  
  780. $query = "DELETE FROM {$wpdb->postmeta} WHERE meta_id IN( " . implode( ', ', $meta_ids ) . ' )'; 
  781. $count = $wpdb->query( $query ); 
  782.  
  783. if ( $count ) { 
  784. foreach ( $meta_ids as $object_id ) { 
  785. wp_cache_delete( $object_id, 'post_meta' ); 
  786.  
  787. // WP native action. 
  788. do_action( 'deleted_post_meta', $meta_ids, null, null, null ); 
  789. unset( $query, $meta_ids, $count, $object_id ); 
  790.  
  791.  
  792. /** 
  793. * Deal with the multiselect (meta-robots-adv) field 
  794. * Removes invalid option combinations, such as 'none, noarchive' 
  795. * Default values have already been removed, so we should have a small result set and 
  796. * (hopefully) even smaller set of invalid results. 
  797. */ 
  798. $query = $wpdb->prepare( 
  799. "SELECT meta_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s",  
  800. self::$meta_prefix . 'meta-robots-adv' 
  801. ); 
  802. $oldies = $wpdb->get_results( $query ); 
  803.  
  804. if ( is_array( $oldies ) && $oldies !== array() ) { 
  805. foreach ( $oldies as $old ) { 
  806. $clean = self::validate_meta_robots_adv( $old->meta_value ); 
  807.  
  808. if ( $clean !== $old->meta_value ) { 
  809. if ( $clean !== self::$meta_fields['advanced']['meta-robots-adv']['default_value'] ) { 
  810. update_metadata_by_mid( 'post', $old->meta_id, $clean ); 
  811. else { 
  812. delete_metadata_by_mid( 'post', $old->meta_id ); 
  813. unset( $query, $oldies, $old, $clean ); 
  814.  
  815. do_action( 'wpseo_meta_clean_up' ); 
  816.  
  817.  
  818. /** 
  819. * Recursively merge a variable number of arrays, using the left array as base,  
  820. * giving priority to the right array. 
  821. * Difference with native array_merge_recursive(): 
  822. * array_merge_recursive converts values with duplicate keys to arrays rather than 
  823. * overwriting the value in the first array with the duplicate value in the second array. 
  824. * array_merge_recursive_distinct does not change the data types of the values in the arrays. 
  825. * Matching keys' values in the second array overwrite those in the first array, as is the 
  826. * case with array_merge. 
  827. * Freely based on information found on http://www.php.net/manual/en/function.array-merge-recursive.php 
  828. * @internal Should be moved to a general utility class 
  829. * @return array 
  830. */ 
  831. public static function array_merge_recursive_distinct() { 
  832.  
  833. $arrays = func_get_args(); 
  834. if ( count( $arrays ) < 2 ) { 
  835. if ( $arrays === array() ) { 
  836. return array(); 
  837. else { 
  838. return $arrays[0]; 
  839.  
  840. $merged = array_shift( $arrays ); 
  841.  
  842. foreach ( $arrays as $array ) { 
  843. foreach ( $array as $key => $value ) { 
  844. if ( is_array( $value ) && ( isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) ) { 
  845. $merged[ $key ] = self::array_merge_recursive_distinct( $merged[ $key ], $value ); 
  846. else { 
  847. $merged[ $key ] = $value; 
  848. unset( $key, $value ); 
  849.  
  850. return $merged; 
  851.  
  852. /** 
  853. * Get a value from $_POST for a given key 
  854. * Returns the $_POST value if exists, returns an empty string if key does not exist 
  855. * @static 
  856. * @param string $key Key of the value to get from $_POST. 
  857. * @return string Returns $_POST value, which will be a string the majority of the time 
  858. * Will return empty string if key does not exists in $_POST 
  859. */ 
  860. public static function get_post_value( $key ) { 
  861. return ( array_key_exists( $key, $_POST ) ) ? $_POST[ $key ] : ''; 
  862.  
  863. /** 
  864. * Counts the total of all the keywords being used for posts except the given one 
  865. * @param string $keyword The keyword to be counted. 
  866. * @param integer $post_id The is of the post to which the keyword belongs. 
  867. * @return array 
  868. */ 
  869. public static function keyword_usage( $keyword, $post_id ) { 
  870.  
  871. if ( empty( $keyword ) ) { 
  872. return array(); 
  873.  
  874. $get_posts = new WP_Query( 
  875. array( 
  876. 'meta_key' => '_yoast_wpseo_focuskw',  
  877. 'meta_value' => $keyword,  
  878. 'post__not_in' => array( $post_id ),  
  879. 'fields' => 'ids',  
  880. 'post_type' => 'any',  
  881.  
  882. /** 
  883. * We only need to return zero, one or two results: 
  884. * - Zero: keyword hasn't been used before 
  885. * - One: Keyword has been used once before 
  886. * - Two or more: Keyword has been used twice before 
  887. */ 
  888. 'posts_per_page' => 2,  
  889. ); 
  890.  
  891. return $get_posts->posts; 
  892. } /** End of class */