class-wpseo-utils.php 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Internals
  6. * @since 1.8.0
  7. */
  8. /**
  9. * Group of utility methods for use by WPSEO.
  10. * All methods are static, this is just a sort of namespacing class wrapper.
  11. */
  12. class WPSEO_Utils {
  13. /**
  14. * Whether the PHP filter extension is enabled.
  15. *
  16. * @since 1.8.0
  17. *
  18. * @var bool $has_filters
  19. */
  20. public static $has_filters;
  21. /**
  22. * Notifications to be shown in the JavaScript console.
  23. *
  24. * @since 3.3.2
  25. *
  26. * @var array
  27. */
  28. protected static $console_notifications = [];
  29. /**
  30. * Check whether the current user is allowed to access the configuration.
  31. *
  32. * @since 1.8.0
  33. *
  34. * @return boolean
  35. */
  36. public static function grant_access() {
  37. // @todo Add deprecation warning.
  38. if ( ! is_multisite() ) {
  39. return true;
  40. }
  41. $options = get_site_option( 'wpseo_ms' );
  42. if ( empty( $options['access'] ) || $options['access'] === 'admin' ) {
  43. return current_user_can( 'wpseo_manage_options' );
  44. }
  45. return is_super_admin();
  46. }
  47. /**
  48. * Check whether file editing is allowed for the .htaccess and robots.txt files.
  49. *
  50. * {@internal current_user_can() checks internally whether a user is on wp-ms and adjusts accordingly.}}
  51. *
  52. * @since 1.8.0
  53. *
  54. * @return bool
  55. */
  56. public static function allow_system_file_edit() {
  57. $allowed = true;
  58. if ( current_user_can( 'edit_files' ) === false ) {
  59. $allowed = false;
  60. }
  61. /**
  62. * Filter: 'wpseo_allow_system_file_edit' - Allow developers to change whether the editing of
  63. * .htaccess and robots.txt is allowed.
  64. *
  65. * @api bool $allowed Whether file editing is allowed.
  66. */
  67. return apply_filters( 'wpseo_allow_system_file_edit', $allowed );
  68. }
  69. /**
  70. * Check if the web server is running on Apache.
  71. *
  72. * @since 1.8.0
  73. *
  74. * @return bool
  75. */
  76. public static function is_apache() {
  77. if ( ! isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
  78. return false;
  79. }
  80. $software = sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
  81. return stripos( $software, 'apache' ) !== false;
  82. }
  83. /**
  84. * Check if the web server is running on Nginx.
  85. *
  86. * @since 1.8.0
  87. *
  88. * @return bool
  89. */
  90. public static function is_nginx() {
  91. if ( ! isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
  92. return false;
  93. }
  94. $software = sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
  95. return stripos( $software, 'nginx' ) !== false;
  96. }
  97. /**
  98. * Register a notification to be shown in the JavaScript console.
  99. *
  100. * @since 3.3.2
  101. *
  102. * @param string $identifier Notification identifier.
  103. * @param string $message Message to be shown.
  104. * @param bool $one_time_only Only show once (if added multiple times).
  105. */
  106. public static function javascript_console_notification( $identifier, $message, $one_time_only = false ) {
  107. static $registered_hook;
  108. if ( is_null( $registered_hook ) ) {
  109. add_action( 'admin_footer', [ __CLASS__, 'localize_console_notices' ], 999 );
  110. $registered_hook = true;
  111. }
  112. $prefix = 'Yoast SEO: ';
  113. if ( substr( $message, 0, strlen( $prefix ) ) !== $prefix ) {
  114. $message = $prefix . $message;
  115. }
  116. if ( $one_time_only ) {
  117. self::$console_notifications[ $identifier ] = $message;
  118. }
  119. else {
  120. self::$console_notifications[] = $message;
  121. }
  122. }
  123. /**
  124. * Localize the console notifications to JavaScript.
  125. *
  126. * @since 3.3.2
  127. */
  128. public static function localize_console_notices() {
  129. if ( empty( self::$console_notifications ) ) {
  130. return;
  131. }
  132. wp_localize_script( WPSEO_Admin_Asset_Manager::PREFIX . 'admin-global-script', 'wpseoConsoleNotifications', array_values( self::$console_notifications ) );
  133. }
  134. /**
  135. * Check whether a url is relative.
  136. *
  137. * @since 1.8.0
  138. *
  139. * @param string $url URL string to check.
  140. *
  141. * @return bool
  142. */
  143. public static function is_url_relative( $url ) {
  144. return ( strpos( $url, 'http' ) !== 0 && strpos( $url, '//' ) !== 0 );
  145. }
  146. /**
  147. * List all the available user roles.
  148. *
  149. * @since 1.8.0
  150. *
  151. * @return array $roles
  152. */
  153. public static function get_roles() {
  154. global $wp_roles;
  155. if ( ! isset( $wp_roles ) ) {
  156. $wp_roles = new WP_Roles();
  157. }
  158. $roles = $wp_roles->get_names();
  159. return $roles;
  160. }
  161. /**
  162. * Standardize whitespace in a string.
  163. *
  164. * Replace line breaks, carriage returns, tabs with a space, then remove double spaces.
  165. *
  166. * @since 1.8.0
  167. *
  168. * @param string $string String input to standardize.
  169. *
  170. * @return string
  171. */
  172. public static function standardize_whitespace( $string ) {
  173. return trim( str_replace( ' ', ' ', str_replace( [ "\t", "\n", "\r", "\f" ], ' ', $string ) ) );
  174. }
  175. /**
  176. * First strip out registered and enclosing shortcodes using native WordPress strip_shortcodes function.
  177. * Then strip out the shortcodes with a filthy regex, because people don't properly register their shortcodes.
  178. *
  179. * @since 1.8.0
  180. *
  181. * @param string $text Input string that might contain shortcodes.
  182. *
  183. * @return string $text String without shortcodes.
  184. */
  185. public static function strip_shortcode( $text ) {
  186. return preg_replace( '`\[[^\]]+\]`s', '', strip_shortcodes( $text ) );
  187. }
  188. /**
  189. * Recursively trim whitespace round a string value or of string values within an array.
  190. * Only trims strings to avoid typecasting a variable (to string).
  191. *
  192. * @since 1.8.0
  193. *
  194. * @param mixed $value Value to trim or array of values to trim.
  195. *
  196. * @return mixed Trimmed value or array of trimmed values.
  197. */
  198. public static function trim_recursive( $value ) {
  199. if ( is_string( $value ) ) {
  200. $value = trim( $value );
  201. }
  202. elseif ( is_array( $value ) ) {
  203. $value = array_map( [ __CLASS__, 'trim_recursive' ], $value );
  204. }
  205. return $value;
  206. }
  207. /**
  208. * Translates a decimal analysis score into a textual one.
  209. *
  210. * @since 1.8.0
  211. *
  212. * @param int $val The decimal score to translate.
  213. * @param bool $css_value Whether to return the i18n translated score or the CSS class value.
  214. *
  215. * @return string
  216. */
  217. public static function translate_score( $val, $css_value = true ) {
  218. $seo_rank = WPSEO_Rank::from_numeric_score( $val );
  219. if ( $css_value ) {
  220. return $seo_rank->get_css_class();
  221. }
  222. return $seo_rank->get_label();
  223. }
  224. /**
  225. * Emulate the WP native sanitize_text_field function in a %%variable%% safe way.
  226. *
  227. * @link https://core.trac.wordpress.org/browser/trunk/src/wp-includes/formatting.php for the original.
  228. *
  229. * Sanitize a string from user input or from the db.
  230. *
  231. * - Check for invalid UTF-8;
  232. * - Convert single < characters to entity;
  233. * - Strip all tags;
  234. * - Remove line breaks, tabs and extra white space;
  235. * - Strip octets - BUT DO NOT REMOVE (part of) VARIABLES WHICH WILL BE REPLACED.
  236. *
  237. * @since 1.8.0
  238. *
  239. * @param string $value String value to sanitize.
  240. *
  241. * @return string
  242. */
  243. public static function sanitize_text_field( $value ) {
  244. $filtered = wp_check_invalid_utf8( $value );
  245. if ( strpos( $filtered, '<' ) !== false ) {
  246. $filtered = wp_pre_kses_less_than( $filtered );
  247. // This will strip extra whitespace for us.
  248. $filtered = wp_strip_all_tags( $filtered, true );
  249. }
  250. else {
  251. $filtered = trim( preg_replace( '`[\r\n\t ]+`', ' ', $filtered ) );
  252. }
  253. $found = false;
  254. while ( preg_match( '`[^%](%[a-f0-9]{2})`i', $filtered, $match ) ) {
  255. $filtered = str_replace( $match[1], '', $filtered );
  256. $found = true;
  257. }
  258. unset( $match );
  259. if ( $found ) {
  260. // Strip out the whitespace that may now exist after removing the octets.
  261. $filtered = trim( preg_replace( '` +`', ' ', $filtered ) );
  262. }
  263. /**
  264. * Filter a sanitized text field string.
  265. *
  266. * @since WP 2.9.0
  267. *
  268. * @param string $filtered The sanitized string.
  269. * @param string $str The string prior to being sanitized.
  270. */
  271. return apply_filters( 'sanitize_text_field', $filtered, $value );
  272. }
  273. /**
  274. * Sanitize a url for saving to the database.
  275. * Not to be confused with the old native WP function.
  276. *
  277. * @todo [JRF => whomever] Check/improve url verification.
  278. *
  279. * @since 1.8.0
  280. *
  281. * @param string $value String URL value to sanitize.
  282. * @param array $allowed_protocols Optional set of allowed protocols.
  283. *
  284. * @return string
  285. */
  286. public static function sanitize_url( $value, $allowed_protocols = [ 'http', 'https' ] ) {
  287. return esc_url_raw( sanitize_text_field( rawurldecode( $value ) ), $allowed_protocols );
  288. }
  289. /**
  290. * Validate a value as boolean.
  291. *
  292. * @since 1.8.0
  293. *
  294. * @param mixed $value Value to validate.
  295. *
  296. * @return bool
  297. */
  298. public static function validate_bool( $value ) {
  299. if ( ! isset( self::$has_filters ) ) {
  300. self::$has_filters = extension_loaded( 'filter' );
  301. }
  302. if ( self::$has_filters ) {
  303. return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
  304. }
  305. else {
  306. return self::emulate_filter_bool( $value );
  307. }
  308. }
  309. /**
  310. * Cast a value to bool.
  311. *
  312. * @since 1.8.0
  313. *
  314. * @param mixed $value Value to cast.
  315. *
  316. * @return bool
  317. */
  318. public static function emulate_filter_bool( $value ) {
  319. $true = [
  320. '1',
  321. 'true',
  322. 'True',
  323. 'TRUE',
  324. 'y',
  325. 'Y',
  326. 'yes',
  327. 'Yes',
  328. 'YES',
  329. 'on',
  330. 'On',
  331. 'ON',
  332. ];
  333. $false = [
  334. '0',
  335. 'false',
  336. 'False',
  337. 'FALSE',
  338. 'n',
  339. 'N',
  340. 'no',
  341. 'No',
  342. 'NO',
  343. 'off',
  344. 'Off',
  345. 'OFF',
  346. ];
  347. if ( is_bool( $value ) ) {
  348. return $value;
  349. }
  350. elseif ( is_int( $value ) && ( $value === 0 || $value === 1 ) ) {
  351. return (bool) $value;
  352. }
  353. elseif ( ( is_float( $value ) && ! is_nan( $value ) ) && ( $value === (float) 0 || $value === (float) 1 ) ) {
  354. return (bool) $value;
  355. }
  356. elseif ( is_string( $value ) ) {
  357. $value = trim( $value );
  358. if ( in_array( $value, $true, true ) ) {
  359. return true;
  360. }
  361. elseif ( in_array( $value, $false, true ) ) {
  362. return false;
  363. }
  364. else {
  365. return false;
  366. }
  367. }
  368. return false;
  369. }
  370. /**
  371. * Validate a value as integer.
  372. *
  373. * @since 1.8.0
  374. *
  375. * @param mixed $value Value to validate.
  376. *
  377. * @return int|bool Int or false in case of failure to convert to int.
  378. */
  379. public static function validate_int( $value ) {
  380. if ( ! isset( self::$has_filters ) ) {
  381. self::$has_filters = extension_loaded( 'filter' );
  382. }
  383. if ( self::$has_filters ) {
  384. return filter_var( $value, FILTER_VALIDATE_INT );
  385. }
  386. else {
  387. return self::emulate_filter_int( $value );
  388. }
  389. }
  390. /**
  391. * Cast a value to integer.
  392. *
  393. * @since 1.8.0
  394. *
  395. * @param mixed $value Value to cast.
  396. *
  397. * @return int|bool
  398. */
  399. public static function emulate_filter_int( $value ) {
  400. if ( is_int( $value ) ) {
  401. return $value;
  402. }
  403. elseif ( is_float( $value ) ) {
  404. if ( (int) $value == $value && ! is_nan( $value ) ) {
  405. return (int) $value;
  406. }
  407. else {
  408. return false;
  409. }
  410. }
  411. elseif ( is_string( $value ) ) {
  412. $value = trim( $value );
  413. if ( $value === '' ) {
  414. return false;
  415. }
  416. elseif ( ctype_digit( $value ) ) {
  417. return (int) $value;
  418. }
  419. elseif ( strpos( $value, '-' ) === 0 && ctype_digit( substr( $value, 1 ) ) ) {
  420. return (int) $value;
  421. }
  422. else {
  423. return false;
  424. }
  425. }
  426. return false;
  427. }
  428. /**
  429. * Clears the WP or W3TC cache depending on which is used.
  430. *
  431. * @since 1.8.0
  432. */
  433. public static function clear_cache() {
  434. if ( function_exists( 'w3tc_pgcache_flush' ) ) {
  435. w3tc_pgcache_flush();
  436. }
  437. elseif ( function_exists( 'wp_cache_clear_cache' ) ) {
  438. wp_cache_clear_cache();
  439. }
  440. }
  441. /**
  442. * Flush W3TC cache after succesfull update/add of taxonomy meta option.
  443. *
  444. * @since 1.8.0
  445. */
  446. public static function flush_w3tc_cache() {
  447. if ( defined( 'W3TC_DIR' ) && function_exists( 'w3tc_objectcache_flush' ) ) {
  448. w3tc_objectcache_flush();
  449. }
  450. }
  451. /**
  452. * Clear rewrite rules.
  453. *
  454. * @since 1.8.0
  455. */
  456. public static function clear_rewrites() {
  457. delete_option( 'rewrite_rules' );
  458. }
  459. /**
  460. * Do simple reliable math calculations without the risk of wrong results.
  461. *
  462. * @link http://floating-point-gui.de/
  463. * @link http://php.net/language.types.float.php See the big red warning.
  464. *
  465. * In the rare case that the bcmath extension would not be loaded, it will return the normal calculation results.
  466. *
  467. * @since 1.5.0
  468. * @since 1.8.0 Moved from stand-alone function to this class.
  469. *
  470. * @param mixed $number1 Scalar (string/int/float/bool).
  471. * @param string $action Calculation action to execute. Valid input:
  472. * '+' or 'add' or 'addition',
  473. * '-' or 'sub' or 'subtract',
  474. * '*' or 'mul' or 'multiply',
  475. * '/' or 'div' or 'divide',
  476. * '%' or 'mod' or 'modulus'
  477. * '=' or 'comp' or 'compare'.
  478. * @param mixed $number2 Scalar (string/int/float/bool).
  479. * @param bool $round Whether or not to round the result. Defaults to false.
  480. * Will be disregarded for a compare operation.
  481. * @param int $decimals Decimals for rounding operation. Defaults to 0.
  482. * @param int $precision Calculation precision. Defaults to 10.
  483. *
  484. * @return mixed Calculation Result or false if either or the numbers isn't scalar or
  485. * an invalid operation was passed.
  486. * - For compare the result will always be an integer.
  487. * - For all other operations, the result will either be an integer (preferred)
  488. * or a float.
  489. */
  490. public static function calc( $number1, $action, $number2, $round = false, $decimals = 0, $precision = 10 ) {
  491. static $bc;
  492. if ( ! is_scalar( $number1 ) || ! is_scalar( $number2 ) ) {
  493. return false;
  494. }
  495. if ( ! isset( $bc ) ) {
  496. $bc = extension_loaded( 'bcmath' );
  497. }
  498. if ( $bc ) {
  499. $number1 = number_format( $number1, 10, '.', '' );
  500. $number2 = number_format( $number2, 10, '.', '' );
  501. }
  502. $result = null;
  503. $compare = false;
  504. switch ( $action ) {
  505. case '+':
  506. case 'add':
  507. case 'addition':
  508. $result = ( $bc ) ? bcadd( $number1, $number2, $precision ) /* string */ : ( $number1 + $number2 );
  509. break;
  510. case '-':
  511. case 'sub':
  512. case 'subtract':
  513. $result = ( $bc ) ? bcsub( $number1, $number2, $precision ) /* string */ : ( $number1 - $number2 );
  514. break;
  515. case '*':
  516. case 'mul':
  517. case 'multiply':
  518. $result = ( $bc ) ? bcmul( $number1, $number2, $precision ) /* string */ : ( $number1 * $number2 );
  519. break;
  520. case '/':
  521. case 'div':
  522. case 'divide':
  523. if ( $bc ) {
  524. $result = bcdiv( $number1, $number2, $precision ); // String, or NULL if right_operand is 0.
  525. }
  526. elseif ( $number2 != 0 ) {
  527. $result = ( $number1 / $number2 );
  528. }
  529. if ( ! isset( $result ) ) {
  530. $result = 0;
  531. }
  532. break;
  533. case '%':
  534. case 'mod':
  535. case 'modulus':
  536. if ( $bc ) {
  537. $result = bcmod( $number1, $number2 ); // String, or NULL if modulus is 0.
  538. }
  539. elseif ( $number2 != 0 ) {
  540. $result = ( $number1 % $number2 );
  541. }
  542. if ( ! isset( $result ) ) {
  543. $result = 0;
  544. }
  545. break;
  546. case '=':
  547. case 'comp':
  548. case 'compare':
  549. $compare = true;
  550. if ( $bc ) {
  551. $result = bccomp( $number1, $number2, $precision ); // Returns int 0, 1 or -1.
  552. }
  553. else {
  554. $result = ( $number1 == $number2 ) ? 0 : ( ( $number1 > $number2 ) ? 1 : - 1 );
  555. }
  556. break;
  557. }
  558. if ( isset( $result ) ) {
  559. if ( $compare === false ) {
  560. if ( $round === true ) {
  561. $result = round( floatval( $result ), $decimals );
  562. if ( $decimals === 0 ) {
  563. $result = (int) $result;
  564. }
  565. }
  566. else {
  567. $result = ( intval( $result ) == $result ) ? intval( $result ) : floatval( $result );
  568. }
  569. }
  570. return $result;
  571. }
  572. return false;
  573. }
  574. /**
  575. * Trim whitespace and NBSP (Non-breaking space) from string.
  576. *
  577. * @since 2.0.0
  578. *
  579. * @param string $string String input to trim.
  580. *
  581. * @return string
  582. */
  583. public static function trim_nbsp_from_string( $string ) {
  584. $find = [ '&nbsp;', chr( 0xC2 ) . chr( 0xA0 ) ];
  585. $string = str_replace( $find, ' ', $string );
  586. $string = trim( $string );
  587. return $string;
  588. }
  589. /**
  590. * Check if a string is a valid datetime.
  591. *
  592. * @since 2.0.0
  593. *
  594. * @param string $datetime String input to check as valid input for DateTime class.
  595. *
  596. * @return bool
  597. */
  598. public static function is_valid_datetime( $datetime ) {
  599. if ( substr( $datetime, 0, 1 ) === '-' ) {
  600. return false;
  601. }
  602. try {
  603. return new DateTime( $datetime ) !== false;
  604. } catch ( Exception $exc ) {
  605. return false;
  606. }
  607. }
  608. /**
  609. * Format the URL to be sure it is okay for using as a redirect url.
  610. *
  611. * This method will parse the URL and combine them in one string.
  612. *
  613. * @since 2.3.0
  614. *
  615. * @param string $url URL string.
  616. *
  617. * @return mixed
  618. */
  619. public static function format_url( $url ) {
  620. $parsed_url = wp_parse_url( $url );
  621. $formatted_url = '';
  622. if ( ! empty( $parsed_url['path'] ) ) {
  623. $formatted_url = $parsed_url['path'];
  624. }
  625. // Prepend a slash if first char != slash.
  626. if ( stripos( $formatted_url, '/' ) !== 0 ) {
  627. $formatted_url = '/' . $formatted_url;
  628. }
  629. // Append 'query' string if it exists.
  630. if ( ! empty( $parsed_url['query'] ) ) {
  631. $formatted_url .= '?' . $parsed_url['query'];
  632. }
  633. return apply_filters( 'wpseo_format_admin_url', $formatted_url );
  634. }
  635. /**
  636. * Get plugin name from file.
  637. *
  638. * @since 2.3.3
  639. *
  640. * @param string $plugin Plugin path relative to plugins directory.
  641. *
  642. * @return string|bool
  643. */
  644. public static function get_plugin_name( $plugin ) {
  645. $plugin_details = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
  646. if ( $plugin_details['Name'] !== '' ) {
  647. return $plugin_details['Name'];
  648. }
  649. return false;
  650. }
  651. /**
  652. * Retrieves the sitename.
  653. *
  654. * @since 3.0.0
  655. *
  656. * @return string
  657. */
  658. public static function get_site_name() {
  659. return wp_strip_all_tags( get_bloginfo( 'name' ), true );
  660. }
  661. /**
  662. * Retrieves the title separator.
  663. *
  664. * @since 3.0.0
  665. *
  666. * @return string
  667. */
  668. public static function get_title_separator() {
  669. $replacement = WPSEO_Options::get_default( 'wpseo_titles', 'separator' );
  670. // Get the titles option and the separator options.
  671. $separator = WPSEO_Options::get( 'separator' );
  672. $seperator_options = WPSEO_Option_Titles::get_instance()->get_separator_options();
  673. // This should always be set, but just to be sure.
  674. if ( isset( $seperator_options[ $separator ] ) ) {
  675. // Set the new replacement.
  676. $replacement = $seperator_options[ $separator ];
  677. }
  678. /**
  679. * Filter: 'wpseo_replacements_filter_sep' - Allow customization of the separator character(s).
  680. *
  681. * @api string $replacement The current separator.
  682. */
  683. return apply_filters( 'wpseo_replacements_filter_sep', $replacement );
  684. }
  685. /**
  686. * Check if the current opened page is a Yoast SEO page.
  687. *
  688. * @since 3.0.0
  689. *
  690. * @return bool
  691. */
  692. public static function is_yoast_seo_page() {
  693. static $is_yoast_seo;
  694. if ( $is_yoast_seo === null ) {
  695. $current_page = filter_input( INPUT_GET, 'page' );
  696. $is_yoast_seo = ( substr( $current_page, 0, 6 ) === 'wpseo_' );
  697. }
  698. return $is_yoast_seo;
  699. }
  700. /**
  701. * Check if the current opened page belongs to Yoast SEO Free.
  702. *
  703. * @since 3.3.0
  704. *
  705. * @param string $current_page The current page the user is on.
  706. *
  707. * @return bool
  708. */
  709. public static function is_yoast_seo_free_page( $current_page ) {
  710. $yoast_seo_free_pages = [
  711. 'wpseo_dashboard',
  712. 'wpseo_titles',
  713. 'wpseo_social',
  714. 'wpseo_advanced',
  715. 'wpseo_tools',
  716. 'wpseo_search_console',
  717. 'wpseo_licenses',
  718. ];
  719. return in_array( $current_page, $yoast_seo_free_pages, true );
  720. }
  721. /**
  722. * Checks if we are in the premium or free plugin.
  723. *
  724. * @return bool True when we are in the premium plugin.
  725. */
  726. public static function is_yoast_seo_premium() {
  727. return defined( 'WPSEO_PREMIUM_PLUGIN_FILE' );
  728. }
  729. /**
  730. * Determine if Yoast SEO is in development mode?
  731. *
  732. * Inspired by JetPack (https://github.com/Automattic/jetpack/blob/master/class.jetpack.php#L1383-L1406).
  733. *
  734. * @since 3.0.0
  735. *
  736. * @return bool
  737. */
  738. public static function is_development_mode() {
  739. $development_mode = false;
  740. if ( defined( 'WPSEO_DEBUG' ) ) {
  741. $development_mode = WPSEO_DEBUG;
  742. }
  743. elseif ( site_url() && false === strpos( site_url(), '.' ) ) {
  744. $development_mode = true;
  745. }
  746. /**
  747. * Filter the Yoast SEO development mode.
  748. *
  749. * @since 3.0
  750. *
  751. * @param bool $development_mode Is Yoast SEOs development mode active.
  752. */
  753. return apply_filters( 'yoast_seo_development_mode', $development_mode );
  754. }
  755. /**
  756. * Retrieve home URL with proper trailing slash.
  757. *
  758. * @since 3.3.0
  759. *
  760. * @param string $path Path relative to home URL.
  761. * @param string|null $scheme Scheme to apply.
  762. *
  763. * @return string Home URL with optional path, appropriately slashed if not.
  764. */
  765. public static function home_url( $path = '', $scheme = null ) {
  766. $home_url = home_url( $path, $scheme );
  767. if ( ! empty( $path ) ) {
  768. return $home_url;
  769. }
  770. $home_path = wp_parse_url( $home_url, PHP_URL_PATH );
  771. if ( '/' === $home_path ) { // Home at site root, already slashed.
  772. return $home_url;
  773. }
  774. if ( is_null( $home_path ) ) { // Home at site root, always slash.
  775. return trailingslashit( $home_url );
  776. }
  777. if ( is_string( $home_path ) ) { // Home in subdirectory, slash if permalink structure has slash.
  778. return user_trailingslashit( $home_url );
  779. }
  780. return $home_url;
  781. }
  782. /**
  783. * Returns a base64 URL for the svg for use in the menu.
  784. *
  785. * @since 3.3.0
  786. *
  787. * @param bool $base64 Whether or not to return base64'd output.
  788. *
  789. * @return string
  790. */
  791. public static function get_icon_svg( $base64 = true ) {
  792. $svg = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" style="fill:#82878c" viewBox="0 0 512 512" role="img" aria-hidden="true" focusable="false"><g><g><g><g><path d="M203.6,395c6.8-17.4,6.8-36.6,0-54l-79.4-204h70.9l47.7,149.4l74.8-207.6H116.4c-41.8,0-76,34.2-76,76V357c0,41.8,34.2,76,76,76H173C189,424.1,197.6,410.3,203.6,395z"/></g><g><path d="M471.6,154.8c0-41.8-34.2-76-76-76h-3L285.7,365c-9.6,26.7-19.4,49.3-30.3,68h216.2V154.8z"/></g></g><path stroke-width="2.974" stroke-miterlimit="10" d="M338,1.3l-93.3,259.1l-42.1-131.9h-89.1l83.8,215.2c6,15.5,6,32.5,0,48c-7.4,19-19,37.3-53,41.9l-7.2,1v76h8.3c81.7,0,118.9-57.2,149.6-142.9L431.6,1.3H338z M279.4,362c-32.9,92-67.6,128.7-125.7,131.8v-45c37.5-7.5,51.3-31,59.1-51.1c7.5-19.3,7.5-40.7,0-60l-75-192.7h52.8l53.3,166.8l105.9-294h58.1L279.4,362z"/></g></g></svg>';
  793. if ( $base64 ) {
  794. return 'data:image/svg+xml;base64,' . base64_encode( $svg );
  795. }
  796. return $svg;
  797. }
  798. /**
  799. * Returns the SVG for the traffic light in the metabox.
  800. *
  801. * @return string
  802. */
  803. public static function traffic_light_svg() {
  804. return <<<SVG
  805. <svg class="yst-traffic-light init" version="1.1" xmlns="http://www.w3.org/2000/svg"
  806. role="img" aria-hidden="true" focusable="false"
  807. x="0px" y="0px" viewBox="0 0 30 47" enable-background="new 0 0 30 47" xml:space="preserve">
  808. <g id="BG_1_">
  809. </g>
  810. <g id="traffic_light">
  811. <g>
  812. <g>
  813. <g>
  814. <path fill="#5B2942" d="M22,0H8C3.6,0,0,3.6,0,7.9v31.1C0,43.4,3.6,47,8,47h14c4.4,0,8-3.6,8-7.9V7.9C30,3.6,26.4,0,22,0z
  815. M27.5,38.8c0,3.1-2.6,5.7-5.8,5.7H8.3c-3.2,0-5.8-2.5-5.8-5.7V8.3c0-1.5,0.6-2.9,1.7-4c1.1-1,2.5-1.6,4.1-1.6h13.4
  816. c1.5,0,3,0.6,4.1,1.6c1.1,1.1,1.7,2.5,1.7,4V38.8z"/>
  817. </g>
  818. <g class="traffic-light-color traffic-light-red">
  819. <ellipse fill="#C8C8C8" cx="15" cy="23.5" rx="5.7" ry="5.6"/>
  820. <ellipse fill="#DC3232" cx="15" cy="10.9" rx="5.7" ry="5.6"/>
  821. <ellipse fill="#C8C8C8" cx="15" cy="36.1" rx="5.7" ry="5.6"/>
  822. </g>
  823. <g class="traffic-light-color traffic-light-orange">
  824. <ellipse fill="#F49A00" cx="15" cy="23.5" rx="5.7" ry="5.6"/>
  825. <ellipse fill="#C8C8C8" cx="15" cy="10.9" rx="5.7" ry="5.6"/>
  826. <ellipse fill="#C8C8C8" cx="15" cy="36.1" rx="5.7" ry="5.6"/>
  827. </g>
  828. <g class="traffic-light-color traffic-light-green">
  829. <ellipse fill="#C8C8C8" cx="15" cy="23.5" rx="5.7" ry="5.6"/>
  830. <ellipse fill="#C8C8C8" cx="15" cy="10.9" rx="5.7" ry="5.6"/>
  831. <ellipse fill="#63B22B" cx="15" cy="36.1" rx="5.7" ry="5.6"/>
  832. </g>
  833. <g class="traffic-light-color traffic-light-empty">
  834. <ellipse fill="#C8C8C8" cx="15" cy="23.5" rx="5.7" ry="5.6"/>
  835. <ellipse fill="#C8C8C8" cx="15" cy="10.9" rx="5.7" ry="5.6"/>
  836. <ellipse fill="#C8C8C8" cx="15" cy="36.1" rx="5.7" ry="5.6"/>
  837. </g>
  838. <g class="traffic-light-color traffic-light-init">
  839. <ellipse fill="#C8C8C8" cx="15" cy="23.5" rx="5.7" ry="5.6"/>
  840. <ellipse fill="#C8C8C8" cx="15" cy="10.9" rx="5.7" ry="5.6"/>
  841. <ellipse fill="#C8C8C8" cx="15" cy="36.1" rx="5.7" ry="5.6"/>
  842. </g>
  843. </g>
  844. </g>
  845. </g>
  846. </svg>
  847. SVG;
  848. }
  849. /**
  850. * Checks if the WP-REST-API is available.
  851. *
  852. * @since 3.6
  853. * @since 3.7 Introduced the $minimum_version parameter.
  854. *
  855. * @param string $minimum_version The minimum version the API should be.
  856. *
  857. * @return bool Returns true if the API is available.
  858. */
  859. public static function is_api_available( $minimum_version = '2.0' ) {
  860. return ( defined( 'REST_API_VERSION' )
  861. && version_compare( REST_API_VERSION, $minimum_version, '>=' ) );
  862. }
  863. /**
  864. * Determine whether or not the metabox should be displayed for a post type.
  865. *
  866. * @param string|null $post_type Optional. The post type to check the visibility of the metabox for.
  867. *
  868. * @return bool Whether or not the metabox should be displayed.
  869. */
  870. protected static function display_post_type_metabox( $post_type = null ) {
  871. if ( ! isset( $post_type ) ) {
  872. $post_type = get_post_type();
  873. }
  874. if ( ! isset( $post_type ) || ! WPSEO_Post_Type::is_post_type_accessible( $post_type ) ) {
  875. return false;
  876. }
  877. if ( $post_type === 'attachment' && WPSEO_Options::get( 'disable-attachment' ) ) {
  878. return false;
  879. }
  880. return WPSEO_Options::get( 'display-metabox-pt-' . $post_type );
  881. }
  882. /**
  883. * Determine whether or not the metabox should be displayed for a taxonomy.
  884. *
  885. * @param string|null $taxonomy Optional. The post type to check the visibility of the metabox for.
  886. *
  887. * @return bool Whether or not the metabox should be displayed.
  888. */
  889. protected static function display_taxonomy_metabox( $taxonomy = null ) {
  890. if ( ! isset( $taxonomy ) || ! in_array( $taxonomy, get_taxonomies( [ 'public' => true ], 'names' ), true ) ) {
  891. return false;
  892. }
  893. return WPSEO_Options::get( 'display-metabox-tax-' . $taxonomy );
  894. }
  895. /**
  896. * Determines whether the metabox is active for the given identifier and type.
  897. *
  898. * @param string $identifier The identifier to check for.
  899. * @param string $type The type to check for.
  900. *
  901. * @return bool Whether or not the metabox is active.
  902. */
  903. public static function is_metabox_active( $identifier, $type ) {
  904. if ( $type === 'post_type' ) {
  905. return self::display_post_type_metabox( $identifier );
  906. }
  907. if ( $type === 'taxonomy' ) {
  908. return self::display_taxonomy_metabox( $identifier );
  909. }
  910. return false;
  911. }
  912. /**
  913. * Determines whether or not WooCommerce is active.
  914. *
  915. * @return bool Whether or not WooCommerce is active.
  916. */
  917. public static function is_woocommerce_active() {
  918. return class_exists( 'Woocommerce' );
  919. }
  920. /**
  921. * Determines whether the plugin is active for the entire network.
  922. *
  923. * @return bool Whether or not the plugin is network-active.
  924. */
  925. public static function is_plugin_network_active() {
  926. static $network_active = null;
  927. if ( ! is_multisite() ) {
  928. return false;
  929. }
  930. // If a cached result is available, bail early.
  931. if ( $network_active !== null ) {
  932. return $network_active;
  933. }
  934. $network_active_plugins = wp_get_active_network_plugins();
  935. // Consider MU plugins and network-activated plugins as network-active.
  936. $network_active = strpos( wp_normalize_path( WPSEO_FILE ), wp_normalize_path( WPMU_PLUGIN_DIR ) ) === 0
  937. || in_array( WP_PLUGIN_DIR . '/' . WPSEO_BASENAME, $network_active_plugins, true );
  938. return $network_active;
  939. }
  940. /**
  941. * Getter for the Adminl10n array. Applies the wpseo_admin_l10n filter.
  942. *
  943. * @return array The Adminl10n array.
  944. */
  945. public static function get_admin_l10n() {
  946. $wpseo_admin_l10n = [];
  947. $additional_entries = apply_filters( 'wpseo_admin_l10n', [] );
  948. if ( is_array( $additional_entries ) ) {
  949. $wpseo_admin_l10n = array_merge( $wpseo_admin_l10n, $additional_entries );
  950. }
  951. return $wpseo_admin_l10n;
  952. }
  953. /**
  954. * Retrieves the analysis worker log level. Defaults to errors only.
  955. *
  956. * Uses bool YOAST_SEO_DEBUG as flag to enable logging. Off equals ERROR.
  957. * Uses string YOAST_SEO_DEBUG_ANALYSIS_WORKER as log level for the Analysis
  958. * Worker. Defaults to INFO.
  959. * Can be: TRACE, DEBUG, INFO, WARN or ERROR.
  960. *
  961. * @return string The log level to use.
  962. */
  963. public static function get_analysis_worker_log_level() {
  964. if ( defined( 'YOAST_SEO_DEBUG' ) && YOAST_SEO_DEBUG ) {
  965. return defined( 'YOAST_SEO_DEBUG_ANALYSIS_WORKER' ) ? YOAST_SEO_DEBUG_ANALYSIS_WORKER : 'INFO';
  966. }
  967. return 'ERROR';
  968. }
  969. /**
  970. * Returns the home url with the following modifications:
  971. *
  972. * In case of a multisite setup we return the network_home_url.
  973. * In case of no multisite setup we return the home_url while overriding the WPML filter.
  974. *
  975. * @codeCoverageIgnore
  976. *
  977. * @return string The home url.
  978. */
  979. public static function get_home_url() {
  980. // Add a new filter to undo WPML's changing of home url.
  981. add_filter( 'wpml_get_home_url', [ 'WPSEO_Utils', 'wpml_get_home_url' ], 10, 2 );
  982. $url = home_url();
  983. // If the plugin is network activated, use the network home URL.
  984. if ( self::is_plugin_network_active() ) {
  985. $url = network_home_url();
  986. }
  987. remove_filter( 'wpml_get_home_url', [ 'WPSEO_Utils', 'wpml_get_home_url' ], 10 );
  988. return $url;
  989. }
  990. /**
  991. * Returns the original URL instead of the language-enriched URL.
  992. * This method gets automatically triggered by the wpml_get_home_url filter.
  993. *
  994. * @codeCoverageIgnore
  995. *
  996. * @param string $home_url The url altered by WPML. Unused.
  997. * @param string $url The url that isn't altered by WPML.
  998. *
  999. * @return string The original url.
  1000. */
  1001. public static function wpml_get_home_url( $home_url, $url ) {
  1002. return $url;
  1003. }
  1004. /**
  1005. * Checks if the current installation supports MyYoast access tokens.
  1006. *
  1007. * @codeCoverageIgnore
  1008. *
  1009. * @return bool True if access_tokens are supported.
  1010. */
  1011. public static function has_access_token_support() {
  1012. return class_exists( 'WPSEO_MyYoast_Client' );
  1013. }
  1014. /**
  1015. * Prepares data for outputting as JSON.
  1016. *
  1017. * @param array $data The data to format.
  1018. *
  1019. * @return false|string The prepared JSON string.
  1020. */
  1021. public static function format_json_encode( $data ) {
  1022. $flags = JSON_UNESCAPED_SLASHES;
  1023. if ( self::is_development_mode() ) {
  1024. $flags = ( $flags | JSON_PRETTY_PRINT );
  1025. /**
  1026. * Filter the Yoast SEO development mode.
  1027. *
  1028. * @api array $data Allows filtering of the JSON data for debug purposes.
  1029. */
  1030. $data = apply_filters( 'wpseo_debug_json_data', $data );
  1031. }
  1032. return wp_json_encode( $data, $flags );
  1033. }
  1034. /**
  1035. * Output a Schema blob.
  1036. *
  1037. * @param array $graph The Schema graph array to output.
  1038. * @param string $class The (optional) class to add to the script tag.
  1039. *
  1040. * @return bool
  1041. */
  1042. public static function schema_output( $graph, $class = 'yoast-schema-graph' ) {
  1043. if ( ! is_array( $graph ) || empty( $graph ) ) {
  1044. return false;
  1045. }
  1046. // phpcs:ignore WordPress.Security.EscapeOutput -- Escaping happens in WPSEO_Utils::schema_tag, which should be whitelisted.
  1047. echo self::schema_tag( $graph, $class );
  1048. return true;
  1049. }
  1050. /**
  1051. * Returns a script tag with Schema blob.
  1052. *
  1053. * @param array $graph The Schema graph array to output.
  1054. * @param string $class The (optional) class to add to the script tag.
  1055. *
  1056. * @return false|string A schema blob with script tags.
  1057. */
  1058. public static function schema_tag( $graph, $class = 'yoast-schema-graph' ) {
  1059. if ( ! is_array( $graph ) || empty( $graph ) ) {
  1060. return false;
  1061. }
  1062. $output = [
  1063. '@context' => 'https://schema.org',
  1064. '@graph' => $graph,
  1065. ];
  1066. return "<script type='application/ld+json' class='" . esc_attr( $class ) . "'>" . self::format_json_encode( $output ) . '</script>' . "\n";
  1067. }
  1068. /**
  1069. * Extends the allowed post tags with accessibility-related attributes.
  1070. *
  1071. * @param array $allowed_post_tags The allowed post tags.
  1072. * @codeCoverageIgnore
  1073. *
  1074. * @return array The allowed tags including post tags, input tags and select tags.
  1075. */
  1076. public static function extend_kses_post_with_a11y( $allowed_post_tags ) {
  1077. static $a11y_tags;
  1078. if ( isset( $a11y_tags ) === false ) {
  1079. $a11y_tags = [
  1080. 'button' => [
  1081. 'aria-expanded' => true,
  1082. 'aria-controls' => true,
  1083. ],
  1084. 'div' => [
  1085. 'tabindex' => true,
  1086. ],
  1087. // Below are attributes that are needed for backwards compatibility (WP < 5.1).
  1088. 'span' => [
  1089. 'aria-hidden' => true,
  1090. ],
  1091. 'input' => [
  1092. 'aria-describedby' => true,
  1093. ],
  1094. 'select' => [
  1095. 'aria-describedby' => true,
  1096. ],
  1097. 'textarea' => [
  1098. 'aria-describedby' => true,
  1099. ],
  1100. ];
  1101. // Add the global allowed attributes to each html element.
  1102. $a11y_tags = array_map( '_wp_add_global_attributes', $a11y_tags );
  1103. }
  1104. return array_merge_recursive( $allowed_post_tags, $a11y_tags );
  1105. }
  1106. /**
  1107. * Extends the allowed post tags with input, select and option tags.
  1108. *
  1109. * @param array $allowed_post_tags The allowed post tags.
  1110. * @codeCoverageIgnore
  1111. *
  1112. * @return array The allowed tags including post tags, input tags, select tags and option tags.
  1113. */
  1114. public static function extend_kses_post_with_forms( $allowed_post_tags ) {
  1115. static $input_tags;
  1116. if ( isset( $input_tags ) === false ) {
  1117. $input_tags = [
  1118. 'input' => [
  1119. 'accept' => true,
  1120. 'accesskey' => true,
  1121. 'align' => true,
  1122. 'alt' => true,
  1123. 'autocomplete' => true,
  1124. 'autofocus' => true,
  1125. 'checked' => true,
  1126. 'contenteditable' => true,
  1127. 'dirname' => true,
  1128. 'disabled' => true,
  1129. 'draggable' => true,
  1130. 'dropzone' => true,
  1131. 'form' => true,
  1132. 'formaction' => true,
  1133. 'formenctype' => true,
  1134. 'formmethod' => true,
  1135. 'formnovalidate' => true,
  1136. 'formtarget' => true,
  1137. 'height' => true,
  1138. 'hidden' => true,
  1139. 'lang' => true,
  1140. 'list' => true,
  1141. 'max' => true,
  1142. 'maxlength' => true,
  1143. 'min' => true,
  1144. 'multiple' => true,
  1145. 'name' => true,
  1146. 'pattern' => true,
  1147. 'placeholder' => true,
  1148. 'readonly' => true,
  1149. 'required' => true,
  1150. 'size' => true,
  1151. 'spellcheck' => true,
  1152. 'src' => true,
  1153. 'step' => true,
  1154. 'tabindex' => true,
  1155. 'translate' => true,
  1156. 'type' => true,
  1157. 'value' => true,
  1158. 'width' => true,
  1159. /*
  1160. * Below are attributes that are needed for backwards compatibility (WP < 5.1).
  1161. * They are used for the social media image in the metabox.
  1162. * These can be removed once we move to the React versions of the social previews.
  1163. */
  1164. 'data-target' => true,
  1165. 'data-target-id' => true,
  1166. ],
  1167. 'select' => [
  1168. 'accesskey' => true,
  1169. 'autofocus' => true,
  1170. 'contenteditable' => true,
  1171. 'disabled' => true,
  1172. 'draggable' => true,
  1173. 'dropzone' => true,
  1174. 'form' => true,
  1175. 'hidden' => true,
  1176. 'lang' => true,
  1177. 'multiple' => true,
  1178. 'name' => true,
  1179. 'onblur' => true,
  1180. 'onchange' => true,
  1181. 'oncontextmenu' => true,
  1182. 'onfocus' => true,
  1183. 'oninput' => true,
  1184. 'oninvalid' => true,
  1185. 'onreset' => true,
  1186. 'onsearch' => true,
  1187. 'onselect' => true,
  1188. 'onsubmit' => true,
  1189. 'required' => true,
  1190. 'size' => true,
  1191. 'spellcheck' => true,
  1192. 'tabindex' => true,
  1193. 'translate' => true,
  1194. ],
  1195. 'option' => [
  1196. 'class' => true,
  1197. 'disabled' => true,
  1198. 'id' => true,
  1199. 'label' => true,
  1200. 'selected' => true,
  1201. 'value' => true,
  1202. ],
  1203. ];
  1204. // Add the global allowed attributes to each html element.
  1205. $input_tags = array_map( '_wp_add_global_attributes', $input_tags );
  1206. }
  1207. return array_merge_recursive( $allowed_post_tags, $input_tags );
  1208. }
  1209. /**
  1210. * Gets an array of enabled features.
  1211. *
  1212. * @return string[] The array of enabled features.
  1213. */
  1214. public static function retrieve_enabled_features() {
  1215. $enabled_features = [];
  1216. if ( defined( 'YOAST_SEO_ENABLED_FEATURES' ) ) {
  1217. $enabled_features = preg_split( '/,\W*/', YOAST_SEO_ENABLED_FEATURES );
  1218. }
  1219. // Make the array of enabled features filterable, so features can be enabled at will.
  1220. $enabled_features = apply_filters( 'wpseo_enable_feature', $enabled_features );
  1221. return $enabled_features;
  1222. }
  1223. /* ********************* DEPRECATED METHODS ********************* */
  1224. /**
  1225. * Returns the language part of a given locale, defaults to english when the $locale is empty.
  1226. *
  1227. * @see WPSEO_Language_Utils::get_language()
  1228. *
  1229. * @deprecated 9.5
  1230. * @codeCoverageIgnore
  1231. *
  1232. * @param string $locale The locale to get the language of.
  1233. *
  1234. * @return string The language part of the locale.
  1235. */
  1236. public static function get_language( $locale ) {
  1237. _deprecated_function( __METHOD__, 'WPSEO 9.5', 'WPSEO_Language_Utils::get_language' );
  1238. return WPSEO_Language_Utils::get_language( $locale );
  1239. }
  1240. /**
  1241. * Returns the user locale for the language to be used in the admin.
  1242. *
  1243. * WordPress 4.7 introduced the ability for users to specify an Admin language
  1244. * different from the language used on the front end. This checks if the feature
  1245. * is available and returns the user's language, with a fallback to the site's language.
  1246. * Can be removed when support for WordPress 4.6 will be dropped, in favor
  1247. * of WordPress get_user_locale() that already fallbacks to the site's locale.
  1248. *
  1249. * @see WPSEO_Language_Utils::get_user_locale()
  1250. *
  1251. * @deprecated 9.5
  1252. * @codeCoverageIgnore
  1253. *
  1254. * @return string The locale.
  1255. */
  1256. public static function get_user_locale() {
  1257. _deprecated_function( __METHOD__, 'WPSEO 9.5', 'WPSEO_Language_Utils::get_user_locale' );
  1258. return WPSEO_Language_Utils::get_user_locale();
  1259. }
  1260. }