Completed
Push — fix/nosara ( 09438e...93ba64 )
by
unknown
11:29
created

Jetpack_Sitemap_Buffer::append()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 4
nop 1
dl 0
loc 21
rs 8.7624
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 protected
31
	 * @since 4.8.0
32
	 * @var int $item_capacity The item capacity.
33
	 */
34
	protected $item_capacity;
35
36
	/**
37
	 * Largest number of bytes the buffer can hold.
38
	 *
39
	 * @access protected
40
	 * @since 4.8.0
41
	 * @var int $byte_capacity The byte capacity.
42
	 */
43
	protected $byte_capacity;
44
45
	/**
46
	 * Flag which detects when the buffer is full.
47
	 *
48
	 * @access protected
49
	 * @since 4.8.0
50
	 * @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.
51
	 */
52
	protected $is_full_flag;
53
54
	/**
55
	 * Flag which detects when the buffer is empty.
56
	 *
57
	 * @access protected
58
	 * @since 4.8.0
59
	 * @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.
60
	 */
61
	protected $is_empty_flag;
62
63
	/**
64
	 * The most recent timestamp seen by the buffer.
65
	 *
66
	 * @access protected
67
	 * @since 4.8.0
68
	 * @var string $timestamp Must be in 'YYYY-MM-DD hh:mm:ss' format.
69
	 */
70
	protected $timestamp;
71
72
	/**
73
	 * The DOM document object that is currently being used to construct the XML doc.
74
	 *
75
	 * @access protected
76
	 * @since 5.3.0
77
	 * @var DOMDocument $doc
78
	 */
79
	protected $doc = null;
80
81
	/**
82
	 * The root DOM element object that holds everything inside. Do not use directly, call
83
	 * the get_root_element getter method instead.
84
	 *
85
	 * @access protected
86
	 * @since 5.3.0
87
	 * @var DOMElement $doc
88
	 */
89
	protected $root = null;
90
91
	/**
92
	 * Helper class to construct sitemap paths.
93
	 *
94
	 * @since 5.3.0
95
	 * @protected
96
	 * @var Jetpack_Sitemap_Finder
97
	 */
98
	protected $finder;
99
100
	/**
101
	 * Construct a new Jetpack_Sitemap_Buffer.
102
	 *
103
	 * @since 4.8.0
104
	 *
105
	 * @param int    $item_limit The maximum size of the buffer in items.
106
	 * @param int    $byte_limit The maximum size of the buffer in bytes.
107
	 * @param string $time The initial datetime of the buffer. Must be in 'YYYY-MM-DD hh:mm:ss' format.
108
	 */
109 View Code Duplication
	public function __construct( $item_limit, $byte_limit, $time ) {
110
		$this->is_full_flag = false;
111
		$this->timestamp = $time;
112
113
		$this->finder = new Jetpack_Sitemap_Finder();
114
		$this->doc = new DOMDocument( '1.0', 'UTF-8' );
115
116
		$this->item_capacity = max( 1, intval( $item_limit ) );
117
		$this->byte_capacity = max( 1, intval( $byte_limit ) ) - strlen( $this->contents() );
118
	}
119
120
	/**
121
	 * Returns a DOM element that contains all sitemap elements.
122
	 *
123
	 * @access protected
124
	 * @since 5.3.0
125
	 * @return DOMElement $root
126
	 */
127
	abstract protected function get_root_element();
128
129
	/**
130
	 * Append an item to the buffer, if there is room for it,
131
	 * and set is_empty_flag to false. If there is no room,
132
	 * we set is_full_flag to true. If $item is null,
133
	 * don't do anything and report success.
134
	 *
135
	 * @since 4.8.0
136
	 *
137
	 * @param string $item The item to be added.
138
	 *
139
	 * @return bool True if the append succeeded, False if not.
140
	 */
141
	public function try_to_add_item( $item ) {
142
		_deprecated_function(
143
			'Jetpack_Sitemap_Buffer::try_to_add_item',
144
			'5.3.0',
145
			'Jetpack_Sitemap_Buffer::append'
146
		);
147
		$this->append( $item );
0 ignored issues
show
Documentation introduced by
$item is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
148
	}
149
150
	/**
151
	 * Append an item to the buffer, if there is room for it,
152
	 * and set is_empty_flag to false. If there is no room,
153
	 * we set is_full_flag to true. If $item is null,
154
	 * don't do anything and report success.
155
	 *
156
	 * @since 5.3.0
157
	 *
158
	 * @param array $array The item to be added.
159
	 *
160
	 * @return bool True if the append succeeded, False if not.
161
	 */
