Completed
Push — update/sitemaps-libxml ( 292726 )
by
unknown
11:41
created

Jetpack_Sitemap_Buffer::get_document()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Sitemaps (per the protocol) are essentially lists of XML fragments;
4
 * lists which are subject to size constraints. The Jetpack_Sitemap_Buffer
5
 * class abstracts the details of constructing these lists while
6
 * maintaining the constraints.
7
 *
8
 * @since 4.8.0
9
 * @package Jetpack
10
 */
11
12
/**
13
 * A buffer for constructing sitemap xml files.
14
 *
15
 * Models a list of strings such that
16
 *
17
 * 1. the list must have a bounded number of entries,
18
 * 2. the concatenation of the strings must have bounded
19
 *      length (including some header and footer strings), and
20
 * 3. each item has a timestamp, and we need to keep track
21
 *      of the most recent timestamp of the items in the list.
22
 *
23
 * @since 4.8.0
24
 */
25
abstract class Jetpack_Sitemap_Buffer {
26
27
	/**
28
	 * Largest number of items the buffer can hold.
29
	 *
30
	 * @access private
31
	 * @since 4.8.0
32
	 * @var int $item_capacity The item capacity.
33
	 */
34
	private $item_capacity;
35
36
	/**
37
	 * Largest number of bytes the buffer can hold.
38
	 *
39
	 * @access private
40
	 * @since 4.8.0
41
	 * @var int $byte_capacity The byte capacity.
42
	 */
43
	private $byte_capacity;
44
45
46
	/**
47
	 * Flag which detects when the buffer is full.
48
	 *
49
	 * @access private
50
	 * @since 4.8.0
51
	 * @var bool $is_full_flag The flag value. This flag is set to false on construction and only flipped to true if we've tried to add something and failed.
52
	 */
53
	private $is_full_flag;
54
55
	/**
56
	 * Flag which detects when the buffer is empty.
57
	 *
58
	 * @access private
59
	 * @since 4.8.0
60
	 * @var bool $is_empty_flag The flag value. This flag is set to true on construction and only flipped to false if we've tried to add something and succeeded.
61
	 */
62
	private $is_empty_flag;
63
64
	/**
65
	 * The most recent timestamp seen by the buffer.
66
	 *
67
	 * @access private
68
	 * @since 4.8.0
69
	 * @var string $timestamp Must be in 'YYYY-MM-DD hh:mm:ss' format.
70
	 */
71
	private $timestamp;
72
73
	/**
74
	 * The DOM document object that is currently being used to construct the XML doc.
75
	 *
76
	 * @access private
77
	 * @since 5.1.0
78
	 * @var DOMDocument $doc
79
	 */
80
	protected $doc = null;
81
82
	/**
83
	 * The root DOM element object that holds everything inside. Do not use directly, call
84
	 * the get_root_element getter method instead.
85
	 *
86
	 * @access private
87
	 * @since 5.1.0
88
	 * @var DOMElement $doc
89
	 */
90
	protected $root = null;
91
92
	/**
93
	 * Helper class to construct sitemap paths.
94
	 *
95
	 * @since 5.1.0
96
	 * @protected
97
	 * @var Jetpack_Sitemap_Finder
98
	 */
99
	protected $finder;
100
101
	/**
102
	 * Construct a new Jetpack_Sitemap_Buffer.
103
	 *
104
	 * @since 4.8.0
105
	 *
106
	 * @param int    $item_limit The maximum size of the buffer in items.
107
	 * @param int    $byte_limit The maximum size of the buffer in bytes.
108
	 * @param string $time The initial datetime of the buffer. Must be in 'YYYY-MM-DD hh:mm:ss' format.
109
	 */
110
	public function __construct( $item_limit, $byte_limit, $time ) {
111
		$this->item_capacity = max( 1, intval( $item_limit ) );
112
113
		mbstring_binary_safe_encoding(); // So we can safely use strlen().
114
		$this->byte_capacity = max( 1, intval( $byte_limit ) );
115
		reset_mbstring_encoding();
116
117
		$this->is_full_flag = false;
118
		$this->is_empty_flag = true;
119
		$this->timestamp = $time;
120
121
		$this->finder = new Jetpack_Sitemap_Finder();
122
		$this->doc = new DOMDocument( '1.0', 'UTF-8' );
123
	}
124
125
	/**
126
	 * Returns a DOM element that contains all sitemap elements.
127
	 *
128
	 * @access protected
129
	 * @since 5.1.0
130
	 * @return DOMElement $root
131
	 */
132
	abstract protected function get_root_element();
133
134
	/**
135
	 * Append an item to the buffer, if there is room for it,
136
	 * and set is_empty_flag to false. If there is no room,
137
	 * we set is_full_flag to true. If $item is null,
138
	 * don't do anything and report success.
139
	 *
140
	 * @since 4.8.0
141
	 *
142
	 * @param string $item The item to be added.
143
	 *
144
	 * @return bool True if the append succeeded, False if not.
145
	 */
146
	public function try_to_add_item( $item ) {
147
		_deprecated_function(
148
			'Jetpack_Sitemap_Buffer::try_to_add_item',
149
			'5.1.0',
150
			'Jetpack_Sitemap_Buffer::append'
151
		);
152
		$this->append( $item );
153
	}
154
155
	/**
156
	 * Append an item to the buffer, if there is room for it,
157
	 * and set is_empty_flag to false. If there is no room,
158
	 * we set is_full_flag to true. If $item is null,
159
	 * don't do anything and report success.
160
	 *
161
	 * @since 5.1.0
162
	 *
163
	 * @param string $item The item to be added.
0 ignored issues
show
Bug introduced by
There is no parameter named $item. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
164
	 *
165
	 * @return bool True if the append succeeded, False if not.
166
	 */
167
	public function append( $array ) {
168
		if ( is_null( $array ) ) {
169
			return true;
170
		}
171
172
		if ( $this->is_full_flag ) {
173
			return false;
174
		}
175
176
		if ( 0 >= $this->item_capacity ) {
177
			$this->is_full_flag = true;
178
			return false;
179
		} else {
180
			$this->item_capacity -= 1;
181
			$this->array_to_xml_string( $array, $this->get_root_element(), $this->doc );
182
183
			// If the new document is over the maximum, we don't add any more
184
			if ( strlen( $this->contents() ) > $this->byte_capacity ) {
185
				$this->is_full_flag = true;
186
			}
187
			return true;
188
		}
189
	}
190
191
	/**
192
	 * Retrieve the contents of the buffer.
193
	 *
194
	 * @since 4.8.0
195
	 *
196
	 * @return string The contents of the buffer (with the footer included).
197
	 */
198
	public function contents() {
199
		return $this->doc->saveXML();
200
	}
201
202
	/**
203
	 * Retrieve the document object.
204
	 *
205
	 * @since 5.1.0
206
	 * @return DOMDocument $doc
207
	 */
208
	public function get_document() {
209
		return $this->doc;
210
	}
211
212
	/**
213
	 * Detect whether the buffer is full.
214
	 *
215
	 * @since 4.8.0
216
	 *
217
	 * @return bool True if the buffer is full, false otherwise.
218
	 */
219
	public function is_full() {
220
		return $this->is_full_flag;
221
	}
222
223
	/**
224
	 * Detect whether the buffer is empty.
225
	 *
226
	 * @since 4.8.0
227
	 *
228
	 * @return bool True if the buffer is empty, false otherwise.
229
	 */
230
	public function is_empty() {
231
		return (
232
			! isset( $this->root )
233
			|| ! $this->root->hasChildNodes()
234
		);
235
	}
236
237
	/**
238
	 * Update the timestamp of the buffer.
239
	 *
240
	 * @since 4.8.0
241
	 *
242
	 * @param string $new_time A datetime string in 'YYYY-MM-DD hh:mm:ss' format.
243
	 */
244
	public function view_time( $new_time ) {
245
		$this->timestamp = max( $this->timestamp, $new_time );
246
	}
247
248
	/**
249
	 * Retrieve the timestamp of the buffer.
250
	 *
251
	 * @since 4.8.0
252
	 *
253
	 * @return string A datetime string in 'YYYY-MM-DD hh:mm:ss' format.
254
	 */
255
	public function last_modified() {
256
		return $this->timestamp;
257
	}
258
259
	/**
260
	 * Render an associative array as an XML string. This is needed because
261
	 * SimpleXMLElement only handles valid XML, but we sometimes want to
262
	 * pass around (possibly invalid) fragments. Note that 'null' values make
263
	 * a tag self-closing; this is only sometimes correct (depending on the
264
	 * version of HTML/XML); see the list of 'void tags'.
265
	 *
266
	 * Example:
267
	 *
268
	 * array(
269
	 *   'html' => array(                    |<html xmlns="foo">
270
	 *     'head' => array(                  |  <head>
271
	 *       'title' => 'Woo!',              |    <title>Woo!</title>
272
	 *     ),                                |  </head>
273
	 *     'body' => array(             ==>  |  <body>
274
	 *       'h2' => 'Some thing',           |    <h2>Some thing</h2>
275
	 *       'p'  => 'it's all up ons',      |    <p>it's all up ons</p>
276
	 *       'br' => null,                   |    <br />
277
	 *     ),                                |  </body>
278
	 *   ),                                  |</html>
279
	 * )
280
	 *
281
	 * @access protected
282
	 * @since 3.9.0
283
	 * @since 4.8.0 Rename, add $depth parameter, and change return type.
284
	 * @since 5.1.0 Refactor, remove $depth parameter, add $parent and $root, make access protected.
285
	 *
286
	 * @param array  $array A recursive associative array of tag/child relationships.
287
	 * @param DOMElement $parent (optional) an element to which new children should be added.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $parent not be DOMElement|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
288
	 * @param DOMDocument $root (optional) the parent document.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $root not be DOMDocument|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
289
	 *
290
	 * @return string|DOMDocument The rendered XML string or an object if root element is specified.
291
	 */
292
	protected function array_to_xml_string( $array, $parent = null, $root = null ) {
293
		$return_string = false;
294
295
		if ( null === $parent ) {
296
			$return_string = true;
297
			$parent = $root = new DOMDocument();
298
		}
299
300
		if ( is_array( $array ) ) {
301
302
			foreach ( $array as $key => $value ) {
303
				$element = $root->createElement( $key );
0 ignored issues
show
Bug introduced by
It seems like $root is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
304
				$parent->appendChild( $element );
305
306
				if ( is_array( $value ) ) {
307
					foreach ( $value as $child_key => $child_value ) {
308
						$child = $root->createElement( $child_key );
309
						$element->appendChild( $child );
310
						$child->appendChild( self::array_to_xml_string( $child_value, $child, $root ) );
311
					}
312
				} else {
313
					$element->appendChild(
314
						$root->createTextNode( $value )
315
					);
316
				}
317
			}
318
		} else {
319
			$element = $root->createTextNode( $array );
320
			$parent->appendChild( $element );
321
		}
322
323
		if ( $return_string ) {
324
			return $root->saveHTML();
325
		} else {
326
			return $element;
0 ignored issues
show
Bug introduced by
The variable $element does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
327
		}
328
	}
329
330
	/**
331
	 * Render an associative array of XML attribute key/value pairs.
332
	 *
333
	 * @access public
334
	 * @since 4.8.0
335
	 *
336
	 * @param array $array Key/value array of attributes.
337
	 *
338
	 * @return string The rendered attribute string.
339
	 */
340
	public static function array_to_xml_attr_string( $array ) {
341
		$doc = new DOMDocument();
342
		$element = $doc->createElement( 'div' );
343
344
		foreach ( $array as $key => $value ) {
345
			$key = preg_replace( '/[^a-zA-Z:_-]/', '_', $key );
346
			$element->setAttribute( $key, $value );
347
		}
348
349
		$doc->appendChild( $element );
350
351
		return substr( trim( $doc->saveHTML() ), 4, -7 );
352
	}
353
354
}
355