PluginProbe
Polylang / trunk
Polylang vtrunk
3.8.9 3.8.8 3.8.7 3.8.6 3.8.5 3.8.4 3.8.3 2.7 2.7.0.1 2.7.1 2.7.2 2.7.3 2.7.4 2.8 2.8.1 2.8.2 2.8.3 2.8.4 2.9 2.9.1 2.9.2 3.0 3.0.1 3.0.2 3.0.3 All 233 releases
polylang / src / modules / Blocks / Language_Switcher / Navigation / Block.php

Block.php in Polylang trunk, at src/modules/Blocks/Language_Switcher/Navigation/Block.php

325 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Polylang
4 */
5
6 namespace WP_Syntex\Polylang\Blocks\Language_Switcher\Navigation;
7
8 use WP_Block;
9 use PLL_Language;
10 use SplObjectStorage;
11 use WP_HTML_Tag_Processor;
12 use WP_Syntex\Polylang\Switcher\Switcher;
13 use WP_Syntex\Polylang\Switcher\Element\Abstract_Element;
14 use WP_Syntex\Polylang\Switcher\Element\Nav as Element;
15 use WP_Syntex\Polylang\Switcher\Settings\Menu as Settings;
16 use WP_Syntex\Polylang\Blocks\Language_Switcher\Abstract_Block;
17
18 /**
19 * Language switcher block for navigation.
20 *
21 * @since 3.2
22 * @since 3.8 Moved to Polylang Core and renamed to Language_Switcher\Navigation\Block.
23 */
24 class Block extends Abstract_Block {
25 /**
26 * Placeholder used to add language name or flag after WordPress renders the link labels.
27 *
28 * @var string
29 */
30 const PLACEHOLDER = '%pll%';
31
32 /**
33 * Trusted locale and label, keyed by the inner navigation blocks created by this switcher.
34 * Blocks parsed from saved content are never the same instances, so they can't be found here.
35 *
36 * @var SplObjectStorage<WP_Block, array{locale: string, label: string}>
37 */
38 private SplObjectStorage $internal_blocks;
39
40 /**
41 * Constructor.
42 *
43 * @since 3.9
44 *
45 * @param \PLL_Base $polylang Polylang object.
46 */
47 public function __construct( &$polylang ) {
48 parent::__construct( $polylang );
49
50 $this->internal_blocks = new SplObjectStorage();
51 }
52
53 /**
54 * Adds the required hooks specific to the navigation language switcher.
55 *
56 * @since 3.2
57 *
58 * @return self
59 */
60 public function init() {
61 parent::init();
62
63 add_action( 'rest_api_init', array( $this, 'register_switcher_menu_item_options_meta_rest_field' ) );
64 add_filter( 'render_block_core/navigation-link', array( $this, 'render_custom_attributes' ), 10, 3 );
65 add_filter( 'render_block_core/navigation-submenu', array( $this, 'render_custom_attributes' ), 10, 3 );
66 add_action( 'init', array( $this, 'register_styles' ) );
67
68 return $this;
69 }
70
71 /**
72 * Registers the styles for the navigation language switcher block.
73 *
74 * @since 3.8
75 *
76 * @return void
77 */
78 public function register_styles(): void {
79 $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
80
81 wp_register_style(
82 'pll-navigation-language-switcher',
83 plugins_url( 'css/build/navigation-language-switcher' . $suffix . '.css', POLYLANG_ROOT_FILE ),
84 array(),
85 POLYLANG_VERSION
86 );
87
88 wp_register_style(
89 'pll-navigation-language-switcher-editor',
90 plugins_url( 'css/build/navigation-language-switcher-editor' . $suffix . '.css', POLYLANG_ROOT_FILE ),
91 array(),
92 POLYLANG_VERSION
93 );
94 }
95
96 /**
97 * Returns the navigation language switcher block name with the Polylang's namespace.
98 *
99 * @since 3.2
100 *
101 * @return string The block name.
102 */
103 protected function get_block_name() {
104 return 'polylang/navigation-language-switcher';
105 }
106
107 /**
108 * Renders the `polylang/navigation-language-switcher` block on server.
109 *
110 * @since 3.1
111 * @since 3.3 Accepts two new parameters, $content and $block.
112 *
113 * @param array $attributes The block attributes.
114 * @param string $content The saved content. Unused.
115 * @param WP_Block $block The parsed block.
116 * @return string The HTML string output to serve.
117 */
118 public function render( $attributes, $content, $block ) {
119 $settings = new Settings( $attributes );
120 $elements = ( new Switcher( $settings, $this->links ) )->get_elements();
121
122 if ( empty( $elements ) ) {
123 return '';
124 }
125
126 $attributes['className'] .= ' ' . wp_apply_generated_classname_support( $block->block_type )['class'];
127
128 if ( 'dropdown' === $settings->layout ) {
129 $top_level_element = $this->get_top_level_element( $settings );
130
131 if ( empty( $top_level_element ) ) {
132 return '';
133 }
134
135 $inner_nav_link_blocks = array();
136 $attributes_wo_classname = array_diff_key( $attributes, array( 'className' => '' ) );
137
138 foreach ( $elements as $element ) {
139 $inner_nav_link_blocks[] = $this->create_inner_block( 'core/navigation-link', $attributes_wo_classname, $element, $block->context );
140 }
141
142 $submenu_block = $this->create_inner_block( 'core/navigation-submenu', $attributes, $top_level_element, $block->context, $inner_nav_link_blocks );
143 $output = $submenu_block->render();
144 } else {
145 $output = '';
146
147 foreach ( $elements as $element ) {
148 $output .= $this->create_inner_block( 'core/navigation-link', $attributes, $element, $block->context )->render();
149 }
150 }
151
152 return $output;
153 }
154
155 /**
156 * Register switcher menu item meta options as a REST API field.
157 *
158 * @since 3.2
159 *
160 * @return void
161 */
162 public function register_switcher_menu_item_options_meta_rest_field() {
163 register_post_meta(
164 'nav_menu_item',
165 '_pll_menu_item',
166 array(
167 'object_subtype' => 'nav_menu_item',
168 'description' => __( 'Language switcher settings', 'polylang' ),
169 'single' => true,
170 'show_in_rest' => array(
171 'schema' => array(
172 'type' => 'object',
173 'additionalProperties' => array(
174 'type' => 'boolean',
175 ),
176 ),
177 ),
178 )
179 );
180 }
181
182 /**
183 * Renders a core/naviagation-link or core/naviagation-submenu block by adding hreflang and lang attributes to the <a> tag
184 * and also the language flag if required.
185 *
186 * @since 3.6
187 *
188 * @param string $block_content The block content.
189 * @param array $block The full block, including name and attributes.
190 * @param WP_Block $instance The block instance.
191 *
192 * @return string A formatted HTML string representing the core/navigation-link or core/navigation-submenu block.
193 */
194 public function render_custom_attributes( $block_content, $block, $instance ) {
195 if ( ! $this->internal_blocks->offsetExists( $instance ) ) {
196 return $block_content;
197 }
198
199 $snapshot = $this->internal_blocks->offsetGet( $instance );
200 $this->internal_blocks->offsetUnset( $instance );
201
202 $content_tags = new WP_HTML_Tag_Processor( $block_content );
203
204 if ( 'core/navigation-submenu' === $instance->name ) {
205 // If `openSubmenusOnClick`, the submenu is rendered as a button, so there are no `<a>` to process.
206 if ( empty( $instance->context['openSubmenusOnClick'] ) && $content_tags->next_tag( array( 'tag_name' => 'a' ) ) ) {
207 $content_tags->set_attribute( 'hreflang', $snapshot['locale'] );
208 $content_tags->set_attribute( 'lang', $snapshot['locale'] );
209 }
210 if ( $content_tags->next_tag( array( 'tag_name' => 'button' ) ) ) {
211 $content_tags->set_attribute(
212 'aria-label',
213 str_replace(
214 static::PLACEHOLDER,
215 __( 'Languages', 'polylang' ),
216 (string) $content_tags->get_attribute( 'aria-label' )
217 )
218 );
219 }
220 } elseif ( $content_tags->next_tag( array( 'tag_name' => 'a' ) ) ) {
221 $content_tags->set_attribute( 'hreflang', $snapshot['locale'] );
222 $content_tags->set_attribute( 'lang', $snapshot['locale'] );
223 }
224
225 $overridden_block_content = $content_tags->get_updated_html();
226
227 return str_replace(
228 static::PLACEHOLDER,
229 $snapshot['label'],
230 $overridden_block_content
231 );
232 }
233
234 /**
235 * Returns the path to the block JSON file directory.
236 * The directory name being used to register a block.
237 *
238 * @since 3.8
239 *
240 * @return string The path to the block.
241 */
242 protected function get_path(): string {
243 return __DIR__;
244 }
245
246 /**
247 * Creates an inner navigation block and stores the locale and label to render for it.
248 *
249 * @since 3.9
250 *
251 * @param string $block_name Core block name.
252 * @param array $attributes Array of polylang/navigation-language-switcher attributes.
253 * @param Abstract_Element $element Switcher element.
254 * @param array $context Block context.
255 * @param WP_Block[] $inner_blocks Optional inner blocks.
256 * @return WP_Block
257 */
258 private function create_inner_block( string $block_name, array $attributes, Abstract_Element $element, array $context, array $inner_blocks = array() ): WP_Block {
259 $block = new WP_Block(
260 array(
261 'blockName' => $block_name,
262 'attrs' => $this->get_core_block_attributes( $attributes, $element ),
263 'innerBlocks' => $inner_blocks,
264 ),
265 $context
266 );
267
268 $this->internal_blocks->offsetSet(
269 $block,
270 array(
271 'locale' => $element->locale,
272 'label' => $this->apply_flag_styles_to_markup( $element->get_label(), $attributes ),
273 )
274 );
275
276 return $block;
277 }
278
279 /**
280 * Returns the top-level language element for a dropdown layout.
281 *
282 * @since 3.9
283 *
284 * @param Settings $settings Switcher settings.
285 * @return Element|null
286 */
287 private function get_top_level_element( Settings $settings ): ?Element {
288 $curlang = $this->links->curlang ?? $this->model->get_default_language();
289
290 if ( ! $curlang instanceof PLL_Language ) {
291 return null;
292 }
293
294 return new Element( $curlang, $settings, $this->links );
295 }
296
297 /**
298 * Returns attributes that fit for core/navigation-link or core/navigation-submenu and specific to polylang/navigation-language-switcher.
299 *
300 * @since 3.6
301 *
302 * @param array $attributes Array of polylang/navigation-language-switcher attributes.
303 * @param Abstract_Element $element Switcher element.
304 * @return array Attributes to be rendered by core.
305 */
306 private function get_core_block_attributes( array $attributes, Abstract_Element $element ): array {
307 $class_name = trim( implode( ' ', $element->item_classes ) );
308
309 if ( ! empty( $attributes['show_flags'] ) ) {
310 $aspect_ratio = $attributes['flag_aspect_ratio'] ?? '3:2';
311 $class_name .= ' pll-aspect-ratio-' . str_replace( ':', '', $aspect_ratio );
312 }
313
314 if ( ! empty( $attributes['className'] ) ) {
315 $class_name .= ' ' . $attributes['className'];
316 }
317
318 return array(
319 'label' => static::PLACEHOLDER,
320 'url' => $element->url,
321 'className' => $class_name,
322 );
323 }
324 }
325