class-wpseo-option-titles.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Internals\Options
  6. */
  7. /**
  8. * Option: wpseo_titles.
  9. */
  10. class WPSEO_Option_Titles extends WPSEO_Option {
  11. /**
  12. * Option name.
  13. *
  14. * @var string
  15. */
  16. public $option_name = 'wpseo_titles';
  17. /**
  18. * Array of defaults for the option.
  19. *
  20. * Shouldn't be requested directly, use $this->get_defaults();
  21. *
  22. * {@internal Note: Some of the default values are added via the translate_defaults() method.}}
  23. *
  24. * @var array
  25. */
  26. protected $defaults = [
  27. // Non-form fields, set via (ajax) function.
  28. 'title_test' => 0,
  29. // Form fields.
  30. 'forcerewritetitle' => false,
  31. 'separator' => 'sc-dash',
  32. 'title-home-wpseo' => '%%sitename%% %%page%% %%sep%% %%sitedesc%%', // Text field.
  33. 'title-author-wpseo' => '', // Text field.
  34. 'title-archive-wpseo' => '%%date%% %%page%% %%sep%% %%sitename%%', // Text field.
  35. 'title-search-wpseo' => '', // Text field.
  36. 'title-404-wpseo' => '', // Text field.
  37. 'metadesc-home-wpseo' => '', // Text area.
  38. 'metadesc-author-wpseo' => '', // Text area.
  39. 'metadesc-archive-wpseo' => '', // Text area.
  40. 'rssbefore' => '', // Text area.
  41. 'rssafter' => '', // Text area.
  42. 'noindex-author-wpseo' => false,
  43. 'noindex-author-noposts-wpseo' => true,
  44. 'noindex-archive-wpseo' => true,
  45. 'disable-author' => false,
  46. 'disable-date' => false,
  47. 'disable-post_format' => false,
  48. 'disable-attachment' => true,
  49. 'is-media-purge-relevant' => false,
  50. 'breadcrumbs-404crumb' => '', // Text field.
  51. 'breadcrumbs-display-blog-page' => true,
  52. 'breadcrumbs-boldlast' => false,
  53. 'breadcrumbs-archiveprefix' => '', // Text field.
  54. 'breadcrumbs-enable' => false,
  55. 'breadcrumbs-home' => '', // Text field.
  56. 'breadcrumbs-prefix' => '', // Text field.
  57. 'breadcrumbs-searchprefix' => '', // Text field.
  58. 'breadcrumbs-sep' => '&raquo;', // Text field.
  59. 'website_name' => '',
  60. 'person_name' => '',
  61. 'person_logo' => '',
  62. 'person_logo_id' => 0,
  63. 'alternate_website_name' => '',
  64. 'company_logo' => '',
  65. 'company_logo_id' => 0,
  66. 'company_name' => '',
  67. 'company_or_person' => 'company',
  68. 'company_or_person_user_id' => false,
  69. 'stripcategorybase' => false,
  70. /**
  71. * Uses enrich_defaults to add more along the lines of:
  72. * - 'title-' . $pt->name => ''; // Text field.
  73. * - 'metadesc-' . $pt->name => ''; // Text field.
  74. * - 'noindex-' . $pt->name => false;
  75. * - 'showdate-' . $pt->name => false;
  76. * - 'display-metabox-pt-' . $pt->name => false;
  77. *
  78. * - 'title-ptarchive-' . $pt->name => ''; // Text field.
  79. * - 'metadesc-ptarchive-' . $pt->name => ''; // Text field.
  80. * - 'bctitle-ptarchive-' . $pt->name => ''; // Text field.
  81. * - 'noindex-ptarchive-' . $pt->name => false;
  82. *
  83. * - 'title-tax-' . $tax->name => '''; // Text field.
  84. * - 'metadesc-tax-' . $tax->name => ''; // Text field.
  85. * - 'noindex-tax-' . $tax->name => false;
  86. * - 'display-metabox-tax-' . $tax->name => false;
  87. */
  88. ];
  89. /**
  90. * Used for "caching" during pageload.
  91. *
  92. * @var array
  93. */
  94. protected $enriched_defaults = null;
  95. /**
  96. * Array of variable option name patterns for the option.
  97. *
  98. * @var array
  99. */
  100. protected $variable_array_key_patterns = [
  101. 'title-',
  102. 'metadesc-',
  103. 'noindex-',
  104. 'showdate-',
  105. 'display-metabox-pt-',
  106. 'bctitle-ptarchive-',
  107. 'post_types-',
  108. 'taxonomy-',
  109. ];
  110. /**
  111. * Array of sub-options which should not be overloaded with multi-site defaults.
  112. *
  113. * @var array
  114. */
  115. public $ms_exclude = [
  116. /* Theme dependent. */
  117. 'title_test',
  118. 'forcerewritetitle',
  119. ];
  120. /**
  121. * Add the actions and filters for the option.
  122. *
  123. * @todo [JRF => testers] Check if the extra actions below would run into problems if an option
  124. * is updated early on and if so, change the call to schedule these for a later action on add/update
  125. * instead of running them straight away.
  126. */
  127. protected function __construct() {
  128. parent::__construct();
  129. add_action( 'update_option_' . $this->option_name, [ 'WPSEO_Utils', 'clear_cache' ] );
  130. add_action( 'init', [ $this, 'end_of_init' ], 999 );
  131. add_action( 'registered_post_type', [ $this, 'invalidate_enrich_defaults_cache' ] );
  132. add_action( 'unregistered_post_type', [ $this, 'invalidate_enrich_defaults_cache' ] );
  133. add_action( 'registered_taxonomy', [ $this, 'invalidate_enrich_defaults_cache' ] );
  134. add_action( 'unregistered_taxonomy', [ $this, 'invalidate_enrich_defaults_cache' ] );
  135. add_filter( 'admin_title', [ 'Yoast_Input_Validation', 'add_yoast_admin_document_title_errors' ] );
  136. }
  137. /**
  138. * Make sure we can recognize the right action for the double cleaning.
  139. */
  140. public function end_of_init() {
  141. do_action( 'wpseo_double_clean_titles' );
  142. }
  143. /**
  144. * Get the singleton instance of this class.
  145. *
  146. * @return self
  147. */
  148. public static function get_instance() {
  149. if ( ! ( self::$instance instanceof self ) ) {
  150. self::$instance = new self();
  151. }
  152. return self::$instance;
  153. }
  154. /**
  155. * Get the available separator options.
  156. *
  157. * @return array
  158. */
  159. public function get_separator_options() {
  160. $separators = wp_list_pluck( self::get_separator_option_list(), 'option' );
  161. /**
  162. * Allow altering the array with separator options.
  163. *
  164. * @api array $separator_options Array with the separator options.
  165. */
  166. $filtered_separators = apply_filters( 'wpseo_separator_options', $separators );
  167. if ( is_array( $filtered_separators ) && $filtered_separators !== [] ) {
  168. $separators = array_merge( $separators, $filtered_separators );
  169. }
  170. return $separators;
  171. }
  172. /**
  173. * Get the available separator options aria-labels.
  174. *
  175. * @return array Array with the separator options aria-labels.
  176. */
  177. public function get_separator_options_for_display() {
  178. $separators = $this->get_separator_options();
  179. $separator_list = self::get_separator_option_list();
  180. $separator_options = [];
  181. foreach ( $separators as $key => $label ) {
  182. $aria_label = isset( $separator_list[ $key ]['label'] ) ? $separator_list[ $key ]['label'] : '';
  183. $separator_options[ $key ] = [
  184. 'label' => $label,
  185. 'aria_label' => $aria_label,
  186. ];
  187. }
  188. return $separator_options;
  189. }
  190. /**
  191. * Translate strings used in the option defaults.
  192. *
  193. * @return void
  194. */
  195. public function translate_defaults() {
  196. /* translators: 1: Author name; 2: Site name. */
  197. $this->defaults['title-author-wpseo'] = sprintf( __( '%1$s, Author at %2$s', 'wordpress-seo' ), '%%name%%', '%%sitename%%' ) . ' %%page%% ';
  198. /* translators: %s expands to the search phrase. */
  199. $this->defaults['title-search-wpseo'] = sprintf( __( 'You searched for %s', 'wordpress-seo' ), '%%searchphrase%%' ) . ' %%page%% %%sep%% %%sitename%%';
  200. $this->defaults['title-404-wpseo'] = __( 'Page not found', 'wordpress-seo' ) . ' %%sep%% %%sitename%%';
  201. /* translators: 1: link to post; 2: link to blog. */
  202. $this->defaults['rssafter'] = sprintf( __( 'The post %1$s appeared first on %2$s.', 'wordpress-seo' ), '%%POSTLINK%%', '%%BLOGLINK%%' );
  203. $this->defaults['breadcrumbs-404crumb'] = __( 'Error 404: Page not found', 'wordpress-seo' );
  204. $this->defaults['breadcrumbs-archiveprefix'] = __( 'Archives for', 'wordpress-seo' );
  205. $this->defaults['breadcrumbs-home'] = __( 'Home', 'wordpress-seo' );
  206. $this->defaults['breadcrumbs-searchprefix'] = __( 'You searched for', 'wordpress-seo' );
  207. }
  208. /**
  209. * Add dynamically created default options based on available post types and taxonomies.
  210. *
  211. * @return void
  212. */
  213. public function enrich_defaults() {
  214. $enriched_defaults = $this->enriched_defaults;
  215. if ( null !== $enriched_defaults ) {
  216. $this->defaults += $enriched_defaults;
  217. return;
  218. }
  219. $enriched_defaults = [];
  220. /*
  221. * Retrieve all the relevant post type and taxonomy arrays.
  222. *
  223. * WPSEO_Post_Type::get_accessible_post_types() should *not* be used here.
  224. * These are the defaults and can be prepared for any public post type.
  225. */
  226. $post_type_objects = get_post_types( [ 'public' => true ], 'objects' );
  227. if ( $post_type_objects ) {
  228. /* translators: %s expands to the name of a post type (plural). */
  229. $archive = sprintf( __( '%s Archive', 'wordpress-seo' ), '%%pt_plural%%' );
  230. foreach ( $post_type_objects as $pt ) {
  231. $enriched_defaults[ 'title-' . $pt->name ] = '%%title%% %%page%% %%sep%% %%sitename%%'; // Text field.
  232. $enriched_defaults[ 'metadesc-' . $pt->name ] = ''; // Text area.
  233. $enriched_defaults[ 'noindex-' . $pt->name ] = false;
  234. $enriched_defaults[ 'showdate-' . $pt->name ] = false;
  235. $enriched_defaults[ 'display-metabox-pt-' . $pt->name ] = true;
  236. $enriched_defaults[ 'post_types-' . $pt->name . '-maintax' ] = 0; // Select box.
  237. if ( ! $pt->_builtin && WPSEO_Post_Type::has_archive( $pt ) ) {
  238. $enriched_defaults[ 'title-ptarchive-' . $pt->name ] = $archive . ' %%page%% %%sep%% %%sitename%%'; // Text field.
  239. $enriched_defaults[ 'metadesc-ptarchive-' . $pt->name ] = ''; // Text area.
  240. $enriched_defaults[ 'bctitle-ptarchive-' . $pt->name ] = ''; // Text field.
  241. $enriched_defaults[ 'noindex-ptarchive-' . $pt->name ] = false;
  242. }
  243. }
  244. }
  245. $taxonomy_objects = get_taxonomies( [ 'public' => true ], 'object' );
  246. if ( $taxonomy_objects ) {
  247. /* translators: %s expands to the variable used for term title. */
  248. $archives = sprintf( __( '%s Archives', 'wordpress-seo' ), '%%term_title%%' );
  249. foreach ( $taxonomy_objects as $tax ) {
  250. $enriched_defaults[ 'title-tax-' . $tax->name ] = $archives . ' %%page%% %%sep%% %%sitename%%'; // Text field.
  251. $enriched_defaults[ 'metadesc-tax-' . $tax->name ] = ''; // Text area.
  252. $enriched_defaults[ 'display-metabox-tax-' . $tax->name ] = true;
  253. $enriched_defaults[ 'noindex-tax-' . $tax->name ] = ( $tax->name === 'post_format' );
  254. if ( ! $tax->_builtin ) {
  255. $enriched_defaults[ 'taxonomy-' . $tax->name . '-ptparent' ] = 0; // Select box;.
  256. }
  257. }
  258. }
  259. $this->enriched_defaults = $enriched_defaults;
  260. $this->defaults += $enriched_defaults;
  261. }
  262. /**
  263. * Invalidates enrich_defaults() cache.
  264. *
  265. * Called from actions:
  266. * - (un)registered_post_type
  267. * - (un)registered_taxonomy
  268. *
  269. * @return void
  270. */
  271. public function invalidate_enrich_defaults_cache() {
  272. $this->enriched_defaults = null;
  273. }
  274. /**
  275. * Validate the option.
  276. *
  277. * @param array $dirty New value for the option.
  278. * @param array $clean Clean value for the option, normally the defaults.
  279. * @param array $old Old value of the option.
  280. *
  281. * @return array Validated clean value for the option to be saved to the database.
  282. */
  283. protected function validate_option( $dirty, $clean, $old ) {
  284. $allowed_post_types = $this->get_allowed_post_types();
  285. foreach ( $clean as $key => $value ) {
  286. $switch_key = $this->get_switch_key( $key );
  287. switch ( $switch_key ) {
  288. /* Breadcrumbs text fields. */
  289. case 'breadcrumbs-404crumb':
  290. case 'breadcrumbs-archiveprefix':
  291. case 'breadcrumbs-home':
  292. case 'breadcrumbs-prefix':
  293. case 'breadcrumbs-searchprefix':
  294. case 'breadcrumbs-sep':
  295. if ( isset( $dirty[ $key ] ) ) {
  296. $clean[ $key ] = wp_kses_post( $dirty[ $key ] );
  297. }
  298. break;
  299. /*
  300. * Text fields.
  301. */
  302. /*
  303. * Covers:
  304. * 'title-home-wpseo', 'title-author-wpseo', 'title-archive-wpseo',
  305. * 'title-search-wpseo', 'title-404-wpseo'
  306. * 'title-' . $pt->name
  307. * 'title-ptarchive-' . $pt->name
  308. * 'title-tax-' . $tax->name
  309. */
  310. case 'website_name':
  311. case 'alternate_website_name':
  312. case 'title-':
  313. if ( isset( $dirty[ $key ] ) ) {
  314. $clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] );
  315. }
  316. break;
  317. case 'company_or_person':
  318. if ( isset( $dirty[ $key ] ) ) {
  319. if ( in_array( $dirty[ $key ], [ 'company', 'person' ], true ) ) {
  320. $clean[ $key ] = $dirty[ $key ];
  321. }
  322. else {
  323. $defaults = $this->get_defaults();
  324. $clean[ $key ] = $defaults['company_or_person'];
  325. }
  326. }
  327. break;
  328. case 'company_logo':
  329. case 'person_logo':
  330. $this->validate_url( $key, $dirty, $old, $clean );
  331. break;
  332. /*
  333. * Covers:
  334. * 'metadesc-home-wpseo', 'metadesc-author-wpseo', 'metadesc-archive-wpseo'
  335. * 'metadesc-' . $pt->name
  336. * 'metadesc-ptarchive-' . $pt->name
  337. * 'metadesc-tax-' . $tax->name
  338. * and also:
  339. * 'bctitle-ptarchive-' . $pt->name
  340. */
  341. case 'metadesc-':
  342. case 'bctitle-ptarchive-':
  343. case 'company_name':
  344. case 'person_name':
  345. if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
  346. $clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] );
  347. }
  348. break;
  349. /*
  350. * Covers: 'rssbefore', 'rssafter'
  351. */
  352. case 'rssbefore':
  353. case 'rssafter':
  354. if ( isset( $dirty[ $key ] ) ) {
  355. $clean[ $key ] = wp_kses_post( $dirty[ $key ] );
  356. }
  357. break;
  358. /* 'post_types-' . $pt->name . '-maintax' fields. */
  359. case 'post_types-':
  360. $post_type = str_replace( [ 'post_types-', '-maintax' ], '', $key );
  361. $taxonomies = get_object_taxonomies( $post_type, 'names' );
  362. if ( isset( $dirty[ $key ] ) ) {
  363. if ( $taxonomies !== [] && in_array( $dirty[ $key ], $taxonomies, true ) ) {
  364. $clean[ $key ] = $dirty[ $key ];
  365. }
  366. elseif ( (string) $dirty[ $key ] === '0' || (string) $dirty[ $key ] === '' ) {
  367. $clean[ $key ] = 0;
  368. }
  369. elseif ( sanitize_title_with_dashes( $dirty[ $key ] ) === $dirty[ $key ] ) {
  370. // Allow taxonomies which may not be registered yet.
  371. $clean[ $key ] = $dirty[ $key ];
  372. }
  373. else {
  374. if ( isset( $old[ $key ] ) ) {
  375. $clean[ $key ] = sanitize_title_with_dashes( $old[ $key ] );
  376. }
  377. /*
  378. * @todo [JRF => whomever] Maybe change the untranslated $pt name in the
  379. * error message to the nicely translated label ?
  380. */
  381. add_settings_error(
  382. $this->group_name, // Slug title of the setting.
  383. $key, // Suffix-id for the error message box.
  384. /* translators: %s expands to a post type. */
  385. sprintf( __( 'Please select a valid taxonomy for post type "%s"', 'wordpress-seo' ), $post_type ), // The error message.
  386. 'error' // Error type, either 'error' or 'updated'.
  387. );
  388. }
  389. }
  390. elseif ( isset( $old[ $key ] ) ) {
  391. $clean[ $key ] = sanitize_title_with_dashes( $old[ $key ] );
  392. }
  393. unset( $taxonomies, $post_type );
  394. break;
  395. /* 'taxonomy-' . $tax->name . '-ptparent' fields. */
  396. case 'taxonomy-':
  397. if ( isset( $dirty[ $key ] ) ) {
  398. if ( $allowed_post_types !== [] && in_array( $dirty[ $key ], $allowed_post_types, true ) ) {
  399. $clean[ $key ] = $dirty[ $key ];
  400. }
  401. elseif ( (string) $dirty[ $key ] === '0' || (string) $dirty[ $key ] === '' ) {
  402. $clean[ $key ] = 0;
  403. }
  404. elseif ( sanitize_key( $dirty[ $key ] ) === $dirty[ $key ] ) {
  405. // Allow taxonomies which may not be registered yet.
  406. $clean[ $key ] = $dirty[ $key ];
  407. }
  408. else {
  409. if ( isset( $old[ $key ] ) ) {
  410. $clean[ $key ] = sanitize_key( $old[ $key ] );
  411. }
  412. /*
  413. * @todo [JRF =? whomever] Maybe change the untranslated $tax name in the
  414. * error message to the nicely translated label ?
  415. */
  416. $tax = str_replace( [ 'taxonomy-', '-ptparent' ], '', $key );
  417. add_settings_error(
  418. $this->group_name, // Slug title of the setting.
  419. '_' . $tax, // Suffix-ID for the error message box.
  420. /* translators: %s expands to a taxonomy slug. */
  421. sprintf( __( 'Please select a valid post type for taxonomy "%s"', 'wordpress-seo' ), $tax ), // The error message.
  422. 'error' // Error type, either 'error' or 'updated'.
  423. );
  424. unset( $tax );
  425. }
  426. }
  427. elseif ( isset( $old[ $key ] ) ) {
  428. $clean[ $key ] = sanitize_key( $old[ $key ] );
  429. }
  430. break;
  431. case 'company_or_person_user_id':
  432. case 'company_logo_id':
  433. case 'person_logo_id':
  434. case 'title_test': /* Integer field - not in form. */
  435. if ( isset( $dirty[ $key ] ) ) {
  436. $int = WPSEO_Utils::validate_int( $dirty[ $key ] );
  437. if ( $int !== false && $int >= 0 ) {
  438. $clean[ $key ] = $int;
  439. }
  440. }
  441. elseif ( isset( $old[ $key ] ) ) {
  442. $int = WPSEO_Utils::validate_int( $old[ $key ] );
  443. if ( $int !== false && $int >= 0 ) {
  444. $clean[ $key ] = $int;
  445. }
  446. }
  447. break;
  448. /* Separator field - Radio. */
  449. case 'separator':
  450. if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) {
  451. // Get separator fields.
  452. $separator_fields = $this->get_separator_options();
  453. // Check if the given separator exists.
  454. if ( isset( $separator_fields[ $dirty[ $key ] ] ) ) {
  455. $clean[ $key ] = $dirty[ $key ];
  456. }
  457. }
  458. break;
  459. /*
  460. * Boolean fields.
  461. */
  462. /*
  463. * Covers:
  464. * 'noindex-author-wpseo', 'noindex-author-noposts-wpseo', 'noindex-archive-wpseo'
  465. * 'noindex-' . $pt->name
  466. * 'noindex-ptarchive-' . $pt->name
  467. * 'noindex-tax-' . $tax->name
  468. * 'forcerewritetitle':
  469. * 'noodp':
  470. * 'noydir':
  471. * 'disable-author':
  472. * 'disable-date':
  473. * 'disable-post_format';
  474. * 'noindex-'
  475. * 'showdate-'
  476. * 'showdate-'. $pt->name
  477. * 'display-metabox-pt-'
  478. * 'display-metabox-pt-'. $pt->name
  479. * 'display-metabox-tax-'
  480. * 'display-metabox-tax-' . $tax->name
  481. * 'breadcrumbs-display-blog-page'
  482. * 'breadcrumbs-boldlast'
  483. * 'breadcrumbs-enable'
  484. * 'stripcategorybase'
  485. * 'is-media-purge-relevant'
  486. */
  487. default:
  488. $clean[ $key ] = ( isset( $dirty[ $key ] ) ? WPSEO_Utils::validate_bool( $dirty[ $key ] ) : false );
  489. break;
  490. }
  491. }
  492. return $clean;
  493. }
  494. /**
  495. * Retrieve a list of the allowed post types as breadcrumb parent for a taxonomy.
  496. * Helper method for validation.
  497. *
  498. * {@internal Don't make static as new types may still be registered.}}
  499. *
  500. * @return array
  501. */
  502. protected function get_allowed_post_types() {
  503. $allowed_post_types = [];
  504. /*
  505. * WPSEO_Post_Type::get_accessible_post_types() should *not* be used here.
  506. */
  507. $post_types = get_post_types( [ 'public' => true ], 'objects' );
  508. if ( get_option( 'show_on_front' ) === 'page' && get_option( 'page_for_posts' ) > 0 ) {
  509. $allowed_post_types[] = 'post';
  510. }
  511. if ( is_array( $post_types ) && $post_types !== [] ) {
  512. foreach ( $post_types as $type ) {
  513. if ( WPSEO_Post_Type::has_archive( $type ) ) {
  514. $allowed_post_types[] = $type->name;
  515. }
  516. }
  517. }
  518. return $allowed_post_types;
  519. }
  520. /**
  521. * Clean a given option value.
  522. *
  523. * @param array $option_value Old (not merged with defaults or filtered) option value to
  524. * clean according to the rules for this option.
  525. * @param string $current_version Optional. Version from which to upgrade, if not set,
  526. * version specific upgrades will be disregarded.
  527. * @param array $all_old_option_values Optional. Only used when importing old options to have
  528. * access to the real old values, in contrast to the saved ones.
  529. *
  530. * @return array Cleaned option.
  531. */
  532. protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) {
  533. static $original = null;
  534. // Double-run this function to ensure renaming of the taxonomy options will work.
  535. if ( ! isset( $original )
  536. && has_action( 'wpseo_double_clean_titles', [ $this, 'clean' ] ) === false
  537. ) {
  538. add_action( 'wpseo_double_clean_titles', [ $this, 'clean' ] );
  539. $original = $option_value;
  540. }
  541. /*
  542. * Move options from very old option to this one.
  543. *
  544. * {@internal Don't rename to the 'current' names straight away as that would prevent
  545. * the rename/unset combi below from working.}}
  546. *
  547. * @todo [JRF] Maybe figure out a smarter way to deal with this.
  548. */
  549. $old_option = null;
  550. if ( isset( $all_old_option_values ) ) {
  551. // Ok, we have an import.
  552. if ( isset( $all_old_option_values['wpseo_indexation'] ) && is_array( $all_old_option_values['wpseo_indexation'] ) && $all_old_option_values['wpseo_indexation'] !== [] ) {
  553. $old_option = $all_old_option_values['wpseo_indexation'];
  554. }
  555. }
  556. else {
  557. $old_option = get_option( 'wpseo_indexation' );
  558. }
  559. if ( is_array( $old_option ) && $old_option !== [] ) {
  560. $move = [
  561. 'noindexauthor' => 'noindex-author',
  562. 'disableauthor' => 'disable-author',
  563. 'noindexdate' => 'noindex-archive',
  564. 'noindexcat' => 'noindex-category',
  565. 'noindextag' => 'noindex-post_tag',
  566. 'noindexpostformat' => 'noindex-post_format',
  567. ];
  568. foreach ( $move as $old => $new ) {
  569. if ( isset( $old_option[ $old ] ) && ! isset( $option_value[ $new ] ) ) {
  570. $option_value[ $new ] = $old_option[ $old ];
  571. }
  572. }
  573. unset( $move, $old, $new );
  574. }
  575. unset( $old_option );
  576. // Fix wrongness created by buggy version 1.2.2.
  577. if ( isset( $option_value['title-home'] ) && $option_value['title-home'] === '%%sitename%% - %%sitedesc%% - 12345' ) {
  578. $option_value['title-home-wpseo'] = '%%sitename%% - %%sitedesc%%';
  579. }
  580. /*
  581. * Renaming these options to avoid ever overwritting these if a (bloody stupid) user /
  582. * programmer would use any of the following as a custom post type or custom taxonomy:
  583. * 'home', 'author', 'archive', 'search', '404', 'subpages'.
  584. *
  585. * Similarly, renaming the tax options to avoid a custom post type and a taxonomy
  586. * with the same name occupying the same option.
  587. */
  588. $rename = [
  589. 'title-home' => 'title-home-wpseo',
  590. 'title-author' => 'title-author-wpseo',
  591. 'title-archive' => 'title-archive-wpseo',
  592. 'title-search' => 'title-search-wpseo',
  593. 'title-404' => 'title-404-wpseo',
  594. 'metadesc-home' => 'metadesc-home-wpseo',
  595. 'metadesc-author' => 'metadesc-author-wpseo',
  596. 'metadesc-archive' => 'metadesc-archive-wpseo',
  597. 'noindex-author' => 'noindex-author-wpseo',
  598. 'noindex-archive' => 'noindex-archive-wpseo',
  599. ];
  600. foreach ( $rename as $old => $new ) {
  601. if ( isset( $option_value[ $old ] ) && ! isset( $option_value[ $new ] ) ) {
  602. $option_value[ $new ] = $option_value[ $old ];
  603. unset( $option_value[ $old ] );
  604. }
  605. }
  606. unset( $rename, $old, $new );
  607. /*
  608. * {@internal This clean-up action can only be done effectively once the taxonomies
  609. * and post_types have been registered, i.e. at the end of the init action.}}
  610. */
  611. if ( isset( $original ) && current_filter() === 'wpseo_double_clean_titles' || did_action( 'wpseo_double_clean_titles' ) > 0 ) {
  612. $rename = [
  613. 'title-' => 'title-tax-',
  614. 'metadesc-' => 'metadesc-tax-',
  615. 'noindex-' => 'noindex-tax-',
  616. 'tax-hideeditbox-' => 'hideeditbox-tax-',
  617. ];
  618. $taxonomy_names = get_taxonomies( [ 'public' => true ], 'names' );
  619. $post_type_names = get_post_types( [ 'public' => true ], 'names' );
  620. $defaults = $this->get_defaults();
  621. if ( $taxonomy_names !== [] ) {
  622. foreach ( $taxonomy_names as $tax ) {
  623. foreach ( $rename as $old_prefix => $new_prefix ) {
  624. if (
  625. ( isset( $original[ $old_prefix . $tax ] ) && ! isset( $original[ $new_prefix . $tax ] ) )
  626. && ( ! isset( $option_value[ $new_prefix . $tax ] )
  627. || ( isset( $option_value[ $new_prefix . $tax ] )
  628. && $option_value[ $new_prefix . $tax ] === $defaults[ $new_prefix . $tax ] ) )
  629. ) {
  630. $option_value[ $new_prefix . $tax ] = $original[ $old_prefix . $tax ];
  631. /*
  632. * Check if there is a cpt with the same name as the tax,
  633. * if so, we should make sure that the old setting hasn't been removed.
  634. */
  635. if ( ! isset( $post_type_names[ $tax ] ) && isset( $option_value[ $old_prefix . $tax ] ) ) {
  636. unset( $option_value[ $old_prefix . $tax ] );
  637. }
  638. else {
  639. if ( isset( $post_type_names[ $tax ] ) && ! isset( $option_value[ $old_prefix . $tax ] ) ) {
  640. $option_value[ $old_prefix . $tax ] = $original[ $old_prefix . $tax ];
  641. }
  642. }
  643. if ( $old_prefix === 'tax-hideeditbox-' ) {
  644. unset( $option_value[ $old_prefix . $tax ] );
  645. }
  646. }
  647. }
  648. }
  649. }
  650. unset( $rename, $taxonomy_names, $post_type_names, $defaults, $tax, $old_prefix, $new_prefix );
  651. }
  652. /*
  653. * Make sure the values of the variable option key options are cleaned as they
  654. * may be retained and would not be cleaned/validated then.
  655. */
  656. if ( is_array( $option_value ) && $option_value !== [] ) {
  657. foreach ( $option_value as $key => $value ) {
  658. $switch_key = $this->get_switch_key( $key );
  659. // Similar to validation routine - any changes made there should be made here too.
  660. switch ( $switch_key ) {
  661. /* Text fields. */
  662. case 'title-':
  663. case 'metadesc-':
  664. case 'bctitle-ptarchive-':
  665. $option_value[ $key ] = WPSEO_Utils::sanitize_text_field( $value );
  666. break;
  667. case 'separator':
  668. if ( ! array_key_exists( $value, $this->get_separator_options() ) ) {
  669. $option_value[ $key ] = false;
  670. }
  671. break;
  672. /*
  673. * Boolean fields.
  674. */
  675. /*
  676. * Covers:
  677. * 'noindex-'
  678. * 'showdate-'
  679. * 'hideeditbox-'
  680. */
  681. default:
  682. $option_value[ $key ] = WPSEO_Utils::validate_bool( $value );
  683. break;
  684. }
  685. }
  686. unset( $key, $value, $switch_key );
  687. }
  688. return $option_value;
  689. }
  690. /**
  691. * Make sure that any set option values relating to post_types and/or taxonomies are retained,
  692. * even when that post_type or taxonomy may not yet have been registered.
  693. *
  694. * {@internal Overrule the abstract class version of this to make sure one extra renamed
  695. * variable key does not get removed. IMPORTANT: keep this method in line with
  696. * the parent on which it is based!}}
  697. *
  698. * @param array $dirty Original option as retrieved from the database.
  699. * @param array $clean Filtered option where any options which shouldn't be in our option
  700. * have already been removed and any options which weren't set
  701. * have been set to their defaults.
  702. *
  703. * @return array
  704. */
  705. protected function retain_variable_keys( $dirty, $clean ) {
  706. if ( ( is_array( $this->variable_array_key_patterns ) && $this->variable_array_key_patterns !== [] ) && ( is_array( $dirty ) && $dirty !== [] ) ) {
  707. // Add the extra pattern.
  708. $patterns = $this->variable_array_key_patterns;
  709. $patterns[] = 'tax-hideeditbox-';
  710. /**
  711. * Allow altering the array with variable array key patterns.
  712. *
  713. * @api array $patterns Array with the variable array key patterns.
  714. */
  715. $patterns = apply_filters( 'wpseo_option_titles_variable_array_key_patterns', $patterns );
  716. foreach ( $dirty as $key => $value ) {
  717. // Do nothing if already in filtered option array.
  718. if ( isset( $clean[ $key ] ) ) {
  719. continue;
  720. }
  721. foreach ( $patterns as $pattern ) {
  722. if ( strpos( $key, $pattern ) === 0 ) {
  723. $clean[ $key ] = $value;
  724. break;
  725. }
  726. }
  727. }
  728. }
  729. return $clean;
  730. }
  731. /**
  732. * Retrieves a list of separator options.
  733. *
  734. * @return array An array of the separator options.
  735. */
  736. protected static function get_separator_option_list() {
  737. $separators = [
  738. 'sc-dash' => [
  739. 'option' => '-',
  740. 'label' => __( 'Dash', 'wordpress-seo' ),
  741. ],
  742. 'sc-ndash' => [
  743. 'option' => '&ndash;',
  744. 'label' => __( 'En dash', 'wordpress-seo' ),
  745. ],
  746. 'sc-mdash' => [
  747. 'option' => '&mdash;',
  748. 'label' => __( 'Em dash', 'wordpress-seo' ),
  749. ],
  750. 'sc-colon' => [
  751. 'option' => ':',
  752. 'label' => __( 'Colon', 'wordpress-seo' ),
  753. ],
  754. 'sc-middot' => [
  755. 'option' => '&middot;',
  756. 'label' => __( 'Middle dot', 'wordpress-seo' ),
  757. ],
  758. 'sc-bull' => [
  759. 'option' => '&bull;',
  760. 'label' => __( 'Bullet', 'wordpress-seo' ),
  761. ],
  762. 'sc-star' => [
  763. 'option' => '*',
  764. 'label' => __( 'Asterisk', 'wordpress-seo' ),
  765. ],
  766. 'sc-smstar' => [
  767. 'option' => '&#8902;',
  768. 'label' => __( 'Low asterisk', 'wordpress-seo' ),
  769. ],
  770. 'sc-pipe' => [
  771. 'option' => '|',
  772. 'label' => __( 'Vertical bar', 'wordpress-seo' ),
  773. ],
  774. 'sc-tilde' => [
  775. 'option' => '~',
  776. 'label' => __( 'Small tilde', 'wordpress-seo' ),
  777. ],
  778. 'sc-laquo' => [
  779. 'option' => '&laquo;',
  780. 'label' => __( 'Left angle quotation mark', 'wordpress-seo' ),
  781. ],
  782. 'sc-raquo' => [
  783. 'option' => '&raquo;',
  784. 'label' => __( 'Right angle quotation mark', 'wordpress-seo' ),
  785. ],
  786. 'sc-lt' => [
  787. 'option' => '&lt;',
  788. 'label' => __( 'Less than sign', 'wordpress-seo' ),
  789. ],
  790. 'sc-gt' => [
  791. 'option' => '&gt;',
  792. 'label' => __( 'Greater than sign', 'wordpress-seo' ),
  793. ],
  794. ];
  795. /**
  796. * Allows altering the separator options array.
  797. *
  798. * @api array $separators Array with the separator options.
  799. */
  800. $separator_list = apply_filters( 'wpseo_separator_option_list', $separators );
  801. if ( ! is_array( $separator_list ) ) {
  802. return $separators;
  803. }
  804. return $separator_list;
  805. }
  806. }