/inc/options/class-wpseo-option-titles.php

  1. <?php 
  2. /** 
  3. * @package WPSEO\Internals\Options 
  4. */ 
  5.  
  6. /** 
  7. * Option: wpseo_titles 
  8. */ 
  9. class WPSEO_Option_Titles extends WPSEO_Option { 
  10.  
  11. /** 
  12. * @var string option name 
  13. */ 
  14. public $option_name = 'wpseo_titles'; 
  15.  
  16. /** 
  17. * @var array Array of defaults for the option 
  18. * Shouldn't be requested directly, use $this->get_defaults(); 
  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. 'noodp' => false,  
  28. 'usemetakeywords' => false,  
  29. 'title-home-wpseo' => '%%sitename%% %%page%% %%sep%% %%sitedesc%%', // Text field. 
  30. 'title-author-wpseo' => '', // Text field. 
  31. 'title-archive-wpseo' => '%%date%% %%page%% %%sep%% %%sitename%%', // Text field. 
  32. 'title-search-wpseo' => '', // Text field. 
  33. 'title-404-wpseo' => '', // Text field. 
  34.  
  35. 'metadesc-home-wpseo' => '', // Text area. 
  36. 'metadesc-author-wpseo' => '', // Text area. 
  37. 'metadesc-archive-wpseo' => '', // Text area. 
  38. 'metakey-home-wpseo' => '', // Text field. 
  39. 'metakey-author-wpseo' => '', // Text field. 
  40.  
  41. 'noindex-subpages-wpseo' => false,  
  42. 'noindex-author-wpseo' => false,  
  43. 'noindex-archive-wpseo' => true,  
  44.  
  45. 'disable-author' => false,  
  46. 'disable-date' => false,  
  47. 'disable-post_format' => false,  
  48.  
  49. /** 
  50. * Uses enrich_defaults to add more along the lines of: 
  51. * - 'title-' . $pt->name => ''; // Text field. 
  52. * - 'metadesc-' . $pt->name => ''; // Text field. 
  53. * - 'metakey-' . $pt->name => ''; // Text field. 
  54. * - 'noindex-' . $pt->name => false; 
  55. * - 'showdate-' . $pt->name => false; 
  56. * - 'hideeditbox-' . $pt->name => false; 
  57. * - 'title-ptarchive-' . $pt->name => ''; // Text field. 
  58. * - 'metadesc-ptarchive-' . $pt->name => ''; // Text field. 
  59. * - 'metakey-ptarchive-' . $pt->name => ''; // Text field. 
  60. * - 'bctitle-ptarchive-' . $pt->name => ''; // Text field. 
  61. * - 'noindex-ptarchive-' . $pt->name => false; 
  62. * - 'title-tax-' . $tax->name => '''; // Text field. 
  63. * - 'metadesc-tax-' . $tax->name => ''; // Text field. 
  64. * - 'metakey-tax-' . $tax->name => ''; // Text field. 
  65. * - 'noindex-tax-' . $tax->name => false; 
  66. * - 'hideeditbox-tax-' . $tax->name => false; 
  67. */ 
  68. ); 
  69.  
  70. /** 
  71. * @var array Array of variable option name patterns for the option 
  72. */ 
  73. protected $variable_array_key_patterns = array( 
  74. 'title-',  
  75. 'metadesc-',  
  76. 'metakey-',  
  77. 'noindex-',  
  78. 'showdate-',  
  79. 'hideeditbox-',  
  80. 'bctitle-ptarchive-',  
  81. ); 
  82.  
  83. /** 
  84. * @var array Array of sub-options which should not be overloaded with multi-site defaults 
  85. */ 
  86. public $ms_exclude = array( 
  87. /** theme dependent */ 
  88. 'title_test',  
  89. 'forcerewritetitle',  
  90. ); 
  91.  
  92. /** 
  93. * @var array Array of the separator options. To get these options use WPSEO_Option_Titles::get_instance()->get_separator_options() 
  94. */ 
  95. private $separator_options = array( 
  96. 'sc-dash' => '-',  
  97. 'sc-ndash' => '–',  
  98. 'sc-mdash' => '—',  
  99. 'sc-middot' => '·',  
  100. 'sc-bull' => '•',  
  101. 'sc-star' => '*',  
  102. 'sc-smstar' => '⋆',  
  103. 'sc-pipe' => '|',  
  104. 'sc-tilde' => '~',  
  105. 'sc-laquo' => '«',  
  106. 'sc-raquo' => '»',  
  107. 'sc-lt' => '<',  
  108. 'sc-gt' => '>',  
  109. ); 
  110.  
  111. /** 
  112. * Add the actions and filters for the option 
  113. * 
  114. * @todo [JRF => testers] Check if the extra actions below would run into problems if an option 
  115. * is updated early on and if so, change the call to schedule these for a later action on add/update 
  116. * instead of running them straight away 
  117. * 
  118. * @return \WPSEO_Option_Titles 
  119. */ 
  120. protected function __construct() { 
  121. parent::__construct(); 
  122. add_action( 'update_option_' . $this->option_name, array( 'WPSEO_Utils', 'clear_cache' ) ); 
  123. add_action( 'init', array( $this, 'end_of_init' ), 999 ); 
  124.  
  125.  
  126. /** 
  127. * Make sure we can recognize the right action for the double cleaning 
  128. */ 
  129. public function end_of_init() { 
  130. do_action( 'wpseo_double_clean_titles' ); 
  131.  
  132. /** 
  133. * Get the singleton instance of this class 
  134. * 
  135. * @return object 
  136. */ 
  137. public static function get_instance() { 
  138. if ( ! ( self::$instance instanceof self ) ) { 
  139. self::$instance = new self(); 
  140.  
  141. return self::$instance; 
  142.  
  143. /** 
  144. * Get the available separator options 
  145. * 
  146. * @return array 
  147. */ 
  148. public function get_separator_options() { 
  149. $separators = $this->separator_options; 
  150.  
  151. /** 
  152. * Allow altering the array with separator options 
  153. * 
  154. * @api array $separator_options Array with the separator options 
  155. */ 
  156. $filtered_separators = apply_filters( 'wpseo_separator_options', $separators ); 
  157.  
  158. if ( is_array( $filtered_separators ) && $filtered_separators !== array() ) { 
  159. $separators = array_merge( $separators, $filtered_separators ); 
  160.  
  161. return $separators; 
  162.  
  163. /** 
  164. * Translate strings used in the option defaults 
  165. * 
  166. * @return void 
  167. */ 
  168. public function translate_defaults() { 
  169. /** translators: 1: Author name; 2: Site name. */ 
  170. $this->defaults['title-author-wpseo'] = sprintf( __( '%1$s, Author at %2$s', 'wordpress-seo' ), '%%name%%', '%%sitename%%' ) . ' %%page%% '; 
  171. $this->defaults['title-search-wpseo'] = sprintf( __( 'You searched for %s', 'wordpress-seo' ), '%%searchphrase%%' ) . ' %%page%% %%sep%% %%sitename%%'; 
  172. $this->defaults['title-404-wpseo'] = __( 'Page not found', 'wordpress-seo' ) . ' %%sep%% %%sitename%%'; 
  173.  
  174.  
  175. /** 
  176. * Add dynamically created default options based on available post types and taxonomies 
  177. * 
  178. * @return void 
  179. */ 
  180. public function enrich_defaults() { 
  181.  
  182. // Retrieve all the relevant post type and taxonomy arrays. 
  183. $post_type_names = get_post_types( array( 'public' => true ), 'names' ); 
  184.  
  185. $post_type_objects_custom = get_post_types( array( 'public' => true, '_builtin' => false ), 'objects' ); 
  186.  
  187. $taxonomy_names = get_taxonomies( array( 'public' => true ), 'names' ); 
  188.  
  189.  
  190. if ( $post_type_names !== array() ) { 
  191. foreach ( $post_type_names as $pt ) { 
  192. $this->defaults[ 'title-' . $pt ] = '%%title%% %%page%% %%sep%% %%sitename%%'; // Text field. 
  193. $this->defaults[ 'metadesc-' . $pt ] = ''; // Text area. 
  194. $this->defaults[ 'metakey-' . $pt ] = ''; // Text field. 
  195. $this->defaults[ 'noindex-' . $pt ] = false; 
  196. $this->defaults[ 'showdate-' . $pt ] = false; 
  197. $this->defaults[ 'hideeditbox-' . $pt ] = false; 
  198. unset( $pt ); 
  199.  
  200. if ( $post_type_objects_custom !== array() ) { 
  201. $archive = sprintf( __( '%s Archive', 'wordpress-seo' ), '%%pt_plural%%' ); 
  202. foreach ( $post_type_objects_custom as $pt ) { 
  203. if ( ! $pt->has_archive ) { 
  204. continue; 
  205.  
  206. $this->defaults[ 'title-ptarchive-' . $pt->name ] = $archive . ' %%page%% %%sep%% %%sitename%%'; // Text field. 
  207. $this->defaults[ 'metadesc-ptarchive-' . $pt->name ] = ''; // Text area. 
  208. $this->defaults[ 'metakey-ptarchive-' . $pt->name ] = ''; // Text field. 
  209. $this->defaults[ 'bctitle-ptarchive-' . $pt->name ] = ''; // Text field. 
  210. $this->defaults[ 'noindex-ptarchive-' . $pt->name ] = false; 
  211. unset( $pt ); 
  212.  
  213. if ( $taxonomy_names !== array() ) { 
  214. $archives = sprintf( __( '%s Archives', 'wordpress-seo' ), '%%term_title%%' ); 
  215. foreach ( $taxonomy_names as $tax ) { 
  216. $this->defaults[ 'title-tax-' . $tax ] = $archives . ' %%page%% %%sep%% %%sitename%%'; // Text field. 
  217. $this->defaults[ 'metadesc-tax-' . $tax ] = ''; // Text area. 
  218. $this->defaults[ 'metakey-tax-' . $tax ] = ''; // Text field. 
  219. $this->defaults[ 'hideeditbox-tax-' . $tax ] = false; 
  220.  
  221. if ( $tax !== 'post_format' ) { 
  222. $this->defaults[ 'noindex-tax-' . $tax ] = false; 
  223. else { 
  224. $this->defaults[ 'noindex-tax-' . $tax ] = true; 
  225. unset( $tax ); 
  226.  
  227.  
  228. /** 
  229. * Validate the option 
  230. * 
  231. * @param array $dirty New value for the option. 
  232. * @param array $clean Clean value for the option, normally the defaults. 
  233. * @param array $old Old value of the option. 
  234. * 
  235. * @return array Validated clean value for the option to be saved to the database 
  236. */ 
  237. protected function validate_option( $dirty, $clean, $old ) { 
  238. foreach ( $clean as $key => $value ) { 
  239. $switch_key = $this->get_switch_key( $key ); 
  240.  
  241. switch ( $switch_key ) { 
  242. /** 
  243. Text fields 
  244. */ 
  245.  
  246. /** 
  247. Covers: 
  248. 'title-home-wpseo', 'title-author-wpseo', 'title-archive-wpseo',  
  249. 'title-search-wpseo', 'title-404-wpseo' 
  250. 'title-' . $pt->name 
  251. 'title-ptarchive-' . $pt->name 
  252. 'title-tax-' . $tax->name 
  253. */ 
  254. case 'title-': 
  255. if ( isset( $dirty[ $key ] ) ) { 
  256. $clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] ); 
  257. break; 
  258.  
  259. /** 
  260. Covers: 
  261. 'metadesc-home-wpseo', 'metadesc-author-wpseo', 'metadesc-archive-wpseo' 
  262. 'metadesc-' . $pt->name 
  263. 'metadesc-ptarchive-' . $pt->name 
  264. 'metadesc-tax-' . $tax->name 
  265. */ 
  266. case 'metadesc-': 
  267. /** 
  268. Covers: 
  269. 'metakey-home-wpseo', 'metakey-author-wpseo' 
  270. 'metakey-' . $pt->name 
  271. 'metakey-ptarchive-' . $pt->name 
  272. 'metakey-tax-' . $tax->name 
  273. */ 
  274. case 'metakey-': 
  275. /** 
  276. Covers: 
  277. ''bctitle-ptarchive-' . $pt->name 
  278. */ 
  279. case 'bctitle-ptarchive-': 
  280. if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) { 
  281. $clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] ); 
  282. break; 
  283.  
  284.  
  285. /** integer field - not in form*/ 
  286. case 'title_test': 
  287. if ( isset( $dirty[ $key ] ) ) { 
  288. $int = WPSEO_Utils::validate_int( $dirty[ $key ] ); 
  289. if ( $int !== false && $int >= 0 ) { 
  290. $clean[ $key ] = $int; 
  291. elseif ( isset( $old[ $key ] ) ) { 
  292. $int = WPSEO_Utils::validate_int( $old[ $key ] ); 
  293. if ( $int !== false && $int >= 0 ) { 
  294. $clean[ $key ] = $int; 
  295. break; 
  296.  
  297. /** Separator field - Radio */ 
  298. case 'separator': 
  299. if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) { 
  300.  
  301. // Get separator fields. 
  302. $separator_fields = $this->get_separator_options(); 
  303.  
  304. // Check if the given separator is exists. 
  305. if ( isset( $separator_fields[ $dirty[ $key ] ] ) ) { 
  306. $clean[ $key ] = $dirty[ $key ]; 
  307. break; 
  308.  
  309. /** 
  310. Boolean fields 
  311. */ 
  312.  
  313. /** 
  314. Covers: 
  315. * 'noindex-subpages-wpseo', 'noindex-author-wpseo', 'noindex-archive-wpseo' 
  316. * 'noindex-' . $pt->name 
  317. * 'noindex-ptarchive-' . $pt->name 
  318. * 'noindex-tax-' . $tax->name 
  319. * 'forcerewritetitle': 
  320. * 'usemetakeywords': 
  321. * 'noodp': 
  322. * 'noydir': 
  323. * 'disable-author': 
  324. * 'disable-date': 
  325. * 'disable-post_format'; 
  326. * 'noindex-' 
  327. * 'showdate-' 
  328. * 'showdate-'. $pt->name 
  329. * 'hideeditbox-' 
  330. * 'hideeditbox-'. $pt->name 
  331. * 'hideeditbox-tax-' . $tax->name 
  332. */ 
  333. default: 
  334. $clean[ $key ] = ( isset( $dirty[ $key ] ) ? WPSEO_Utils::validate_bool( $dirty[ $key ] ) : false ); 
  335. break; 
  336.  
  337. return $clean; 
  338.  
  339.  
  340. /** 
  341. * Clean a given option value 
  342. * 
  343. * @param array $option_value Old (not merged with defaults or filtered) option value to 
  344. * clean according to the rules for this option. 
  345. * @param string $current_version (optional) Version from which to upgrade, if not set,  
  346. * version specific upgrades will be disregarded. 
  347. * @param array $all_old_option_values (optional) Only used when importing old options to have 
  348. * access to the real old values, in contrast to the saved ones. 
  349. * 
  350. * @return array Cleaned option 
  351. */ 
  352. protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) { 
  353. static $original = null; 
  354.  
  355. // Double-run this function to ensure renaming of the taxonomy options will work. 
  356. if ( ! isset( $original ) && has_action( 'wpseo_double_clean_titles', array( 
  357. $this,  
  358. 'clean',  
  359. ) ) === false 
  360. ) { 
  361. add_action( 'wpseo_double_clean_titles', array( $this, 'clean' ) ); 
  362. $original = $option_value; 
  363.  
  364. /** 
  365. Move options from very old option to this one 
  366. @internal Don't rename to the 'current' names straight away as that would prevent 
  367. the rename/unset combi below from working 
  368. @todo [JRF] maybe figure out a smarter way to deal with this 
  369. */ 
  370. $old_option = null; 
  371. if ( isset( $all_old_option_values ) ) { 
  372. // Ok, we have an import. 
  373. if ( isset( $all_old_option_values['wpseo_indexation'] ) && is_array( $all_old_option_values['wpseo_indexation'] ) && $all_old_option_values['wpseo_indexation'] !== array() ) { 
  374. $old_option = $all_old_option_values['wpseo_indexation']; 
  375. else { 
  376. $old_option = get_option( 'wpseo_indexation' ); 
  377. if ( is_array( $old_option ) && $old_option !== array() ) { 
  378. $move = array( 
  379. 'noindexauthor' => 'noindex-author',  
  380. 'disableauthor' => 'disable-author',  
  381. 'noindexdate' => 'noindex-archive',  
  382. 'noindexcat' => 'noindex-category',  
  383. 'noindextag' => 'noindex-post_tag',  
  384. 'noindexpostformat' => 'noindex-post_format',  
  385. 'noindexsubpages' => 'noindex-subpages',  
  386. ); 
  387. foreach ( $move as $old => $new ) { 
  388. if ( isset( $old_option[ $old ] ) && ! isset( $option_value[ $new ] ) ) { 
  389. $option_value[ $new ] = $old_option[ $old ]; 
  390. unset( $move, $old, $new ); 
  391. unset( $old_option ); 
  392.  
  393.  
  394. // Fix wrongness created by buggy version 1.2.2. 
  395. if ( isset( $option_value['title-home'] ) && $option_value['title-home'] === '%%sitename%% - %%sitedesc%% - 12345' ) { 
  396. $option_value['title-home-wpseo'] = '%%sitename%% - %%sitedesc%%'; 
  397.  
  398.  
  399. /** 
  400. Renaming these options to avoid ever overwritting these if a (bloody stupid) user / 
  401. programmer would use any of the following as a custom post type or custom taxonomy: 
  402. 'home', 'author', 'archive', 'search', '404', 'subpages' 
  403.   
  404. Similarly, renaming the tax options to avoid a custom post type and a taxonomy 
  405. with the same name occupying the same option 
  406. */ 
  407. $rename = array( 
  408. 'title-home' => 'title-home-wpseo',  
  409. 'title-author' => 'title-author-wpseo',  
  410. 'title-archive' => 'title-archive-wpseo',  
  411. 'title-search' => 'title-search-wpseo',  
  412. 'title-404' => 'title-404-wpseo',  
  413. 'metadesc-home' => 'metadesc-home-wpseo',  
  414. 'metadesc-author' => 'metadesc-author-wpseo',  
  415. 'metadesc-archive' => 'metadesc-archive-wpseo',  
  416. 'metakey-home' => 'metakey-home-wpseo',  
  417. 'metakey-author' => 'metakey-author-wpseo',  
  418. 'noindex-subpages' => 'noindex-subpages-wpseo',  
  419. 'noindex-author' => 'noindex-author-wpseo',  
  420. 'noindex-archive' => 'noindex-archive-wpseo',  
  421. ); 
  422. foreach ( $rename as $old => $new ) { 
  423. if ( isset( $option_value[ $old ] ) && ! isset( $option_value[ $new ] ) ) { 
  424. $option_value[ $new ] = $option_value[ $old ]; 
  425. unset( $option_value[ $old ] ); 
  426. unset( $rename, $old, $new ); 
  427.  
  428.  
  429. /** 
  430. * @internal This clean-up action can only be done effectively once the taxonomies and post_types 
  431. * have been registered, i.e. at the end of the init action. 
  432. */ 
  433. if ( isset( $original ) && current_filter() === 'wpseo_double_clean_titles' || did_action( 'wpseo_double_clean_titles' ) > 0 ) { 
  434. $rename = array( 
  435. 'title-' => 'title-tax-',  
  436. 'metadesc-' => 'metadesc-tax-',  
  437. 'metakey-' => 'metakey-tax-',  
  438. 'noindex-' => 'noindex-tax-',  
  439. 'tax-hideeditbox-' => 'hideeditbox-tax-',  
  440.  
  441. ); 
  442. $taxonomy_names = get_taxonomies( array( 'public' => true ), 'names' ); 
  443. $post_type_names = get_post_types( array( 'public' => true ), 'names' ); 
  444. $defaults = $this->get_defaults(); 
  445. if ( $taxonomy_names !== array() ) { 
  446. foreach ( $taxonomy_names as $tax ) { 
  447. foreach ( $rename as $old_prefix => $new_prefix ) { 
  448. if ( 
  449. ( isset( $original[ $old_prefix . $tax ] ) && ! isset( $original[ $new_prefix . $tax ] ) ) 
  450. && ( ! isset( $option_value[ $new_prefix . $tax ] ) 
  451. || ( isset( $option_value[ $new_prefix . $tax ] ) 
  452. && $option_value[ $new_prefix . $tax ] === $defaults[ $new_prefix . $tax ] ) ) 
  453. ) { 
  454. $option_value[ $new_prefix . $tax ] = $original[ $old_prefix . $tax ]; 
  455.  
  456. /** 
  457. Check if there is a cpt with the same name as the tax,  
  458. if so, we should make sure that the old setting hasn't been removed 
  459. */ 
  460. if ( ! isset( $post_type_names[ $tax ] ) && isset( $option_value[ $old_prefix . $tax ] ) ) { 
  461. unset( $option_value[ $old_prefix . $tax ] ); 
  462. else { 
  463. if ( isset( $post_type_names[ $tax ] ) && ! isset( $option_value[ $old_prefix . $tax ] ) ) { 
  464. $option_value[ $old_prefix . $tax ] = $original[ $old_prefix . $tax ]; 
  465.  
  466. if ( $old_prefix === 'tax-hideeditbox-' ) { 
  467. unset( $option_value[ $old_prefix . $tax ] ); 
  468. unset( $rename, $taxonomy_names, $post_type_names, $defaults, $tax, $old_prefix, $new_prefix ); 
  469.  
  470.  
  471. /** 
  472. Make sure the values of the variable option key options are cleaned as they 
  473. may be retained and would not be cleaned/validated then 
  474. */ 
  475. if ( is_array( $option_value ) && $option_value !== array() ) { 
  476. foreach ( $option_value as $key => $value ) { 
  477. $switch_key = $this->get_switch_key( $key ); 
  478.  
  479. // Similar to validation routine - any changes made there should be made here too. 
  480. switch ( $switch_key ) { 
  481. /** text fields */ 
  482. case 'title-': 
  483. case 'metadesc-': 
  484. case 'metakey-': 
  485. case 'bctitle-ptarchive-': 
  486. $option_value[ $key ] = WPSEO_Utils::sanitize_text_field( $value ); 
  487. break; 
  488.  
  489. case 'separator': 
  490. if ( ! array_key_exists( $value, $this->get_separator_options() ) ) { 
  491. $option_value[ $key ] = false; 
  492. break; 
  493.  
  494. /** 
  495. Boolean fields 
  496. */ 
  497.  
  498. /** 
  499. Covers: 
  500. * 'noindex-' 
  501. * 'showdate-' 
  502. * 'hideeditbox-' 
  503. */ 
  504. default: 
  505. $option_value[ $key ] = WPSEO_Utils::validate_bool( $value ); 
  506. break; 
  507. unset( $key, $value, $switch_key ); 
  508.  
  509. return $option_value; 
  510.  
  511.  
  512. /** 
  513. * Make sure that any set option values relating to post_types and/or taxonomies are retained,  
  514. * even when that post_type or taxonomy may not yet have been registered. 
  515. * 
  516. * @internal Overrule the abstract class version of this to make sure one extra renamed variable key 
  517. * does not get removed. IMPORTANT: keep this method in line with the parent on which it is based! 
  518. * 
  519. * @param array $dirty Original option as retrieved from the database. 
  520. * @param array $clean Filtered option where any options which shouldn't be in our option 
  521. * have already been removed and any options which weren't set 
  522. * have been set to their defaults. 
  523. * 
  524. * @return array 
  525. */ 
  526. protected function retain_variable_keys( $dirty, $clean ) { 
  527. if ( ( is_array( $this->variable_array_key_patterns ) && $this->variable_array_key_patterns !== array() ) && ( is_array( $dirty ) && $dirty !== array() ) ) { 
  528.  
  529. // Add the extra pattern. 
  530. $patterns = $this->variable_array_key_patterns; 
  531. $patterns[] = 'tax-hideeditbox-'; 
  532.  
  533. /** 
  534. * Allow altering the array with variable array key patterns 
  535. * 
  536. * @api array $patterns Array with the variable array key patterns 
  537. */ 
  538. $patterns = apply_filters( 'wpseo_option_titles_variable_array_key_patterns', $patterns ); 
  539.  
  540. foreach ( $dirty as $key => $value ) { 
  541.  
  542. // Do nothing if already in filtered option array. 
  543. if ( isset( $clean[ $key ] ) ) { 
  544. continue; 
  545.  
  546. foreach ( $patterns as $pattern ) { 
  547. if ( strpos( $key, $pattern ) === 0 ) { 
  548. $clean[ $key ] = $value; 
  549. break; 
  550.  
  551. return $clean; 
.