class-wpseo-replace-vars.php 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Internals
  6. * @since 1.5.4
  7. */
  8. // Avoid direct calls to this file.
  9. if ( ! defined( 'WPSEO_VERSION' ) ) {
  10. header( 'Status: 403 Forbidden' );
  11. header( 'HTTP/1.1 403 Forbidden' );
  12. exit();
  13. }
  14. /**
  15. * Class: WPSEO_Replace_Vars.
  16. *
  17. * This class implements the replacing of `%%variable_placeholders%%` with their real value based on the current
  18. * requested page/post/cpt/etc in text strings.
  19. */
  20. class WPSEO_Replace_Vars {
  21. /**
  22. * @var array Default post/page/cpt information.
  23. */
  24. protected $defaults = array(
  25. 'ID' => '',
  26. 'name' => '',
  27. 'post_author' => '',
  28. 'post_content' => '',
  29. 'post_date' => '',
  30. 'post_excerpt' => '',
  31. 'post_modified' => '',
  32. 'post_title' => '',
  33. 'taxonomy' => '',
  34. 'term_id' => '',
  35. 'term404' => '',
  36. );
  37. /**
  38. * @var object Current post/page/cpt information.
  39. */
  40. protected $args;
  41. /**
  42. * @var array Help texts for use in WPSEO -> Search appearance tabs.
  43. */
  44. protected static $help_texts = array();
  45. /**
  46. * @var array Register of additional variable replacements registered by other plugins/themes.
  47. */
  48. protected static $external_replacements = array();
  49. /**
  50. * Constructor.
  51. *
  52. * @return \WPSEO_Replace_Vars
  53. */
  54. public function __construct() {
  55. }
  56. /**
  57. * Setup the help texts and external replacements as statics so they will be available to all instances.
  58. */
  59. public static function setup_statics_once() {
  60. if ( self::$help_texts === array() ) {
  61. self::set_basic_help_texts();
  62. self::set_advanced_help_texts();
  63. }
  64. if ( self::$external_replacements === array() ) {
  65. /**
  66. * Action: 'wpseo_register_extra_replacements' - Allows for registration of additional
  67. * variables to replace.
  68. */
  69. do_action( 'wpseo_register_extra_replacements' );
  70. }
  71. }
  72. /**
  73. * Register new replacement %%variables%%.
  74. * For use by other plugins/themes to register extra variables.
  75. *
  76. * @see wpseo_register_var_replacement() for a usage example.
  77. *
  78. * @param string $var The name of the variable to replace, i.e. '%%var%%'
  79. * - the surrounding %% are optional.
  80. * @param mixed $replace_function Function or method to call to retrieve the replacement value for the variable
  81. * Uses the same format as add_filter/add_action function parameter and
  82. * should *return* the replacement value. DON'T echo it.
  83. * @param string $type Type of variable: 'basic' or 'advanced', defaults to 'advanced'.
  84. * @param string $help_text Help text to be added to the help tab for this variable.
  85. *
  86. * @return bool Whether the replacement function was succesfully registered.
  87. */
  88. public static function register_replacement( $var, $replace_function, $type = 'advanced', $help_text = '' ) {
  89. $success = false;
  90. if ( is_string( $var ) && $var !== '' ) {
  91. $var = self::remove_var_delimiter( $var );
  92. if ( preg_match( '`^[A-Z0-9_-]+$`i', $var ) === false ) {
  93. trigger_error( esc_html__( 'A replacement variable can only contain alphanumeric characters, an underscore or a dash. Try renaming your variable.', 'wordpress-seo' ), E_USER_WARNING );
  94. }
  95. elseif ( strpos( $var, 'cf_' ) === 0 || strpos( $var, 'ct_' ) === 0 ) {
  96. trigger_error( esc_html__( '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 );
  97. }
  98. elseif ( ! method_exists( __CLASS__, 'retrieve_' . $var ) ) {
  99. if ( $var !== '' && ! isset( self::$external_replacements[ $var ] ) ) {
  100. self::$external_replacements[ $var ] = $replace_function;
  101. $replacement_variable = new WPSEO_Replacement_Variable( $var, $var, $help_text );
  102. self::register_help_text( $type, $replacement_variable );
  103. $success = true;
  104. }
  105. else {
  106. trigger_error( esc_html__( 'A replacement variable with the same name has already been registered. Try making your variable name unique.', 'wordpress-seo' ), E_USER_WARNING );
  107. }
  108. }
  109. else {
  110. trigger_error( esc_html__( '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 );
  111. }
  112. }
  113. return $success;
  114. }
  115. /**
  116. * Replace `%%variable_placeholders%%` with their real value based on the current requested page/post/cpt/etc.
  117. *
  118. * @param string $string The string to replace the variables in.
  119. * @param array $args The object some of the replacement values might come from,
  120. * could be a post, taxonomy or term.
  121. * @param array $omit Variables that should not be replaced by this function.
  122. *
  123. * @return string
  124. */
  125. public function replace( $string, $args, $omit = array() ) {
  126. $string = wp_strip_all_tags( $string );
  127. // Let's see if we can bail super early.
  128. if ( strpos( $string, '%%' ) === false ) {
  129. return WPSEO_Utils::standardize_whitespace( $string );
  130. }
  131. $args = (array) $args;
  132. if ( isset( $args['post_content'] ) && ! empty( $args['post_content'] ) ) {
  133. $args['post_content'] = WPSEO_Utils::strip_shortcode( $args['post_content'] );
  134. }
  135. if ( isset( $args['post_excerpt'] ) && ! empty( $args['post_excerpt'] ) ) {
  136. $args['post_excerpt'] = WPSEO_Utils::strip_shortcode( $args['post_excerpt'] );
  137. }
  138. $this->args = (object) wp_parse_args( $args, $this->defaults );
  139. // Clean $omit array.
  140. if ( is_array( $omit ) && $omit !== array() ) {
  141. $omit = array_map( array( __CLASS__, 'remove_var_delimiter' ), $omit );
  142. }
  143. $replacements = array();
  144. if ( preg_match_all( '`%%([^%]+(%%single)?)%%?`iu', $string, $matches ) ) {
  145. $replacements = $this->set_up_replacements( $matches, $omit );
  146. }
  147. /**
  148. * Filter: 'wpseo_replacements' - Allow customization of the replacements before they are applied.
  149. *
  150. * @api array $replacements The replacements.
  151. *
  152. * @param array $args The object some of the replacement values might come from,
  153. * could be a post, taxonomy or term.
  154. */
  155. $replacements = apply_filters( 'wpseo_replacements', $replacements, $this->args );
  156. // Do the actual replacements.
  157. if ( is_array( $replacements ) && $replacements !== array() ) {
  158. $string = str_replace( array_keys( $replacements ), array_values( $replacements ), $string );
  159. }
  160. /**
  161. * Filter: 'wpseo_replacements_final' - Allow overruling of whether or not to remove placeholders
  162. * which didn't yield a replacement.
  163. *
  164. * @example <code>add_filter( 'wpseo_replacements_final', '__return_false' );</code>
  165. *
  166. * @api bool $final
  167. */
  168. if ( apply_filters( 'wpseo_replacements_final', true ) === true && ( isset( $matches[1] ) && is_array( $matches[1] ) ) ) {
  169. // Remove non-replaced variables.
  170. $remove = array_diff( $matches[1], $omit ); // Make sure the $omit variables do not get removed.
  171. $remove = array_map( array( __CLASS__, 'add_var_delimiter' ), $remove );
  172. $string = str_replace( $remove, '', $string );
  173. }
  174. // Undouble separators which have nothing between them, i.e. where a non-replaced variable was removed.
  175. if ( isset( $replacements['%%sep%%'] ) && ( is_string( $replacements['%%sep%%'] ) && $replacements['%%sep%%'] !== '' ) ) {
  176. $q_sep = preg_quote( $replacements['%%sep%%'], '`' );
  177. $string = preg_replace( '`' . $q_sep . '(?:\s*' . $q_sep . ')*`u', $replacements['%%sep%%'], $string );
  178. }
  179. // Remove superfluous whitespace.
  180. $string = WPSEO_Utils::standardize_whitespace( $string );
  181. return $string;
  182. }
  183. /**
  184. * Retrieve the replacements for the variables found.
  185. *
  186. * @param array $matches Variables found in the original string - regex result.
  187. * @param array $omit Variables that should not be replaced by this function.
  188. *
  189. * @return array Retrieved replacements - this might be a smaller array as some variables
  190. * may not yield a replacement in certain contexts.
  191. */
  192. private function set_up_replacements( $matches, $omit ) {
  193. $replacements = array();
  194. // @todo -> Figure out a way to deal with external functions starting with cf_/ct_.
  195. foreach ( $matches[1] as $k => $var ) {
  196. // Don't set up replacements which should be omitted.
  197. if ( in_array( $var, $omit, true ) ) {
  198. continue;
  199. }
  200. // Deal with variable variable names first.
  201. if ( strpos( $var, 'cf_' ) === 0 ) {
  202. $replacement = $this->retrieve_cf_custom_field_name( $var );
  203. }
  204. elseif ( strpos( $var, 'ct_desc_' ) === 0 ) {
  205. $replacement = $this->retrieve_ct_desc_custom_tax_name( $var );
  206. }
  207. elseif ( strpos( $var, 'ct_' ) === 0 ) {
  208. $single = ( isset( $matches[2][ $k ] ) && $matches[2][ $k ] !== '' ) ? true : false;
  209. $replacement = $this->retrieve_ct_custom_tax_name( $var, $single );
  210. } // Deal with non-variable variable names.
  211. elseif ( method_exists( $this, 'retrieve_' . $var ) ) {
  212. $method_name = 'retrieve_' . $var;
  213. $replacement = $this->$method_name();
  214. } // Deal with externally defined variable names.
  215. elseif ( isset( self::$external_replacements[ $var ] ) && ! is_null( self::$external_replacements[ $var ] ) ) {
  216. $replacement = call_user_func( self::$external_replacements[ $var ], $var, $this->args );
  217. }
  218. // Replacement retrievals can return null if no replacement can be determined, root those outs.
  219. if ( isset( $replacement ) ) {
  220. $var = self::add_var_delimiter( $var );
  221. $replacements[ $var ] = $replacement;
  222. }
  223. unset( $replacement, $single, $method_name );
  224. }
  225. return $replacements;
  226. }
  227. /* *********************** BASIC VARIABLES ************************** */
  228. /**
  229. * Retrieve the post/cpt categories (comma separated) for use as replacement string.
  230. *
  231. * @return string|null
  232. */
  233. private function retrieve_category() {
  234. $replacement = null;
  235. if ( ! empty( $this->args->ID ) ) {
  236. $cat = $this->get_terms( $this->args->ID, 'category' );
  237. if ( $cat !== '' ) {
  238. $replacement = $cat;
  239. }
  240. }
  241. if ( ( ! isset( $replacement ) || $replacement === '' ) && ( isset( $this->args->cat_name ) && ! empty( $this->args->cat_name ) ) ) {
  242. $replacement = $this->args->cat_name;
  243. }
  244. return $replacement;
  245. }
  246. /**
  247. * Retrieve the category description for use as replacement string.
  248. *
  249. * @return string|null
  250. */
  251. private function retrieve_category_description() {
  252. return $this->retrieve_term_description();
  253. }
  254. /**
  255. * Retrieve the date of the post/page/cpt for use as replacement string.
  256. *
  257. * @return string|null
  258. */
  259. private function retrieve_date() {
  260. $replacement = null;
  261. if ( $this->args->post_date !== '' ) {
  262. $replacement = mysql2date( get_option( 'date_format' ), $this->args->post_date, true );
  263. }
  264. else {
  265. if ( get_query_var( 'day' ) && get_query_var( 'day' ) !== '' ) {
  266. $replacement = get_the_date();
  267. }
  268. else {
  269. if ( single_month_title( ' ', false ) && single_month_title( ' ', false ) !== '' ) {
  270. $replacement = single_month_title( ' ', false );
  271. }
  272. elseif ( get_query_var( 'year' ) !== '' ) {
  273. $replacement = get_query_var( 'year' );
  274. }
  275. }
  276. }
  277. return $replacement;
  278. }
  279. /**
  280. * Retrieve the post/page/cpt excerpt for use as replacement string.
  281. * The excerpt will be auto-generated if it does not exist.
  282. *
  283. * @return string|null
  284. */
  285. private function retrieve_excerpt() {
  286. $replacement = null;
  287. // The check `post_password_required` is because excerpt must be hidden for a post with a password.
  288. if ( ! empty( $this->args->ID ) && ! post_password_required( $this->args->ID ) ) {
  289. if ( $this->args->post_excerpt !== '' ) {
  290. $replacement = wp_strip_all_tags( $this->args->post_excerpt );
  291. }
  292. elseif ( $this->args->post_content !== '' ) {
  293. $replacement = wp_html_excerpt( strip_shortcodes( $this->args->post_content ), 156 );
  294. // Trim the auto-generated string to a word boundary.
  295. $replacement = substr( $replacement, 0, strrpos( $replacement, ' ' ) );
  296. }
  297. }
  298. return $replacement;
  299. }
  300. /**
  301. * Retrieve the post/page/cpt excerpt for use as replacement string (without auto-generation).
  302. *
  303. * @return string|null
  304. */
  305. private function retrieve_excerpt_only() {
  306. $replacement = null;
  307. // The check `post_password_required` is because excerpt must be hidden for a post with a password.
  308. if ( ! empty( $this->args->ID ) && $this->args->post_excerpt !== '' && ! post_password_required( $this->args->ID ) ) {
  309. $replacement = wp_strip_all_tags( $this->args->post_excerpt );
  310. }
  311. return $replacement;
  312. }
  313. /**
  314. * Retrieve the title of the parent page of the current page/cpt for use as replacement string.
  315. * Only applicable for hierarchical post types.
  316. *
  317. * @todo Check: shouldn't this use $this->args as well ?
  318. *
  319. * @return string|null
  320. */
  321. private function retrieve_parent_title() {
  322. $replacement = null;
  323. if ( ! isset( $replacement ) && ( ( is_singular() || is_admin() ) && isset( $GLOBALS['post'] ) ) ) {
  324. if ( isset( $GLOBALS['post']->post_parent ) && 0 !== $GLOBALS['post']->post_parent ) {
  325. $replacement = get_the_title( $GLOBALS['post']->post_parent );
  326. }
  327. }
  328. return $replacement;
  329. }
  330. /**
  331. * Retrieve the current search phrase for use as replacement string.
  332. *
  333. * @return string|null
  334. */
  335. private function retrieve_searchphrase() {
  336. $replacement = null;
  337. if ( ! isset( $replacement ) ) {
  338. $search = get_query_var( 's' );
  339. if ( $search !== '' ) {
  340. $replacement = esc_html( $search );
  341. }
  342. }
  343. return $replacement;
  344. }
  345. /**
  346. * Retrieve the separator for use as replacement string.
  347. *
  348. * @return string
  349. */
  350. private function retrieve_sep() {
  351. return WPSEO_Utils::get_title_separator();
  352. }
  353. /**
  354. * Retrieve the site's tag line / description for use as replacement string.
  355. *
  356. * @return string|null
  357. */
  358. private function retrieve_sitedesc() {
  359. static $replacement;
  360. if ( ! isset( $replacement ) ) {
  361. $description = wp_strip_all_tags( get_bloginfo( 'description' ) );
  362. if ( $description !== '' ) {
  363. $replacement = $description;
  364. }
  365. }
  366. return $replacement;
  367. }
  368. /**
  369. * Retrieve the site's name for use as replacement string.
  370. *
  371. * @return string|null
  372. */
  373. private function retrieve_sitename() {
  374. static $replacement;
  375. if ( ! isset( $replacement ) ) {
  376. $sitename = WPSEO_Utils::get_site_name();
  377. if ( $sitename !== '' ) {
  378. $replacement = $sitename;
  379. }
  380. }
  381. return $replacement;
  382. }
  383. /**
  384. * Retrieve the current tag/tags for use as replacement string.
  385. *
  386. * @return string|null
  387. */
  388. private function retrieve_tag() {
  389. $replacement = null;
  390. if ( isset( $this->args->ID ) ) {
  391. $tags = $this->get_terms( $this->args->ID, 'post_tag' );
  392. if ( $tags !== '' ) {
  393. $replacement = $tags;
  394. }
  395. }
  396. return $replacement;
  397. }
  398. /**
  399. * Retrieve the tag description for use as replacement string.
  400. *
  401. * @return string|null
  402. */
  403. private function retrieve_tag_description() {
  404. return $this->retrieve_term_description();
  405. }
  406. /**
  407. * Retrieve the term description for use as replacement string.
  408. *
  409. * @return string|null
  410. */
  411. private function retrieve_term_description() {
  412. $replacement = null;
  413. if ( isset( $this->args->term_id ) && ! empty( $this->args->taxonomy ) ) {
  414. $term_desc = get_term_field( 'description', $this->args->term_id, $this->args->taxonomy );
  415. if ( $term_desc !== '' ) {
  416. $replacement = wp_strip_all_tags( $term_desc );
  417. }
  418. }
  419. return $replacement;
  420. }
  421. /**
  422. * Retrieve the term name for use as replacement string.
  423. *
  424. * @return string|null
  425. */
  426. private function retrieve_term_title() {
  427. $replacement = null;
  428. if ( ! empty( $this->args->taxonomy ) && ! empty( $this->args->name ) ) {
  429. $replacement = $this->args->name;
  430. }
  431. return $replacement;
  432. }
  433. /**
  434. * Retrieve the title of the post/page/cpt for use as replacement string.
  435. *
  436. * @return string|null
  437. */
  438. private function retrieve_title() {
  439. $replacement = null;
  440. if ( is_string( $this->args->post_title ) && $this->args->post_title !== '' ) {
  441. $replacement = stripslashes( $this->args->post_title );
  442. }
  443. return $replacement;
  444. }
  445. /**
  446. * Retrieve primary category for use as replacement string.
  447. *
  448. * @return bool|int|null
  449. */
  450. private function retrieve_primary_category() {
  451. $primary_category = null;
  452. if ( ! empty( $this->args->ID ) ) {
  453. $wpseo_primary_category = new WPSEO_Primary_Term( 'category', $this->args->ID );
  454. $term_id = $wpseo_primary_category->get_primary_term();
  455. $term = get_term( $term_id );
  456. if ( ! is_wp_error( $term ) && ! empty( $term ) ) {
  457. $primary_category = $term->name;
  458. }
  459. }
  460. return $primary_category;
  461. }
  462. /**
  463. * Retrieve the string generated by get_the_archive_title().
  464. *
  465. * @return string|null
  466. */
  467. private function retrieve_archive_title() {
  468. return get_the_archive_title();
  469. }
  470. /* *********************** ADVANCED VARIABLES ************************** */
  471. /**
  472. * Determine the page numbering of the current post/page/cpt.
  473. *
  474. * @param string $request Either 'nr'|'max' - whether to return the page number or the max number of pages.
  475. *
  476. * @return int|null
  477. */
  478. private function determine_pagenumbering( $request = 'nr' ) {
  479. global $wp_query, $post;
  480. $max_num_pages = null;
  481. $page_number = null;
  482. $max_num_pages = 1;
  483. if ( ! is_singular() ) {
  484. $page_number = get_query_var( 'paged' );
  485. if ( $page_number === 0 || $page_number === '' ) {
  486. $page_number = 1;
  487. }
  488. if ( ! empty( $wp_query->max_num_pages ) ) {
  489. $max_num_pages = $wp_query->max_num_pages;
  490. }
  491. }
  492. else {
  493. $page_number = get_query_var( 'page' );
  494. if ( $page_number === 0 || $page_number === '' ) {
  495. $page_number = 1;
  496. }
  497. if ( isset( $post->post_content ) ) {
  498. $max_num_pages = ( substr_count( $post->post_content, '<!--nextpage-->' ) + 1 );
  499. }
  500. }
  501. $return = null;
  502. switch ( $request ) {
  503. case 'nr':
  504. $return = $page_number;
  505. break;
  506. case 'max':
  507. $return = $max_num_pages;
  508. break;
  509. }
  510. return $return;
  511. }
  512. /**
  513. * Determine the post type names for the current post/page/cpt.
  514. *
  515. * @param string $request Either 'single'|'plural' - whether to return the single or plural form.
  516. *
  517. * @return string|null
  518. */
  519. private function determine_pt_names( $request = 'single' ) {
  520. global $wp_query;
  521. $pt_single = null;
  522. $pt_plural = null;
  523. $post_type = '';
  524. 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() ) ) ) {
  525. $post_type = $wp_query->query_vars['post_type'];
  526. }
  527. elseif ( isset( $this->args->post_type ) && ( is_string( $this->args->post_type ) && $this->args->post_type !== '' ) ) {
  528. $post_type = $this->args->post_type;
  529. }
  530. else {
  531. // Make it work in preview mode.
  532. $post = $wp_query->get_queried_object();
  533. if ( $post instanceof WP_Post ) {
  534. $post_type = $post->post_type;
  535. }
  536. }
  537. if ( is_array( $post_type ) ) {
  538. $post_type = reset( $post_type );
  539. }
  540. if ( $post_type !== '' ) {
  541. $pt = get_post_type_object( $post_type );
  542. $pt_single = $pt->name;
  543. $pt_plural = $pt->name;
  544. if ( isset( $pt->labels->singular_name ) ) {
  545. $pt_single = $pt->labels->singular_name;
  546. }
  547. if ( isset( $pt->labels->name ) ) {
  548. $pt_plural = $pt->labels->name;
  549. }
  550. }
  551. $return = null;
  552. switch ( $request ) {
  553. case 'single':
  554. $return = $pt_single;
  555. break;
  556. case 'plural':
  557. $return = $pt_plural;
  558. break;
  559. }
  560. return $return;
  561. }
  562. /**
  563. * Retrieve the attachment caption for use as replacement string.
  564. *
  565. * @return string|null
  566. */
  567. private function retrieve_caption() {
  568. return $this->retrieve_excerpt_only();
  569. }
  570. /**
  571. * Retrieve a post/page/cpt's custom field value for use as replacement string.
  572. *
  573. * @param string $var The complete variable to replace which includes the name of
  574. * the custom field which value is to be retrieved.
  575. *
  576. * @return string|null
  577. */
  578. private function retrieve_cf_custom_field_name( $var ) {
  579. global $post;
  580. $replacement = null;
  581. if ( is_string( $var ) && $var !== '' ) {
  582. $field = substr( $var, 3 );
  583. if ( ( is_singular() || is_admin() ) && ( is_object( $post ) && isset( $post->ID ) ) ) {
  584. $name = get_post_meta( $post->ID, $field, true );
  585. if ( $name !== '' ) {
  586. $replacement = $name;
  587. }
  588. }
  589. }
  590. return $replacement;
  591. }
  592. /**
  593. * Retrieve a post/page/cpt's custom taxonomies for use as replacement string.
  594. *
  595. * @param string $var The complete variable to replace which includes the name of
  596. * the custom taxonomy which value(s) is to be retrieved.
  597. * @param bool $single Whether to retrieve only the first or all values for the taxonomy.
  598. *
  599. * @return string|null
  600. */
  601. private function retrieve_ct_custom_tax_name( $var, $single = false ) {
  602. $replacement = null;
  603. if ( ( is_string( $var ) && $var !== '' ) && ! empty( $this->args->ID ) ) {
  604. $tax = substr( $var, 3 );
  605. $name = $this->get_terms( $this->args->ID, $tax, $single );
  606. if ( $name !== '' ) {
  607. $replacement = $name;
  608. }
  609. }
  610. return $replacement;
  611. }
  612. /**
  613. * Retrieve a post/page/cpt's custom taxonomies description for use as replacement string.
  614. *
  615. * @param string $var The complete variable to replace which includes the name of
  616. * the custom taxonomy which description is to be retrieved.
  617. *
  618. * @return string|null
  619. */
  620. private function retrieve_ct_desc_custom_tax_name( $var ) {
  621. global $post;
  622. $replacement = null;
  623. if ( is_string( $var ) && $var !== '' ) {
  624. $tax = substr( $var, 8 );
  625. if ( is_object( $post ) && isset( $post->ID ) ) {
  626. $terms = get_the_terms( $post->ID, $tax );
  627. if ( is_array( $terms ) && $terms !== array() ) {
  628. $term = current( $terms );
  629. $term_desc = get_term_field( 'description', $term->term_id, $tax );
  630. if ( $term_desc !== '' ) {
  631. $replacement = wp_strip_all_tags( $term_desc );
  632. }
  633. }
  634. }
  635. }
  636. return $replacement;
  637. }
  638. /**
  639. * Retrieve the current date for use as replacement string.
  640. *
  641. * @return string The formatted current date.
  642. */
  643. private function retrieve_currentdate() {
  644. static $replacement;
  645. if ( ! isset( $replacement ) ) {
  646. $replacement = date_i18n( get_option( 'date_format' ) );
  647. }
  648. return $replacement;
  649. }
  650. /**
  651. * Retrieve the current day for use as replacement string.
  652. *
  653. * @return string The current day.
  654. */
  655. private function retrieve_currentday() {
  656. static $replacement;
  657. if ( ! isset( $replacement ) ) {
  658. $replacement = date_i18n( 'j' );
  659. }
  660. return $replacement;
  661. }
  662. /**
  663. * Retrieve the current month for use as replacement string.
  664. *
  665. * @return string The current month.
  666. */
  667. private function retrieve_currentmonth() {
  668. static $replacement;
  669. if ( ! isset( $replacement ) ) {
  670. $replacement = date_i18n( 'F' );
  671. }
  672. return $replacement;
  673. }
  674. /**
  675. * Retrieve the current time for use as replacement string.
  676. *
  677. * @return string The formatted current time.
  678. */
  679. private function retrieve_currenttime() {
  680. static $replacement;
  681. if ( ! isset( $replacement ) ) {
  682. $replacement = date_i18n( get_option( 'time_format' ) );
  683. }
  684. return $replacement;
  685. }
  686. /**
  687. * Retrieve the current year for use as replacement string.
  688. *
  689. * @return string The current year.
  690. */
  691. private function retrieve_currentyear() {
  692. static $replacement;
  693. if ( ! isset( $replacement ) ) {
  694. $replacement = date_i18n( 'Y' );
  695. }
  696. return $replacement;
  697. }
  698. /**
  699. * Retrieve the post/page/cpt's focus keyword for use as replacement string.
  700. *
  701. * @return string|null
  702. */
  703. private function retrieve_focuskw() {
  704. $replacement = null;
  705. if ( ! empty( $this->args->ID ) ) {
  706. $focus_kw = WPSEO_Meta::get_value( 'focuskw', $this->args->ID );
  707. if ( $focus_kw !== '' ) {
  708. $replacement = $focus_kw;
  709. }
  710. }
  711. return $replacement;
  712. }
  713. /**
  714. * Retrieve the post/page/cpt ID for use as replacement string.
  715. *
  716. * @return string|null
  717. */
  718. private function retrieve_id() {
  719. $replacement = null;
  720. if ( ! empty( $this->args->ID ) ) {
  721. $replacement = $this->args->ID;
  722. }
  723. return $replacement;
  724. }
  725. /**
  726. * Retrieve the post/page/cpt modified time for use as replacement string.
  727. *
  728. * @return string|null
  729. */
  730. private function retrieve_modified() {
  731. $replacement = null;
  732. if ( ! empty( $this->args->post_modified ) ) {
  733. $replacement = mysql2date( get_option( 'date_format' ), $this->args->post_modified, true );
  734. }
  735. return $replacement;
  736. }
  737. /**
  738. * Retrieve the post/page/cpt author's "nice name" for use as replacement string.
  739. *
  740. * @return string|null
  741. */
  742. private function retrieve_name() {
  743. $replacement = null;
  744. $user_id = $this->retrieve_userid();
  745. $name = get_the_author_meta( 'display_name', $user_id );
  746. if ( $name !== '' ) {
  747. $replacement = $name;
  748. }
  749. return $replacement;
  750. }
  751. /**
  752. * Retrieve the post/page/cpt author's users description for use as a replacement string.
  753. *
  754. * @return null|string
  755. */
  756. private function retrieve_user_description() {
  757. $replacement = null;
  758. $user_id = $this->retrieve_userid();
  759. $description = get_the_author_meta( 'description', $user_id );
  760. if ( $description !== '' ) {
  761. $replacement = $description;
  762. }
  763. return $replacement;
  764. }
  765. /**
  766. * Retrieve the current page number with context (i.e. 'page 2 of 4') for use as replacement string.
  767. *
  768. * @return string
  769. */
  770. private function retrieve_page() {
  771. $replacement = null;
  772. $max = $this->determine_pagenumbering( 'max' );
  773. $nr = $this->determine_pagenumbering( 'nr' );
  774. $sep = $this->retrieve_sep();
  775. if ( $max > 1 && $nr > 1 ) {
  776. /* translators: 1: current page number, 2: total number of pages. */
  777. $replacement = sprintf( $sep . ' ' . __( 'Page %1$d of %2$d', 'wordpress-seo' ), $nr, $max );
  778. }
  779. return $replacement;
  780. }
  781. /**
  782. * Retrieve the current page number for use as replacement string.
  783. *
  784. * @return string|null
  785. */
  786. private function retrieve_pagenumber() {
  787. $replacement = null;
  788. $nr = $this->determine_pagenumbering( 'nr' );
  789. if ( isset( $nr ) && $nr > 0 ) {
  790. $replacement = (string) $nr;
  791. }
  792. return $replacement;
  793. }
  794. /**
  795. * Retrieve the current page total for use as replacement string.
  796. *
  797. * @return string|null
  798. */
  799. private function retrieve_pagetotal() {
  800. $replacement = null;
  801. $max = $this->determine_pagenumbering( 'max' );
  802. if ( isset( $max ) && $max > 0 ) {
  803. $replacement = (string) $max;
  804. }
  805. return $replacement;
  806. }
  807. /**
  808. * Retrieve the post type plural label for use as replacement string.
  809. *
  810. * @return string|null
  811. */
  812. private function retrieve_pt_plural() {
  813. $replacement = null;
  814. $name = $this->determine_pt_names( 'plural' );
  815. if ( isset( $name ) && $name !== '' ) {
  816. $replacement = $name;
  817. }
  818. return $replacement;
  819. }
  820. /**
  821. * Retrieve the post type single label for use as replacement string.
  822. *
  823. * @return string|null
  824. */
  825. private function retrieve_pt_single() {
  826. $replacement = null;
  827. $name = $this->determine_pt_names( 'single' );
  828. if ( isset( $name ) && $name !== '' ) {
  829. $replacement = $name;
  830. }
  831. return $replacement;
  832. }
  833. /**
  834. * Retrieve the slug which caused the 404 for use as replacement string.
  835. *
  836. * @return string|null
  837. */
  838. private function retrieve_term404() {
  839. $replacement = null;
  840. if ( $this->args->term404 !== '' ) {
  841. $replacement = sanitize_text_field( str_replace( '-', ' ', $this->args->term404 ) );
  842. }
  843. else {
  844. $error_request = get_query_var( 'pagename' );
  845. if ( $error_request !== '' ) {
  846. $replacement = sanitize_text_field( str_replace( '-', ' ', $error_request ) );
  847. }
  848. else {
  849. $error_request = get_query_var( 'name' );
  850. if ( $error_request !== '' ) {
  851. $replacement = sanitize_text_field( str_replace( '-', ' ', $error_request ) );
  852. }
  853. }
  854. }
  855. return $replacement;
  856. }
  857. /**
  858. * Retrieve the post/page/cpt author's user id for use as replacement string.
  859. *
  860. * @return string
  861. */
  862. private function retrieve_userid() {
  863. $replacement = ! empty( $this->args->post_author ) ? $this->args->post_author : get_query_var( 'author' );
  864. return $replacement;
  865. }
  866. /* *********************** HELP TEXT RELATED ************************** */
  867. /**
  868. * Create a variable help text table.
  869. *
  870. * @param string $type Either 'basic' or 'advanced'.
  871. *
  872. * @return string Help text table.
  873. */
  874. private static function create_variable_help_table( $type ) {
  875. if ( ! in_array( $type, array( 'basic', 'advanced' ), true ) ) {
  876. return '';
  877. }
  878. $table = '
  879. <table class="yoast_help yoast-table-scrollable">
  880. <thead>
  881. <tr>
  882. <th scope="col">' . esc_html__( 'Label', 'wordpress-seo' ) . '</th>
  883. <th scope="col">' . esc_html__( 'Variable', 'wordpress-seo' ) . '</th>
  884. <th scope="col">' . esc_html__( 'Description', 'wordpress-seo' ) . '</th>
  885. </tr>
  886. </thead>
  887. <tbody>';
  888. foreach ( self::$help_texts[ $type ] as $replacement_variable ) {
  889. $table .= '
  890. <tr>
  891. <td class="yoast-variable-label">' . esc_html( $replacement_variable->get_label() ) . '</td>
  892. <td class="yoast-variable-name">%%' . esc_html( $replacement_variable->get_variable() ) . '%%</td>
  893. <td class="yoast-variable-desc">' . esc_html( $replacement_variable->get_description() ) . '</td>
  894. </tr>';
  895. }
  896. $table .= '
  897. </tbody>
  898. </table>';
  899. return $table;
  900. }
  901. /**
  902. * Create the help text table for the basic variables for use in a help tab.
  903. *
  904. * @return string
  905. */
  906. public static function get_basic_help_texts() {
  907. return self::create_variable_help_table( 'basic' );
  908. }
  909. /**
  910. * Create the help text table for the advanced variables for use in a help tab.
  911. *
  912. * @return string
  913. */
  914. public static function get_advanced_help_texts() {
  915. return self::create_variable_help_table( 'advanced' );
  916. }
  917. /**
  918. * Set the help text for a user/plugin/theme defined extra variable.
  919. *
  920. * @param string $type Type of variable: 'basic' or 'advanced'.
  921. * @param WPSEO_Replacement_Variable $replacement_variable The replacement variable to register.
  922. */
  923. private static function register_help_text( $type, WPSEO_Replacement_Variable $replacement_variable ) {
  924. $identifier = $replacement_variable->get_variable();
  925. if ( ( is_string( $type ) && in_array( $type, array(
  926. 'basic',
  927. 'advanced',
  928. ), true ) ) && ( $identifier !== '' && ! isset( self::$help_texts[ $type ][ $identifier ] ) )
  929. ) {
  930. self::$help_texts[ $type ][ $identifier ] = $replacement_variable;
  931. }
  932. }
  933. /**
  934. * Generates a list of replacement variables based on the help texts.
  935. *
  936. * @return array List of replace vars.
  937. */
  938. public function get_replacement_variables_list() {
  939. self::setup_statics_once();
  940. $replacement_variables = array_merge(
  941. $this->get_replacement_variables(),
  942. WPSEO_Custom_Fields::get_custom_fields(),
  943. WPSEO_Custom_Taxonomies::get_custom_taxonomies()
  944. );
  945. return array_map( array( $this, 'format_replacement_variable' ), $replacement_variables );
  946. }
  947. /**
  948. * Creates a merged associative array of both the basic and advanced help texts.
  949. *
  950. * @return array Array with the replacement variables.
  951. */
  952. private function get_replacement_variables() {
  953. $help_texts = array_merge( self::$help_texts['basic'], self::$help_texts['advanced'] );
  954. return array_filter( array_keys( $help_texts ), array( $this, 'is_not_prefixed' ) );
  955. }
  956. /**
  957. * Checks whether the replacement variable contains a `ct_` or `cf_` prefix, because they follow different logic.
  958. *
  959. * @param string $replacement_variable The replacement variable.
  960. *
  961. * @return bool True when the replacement variable is not prefixed.
  962. */
  963. private function is_not_prefixed( $replacement_variable ) {
  964. $prefixes = array( 'cf_', 'ct_' );
  965. $prefix = $this->get_prefix( $replacement_variable );
  966. return ! in_array( $prefix, $prefixes, true );
  967. }
  968. /**
  969. * Strip the prefix from a replacement variable name.
  970. *
  971. * @param string $replacement_variable The replacement variable.
  972. *
  973. * @return string The replacement variable name without the prefix.
  974. */
  975. private function strip_prefix( $replacement_variable ) {
  976. return substr( $replacement_variable, 3 );
  977. }
  978. /**
  979. * Gets the prefix from a replacement variable name.
  980. *
  981. * @param string $replacement_variable The replacement variable.
  982. *
  983. * @return string The prefix of the replacement variable.
  984. */
  985. private function get_prefix( $replacement_variable ) {
  986. return substr( $replacement_variable, 0, 3 );
  987. }
  988. /**
  989. * Strips 'desc_' if present, and appends ' description' at the end.
  990. *
  991. * @param string $label The replacement variable.
  992. *
  993. * @return string The altered replacement variable name.
  994. */
  995. private function handle_description( $label ) {
  996. if ( strpos( $label, 'desc_' ) === 0 ) {
  997. return substr( $label, 5 ) . ' description';
  998. }
  999. return $label;
  1000. }
  1001. /**
  1002. * Creates a label for prefixed replacement variables that matches the format in the editors.
  1003. *
  1004. * @param string $replacement_variable The replacement variable.
  1005. *
  1006. * @return string The replacement variable label.
  1007. */
  1008. private function get_label( $replacement_variable ) {
  1009. $prefix = $this->get_prefix( $replacement_variable );
  1010. if ( $prefix === 'cf_' ) {
  1011. return $this->strip_prefix( $replacement_variable ) . ' (custom field)';
  1012. }
  1013. if ( $prefix === 'ct_' ) {
  1014. $label = $this->strip_prefix( $replacement_variable );
  1015. $label = $this->handle_description( $label );
  1016. return ucfirst( $label . ' (custom taxonomy)' );
  1017. }
  1018. if ( $prefix === 'pt_' ) {
  1019. if ( $replacement_variable === 'pt_single' ) {
  1020. return 'Post type (singular)';
  1021. }
  1022. return 'Post type (plural)';
  1023. }
  1024. return '';
  1025. }
  1026. /**
  1027. * Formats the replacement variables.
  1028. *
  1029. * @param string $replacement_variable The replacement variable to format.
  1030. *
  1031. * @return array The formatted replacement variable.
  1032. */
  1033. private function format_replacement_variable( $replacement_variable ) {
  1034. return array(
  1035. 'name' => $replacement_variable,
  1036. 'value' => '',
  1037. 'label' => $this->get_label( $replacement_variable ),
  1038. );
  1039. }
  1040. /**
  1041. * Retrieves the custom field names as an array.
  1042. *
  1043. * @see WordPress core: wp-admin/includes/template.php. Reused query from it.
  1044. *
  1045. * @return array The custom fields.
  1046. */
  1047. private function get_custom_fields() {
  1048. global $wpdb;
  1049. /**
  1050. * Filters the number of custom fields to retrieve for the drop-down
  1051. * in the Custom Fields meta box.
  1052. *
  1053. * @since 2.1.0
  1054. *
  1055. * @param int $limit Number of custom fields to retrieve. Default 30.
  1056. */
  1057. $limit = apply_filters( 'postmeta_form_limit', 30 );
  1058. $sql = "SELECT DISTINCT meta_key
  1059. FROM $wpdb->postmeta
  1060. WHERE meta_key NOT BETWEEN '_' AND '_z'
  1061. HAVING meta_key NOT LIKE %s
  1062. ORDER BY meta_key
  1063. LIMIT %d";
  1064. $fields = $wpdb->get_col( $wpdb->prepare( $sql, $wpdb->esc_like( '_' ) . '%', $limit ) );
  1065. if ( is_array( $fields ) ) {
  1066. return array_map( array( $this, 'add_custom_field_prefix' ), $fields );
  1067. }
  1068. return array();
  1069. }
  1070. /**
  1071. * Adds the cf_ prefix to a field.
  1072. *
  1073. * @param string $field The field to prefix.
  1074. *
  1075. * @return string The prefixed field.
  1076. */
  1077. private function add_custom_field_prefix( $field ) {
  1078. return 'cf_' . $field;
  1079. }
  1080. /**
  1081. * Gets the names of the custom taxonomies, prepends 'ct_' and 'ct_desc', and returns them in an array.
  1082. *
  1083. * @return array The custom taxonomy prefixed names.
  1084. */
  1085. private function get_custom_taxonomies() {
  1086. $args = array(
  1087. 'public' => true,
  1088. '_builtin' => false,
  1089. );
  1090. $output = 'names';
  1091. $operator = 'and';
  1092. $custom_taxonomies = get_taxonomies( $args, $output, $operator );
  1093. if ( is_array( $custom_taxonomies ) ) {
  1094. $ct_replace_vars = array();
  1095. foreach ( $custom_taxonomies as $custom_taxonomy ) {
  1096. array_push( $ct_replace_vars, 'ct_' . $custom_taxonomy, 'ct_desc_' . $custom_taxonomy );
  1097. }
  1098. return $ct_replace_vars;
  1099. }
  1100. return array();
  1101. }
  1102. /**
  1103. * Set/translate the help texts for the WPSEO standard basic variables.
  1104. */
  1105. private static function set_basic_help_texts() {
  1106. $replacement_variables = array(
  1107. new WPSEO_Replacement_Variable( 'date', __( 'Date', 'wordpress-seo' ), __( 'Replaced with the date of the post/page', 'wordpress-seo' ) ),
  1108. new WPSEO_Replacement_Variable( 'title', __( 'Title', 'wordpress-seo' ), __( 'Replaced with the title of the post/page', 'wordpress-seo' ) ),
  1109. new WPSEO_Replacement_Variable( 'parent_title', __( 'Parent title', 'wordpress-seo' ), __( 'Replaced with the title of the parent page of the current page', 'wordpress-seo' ) ),
  1110. new WPSEO_Replacement_Variable( 'archive_title', __( 'Archive title', 'wordpress-seo' ), __( 'Replaced with the normal title for an archive generated by WordPress', 'wordpress-seo' ) ),
  1111. new WPSEO_Replacement_Variable( 'sitename', __( 'Site title', 'wordpress-seo' ), __( 'The site\'s name', 'wordpress-seo' ) ),
  1112. new WPSEO_Replacement_Variable( 'sitedesc', __( 'Tagline', 'wordpress-seo' ), __( 'The site\'s tagline', 'wordpress-seo' ) ),
  1113. new WPSEO_Replacement_Variable( 'excerpt', __( 'Excerpt', 'wordpress-seo' ), __( 'Replaced with the post/page excerpt (or auto-generated if it does not exist)', 'wordpress-seo' ) ),
  1114. new WPSEO_Replacement_Variable( 'excerpt_only', __( 'Excerpt only', 'wordpress-seo' ), __( 'Replaced with the post/page excerpt (without auto-generation)', 'wordpress-seo' ) ),
  1115. new WPSEO_Replacement_Variable( 'tag', __( 'Tag', 'wordpress-seo' ), __( 'Replaced with the current tag/tags', 'wordpress-seo' ) ),
  1116. new WPSEO_Replacement_Variable( 'category', __( 'Category', 'wordpress-seo' ), __( 'Replaced with the post categories (comma separated)', 'wordpress-seo' ) ),
  1117. new WPSEO_Replacement_Variable( 'primary_category', __( 'Primary category', 'wordpress-seo' ), __( 'Replaced with the primary category of the post/page', 'wordpress-seo' ) ),
  1118. new WPSEO_Replacement_Variable( 'category_description', __( 'Category description', 'wordpress-seo' ), __( 'Replaced with the category description', 'wordpress-seo' ) ),
  1119. new WPSEO_Replacement_Variable( 'tag_description', __( 'Tag description', 'wordpress-seo' ), __( 'Replaced with the tag description', 'wordpress-seo' ) ),
  1120. new WPSEO_Replacement_Variable( 'term_description', __( 'Term description', 'wordpress-seo' ), __( 'Replaced with the term description', 'wordpress-seo' ) ),
  1121. new WPSEO_Replacement_Variable( 'term_title', __( 'Term title', 'wordpress-seo' ), __( 'Replaced with the term name', 'wordpress-seo' ) ),
  1122. new WPSEO_Replacement_Variable( 'searchphrase', __( 'Search phrase', 'wordpress-seo' ), __( 'Replaced with the current search phrase', 'wordpress-seo' ) ),
  1123. new WPSEO_Replacement_Variable( 'sep', __( 'Separator', 'wordpress-seo' ), sprintf(
  1124. /* translators: %s: wp_title() function. */
  1125. __( 'The separator defined in your theme\'s %s tag.', 'wordpress-seo' ),
  1126. // '<code>wp_title()</code>'
  1127. 'wp_title()'
  1128. ) ),
  1129. );
  1130. foreach ( $replacement_variables as $replacement_variable ) {
  1131. self::register_help_text( 'basic', $replacement_variable );
  1132. }
  1133. }
  1134. /**
  1135. * Set/translate the help texts for the WPSEO standard advanced variables.
  1136. */
  1137. private static function set_advanced_help_texts() {
  1138. $replacement_variables = array(
  1139. new WPSEO_Replacement_Variable( 'pt_single', __( 'Post type (singular)', 'wordpress-seo' ), __( 'Replaced with the content type single label', 'wordpress-seo' ) ),
  1140. new WPSEO_Replacement_Variable( 'pt_plural', __( 'Post type (plural)', 'wordpress-seo' ), __( 'Replaced with the content type plural label', 'wordpress-seo' ) ),
  1141. new WPSEO_Replacement_Variable( 'modified', __( 'Modified', 'wordpress-seo' ), __( 'Replaced with the post/page modified time', 'wordpress-seo' ) ),
  1142. new WPSEO_Replacement_Variable( 'id', __( 'ID', 'wordpress-seo' ), __( 'Replaced with the post/page ID', 'wordpress-seo' ) ),
  1143. new WPSEO_Replacement_Variable( 'name', __( 'Name', 'wordpress-seo' ), __( 'Replaced with the post/page author\'s \'nicename\'', 'wordpress-seo' ) ),
  1144. new WPSEO_Replacement_Variable( 'user_description', __( 'User description', 'wordpress-seo' ), __( 'Replaced with the post/page author\'s \'Biographical Info\'', 'wordpress-seo' ) ),
  1145. new WPSEO_Replacement_Variable( 'page', __( 'Page number', 'wordpress-seo' ), __( 'Replaced with the current page number with context (i.e. page 2 of 4)', 'wordpress-seo' ) ),
  1146. new WPSEO_Replacement_Variable( 'pagetotal', __( 'Pagetotal', 'wordpress-seo' ), __( 'Replaced with the current page total', 'wordpress-seo' ) ),
  1147. new WPSEO_Replacement_Variable( 'pagenumber', __( 'Pagenumber', 'wordpress-seo' ), __( 'Replaced with the current page number', 'wordpress-seo' ) ),
  1148. new WPSEO_Replacement_Variable( 'caption', __( 'Caption', 'wordpress-seo' ), __( 'Attachment caption', 'wordpress-seo' ) ),
  1149. new WPSEO_Replacement_Variable( 'focuskw', __( 'Focus keyword', 'wordpress-seo' ), __( 'Replaced with the posts focus keyword', 'wordpress-seo' ) ),
  1150. new WPSEO_Replacement_Variable( 'term404', __( 'Term404', 'wordpress-seo' ), __( 'Replaced with the slug which caused the 404', 'wordpress-seo' ) ),
  1151. new WPSEO_Replacement_Variable( 'cf_<custom-field-name>', '<custom-field-name> ' . __( '(custom field)', 'wordpress-seo' ), __( 'Replaced with a posts custom field value', 'wordpress-seo' ) ),
  1152. new WPSEO_Replacement_Variable( 'ct_<custom-tax-name>', '<custom-tax-name> ' . __( '(custom taxonomy)', 'wordpress-seo' ), __( 'Replaced with a posts custom taxonomies, comma separated.', 'wordpress-seo' ) ),
  1153. new WPSEO_Replacement_Variable( 'ct_desc_<custom-tax-name>', '<custom-tax-name> ' . __( 'description (custom taxonomy)', 'wordpress-seo' ), __( 'Replaced with a custom taxonomies description', 'wordpress-seo' ) ),
  1154. );
  1155. foreach ( $replacement_variables as $replacement_variable ) {
  1156. self::register_help_text( 'advanced', $replacement_variable );
  1157. }
  1158. }
  1159. /* *********************** GENERAL HELPER METHODS ************************** */
  1160. /**
  1161. * Remove the '%%' delimiters from a variable string.
  1162. *
  1163. * @param string $string Variable string to be cleaned.
  1164. *
  1165. * @return string
  1166. */
  1167. private static function remove_var_delimiter( $string ) {
  1168. return trim( $string, '%' );
  1169. }
  1170. /**
  1171. * Add the '%%' delimiters to a variable string.
  1172. *
  1173. * @param string $string Variable string to be delimited.
  1174. *
  1175. * @return string
  1176. */
  1177. private static function add_var_delimiter( $string ) {
  1178. return '%%' . $string . '%%';
  1179. }
  1180. /**
  1181. * Retrieve a post's terms, comma delimited.
  1182. *
  1183. * @param int $id ID of the post to get the terms for.
  1184. * @param string $taxonomy The taxonomy to get the terms for this post from.
  1185. * @param bool $return_single If true, return the first term.
  1186. *
  1187. * @return string Either a single term or a comma delimited string of terms.
  1188. */
  1189. public function get_terms( $id, $taxonomy, $return_single = false ) {
  1190. $output = '';
  1191. // If we're on a specific tag, category or taxonomy page, use that.
  1192. if ( is_category() || is_tag() || is_tax() ) {
  1193. $term = $GLOBALS['wp_query']->get_queried_object();
  1194. $output = $term->name;
  1195. }
  1196. elseif ( ! empty( $id ) && ! empty( $taxonomy ) ) {
  1197. $terms = get_the_terms( $id, $taxonomy );
  1198. if ( is_array( $terms ) && $terms !== array() ) {
  1199. foreach ( $terms as $term ) {
  1200. if ( $return_single ) {
  1201. $output = $term->name;
  1202. break;
  1203. }
  1204. else {
  1205. $output .= $term->name . ', ';
  1206. }
  1207. }
  1208. $output = rtrim( trim( $output ), ',' );
  1209. }
  1210. }
  1211. unset( $terms, $term );
  1212. /**
  1213. * Allows filtering of the terms list used to replace %%category%%, %%tag%% and %%ct_<custom-tax-name>%% variables.
  1214. *
  1215. * @api string $output Comma-delimited string containing the terms.
  1216. */
  1217. return apply_filters( 'wpseo_terms', $output );
  1218. }
  1219. } /* End of class WPSEO_Replace_Vars */
  1220. /**
  1221. * Setup the class statics when the file is first loaded.
  1222. */
  1223. WPSEO_Replace_Vars::setup_statics_once();