PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.8-beta
Jetpack – WP Security, Backup, Speed, & Growth v15.8-beta
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / sitemaps / sitemap-buffer-xmlwriter.php
jetpack / modules / sitemaps Last commit date
sitemap-buffer-factory.php 6 months ago sitemap-buffer-fallback.php 6 months ago sitemap-buffer-image-fallback.php 6 months ago sitemap-buffer-image-xmlwriter.php 6 months ago sitemap-buffer-image.php 6 months ago sitemap-buffer-master-fallback.php 6 months ago sitemap-buffer-master-xmlwriter.php 6 months ago sitemap-buffer-master.php 6 months ago sitemap-buffer-news-fallback.php 6 months ago sitemap-buffer-news-xmlwriter.php 6 months ago sitemap-buffer-news.php 6 months ago sitemap-buffer-page-fallback.php 6 months ago sitemap-buffer-page-xmlwriter.php 6 months ago sitemap-buffer-page.php 6 months ago sitemap-buffer-video-fallback.php 6 months ago sitemap-buffer-video-xmlwriter.php 6 months ago sitemap-buffer-video.php 6 months ago sitemap-buffer-xmlwriter.php 6 months ago sitemap-buffer.php 6 months ago sitemap-builder.php 6 months ago sitemap-constants.php 6 months ago sitemap-finder.php 6 months ago sitemap-librarian.php 6 months ago sitemap-logger.php 6 months ago sitemap-state.php 6 months ago sitemap-stylist.php 6 months ago sitemaps.php 6 months ago
sitemap-buffer-xmlwriter.php
379 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * XMLWriter implementation of the sitemap buffer.
4 *
5 * @since 14.6
6 * @package automattic/jetpack
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit( 0 );
11 }
12
13 /**
14 * A buffer for constructing sitemap xml files using XMLWriter.
15 *
16 * @since 14.6
17 */
18 abstract class Jetpack_Sitemap_Buffer_XMLWriter {
19
20 /**
21 * Largest number of items the buffer can hold.
22 *
23 * @access protected
24 * @since 14.6
25 * @var int $item_capacity The item capacity.
26 */
27 protected $item_capacity;
28
29 /**
30 * Largest number of bytes the buffer can hold.
31 *
32 * @access protected
33 * @since 14.6
34 * @var int $byte_capacity The byte capacity.
35 */
36 protected $byte_capacity;
37
38 /**
39 * Flag which detects when the buffer is full.
40 *
41 * @access protected
42 * @since 14.6
43 * @var bool $is_full_flag The flag value.
44 */
45 protected $is_full_flag;
46
47 /**
48 * Flag which detects when the buffer is empty.
49 * Set true on construction and flipped to false only after a successful append.
50 *
51 * @since 15.0
52 * @var bool
53 */
54 protected $is_empty_flag = true;
55
56 /**
57 * The most recent timestamp seen by the buffer.
58 *
59 * @access protected
60 * @since 14.6
61 * @var string $timestamp Must be in 'YYYY-MM-DD hh:mm:ss' format.
62 */
63 protected $timestamp;
64
65 /**
66 * The XMLWriter instance used to construct the XML.
67 *
68 * @access protected
69 * @since 14.6
70 * @var XMLWriter $writer
71 */
72 protected $writer;
73
74 /**
75 * Helper class to construct sitemap paths.
76 *
77 * @since 14.6
78 * @protected
79 * @var Jetpack_Sitemap_Finder
80 */
81 protected $finder;
82
83 /**
84 * The XML content chunks collected from XMLWriter.
85 *
86 * Collect chunks and join once at the end to reduce string reallocations
87 * and improve performance on large sitemaps.
88 *
89 * @access protected
90 * @since 15.0
91 * @var array $chunks
92 */
93 protected $chunks = array();
94
95 /**
96 * Tracks whether the root element has been started.
97 *
98 * @since 15.0
99 * @var bool
100 */
101 protected $root_started = false;
102
103 /**
104 * Mirror DOMDocument built on-demand for jetpack_print_sitemap compatibility.
105 *
106 * @since 15.0
107 * @var DOMDocument|null
108 */
109 protected $dom_document = null;
110
111 /**
112 * Tracks whether XMLWriter document has been finalized (closed and flushed).
113 *
114 * @since 15.0
115 * @var bool
116 */
117 protected $is_finalized = false;
118
119 /**
120 * Construct a new Jetpack_Sitemap_Buffer_XMLWriter.
121 *
122 * @since 14.6
123 *
124 * @param int $item_limit The maximum size of the buffer in items.
125 * @param int $byte_limit The maximum size of the buffer in bytes.
126 * @param string $time The initial datetime of the buffer. Must be in 'YYYY-MM-DD hh:mm:ss' format.
127 */
128 public function __construct( $item_limit, $byte_limit, $time ) {
129 $this->is_full_flag = false;
130 $this->is_empty_flag = true;
131 $this->timestamp = $time;
132 $this->finder = new Jetpack_Sitemap_Finder();
133
134 $this->writer = new XMLWriter();
135 $this->writer->openMemory();
136 $this->writer->setIndent( true );
137 $this->writer->startDocument( '1.0', 'UTF-8' );
138
139 $this->item_capacity = max( 1, (int) $item_limit );
140 $this->byte_capacity = max( 1, (int) $byte_limit );
141
142 // Capture and account the XML declaration bytes to mirror DOM behavior.
143 $declaration = $this->writer->outputMemory( true );
144 $this->chunks[] = $declaration;
145 $this->byte_capacity -= strlen( $declaration );
146
147 // Allow subclasses to write comments and processing instructions only.
148 $this->initialize_buffer();
149
150 // Capture pre-root bytes (comments/PI). Do not subtract from capacity.
151 $pre_root_output = $this->writer->outputMemory( true );
152 $this->chunks[] = $pre_root_output;
153 }
154
155 /**
156 * Initialize the buffer with any required headers or setup.
157 * This should be implemented by child classes.
158 *
159 * @access protected
160 * @since 14.6
161 */
162 abstract protected function initialize_buffer();
163
164 /**
165 * Start the root element (e.g., urlset or sitemapindex) and write its attributes.
166 * Implemented by subclasses.
167 *
168 * @since 15.0
169 * @access protected
170 * @return void
171 */
172 abstract protected function start_root();
173
174 /**
175 * Ensure the root element has been started and account its bytes once.
176 *
177 * @since 15.0
178 * @access protected
179 * @return void
180 */
181 protected function ensure_root_started() {
182 if ( $this->root_started ) {
183 return;
184 }
185 $this->start_root();
186 $root_chunk = $this->writer->outputMemory( true );
187 $this->chunks[] = $root_chunk;
188 $this->byte_capacity -= strlen( $root_chunk );
189 $this->root_started = true;
190 }
191
192 /**
193 * Finalize writer output once by closing the root and document and flushing.
194 *
195 * @since 15.0
196 * @access protected
197 * @return void
198 */
199 protected function finalize_writer_output() {
200 if ( $this->is_finalized ) {
201 return;
202 }
203 $this->ensure_root_started();
204 $this->writer->endElement(); // End root element (urlset/sitemapindex)
205 $this->writer->endDocument();
206 $final_content = $this->writer->outputMemory( true );
207 $this->chunks[] = $final_content;
208 $this->is_finalized = true;
209 }
210
211 /**
212 * Append an item to the buffer.
213 *
214 * @since 14.6
215 *
216 * @param array $array The item to be added.
217 * @return bool True if the append succeeded, False if not.
218 */
219 public function append( $array ) {
220 if ( $array === null ) {
221 return true;
222 }
223
224 if ( $this->is_full_flag ) {
225 return false;
226 }
227
228 if ( 0 >= $this->item_capacity || 0 >= $this->byte_capacity ) {
229 $this->is_full_flag = true;
230 return false;
231 }
232
233 // Ensure root is started on first append and account its bytes.
234 $this->ensure_root_started();
235
236 // Attempt to render the item. Subclasses may decide to skip writing
237 // if the input structure is invalid for that sitemap type.
238 $this->append_item( $array );
239
240 // Capture only the bytes produced by this item.
241 $new_content = $this->writer->outputMemory( true );
242
243 // If nothing was written, treat as a no-op: keep the buffer "empty"
244 // and do not consume item/byte capacities.
245 if ( '' === $new_content ) {
246 return true;
247 }
248
249 // Persist newly written bytes and update capacities.
250 $this->chunks[] = $new_content;
251 $this->item_capacity -= 1;
252 $this->byte_capacity -= strlen( $new_content );
253 $this->is_empty_flag = false;
254
255 // Check both capacity limits.
256 if ( 0 >= $this->item_capacity || $this->byte_capacity <= 0 ) {
257 $this->is_full_flag = true;
258 }
259
260 return true;
261 }
262
263 /**
264 * Append a specific item to the buffer.
265 * This should be implemented by child classes.
266 *
267 * @access protected
268 * @since 14.6
269 * @param array $array The item to be added.
270 */
271 abstract protected function append_item( $array );
272
273 /**
274 * Recursively writes XML elements from an associative array.
275 *
276 * This method iterates through an array and writes XML elements using the XMLWriter instance.
277 * If a value in the array is itself an array, it calls itself recursively.
278 *
279 * @access protected
280 * @since 15.0
281 *
282 * @param array $data The array to convert to XML.
283 */
284 protected function array_to_xml( $data ) {
285 foreach ( (array) $data as $tag => $value ) {
286 if ( is_array( $value ) ) {
287 $this->writer->startElement( $tag );
288 $this->array_to_xml( $value );
289 $this->writer->endElement();
290 } else {
291 // Write raw text; XMLWriter will escape XML-reserved chars, matching DOMDocument behavior.
292 $this->writer->writeElement( $tag, (string) $value );
293 }
294 }
295 }
296
297 /**
298 * Retrieve the contents of the buffer.
299 *
300 * @since 14.6
301 * @return string The contents of the buffer.
302 */
303 public function contents() {
304 $this->finalize_writer_output();
305 if ( $this->dom_document instanceof DOMDocument ) {
306 return $this->dom_document->saveXML();
307 }
308 if ( empty( $this->chunks ) ) {
309 // If buffer is empty, return a minimal valid XML structure
310 return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"></urlset>";
311 }
312 return implode( '', $this->chunks );
313 }
314
315 /**
316 * Detect whether the buffer is full.
317 *
318 * @since 14.6
319 * @return bool True if the buffer is full, false otherwise.
320 */
321 public function is_full() {
322 return $this->is_full_flag;
323 }
324
325 /**
326 * Detect whether the buffer is empty.
327 *
328 * @since 14.6
329 * @return bool True if the buffer is empty, false otherwise.
330 */
331 public function is_empty() {
332 return $this->is_empty_flag;
333 }
334
335 /**
336 * Update the timestamp of the buffer.
337 *
338 * @since 14.6
339 * @param string $new_time A datetime string in 'YYYY-MM-DD hh:mm:ss' format.
340 */
341 public function view_time( $new_time ) {
342 $this->timestamp = max( $this->timestamp, $new_time );
343 }
344
345 /**
346 * Retrieve the timestamp of the buffer.
347 *
348 * @since 14.6
349 * @return string A datetime string in 'YYYY-MM-DD hh:mm:ss' format.
350 */
351 public function last_modified() {
352 return $this->timestamp;
353 }
354
355 /**
356 * Compatibility method for the old DOMDocument implementation.
357 * This is only here to satisfy the jetpack_print_sitemap filter.
358 *
359 * @since 14.6
360 * @return DOMDocument DOM representation of the current sitemap contents.
361 */
362 public function get_document() {
363 if ( $this->dom_document instanceof DOMDocument ) {
364 return $this->dom_document;
365 }
366
367 $this->finalize_writer_output();
368
369 $dom = new DOMDocument( '1.0', 'UTF-8' );
370 $dom->formatOutput = true; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
371 $dom->preserveWhiteSpace = false; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
372 // Load current XML content into DOM for compatibility with filters.
373 @$dom->loadXML( implode( '', $this->chunks ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Avoid fatal on unexpected content
374
375 $this->dom_document = $dom;
376 return $this->dom_document;
377 }
378 }
379