search.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. <?php
  2. /**
  3. * Server-side rendering of the `core/search` block.
  4. *
  5. * @package WordPress
  6. */
  7. /**
  8. * Dynamically renders the `core/search` block.
  9. *
  10. * @param array $attributes The block attributes.
  11. *
  12. * @return string The search block markup.
  13. */
  14. function render_block_core_search( $attributes ) {
  15. // Older versions of the Search block defaulted the label and buttonText
  16. // attributes to `__( 'Search' )` meaning that many posts contain `<!--
  17. // wp:search /-->`. Support these by defaulting an undefined label and
  18. // buttonText to `__( 'Search' )`.
  19. $attributes = wp_parse_args(
  20. $attributes,
  21. array(
  22. 'label' => __( 'Search' ),
  23. 'buttonText' => __( 'Search' ),
  24. )
  25. );
  26. $input_id = wp_unique_id( 'wp-block-search__input-' );
  27. $classnames = classnames_for_block_core_search( $attributes );
  28. $show_label = ( ! empty( $attributes['showLabel'] ) ) ? true : false;
  29. $use_icon_button = ( ! empty( $attributes['buttonUseIcon'] ) ) ? true : false;
  30. $show_input = ( ! empty( $attributes['buttonPosition'] ) && 'button-only' === $attributes['buttonPosition'] ) ? false : true;
  31. $show_button = ( ! empty( $attributes['buttonPosition'] ) && 'no-button' === $attributes['buttonPosition'] ) ? false : true;
  32. $query_params = ( ! empty( $attributes['query'] ) ) ? $attributes['query'] : array();
  33. $input_markup = '';
  34. $button_markup = '';
  35. $query_params_markup = '';
  36. $inline_styles = styles_for_block_core_search( $attributes );
  37. $color_classes = get_color_classes_for_block_core_search( $attributes );
  38. $typography_classes = get_typography_classes_for_block_core_search( $attributes );
  39. $is_button_inside = ! empty( $attributes['buttonPosition'] ) &&
  40. 'button-inside' === $attributes['buttonPosition'];
  41. // Border color classes need to be applied to the elements that have a border color.
  42. $border_color_classes = get_border_color_classes_for_block_core_search( $attributes );
  43. $label_inner_html = empty( $attributes['label'] ) ? __( 'Search' ) : wp_kses_post( $attributes['label'] );
  44. $label_markup = sprintf(
  45. '<label for="%1$s" class="wp-block-search__label screen-reader-text">%2$s</label>',
  46. esc_attr( $input_id ),
  47. $label_inner_html
  48. );
  49. if ( $show_label && ! empty( $attributes['label'] ) ) {
  50. $label_classes = array( 'wp-block-search__label' );
  51. if ( ! empty( $typography_classes ) ) {
  52. $label_classes[] = $typography_classes;
  53. }
  54. $label_markup = sprintf(
  55. '<label for="%1$s" class="%2$s" %3$s>%4$s</label>',
  56. esc_attr( $input_id ),
  57. esc_attr( implode( ' ', $label_classes ) ),
  58. $inline_styles['label'],
  59. $label_inner_html
  60. );
  61. }
  62. if ( $show_input ) {
  63. $input_classes = array( 'wp-block-search__input' );
  64. if ( $is_button_inside ) {
  65. $input_classes[] = $border_color_classes;
  66. }
  67. if ( ! empty( $typography_classes ) ) {
  68. $input_classes[] = $typography_classes;
  69. }
  70. $input_markup = sprintf(
  71. '<input type="search" id="%s" class="wp-block-search__input %s" name="s" value="%s" placeholder="%s" %s required />',
  72. $input_id,
  73. esc_attr( implode( ' ', $input_classes ) ),
  74. get_search_query(),
  75. esc_attr( $attributes['placeholder'] ),
  76. $inline_styles['input']
  77. );
  78. }
  79. if ( count( $query_params ) > 0 ) {
  80. foreach ( $query_params as $param => $value ) {
  81. $query_params_markup .= sprintf(
  82. '<input type="hidden" name="%s" value="%s" />',
  83. esc_attr( $param ),
  84. esc_attr( $value )
  85. );
  86. }
  87. }
  88. if ( $show_button ) {
  89. $button_classes = array( 'wp-block-search__button' );
  90. $button_internal_markup = '';
  91. if ( ! empty( $color_classes ) ) {
  92. $button_classes[] = $color_classes;
  93. }
  94. if ( ! empty( $typography_classes ) ) {
  95. $button_classes[] = $typography_classes;
  96. }
  97. $aria_label = '';
  98. if ( ! $is_button_inside && ! empty( $border_color_classes ) ) {
  99. $button_classes[] = $border_color_classes;
  100. }
  101. if ( ! $use_icon_button ) {
  102. if ( ! empty( $attributes['buttonText'] ) ) {
  103. $button_internal_markup = wp_kses_post( $attributes['buttonText'] );
  104. }
  105. } else {
  106. $aria_label = sprintf( 'aria-label="%s"', esc_attr( wp_strip_all_tags( $attributes['buttonText'] ) ) );
  107. $button_classes[] = 'has-icon';
  108. $button_internal_markup =
  109. '<svg class="search-icon" viewBox="0 0 24 24" width="24" height="24">
  110. <path d="M13.5 6C10.5 6 8 8.5 8 11.5c0 1.1.3 2.1.9 3l-3.4 3 1 1.1 3.4-2.9c1 .9 2.2 1.4 3.6 1.4 3 0 5.5-2.5 5.5-5.5C19 8.5 16.5 6 13.5 6zm0 9.5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"></path>
  111. </svg>';
  112. }
  113. // Include the button element class.
  114. $button_classes[] = wp_theme_get_element_class_name( 'button' );
  115. $button_markup = sprintf(
  116. '<button type="submit" class="%s" %s %s>%s</button>',
  117. esc_attr( implode( ' ', $button_classes ) ),
  118. $inline_styles['button'],
  119. $aria_label,
  120. $button_internal_markup
  121. );
  122. }
  123. $field_markup_classes = $is_button_inside ? $border_color_classes : '';
  124. $field_markup = sprintf(
  125. '<div class="wp-block-search__inside-wrapper %s" %s>%s</div>',
  126. esc_attr( $field_markup_classes ),
  127. $inline_styles['wrapper'],
  128. $input_markup . $query_params_markup . $button_markup
  129. );
  130. $wrapper_attributes = get_block_wrapper_attributes(
  131. array( 'class' => $classnames )
  132. );
  133. return sprintf(
  134. '<form role="search" method="get" action="%s" %s>%s</form>',
  135. esc_url( home_url( '/' ) ),
  136. $wrapper_attributes,
  137. $label_markup . $field_markup
  138. );
  139. }
  140. /**
  141. * Registers the `core/search` block on the server.
  142. */
  143. function register_block_core_search() {
  144. register_block_type_from_metadata(
  145. __DIR__ . '/search',
  146. array(
  147. 'render_callback' => 'render_block_core_search',
  148. )
  149. );
  150. }
  151. add_action( 'init', 'register_block_core_search' );
  152. /**
  153. * Builds the correct top level classnames for the 'core/search' block.
  154. *
  155. * @param array $attributes The block attributes.
  156. *
  157. * @return string The classnames used in the block.
  158. */
  159. function classnames_for_block_core_search( $attributes ) {
  160. $classnames = array();
  161. if ( ! empty( $attributes['buttonPosition'] ) ) {
  162. if ( 'button-inside' === $attributes['buttonPosition'] ) {
  163. $classnames[] = 'wp-block-search__button-inside';
  164. }
  165. if ( 'button-outside' === $attributes['buttonPosition'] ) {
  166. $classnames[] = 'wp-block-search__button-outside';
  167. }
  168. if ( 'no-button' === $attributes['buttonPosition'] ) {
  169. $classnames[] = 'wp-block-search__no-button';
  170. }
  171. if ( 'button-only' === $attributes['buttonPosition'] ) {
  172. $classnames[] = 'wp-block-search__button-only';
  173. }
  174. }
  175. if ( isset( $attributes['buttonUseIcon'] ) ) {
  176. if ( ! empty( $attributes['buttonPosition'] ) && 'no-button' !== $attributes['buttonPosition'] ) {
  177. if ( $attributes['buttonUseIcon'] ) {
  178. $classnames[] = 'wp-block-search__icon-button';
  179. } else {
  180. $classnames[] = 'wp-block-search__text-button';
  181. }
  182. }
  183. }
  184. return implode( ' ', $classnames );
  185. }
  186. /**
  187. * This generates a CSS rule for the given border property and side if provided.
  188. * Based on whether the Search block is configured to display the button inside
  189. * or not, the generated rule is injected into the appropriate collection of
  190. * styles for later application in the block's markup.
  191. *
  192. * @param array $attributes The block attributes.
  193. * @param string $property Border property to generate rule for e.g. width or color.
  194. * @param string $side Optional side border. The dictates the value retrieved and final CSS property.
  195. * @param array $wrapper_styles Current collection of wrapper styles.
  196. * @param array $button_styles Current collection of button styles.
  197. * @param array $input_styles Current collection of input styles.
  198. *
  199. * @return void
  200. */
  201. function apply_block_core_search_border_style( $attributes, $property, $side, &$wrapper_styles, &$button_styles, &$input_styles ) {
  202. $is_button_inside = 'button-inside' === _wp_array_get( $attributes, array( 'buttonPosition' ), false );
  203. $path = array( 'style', 'border', $property );
  204. if ( $side ) {
  205. array_splice( $path, 2, 0, $side );
  206. }
  207. $value = _wp_array_get( $attributes, $path, false );
  208. if ( empty( $value ) ) {
  209. return;
  210. }
  211. if ( 'color' === $property && $side ) {
  212. $has_color_preset = str_contains( $value, 'var:preset|color|' );
  213. if ( $has_color_preset ) {
  214. $named_color_value = substr( $value, strrpos( $value, '|' ) + 1 );
  215. $value = sprintf( 'var(--wp--preset--color--%s)', $named_color_value );
  216. }
  217. }
  218. $property_suffix = $side ? sprintf( '%s-%s', $side, $property ) : $property;
  219. if ( $is_button_inside ) {
  220. $wrapper_styles[] = sprintf( 'border-%s: %s;', $property_suffix, esc_attr( $value ) );
  221. } else {
  222. $button_styles[] = sprintf( 'border-%s: %s;', $property_suffix, esc_attr( $value ) );
  223. $input_styles[] = sprintf( 'border-%s: %s;', $property_suffix, esc_attr( $value ) );
  224. }
  225. }
  226. /**
  227. * This adds CSS rules for a given border property e.g. width or color. It
  228. * injects rules into the provided wrapper, button and input style arrays for
  229. * uniform "flat" borders or those with individual sides configured.
  230. *
  231. * @param array $attributes The block attributes.
  232. * @param string $property Border property to generate rule for e.g. width or color.
  233. * @param array $wrapper_styles Current collection of wrapper styles.
  234. * @param array $button_styles Current collection of button styles.
  235. * @param array $input_styles Current collection of input styles.
  236. *
  237. * @return void
  238. */
  239. function apply_block_core_search_border_styles( $attributes, $property, &$wrapper_styles, &$button_styles, &$input_styles ) {
  240. apply_block_core_search_border_style( $attributes, $property, null, $wrapper_styles, $button_styles, $input_styles );
  241. apply_block_core_search_border_style( $attributes, $property, 'top', $wrapper_styles, $button_styles, $input_styles );
  242. apply_block_core_search_border_style( $attributes, $property, 'right', $wrapper_styles, $button_styles, $input_styles );
  243. apply_block_core_search_border_style( $attributes, $property, 'bottom', $wrapper_styles, $button_styles, $input_styles );
  244. apply_block_core_search_border_style( $attributes, $property, 'left', $wrapper_styles, $button_styles, $input_styles );
  245. }
  246. /**
  247. * Builds an array of inline styles for the search block.
  248. *
  249. * The result will contain one entry for shared styles such as those for the
  250. * inner input or button and a second for the inner wrapper should the block
  251. * be positioning the button "inside".
  252. *
  253. * @param array $attributes The block attributes.
  254. *
  255. * @return array Style HTML attribute.
  256. */
  257. function styles_for_block_core_search( $attributes ) {
  258. $wrapper_styles = array();
  259. $button_styles = array();
  260. $input_styles = array();
  261. $label_styles = array();
  262. $is_button_inside = ! empty( $attributes['buttonPosition'] ) &&
  263. 'button-inside' === $attributes['buttonPosition'];
  264. $show_label = ( isset( $attributes['showLabel'] ) ) && false !== $attributes['showLabel'];
  265. // Add width styles.
  266. $has_width = ! empty( $attributes['width'] ) && ! empty( $attributes['widthUnit'] );
  267. $button_only = ! empty( $attributes['buttonPosition'] ) && 'button-only' === $attributes['buttonPosition'];
  268. if ( $has_width && ! $button_only ) {
  269. $wrapper_styles[] = sprintf(
  270. 'width: %d%s;',
  271. esc_attr( $attributes['width'] ),
  272. esc_attr( $attributes['widthUnit'] )
  273. );
  274. }
  275. // Add border width and color styles.
  276. apply_block_core_search_border_styles( $attributes, 'width', $wrapper_styles, $button_styles, $input_styles );
  277. apply_block_core_search_border_styles( $attributes, 'color', $wrapper_styles, $button_styles, $input_styles );
  278. apply_block_core_search_border_styles( $attributes, 'style', $wrapper_styles, $button_styles, $input_styles );
  279. // Add border radius styles.
  280. $has_border_radius = ! empty( $attributes['style']['border']['radius'] );
  281. if ( $has_border_radius ) {
  282. $default_padding = '4px';
  283. $border_radius = $attributes['style']['border']['radius'];
  284. if ( is_array( $border_radius ) ) {
  285. // Apply styles for individual corner border radii.
  286. foreach ( $border_radius as $key => $value ) {
  287. if ( null !== $value ) {
  288. // Convert camelCase key to kebab-case.
  289. $name = strtolower( preg_replace( '/(?<!^)[A-Z]/', '-$0', $key ) );
  290. // Add shared styles for individual border radii for input & button.
  291. $border_style = sprintf(
  292. 'border-%s-radius: %s;',
  293. esc_attr( $name ),
  294. esc_attr( $value )
  295. );
  296. $input_styles[] = $border_style;
  297. $button_styles[] = $border_style;
  298. // Add adjusted border radius styles for the wrapper element
  299. // if button is positioned inside.
  300. if ( $is_button_inside && intval( $value ) !== 0 ) {
  301. $wrapper_styles[] = sprintf(
  302. 'border-%s-radius: calc(%s + %s);',
  303. esc_attr( $name ),
  304. esc_attr( $value ),
  305. $default_padding
  306. );
  307. }
  308. }
  309. }
  310. } else {
  311. // Numeric check is for backwards compatibility purposes.
  312. $border_radius = is_numeric( $border_radius ) ? $border_radius . 'px' : $border_radius;
  313. $border_style = sprintf( 'border-radius: %s;', esc_attr( $border_radius ) );
  314. $input_styles[] = $border_style;
  315. $button_styles[] = $border_style;
  316. if ( $is_button_inside && intval( $border_radius ) !== 0 ) {
  317. // Adjust wrapper border radii to maintain visual consistency
  318. // with inner elements when button is positioned inside.
  319. $wrapper_styles[] = sprintf(
  320. 'border-radius: calc(%s + %s);',
  321. esc_attr( $border_radius ),
  322. $default_padding
  323. );
  324. }
  325. }
  326. }
  327. // Add color styles.
  328. $has_text_color = ! empty( $attributes['style']['color']['text'] );
  329. if ( $has_text_color ) {
  330. $button_styles[] = sprintf( 'color: %s;', $attributes['style']['color']['text'] );
  331. }
  332. $has_background_color = ! empty( $attributes['style']['color']['background'] );
  333. if ( $has_background_color ) {
  334. $button_styles[] = sprintf( 'background-color: %s;', $attributes['style']['color']['background'] );
  335. }
  336. $has_custom_gradient = ! empty( $attributes['style']['color']['gradient'] );
  337. if ( $has_custom_gradient ) {
  338. $button_styles[] = sprintf( 'background: %s;', $attributes['style']['color']['gradient'] );
  339. }
  340. // Get typography styles to be shared across inner elements.
  341. $typography_styles = esc_attr( get_typography_styles_for_block_core_search( $attributes ) );
  342. if ( ! empty( $typography_styles ) ) {
  343. $label_styles [] = $typography_styles;
  344. $button_styles[] = $typography_styles;
  345. $input_styles [] = $typography_styles;
  346. }
  347. // Typography text-decoration is only applied to the label and button.
  348. if ( ! empty( $attributes['style']['typography']['textDecoration'] ) ) {
  349. $text_decoration_value = sprintf( 'text-decoration: %s;', esc_attr( $attributes['style']['typography']['textDecoration'] ) );
  350. $button_styles[] = $text_decoration_value;
  351. // Input opts out of text decoration.
  352. if ( $show_label ) {
  353. $label_styles[] = $text_decoration_value;
  354. }
  355. }
  356. return array(
  357. 'input' => ! empty( $input_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $input_styles ) ) ) ) : '',
  358. 'button' => ! empty( $button_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $button_styles ) ) ) ) : '',
  359. 'wrapper' => ! empty( $wrapper_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $wrapper_styles ) ) ) ) : '',
  360. 'label' => ! empty( $label_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $label_styles ) ) ) ) : '',
  361. );
  362. }
  363. /**
  364. * Returns typography classnames depending on whether there are named font sizes/families .
  365. *
  366. * @param array $attributes The block attributes.
  367. *
  368. * @return string The typography color classnames to be applied to the block elements.
  369. */
  370. function get_typography_classes_for_block_core_search( $attributes ) {
  371. $typography_classes = array();
  372. $has_named_font_family = ! empty( $attributes['fontFamily'] );
  373. $has_named_font_size = ! empty( $attributes['fontSize'] );
  374. if ( $has_named_font_size ) {
  375. $typography_classes[] = sprintf( 'has-%s-font-size', esc_attr( $attributes['fontSize'] ) );
  376. }
  377. if ( $has_named_font_family ) {
  378. $typography_classes[] = sprintf( 'has-%s-font-family', esc_attr( $attributes['fontFamily'] ) );
  379. }
  380. return implode( ' ', $typography_classes );
  381. }
  382. /**
  383. * Returns typography styles to be included in an HTML style tag.
  384. * This excludes text-decoration, which is applied only to the label and button elements of the search block.
  385. *
  386. * @param array $attributes The block attributes.
  387. *
  388. * @return string A string of typography CSS declarations.
  389. */
  390. function get_typography_styles_for_block_core_search( $attributes ) {
  391. $typography_styles = array();
  392. // Add typography styles.
  393. if ( ! empty( $attributes['style']['typography']['fontSize'] ) ) {
  394. $typography_styles[] = sprintf(
  395. 'font-size: %s;',
  396. wp_get_typography_font_size_value(
  397. array(
  398. 'size' => $attributes['style']['typography']['fontSize'],
  399. )
  400. )
  401. );
  402. }
  403. if ( ! empty( $attributes['style']['typography']['fontFamily'] ) ) {
  404. $typography_styles[] = sprintf( 'font-family: %s;', $attributes['style']['typography']['fontFamily'] );
  405. }
  406. if ( ! empty( $attributes['style']['typography']['letterSpacing'] ) ) {
  407. $typography_styles[] = sprintf( 'letter-spacing: %s;', $attributes['style']['typography']['letterSpacing'] );
  408. }
  409. if ( ! empty( $attributes['style']['typography']['fontWeight'] ) ) {
  410. $typography_styles[] = sprintf( 'font-weight: %s;', $attributes['style']['typography']['fontWeight'] );
  411. }
  412. if ( ! empty( $attributes['style']['typography']['fontStyle'] ) ) {
  413. $typography_styles[] = sprintf( 'font-style: %s;', $attributes['style']['typography']['fontStyle'] );
  414. }
  415. if ( ! empty( $attributes['style']['typography']['lineHeight'] ) ) {
  416. $typography_styles[] = sprintf( 'line-height: %s;', $attributes['style']['typography']['lineHeight'] );
  417. }
  418. if ( ! empty( $attributes['style']['typography']['textTransform'] ) ) {
  419. $typography_styles[] = sprintf( 'text-transform: %s;', $attributes['style']['typography']['textTransform'] );
  420. }
  421. return implode( '', $typography_styles );
  422. }
  423. /**
  424. * Returns border color classnames depending on whether there are named or custom border colors.
  425. *
  426. * @param array $attributes The block attributes.
  427. *
  428. * @return string The border color classnames to be applied to the block elements.
  429. */
  430. function get_border_color_classes_for_block_core_search( $attributes ) {
  431. $border_color_classes = array();
  432. $has_custom_border_color = ! empty( $attributes['style']['border']['color'] );
  433. $has_named_border_color = ! empty( $attributes['borderColor'] );
  434. if ( $has_custom_border_color || $has_named_border_color ) {
  435. $border_color_classes[] = 'has-border-color';
  436. }
  437. if ( $has_named_border_color ) {
  438. $border_color_classes[] = sprintf( 'has-%s-border-color', esc_attr( $attributes['borderColor'] ) );
  439. }
  440. return implode( ' ', $border_color_classes );
  441. }
  442. /**
  443. * Returns color classnames depending on whether there are named or custom text and background colors.
  444. *
  445. * @param array $attributes The block attributes.
  446. *
  447. * @return string The color classnames to be applied to the block elements.
  448. */
  449. function get_color_classes_for_block_core_search( $attributes ) {
  450. $classnames = array();
  451. // Text color.
  452. $has_named_text_color = ! empty( $attributes['textColor'] );
  453. $has_custom_text_color = ! empty( $attributes['style']['color']['text'] );
  454. if ( $has_named_text_color ) {
  455. $classnames[] = sprintf( 'has-text-color has-%s-color', $attributes['textColor'] );
  456. } elseif ( $has_custom_text_color ) {
  457. // If a custom 'textColor' was selected instead of a preset, still add the generic `has-text-color` class.
  458. $classnames[] = 'has-text-color';
  459. }
  460. // Background color.
  461. $has_named_background_color = ! empty( $attributes['backgroundColor'] );
  462. $has_custom_background_color = ! empty( $attributes['style']['color']['background'] );
  463. $has_named_gradient = ! empty( $attributes['gradient'] );
  464. $has_custom_gradient = ! empty( $attributes['style']['color']['gradient'] );
  465. if (
  466. $has_named_background_color ||
  467. $has_custom_background_color ||
  468. $has_named_gradient ||
  469. $has_custom_gradient
  470. ) {
  471. $classnames[] = 'has-background';
  472. }
  473. if ( $has_named_background_color ) {
  474. $classnames[] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] );
  475. }
  476. if ( $has_named_gradient ) {
  477. $classnames[] = sprintf( 'has-%s-gradient-background', $attributes['gradient'] );
  478. }
  479. return implode( ' ', $classnames );
  480. }