162
	public function append( $array ) {
163
		if ( is_null( $array ) ) {
164
			return true;
165
		}
166
167
		if ( $this->is_full_flag ) {
168
			return false;
169
		}
170
171
		if ( 0 >= $this->item_capacity || 0 >= $this->byte_capacity ) {
172
			$this->is_full_flag = true;
173
			return false;
174
		} else {
175
			$this->item_capacity -= 1;
176
			$added_element = $this->array_to_xml_string( $array, $this->get_root_element(), $this->doc );
177
178
			$this->byte_capacity -= strlen( $this->doc->saveXML( $added_element ) );
179
180
			return true;
181
		}
182
	}
183
184
	/**
185
	 * Retrieve the contents of the buffer.
186
	 *
187
	 * @since 4.8.0
188
	 *
189
	 * @return string The contents of the buffer (with the footer included).
190
	 */
191
	public function contents() {
192
		return $this->doc->saveXML();
193
	}
194
195
	/**
196
	 * Retrieve the document object.
197
	 *
198
	 * @since 5.3.0
199
	 * @return DOMDocument $doc
200
	 */
201
	public function get_document() {
202
		return $this->doc;
203
	}
204
205
	/**
206
	 * Detect whether the buffer is full.
207
	 *
208
	 * @since 4.8.0
209
	 *
210
	 * @return bool True if the buffer is full, false otherwise.
211
	 */
212
	public function is_full() {
213
		return $this->is_full_flag;
214
	}
215
216
	/**
217
	 * Detect whether the buffer is empty.
218
	 *
219
	 * @since 4.8.0
220
	 *
221
	 * @return bool True if the buffer is empty, false otherwise.
222
	 */
223
	public function is_empty() {
224
		return (
225
			! isset( $this->root )
226
			|| ! $this->root->hasChildNodes()
227
		);
228
	}
229
230
	/**
231
	 * Update the timestamp of the buffer.
232
	 *
233
	 * @since 4.8.0
234
	 *
235
	 * @param string $new_time A datetime string in 'YYYY-MM-DD hh:mm:ss' format.
236
	 */
237
	public function view_time( $new_time ) {
238
		$this->timestamp = max( $this->timestamp, $new_time );
239
	}
240
241
	/**
242
	 * Retrieve the timestamp of the buffer.
243
	 *
244
	 * @since 4.8.0
245
	 *
246
	 * @return string A datetime string in 'YYYY-MM-DD hh:mm:ss' format.
247
	 */
248
	public function last_modified() {
249
		return $this->timestamp;
250
	}
251
252
	/**
253
	 * Render an associative array as an XML string. This is needed because
254
	 * SimpleXMLElement only handles valid XML, but we sometimes want to
255
	 * pass around (possibly invalid) fragments. Note that 'null' values make
256
	 * a tag self-closing; this is only sometimes correct (depending on the
257
	 * version of HTML/XML); see the list of 'void tags'.
258
	 *
259
	 * Example:
260
	 *
261
	 * array(
262
	 *   'html' => array(                    |<html xmlns="foo">
263
	 *     'head' => array(                  |  <head>
264
	 *       'title' => 'Woo!',              |    <title>Woo!</title>
265
	 *     ),                                |  </head>
266
	 *     'body' => array(             ==>  |  <body>
267
	 *       'h2' => 'Some thing',           |    <h2>Some thing</h2>
268
	 *       'p'  => 'it's all up ons',      |    <p>it's all up ons</p>
269
	 *       'br' => null,                   |    <br />
270
	 *     ),                                |  </body>
271
	 *   ),                                  |</html>
272
	 * )
273
	 *
274
	 * @access protected
275
	 * @since 3.9.0
276
	 * @since 4.8.0 Rename, add $depth parameter, and change return type.
277
	 * @since 5.3.0 Refactor, remove $depth parameter, add $parent and $root, make access protected.
278
	 *
279
	 * @param array  $array A recursive associative array of tag/child relationships.
280
	 * @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...
281
	 * @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...
282
	 *
283
	 * @return string|DOMDocument The rendered XML string or an object if root element is specified.
284
	 */
285
	protected function array_to_xml_string( $array, $parent = null, $root = null ) {
286
		$return_string = false;
287
288
		if ( null === $parent ) {
289
			$return_string = true;
290
			$parent = $root = new DOMDocument();
291
		}
292
293
		if ( is_array( $array ) ) {
294
295
			foreach ( $array as $key => $value ) {
296
				$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...
297
				$parent->appendChild( $element );
298
299
				if ( is_array( $value ) ) {
300
					foreach ( $value as $child_key => $child_value ) {
301
						$child = $root->createElement( $child_key );
302
						$element->appendChild( $child );
303
						$child->appendChild( self::array_to_xml_string( $child_value, $child, $root ) );
304
					}
305
				} else {
306
					$element->appendChild(
307
						$root->createTextNode( $value )
308
					);
309
				}
310
			}
311
		} else {
312
			$element = $root->createTextNode( $array );
313
			$parent->appendChild( $element );
314
		}
315
316
		if ( $return_string ) {
317
			return $root->saveHTML();
318
		} else {
319
			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...
320
		}
321
	}
322
}
323