PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.5
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.5
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / presenters / breadcrumbs-presenter.php

breadcrumbs-presenter.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 28.5, at src/presenters/breadcrumbs-presenter.php

282 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yoast\WP\SEO\Presenters;
4
5 use Yoast\WP\SEO\Presentations\Indexable_Presentation;
6
7 /**
8 * Presenter class for the breadcrumbs.
9 */
10 class Breadcrumbs_Presenter extends Abstract_Indexable_Presenter {
11
12 /**
13 * The id attribute.
14 *
15 * @var string
16 */
17 private $id;
18
19 /**
20 * The class name attribute.
21 *
22 * @var string
23 */
24 private $class;
25
26 /**
27 * The wrapper element name.
28 *
29 * @var string
30 */
31 private $wrapper;
32
33 /**
34 * Separator to use.
35 *
36 * @var string
37 */
38 private $separator;
39
40 /**
41 * The element.
42 *
43 * @var string
44 */
45 private $element;
46
47 /**
48 * Presents the breadcrumbs.
49 *
50 * @return string The breadcrumbs HTML.
51 */
52 public function present() {
53 $breadcrumbs = $this->get();
54 if ( ! \is_array( $breadcrumbs ) || empty( $breadcrumbs ) ) {
55 return '';
56 }
57
58 $links = [];
59 $total = \count( $breadcrumbs );
60 foreach ( $breadcrumbs as $index => $breadcrumb ) {
61 $links[ $index ] = $this->crumb_to_link( $breadcrumb, $index, $total );
62 }
63
64 // Removes any effectively empty links.
65 $links = \array_map( 'trim', $links );
66 $links = \array_filter( $links );
67 $output = \implode( $this->get_separator(), $links );
68
69 if ( empty( $output ) ) {
70 return '';
71 }
72
73 $output = '<' . $this->get_wrapper() . $this->get_id() . $this->get_class() . '>' . $output . '</' . $this->get_wrapper() . '>';
74 $output = $this->filter( $output );
75
76 $prefix = $this->helpers->options->get( 'breadcrumbs-prefix' );
77 if ( $prefix !== '' ) {
78 $output = "\t" . $prefix . "\n" . $output;
79 }
80
81 return $output;
82 }
83
84 /**
85 * Gets the raw value of a presentation.
86 *
87 * @return array The raw value.
88 */
89 public function get() {
90 return $this->presentation->breadcrumbs;
91 }
92
93 /**
94 * Filters the output.
95 *
96 * @param string $output The HTML output.
97 *
98 * @return string The filtered output.
99 */
100 protected function filter( $output ) {
101 /**
102 * Filter: 'wpseo_breadcrumb_output' - Allow changing the HTML output of the Yoast SEO breadcrumbs class.
103 *
104 * @param string $output The HTML output.
105 * @param Indexable_Presentation $presentation The presentation of an indexable.
106 */
107 return \apply_filters( 'wpseo_breadcrumb_output', $output, $this->presentation );
108 }
109
110 /**
111 * Create a breadcrumb element string.
112 *
113 * @param array $breadcrumb Link info array containing the keys:
114 * 'text' => (string) link text.
115 * 'url' => (string) link url.
116 * (optional) 'title' => (string) link title attribute text.
117 * @param int $index Index for the current breadcrumb.
118 * @param int $total The total number of breadcrumbs.
119 *
120 * @return string The breadcrumb link.
121 */
122 protected function crumb_to_link( $breadcrumb, $index, $total ) {
123 $link = '';
124
125 if ( ! isset( $breadcrumb['text'] ) || ! \is_string( $breadcrumb['text'] ) || empty( $breadcrumb['text'] ) ) {
126 return $link;
127 }
128
129 $text = \trim( $breadcrumb['text'] );
130
131 if (
132 $index < ( $total - 1 )
133 && isset( $breadcrumb['url'] )
134 && \is_string( $breadcrumb['url'] )
135 && ! empty( $breadcrumb['url'] )
136 ) {
137 // If it's not the last element and we have a url.
138 $link .= '<' . $this->get_element() . '>';
139 $title_attr = isset( $breadcrumb['title'] ) ? ' title="' . \esc_attr( $breadcrumb['title'] ) . '"' : '';
140 $link .= '<a';
141
142 if ( $this->should_link_target_blank() ) {
143 $link .= ' target="_blank"';
144 }
145 $link .= ' href="' . \esc_url( $breadcrumb['url'] ) . '"' . $title_attr . '>' . $text . '</a>';
146 $link .= '</' . $this->get_element() . '>';
147 }
148 elseif ( $index === ( $total - 1 ) ) {
149 // If it's the last element.
150
151 if ( $this->helpers->options->get( 'breadcrumbs-boldlast' ) === true ) {
152 $text = '<strong>' . $text . '</strong>';
153 }
154
155 $link .= '<' . $this->get_element() . ' class="breadcrumb_last" aria-current="page">' . $text . '</' . $this->get_element() . '>';
156 }
157 else {
158 // It's not the last element and has no url.
159 $link .= '<' . $this->get_element() . '>' . $text . '</' . $this->get_element() . '>';
160 }
161
162 /**
163 * Filter: 'wpseo_breadcrumb_single_link' - Allow changing of each link being put out by the Yoast SEO breadcrumbs class.
164 *
165 * @param string $link_output The output string.
166 * @param array $link The breadcrumb link array.
167 */
168 return \apply_filters( 'wpseo_breadcrumb_single_link', $link, $breadcrumb );
169 }
170
171 /**
172 * Retrieves HTML ID attribute.
173 *
174 * @return string The id attribute.
175 */
176 protected function get_id() {
177 if ( ! $this->id ) {
178 /**
179 * Filter: 'wpseo_breadcrumb_output_id' - Allow changing the HTML ID on the Yoast SEO breadcrumbs wrapper element.
180 *
181 * @param string $unsigned ID to add to the wrapper element.
182 */
183 $this->id = \apply_filters( 'wpseo_breadcrumb_output_id', '' );
184 if ( ! \is_string( $this->id ) ) {
185 return '';
186 }
187
188 if ( $this->id !== '' ) {
189 $this->id = ' id="' . \esc_attr( $this->id ) . '"';
190 }
191 }
192
193 return $this->id;
194 }
195
196 /**
197 * Retrieves HTML Class attribute.
198 *
199 * @return string The class attribute.
200 */
201 protected function get_class() {
202 if ( ! $this->class ) {
203 /**
204 * Filter: 'wpseo_breadcrumb_output_class' - Allow changing the HTML class on the Yoast SEO breadcrumbs wrapper element.
205 *
206 * @param string $unsigned Class to add to the wrapper element.
207 */
208 $this->class = \apply_filters( 'wpseo_breadcrumb_output_class', '' );
209 if ( ! \is_string( $this->class ) ) {
210 return '';
211 }
212
213 if ( $this->class !== '' ) {
214 $this->class = ' class="' . \esc_attr( $this->class ) . '"';
215 }
216 }
217
218 return $this->class;
219 }
220
221 /**
222 * Retrieves the wrapper element name.
223 *
224 * @return string The wrapper element name.
225 */
226 protected function get_wrapper() {
227 if ( ! $this->wrapper ) {
228 $this->wrapper = \apply_filters( 'wpseo_breadcrumb_output_wrapper', 'span' );
229 $this->wrapper = \tag_escape( $this->wrapper );
230 if ( ! \is_string( $this->wrapper ) || $this->wrapper === '' ) {
231 $this->wrapper = 'span';
232 }
233 }
234
235 return $this->wrapper;
236 }
237
238 /**
239 * Retrieves the separator.
240 *
241 * @return string The separator.
242 */
243 protected function get_separator() {
244 if ( ! $this->separator ) {
245 $this->separator = \apply_filters( 'wpseo_breadcrumb_separator', $this->helpers->options->get( 'breadcrumbs-sep' ) );
246 $this->separator = ' ' . $this->separator . ' ';
247 }
248
249 return $this->separator;
250 }
251
252 /**
253 * Retrieves the crumb element name.
254 *
255 * @return string The element to use.
256 */
257 protected function get_element() {
258 if ( ! $this->element ) {
259 $this->element = \esc_attr( \apply_filters( 'wpseo_breadcrumb_single_link_wrapper', 'span' ) );
260 }
261
262 return $this->element;
263 }
264
265 /**
266 * This is needed because when the editor is loaded in an Iframe the link needs to open in a different browser window.
267 * We don't want this behaviour in the front-end and the way to check this is to check if the block is rendered in a REST request with the `context` set as 'edit'. Thus being in the editor.
268 *
269 * @return bool returns if the breadcrumb should be opened in another window.
270 */
271 private function should_link_target_blank(): bool {
272 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
273 if ( isset( $_GET['context'] ) && \is_string( $_GET['context'] ) ) {
274 // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing.
275 if ( \wp_unslash( $_GET['context'] ) === 'edit' ) {
276 return true;
277 }
278 }
279 return false;
280 }
281 }
282