Parser.php
7.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
<?php
namespace WPML\BlockEditor\Blocks\LanguageSwitcher;
use WPML\BlockEditor\Blocks\LanguageSwitcher;
use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\BothLanguages;
use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\CurrentLanguage;
use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\LabelTemplateInterface;
use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\LanguageCode;
use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\NativeLanguage;
use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItemTemplate;
use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageSwitcherTemplate;
use WPML\FP\Obj;
class Parser {
const PATH_LANGUAGE_ITEM = '//*[@data-wpml="language-item"]';
const PATH_CURRENT_LANGUAGE_ITEM = '//*[@data-wpml="current-language-item"]';
const PATH_ITEM_LINK = '/*[@data-wpml="link"]';
const PATH_ITEM_LABEL = '/*[@data-wpml="label"]';
const PATH_ITEM_CODE_LABEL_TYPE = '/*[@data-wpml-label-type="code"]';
const PATH_ITEM_CURRENT_LABEL_TYPE = '/*[@data-wpml-label-type="current"]';
const PATH_ITEM_BOTH_LABEL_TYPE = '/*[@data-wpml-label-type="both"]';
const PATH_ITEM_NATIVE_LABEL_TYPE = '/*[@data-wpml-label-type="native"]';
const PATH_ITEM_FLAG_URL = '/*[@data-wpml="flag-url"]';
const PATH_LANGUAGE_ITEM_CONTAINER = '(//*[@data-wpml="language-item"])[last()]/..';
const PATH_CONTAINER_TEMPLATE = '(%s)[last()]/..';
const LABEL_TYPES = [
CurrentLanguage::class,
LanguageCode::class,
NativeLanguage::class,
BothLanguages::class,
];
/**
* @param string $blockHTML
*
* @return null|LanguageSwitcherTemplate
*/
public function parse( $blockAttrs, $blockHTML, $sourceBlock, $context ) {
if ( empty( $blockHTML ) ) {
return null;
}
// converts double quotes around font family to its corresponding XML entity in order to make it render properly on frontend.
$blockHTML = $this->maybeFixFontFamilyInStyle( $blockHTML );
// Replace some classNames according to values in context if the current block is Navigation Language Switcher
// We use values from context to inherit them from the parent Navigation Block
if ( $sourceBlock->name === LanguageSwitcher::BLOCK_NAVIGATION_LANGUAGE_SWITCHER ) {
// Replaces classNames to control openOnClick and showArrow settings according to values in context for the navigation LS
$blockHTML = $this->maybeReplaceSubmenuClassnamesForNavBlock( $blockHTML, $blockAttrs, $context );
// Replaces classNames to control orientation settings according to values in context for the navigation LS
$blockHTML = $this->maybeReplaceOrientationClassnamesForNavBlock( $blockHTML, $context );
}
$blockDOMDocument = new \DOMDocument();
libxml_use_internal_errors( true );
$blockDOMDocument->loadHTML( $blockHTML );
$errors = libxml_get_errors();
// todo: catch real errors here, this is required because usage of html5 tags will work, but will throw a warning.
$domXPath = new \DOMXpath( $blockDOMDocument );
$currentLanguageItemContainerNode = $this->getContainerNode( self::PATH_CURRENT_LANGUAGE_ITEM, $domXPath );
$currentLanguageItemLabel = $this->getLanguageItemlabel( $domXPath, self::PATH_CURRENT_LANGUAGE_ITEM );
$currentLanguageItemTemplate = new LanguageItemTemplate(
$this->getTemplateNode( self::PATH_CURRENT_LANGUAGE_ITEM, $currentLanguageItemContainerNode, $domXPath ),
$currentLanguageItemContainerNode,
$currentLanguageItemLabel
);
$languageItemContainerNode = $this->getContainerNode( self::PATH_LANGUAGE_ITEM, $domXPath );
$languageItemLabel = $this->getLanguageItemlabel( $domXPath, self::PATH_LANGUAGE_ITEM );
$languageItemTemplateNode = $this->getTemplateNode( self::PATH_LANGUAGE_ITEM, $languageItemContainerNode, $domXPath );
$languageItemTemplate = new LanguageItemTemplate(
$languageItemTemplateNode,
$languageItemContainerNode,
$languageItemLabel
);
return new LanguageSwitcherTemplate( $languageItemTemplate, $currentLanguageItemTemplate, $blockDOMDocument );
}
/**
* @param string $selector
* @param \DOMXpath $domQuery
*
* @return \DOMNode
*/
private function getContainerNode( $selector, $domQuery ) {
return $domQuery->query( sprintf( self::PATH_CONTAINER_TEMPLATE, $selector ) )->item( 0 );
}
/**
* @param string $selector
* @param \DOMNode $container
* @param \DOMXpath $domQuery
*
* @return \DOMNode
*/
private function getTemplateNode( $selector, $container, $domQuery ) {
$itemQuery = $domQuery->query( $selector );
$firstItem = $itemQuery->item( 0 );
if ( empty( $firstItem ) ) {
return null;
}
$template = $firstItem->cloneNode( true );
foreach ( $itemQuery as $item ) {
if ( $item instanceof \DOMElement ) {
$container->removeChild( $item );
} else if ( $item instanceof \DOMNode ) {
$item->remove();
}
}
return $template;
}
/**
* @param \DOMXPath $DOMXpath
*
* @return ?LabelTemplateInterface
*/
private function getLanguageItemlabel( $DOMXpath, $XPathPrefix ) {
foreach ( static::LABEL_TYPES as $labelTypeClass ) {
/** @var LabelTemplateInterface $labelType */
$labelType = new $labelTypeClass();
if ( $labelType->matchesXPath( $DOMXpath, $XPathPrefix ) ) {
return $labelType;
}
}
return null;
}
private function maybeFixFontFamilyInStyle( $blockHTML ) {
$fontFamilyValuePattern = '/\\--font-family:(.*?)\\;/';
$convertDoubleQuoteToXMLEntity = function ( $matches ) {
return str_replace( '"', '"', $matches[0] );
};
return preg_replace_callback( $fontFamilyValuePattern, $convertDoubleQuoteToXMLEntity, $blockHTML );
}
/**
* @param string $blockHTML
* @param array $source_block
* @param array $context
*
* @return string
*/
private function maybeReplaceSubmenuClassnamesForNavBlock( $blockHTML, $blockAttrs, $context ) {
$navigationLsHasSubMenuInSameBlock = Obj::propOr( null, 'navigationLsHasSubMenuInSameBlock', $blockAttrs );
if ( ! $navigationLsHasSubMenuInSameBlock ) {
$openOnClick = Obj::propOr( null, 'layoutOpenOnClick', $blockAttrs );
$showArrow = Obj::propOr( null, 'layoutShowArrow', $blockAttrs );
} else {
// If the navigation LS has submenu block inside the same parent navigation block,
// we inherit the values of openOnClick and showArrow settings from the parent block,
// otherwise the values from navigation LS attributes will be used
$openOnClick = Obj::propOr( null, 'openSubmenusOnClick', $context );
$showArrow = Obj::propOr( null, 'showSubmenuIcon', $context );
}
if ( $openOnClick !== null ) {
$openOnClickClass = 'open-on-click';
$openOnHoverClass = 'open-on-hover-click';
$blockHTML = $openOnClick
? str_replace( $openOnHoverClass, $openOnClickClass, $blockHTML )
: str_replace( $openOnClickClass, $openOnHoverClass, $blockHTML );
}
if ( $showArrow !== null ) {
$blockHTML = ! $showArrow
? str_replace( 'wpml-ls-dropdown', 'wpml-ls-dropdown hide-arrow', $blockHTML )
: $blockHTML;
}
return $blockHTML;
}
/**
* @param string $blockHTML
* @param array $context
*
* @return string
*/
private function maybeReplaceOrientationClassnamesForNavBlock( $blockHTML, $context ) {
$orientation = Obj::pathOr( null, [ 'layout', 'orientation' ], $context );
if ( $orientation === null ) {
return $blockHTML;
}
$verticalClasses = [ 'vertical-list', 'isVertical' ];
$horizontalClasses = [ 'horizontal-list', 'isHorizontal' ];
return $orientation === 'horizontal'
? str_replace( $verticalClasses, $horizontalClasses, $blockHTML )
: str_replace( $horizontalClasses, $verticalClasses, $blockHTML );
}
}