class-wpseo-option-titles.php 27 KB

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