PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.3
1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 All 29 releases
← All changes | includes/wp-cache-constant.php +494 -12 1.1.71.3.3 View file →
@@ -13,8 +13,486 @@
13 13 */
14 14
15 15 defined( 'ABSPATH' ) || exit;
16 16
17 +if ( ! function_exists( 'xspeed_has_canonical_dropin_signature' ) ) {
18 + /**
19 + * Prove xSpeed ownership from an exact standalone PHP comment marker.
20 + *
21 + * A loose substring is unsafe: foreign code or data can mention the token.
22 + * Tokenization excludes strings, while the anchored pattern excludes prose.
23 + *
24 + * @param string $source advanced-cache.php contents.
25 + * @return bool
26 + */
27 + function xspeed_has_canonical_dropin_signature( $source ) {
28 + if ( ! is_string( $source ) || '' === $source || ! function_exists( 'token_get_all' ) ) {
29 + return false;
30 + }
31 + /*
32 + * ONE channel: an anchored token comment. That is what every xSpeed
33 + * drop-in ever shipped carries (verified across this file's whole
34 + * history).
35 + *
36 + * There used to be a second channel that accepted a PHP identifier
37 + * named XSPEED_DROPIN, and it leaked three times: first any mention
38 + * of the identifier anywhere in the file, then — once narrowed to a
39 + * `const` declaration — `use const XSPEED_DROPIN` and
40 + * `class Foo { const XSPEED_DROPIN = 1; }`. Each fix narrowed the
41 + * shape and the next review found another one, because "a token
42 + * arrangement only we would write" is not a property you can pin
43 + * down by enumeration.
44 + *
45 + * It is gone rather than narrowed a fourth time. remove_dropin() and
46 + * uninstall.php DELETE on this verdict, so a false positive destroys
47 + * another plugin's live page cache; the channel bought nothing our
48 + * own files ever needed.
49 + */
50 + $comments = array();
51 + foreach ( token_get_all( $source ) as $token ) {
52 + $id = is_array( $token ) ? $token[0] : null;
53 + if ( T_OPEN_TAG === $id || T_WHITESPACE === $id ) {
54 + continue;
55 + }
56 + // A BOM, or blank bytes, before `<?php` arrive as inline HTML. An
57 + // editor or FTP client re-saving our own drop-in that way must not
58 + // turn it foreign — we would then refuse to update or remove our
59 + // own file, and uninstall would leave it behind.
60 + if ( T_INLINE_HTML === $id && '' === trim( $token[1], " \t\r\n\0\x0B\xEF\xBB\xBF" ) ) {
61 + continue;
62 + }
63 + if ( ! in_array( $id, array( T_COMMENT, T_DOC_COMMENT ), true ) ) {
64 + break; // Real code before any comment: there is no header.
65 + }
66 + foreach ( preg_split( '/\R/', $token[1] ) ?: array() as $line ) {
67 + $line = preg_replace( '/^\s*(?:\/\*+|\*|\/\/|#)\s*/', '', $line );
68 + $line = preg_replace( '/\s*\*\/\s*$/', '', (string) $line );
69 + if ( '' !== trim( (string) $line ) ) {
70 + $comments[] = trim( (string) $line );
71 + }
72 + }
73 + break;
74 + }
75 + // The marker must OPEN the header. A competitor that documents rival
76 + // markers line by line, or carries an interop note, has not handed us
77 + // its file — and this verdict authorizes deleting their live drop-in.
78 + $first = isset( $comments[0] ) ? $comments[0] : '';
79 + if ( '' !== $first && preg_match( '/^XSPEED_DROPIN(?:\s+(?:v(?:ersion)?\s*)?\d[A-Za-z0-9._-]*)?\s*$/i', $first ) ) {
80 + return true;
81 + }
82 + return false;
83 + }
84 +}
85 +
86 +if ( ! function_exists( 'xspeed_wp_cache_receipt_matches' ) ) {
87 + /**
88 + * Current-source proof that the stored xSpeed receipt still owns the line.
89 + *
90 + * @param string $source wp-config.php contents.
91 + * @param string $receipt Stored ownership receipt.
92 + * @return bool
93 + */
94 + function xspeed_wp_cache_receipt_matches( $source, $receipt ) {
95 + return is_string( $source ) && is_string( $receipt )
96 + && 1 === preg_match( '/^[a-f0-9]{32}$/', $receipt )
97 + && false !== strpos( $source, 'xSpeed owner:' . $receipt );
98 + }
99 +}
100 +
101 +if ( ! function_exists( 'xspeed_alternative_block_openers' ) ) {
102 + /**
103 + * Token indexes of the `:` that opens an alternative-syntax block.
104 + *
105 + * Resolved by walking each control structure's condition to its matching
106 + * `)` and asking what follows, because the condition can contain anything
107 + * — a `for` header always contains semicolons, a closure or `match` in an
108 + * `if` contains braces, a named argument contains its own colon. An
109 + * earlier version watched for a `:` after an opener keyword and let any
110 + * `;` or `{` cancel it, which meant `for ( $i = 0; $i < $n; $i++ ):`
111 + * never registered at all and a define inside it read as top level —
112 + * licensing a rewrite of somebody's conditional configuration.
113 + *
114 + * `else` and `elseif` are deliberately absent: they CONTINUE the block
115 + * their `if` opened, and counting them left the depth permanently
116 + * inflated so every later top-level define looked conditional.
117 + *
118 + * @param array<int,mixed> $raw Output of token_get_all().
119 + * @return array<int,bool> Indexes, as keys.
120 + */
121 + function xspeed_alternative_block_openers( array $raw ) {
122 + $trivia = array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT );
123 + $openers = array( T_IF, T_WHILE, T_FOR, T_FOREACH, T_SWITCH );
124 + if ( defined( 'T_DECLARE' ) ) {
125 + $openers[] = T_DECLARE;
126 + }
127 + $next = static function ( $from ) use ( $raw, $trivia ) {
128 + $count = count( $raw );
129 + for ( $i = $from; $i < $count; $i++ ) {
130 + if ( ! is_array( $raw[ $i ] ) || ! in_array( $raw[ $i ][0], $trivia, true ) ) {
131 + return $i;
132 + }
133 + }
134 + return null;
135 + };
136 +
137 + $opens = array();
138 + $count = count( $raw );
139 + for ( $i = 0; $i < $count; $i++ ) {
140 + if ( ! is_array( $raw[ $i ] ) || ! in_array( $raw[ $i ][0], $openers, true ) ) {
141 + continue;
142 + }
143 + $open = $next( $i + 1 );
144 + if ( null === $open || '(' !== $raw[ $open ] ) {
145 + continue;
146 + }
147 + $depth = 0;
148 + $close = null;
149 + for ( $j = $open; $j < $count; $j++ ) {
150 + if ( '(' === $raw[ $j ] ) {
151 + ++$depth;
152 + } elseif ( ')' === $raw[ $j ] ) {
153 + --$depth;
154 + if ( 0 === $depth ) {
155 + $close = $j;
156 + break;
157 + }
158 + }
159 + }
160 + if ( null === $close ) {
161 + continue;
162 + }
163 + $after = $next( $close + 1 );
164 + if ( null !== $after && ':' === $raw[ $after ] ) {
165 + $opens[ $after ] = true;
166 + }
167 + }
168 + return $opens;
169 + }
170 +}
171 +
172 +if ( ! function_exists( 'xspeed_parse_wp_cache_defines' ) ) {
173 + /**
174 + * Token-aware WP_CACHE parser.
175 + *
176 + * Comments and strings are tokens, so text that merely contains a fake
177 + * define can never become configuration. Offsets are byte offsets into the
178 + * original source and are used by the rewriter below.
179 + *
180 + * @param string $source wp-config.php contents.
181 + * @return array{state:string,defines:array<int,array{start:int,end:int,value:string}>}
182 + */
183 + function xspeed_parse_wp_cache_defines( $source ) {
184 + if ( ! is_string( $source ) || '' === $source || ! function_exists( 'token_get_all' ) ) {
185 + return array(
186 + 'state' => 'undefined',
187 + 'defines' => array(),
188 + );
189 + }
190 +
191 + $raw = token_get_all( $source );
192 + $tokens = array();
193 + $offset = 0;
194 + $depth = 0;
195 + $alt = 0;
196 + $alt_opens = xspeed_alternative_block_openers( $raw );
197 + $alt_close = array();
198 + foreach ( array( 'T_ENDIF', 'T_ENDWHILE', 'T_ENDFOR', 'T_ENDFOREACH', 'T_ENDSWITCH', 'T_ENDDECLARE' ) as $name ) {
199 + if ( defined( $name ) ) {
200 + $alt_close[] = constant( $name );
201 + }
202 + }
203 + foreach ( $raw as $index => $token ) {
204 + $id = is_array( $token ) ? $token[0] : null;
205 + $text = is_array( $token ) ? $token[1] : $token;
206 + /*
207 + * Brace depth at the START of each token. A define nested inside
208 + * anything — a host's `if ( ! defined( 'WP_CACHE' ) ) { … }`, an
209 + * environment switch — is not a statement we may rewrite: the
210 + * literal we read is not necessarily what runs, and replacing it
211 + * edits someone's conditional configuration.
212 + *
213 + * Only RAW `{` / `}` count. A `}` inside an interpolated string
214 + * ("}{$a}", or the heredoc equivalent) arrives as a typed
215 + * T_ENCAPSED_AND_WHITESPACE token whose text happens to be `}`;
216 + * counting it desynced the depth and unmasked a define that
217 + * really was inside someone's block. The interpolation OPENERS
218 + * are typed (T_CURLY_OPEN / T_DOLLAR_OPEN_CURLY_BRACES) and are
219 + * closed by a raw `}`, so they are counted to keep the pair
220 + * balanced.
221 + */
222 + /*
223 + * Alternative syntax opens a block with `:` and closes it with an
224 + * `endif`/`endwhile`/... keyword, no braces at all. Tracking only
225 + * braces meant a define that was not the FIRST statement of such
226 + * a block read as top level — the boundary token in front of it
227 + * is the previous statement's `;` — so an enable rewrote a host's
228 + * staging-only define in place and a disable cut it out of the
229 + * block.
230 + */
231 + if ( in_array( $id, $alt_close, true ) && $alt > 0 ) {
232 + --$alt;
233 + }
234 + // A BOM or stray blank bytes before `<?php` arrive as inline HTML
235 + // and must not be mistaken for code.
236 + if ( T_INLINE_HTML === $id && '' === trim( $text, " \t\r\n\0\x0B\xEF\xBB\xBF" ) ) {
237 + $offset += strlen( $text );
238 + continue;
239 + }
240 + $raw_punct = ( null === $id );
241 + if ( $raw_punct && '}' === $text && $depth > 0 ) {
242 + --$depth;
243 + }
244 + $tokens[] = array(
245 + 'id' => $id,
246 + 'text' => $text,
247 + 'start' => $offset,
248 + 'end' => $offset + strlen( $text ),
249 + 'depth' => $depth + $alt,
250 + );
251 + if ( ( $raw_punct && '{' === $text )
252 + || ( defined( 'T_CURLY_OPEN' ) && T_CURLY_OPEN === $id )
253 + || ( defined( 'T_DOLLAR_OPEN_CURLY_BRACES' ) && T_DOLLAR_OPEN_CURLY_BRACES === $id ) ) {
254 + ++$depth;
255 + } elseif ( isset( $alt_opens[ $index ] ) ) {
256 + ++$alt;
257 + }
258 + $offset += strlen( $text );
259 + }
260 +
261 + $is_trivia = static function ( $token ) {
262 + return in_array( $token['id'], array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ), true );
263 + };
264 + $next = static function ( $index ) use ( $tokens, $is_trivia ) {
265 + for ( $index++; isset( $tokens[ $index ] ); $index++ ) {
266 + if ( ! $is_trivia( $tokens[ $index ] ) ) {
267 + return $index;
268 + }
269 + }
270 + return null;
271 + };
272 + $previous = static function ( $index ) use ( $tokens, $is_trivia ) {
273 + for ( $index--; 0 <= $index; $index-- ) {
274 + if ( ! $is_trivia( $tokens[ $index ] ) ) {
275 + return $index;
276 + }
277 + }
278 + return null;
279 + };
280 +
281 + $defines = array();
282 + $count = count( $tokens );
283 + for ( $i = 0; $i < $count; $i++ ) {
284 + /*
285 + * `define` and `\define` are the same call, and wp-config.php files
286 + * in the wild are written both ways. PHP 8 tokenises the qualified
287 + * form as ONE T_NAME_FULLY_QUALIFIED token (`\define`), so matching
288 + * T_STRING alone made the define invisible: the detector then read
289 + * a site with WP_CACHE on as unclaimed and let an acquisition
290 + * through, and the rewriter inserted a SECOND define instead of
291 + * replacing the one that was there.
292 + *
293 + * PHP 7.4 splits it into T_NS_SEPARATOR + T_STRING, which did
294 + * match — so the behaviour differed by PHP version, and there the
295 + * recorded start missed the leading `\`, leaving a bare backslash
296 + * in wp-config.php after a strip. `$start` covers the separator
297 + * for that reason.
298 + */
299 + $start = $tokens[ $i ]['start'];
300 + if ( defined( 'T_NAME_FULLY_QUALIFIED' ) && T_NAME_FULLY_QUALIFIED === $tokens[ $i ]['id'] ) {
301 + if ( '\\define' !== strtolower( $tokens[ $i ]['text'] ) ) {
302 + continue;
303 + }
304 + } elseif ( T_STRING === $tokens[ $i ]['id'] && 'define' === strtolower( $tokens[ $i ]['text'] ) ) {
305 + $prior = $previous( $i );
306 + if ( null !== $prior && in_array( $tokens[ $prior ]['id'], array( T_OBJECT_OPERATOR, T_DOUBLE_COLON ), true ) ) {
307 + continue;
308 + }
309 + if ( null !== $prior && T_NS_SEPARATOR === $tokens[ $prior ]['id'] ) {
310 + // PHP 7.4 splits `\define` into T_NS_SEPARATOR + T_STRING.
311 + // `Foo\define` splits the same way, so check what sits in
312 + // front of the separator: a name there means someone
313 + // else's function, not core's, and stepping $start back
314 + // over the separator would leave `Foo` dangling.
315 + $before = $previous( $prior );
316 + if ( null !== $before && in_array( $tokens[ $before ]['id'], array( T_STRING, T_NS_SEPARATOR ), true ) ) {
317 + continue;
318 + }
319 + $start = $tokens[ $prior ]['start'];
320 + }
321 + } else {
322 + continue;
323 + }
324 + $open = $next( $i );
325 + $name = null !== $open ? $next( $open ) : null;
326 + if ( null === $name || '(' !== $tokens[ $open ]['text'] || T_CONSTANT_ENCAPSED_STRING !== $tokens[ $name ]['id'] ) {
327 + continue;
328 + }
329 + $constant = trim( $tokens[ $name ]['text'], "'\"" );
330 + $comma = $next( $name );
331 + if ( 'WP_CACHE' !== strtoupper( $constant ) || null === $comma || ',' !== $tokens[ $comma ]['text'] ) {
332 + continue;
333 + }
334 +
335 + $depth = 1;
336 + $close = null;
337 + for ( $j = $comma + 1; $j < $count; $j++ ) {
338 + if ( '(' === $tokens[ $j ]['text'] ) {
339 + ++$depth;
340 + } elseif ( ')' === $tokens[ $j ]['text'] && 0 === --$depth ) {
341 + $close = $j;
342 + break;
343 + }
344 + }
345 + if ( null === $close ) {
346 + continue;
347 + }
348 + $end_token = $next( $close );
349 + $end = ( null !== $end_token && ';' === $tokens[ $end_token ]['text'] ) ? $tokens[ $end_token ]['end'] : $tokens[ $close ]['end'];
350 + $value = '';
351 + for ( $j = $comma + 1; $j < $close; $j++ ) {
352 + if ( ! $is_trivia( $tokens[ $j ] ) ) {
353 + $value .= $tokens[ $j ]['text'];
354 + }
355 + }
356 + /*
357 + * Is this define a statement of its own, or the body of something?
358 + *
359 + * Brace depth alone missed the braceless forms, which are the
360 + * common ones in a wp-config.php:
361 + *
362 + * if ( ! defined( 'WP_CACHE' ) ) define( 'WP_CACHE', true );
363 + * if ( $host === 'staging' )
364 + * define( 'WP_CACHE', false );
365 + *
366 + * Both read as top level, so an enable rewrote a host's
367 + * conditional in place and a disable cut the statement out —
368 + * leaving a dangling `if` that silently captures whatever
369 + * statement follows it. A define whose `DB_NAME` neighbour became
370 + * conditional is a far worse outcome than a refusal.
371 + *
372 + * A real statement can only follow `<?php`, `;`, `{`, `}`, or
373 + * nothing at all. Anything else — `)` closing a control
374 + * condition, `else`, `do`, the `:` of alternative syntax — means
375 + * we are the controlled statement. That closes the `if ( … ):
376 + * … endif;` gap too.
377 + */
378 + $before_statement = $previous( $i );
379 + if ( null !== $before_statement && $start !== $tokens[ $i ]['start'] ) {
380 + // The qualified form: step back past the separator we adopted.
381 + $before_statement = $previous( $before_statement );
382 + }
383 + $boundary = null === $before_statement
384 + || T_OPEN_TAG === $tokens[ $before_statement ]['id']
385 + || in_array( $tokens[ $before_statement ]['text'], array( ';', '{', '}' ), true );
386 +
387 + $defines[] = array(
388 + 'start' => $start,
389 + 'end' => $end,
390 + 'value' => $value,
391 + 'conditional' => $tokens[ $i ]['depth'] > 0 || ! $boundary,
392 + );
393 + $i = $close;
394 + }
395 +
396 + if ( 0 === count( $defines ) ) {
397 + $state = 'undefined';
398 + } elseif ( 1 < count( $defines ) ) {
399 + $state = 'duplicate';
400 + } elseif ( ! empty( $defines[0]['conditional'] ) ) {
401 + $state = 'conditional';
402 + } else {
403 + $value = strtolower( trim( $defines[0]['value'] ) );
404 + if ( preg_match( '/^(?:true|1|[\'\"]1[\'\"])$/i', $value ) ) {
405 + $state = 'true';
406 + } elseif ( preg_match( '/^(?:false|0|null|[\'\"](?:0)?[\'\"])$/i', $value ) ) {
407 + $state = 'false';
408 + } else {
409 + $state = 'dynamic';
410 + }
411 + }
412 + return array(
413 + 'state' => $state,
414 + 'defines' => $defines,
415 + );
416 + }
417 +}
418 +
419 +if ( ! function_exists( 'xspeed_drop_receipt_comment' ) ) {
420 + /**
421 + * Remove the `// xSpeed owner:<hex>` receipt that trails a WP_CACHE
422 + * define, taking the whole comment with it.
423 + *
424 + * The parser's offsets stop at the `;`, so the receipt we wrote sits
425 + * outside every rewrite and every removal. Consuming only as far as the
426 + * hex left anything a user had appended to OUR comment standing as bare
427 + * code:
428 + *
429 + * define( 'WP_CACHE', true ); // xSpeed owner:abc123 do not remove
430 + *
431 + * became `do not remove` at top level — a parse error in wp-config.php,
432 + * so the site white-screens. A `//` comment runs to end of line by
433 + * definition, so consuming to end of line removes exactly the comment
434 + * token and nothing else. It also clears the stacked receipts left by
435 + * the historic append bug, which all share that one line.
436 + *
437 + * @param string $tail Source immediately after the define's `;`.
438 + * @return string
439 + */
440 + function xspeed_drop_receipt_comment( $tail ) {
441 + if ( ! is_string( $tail ) ) {
442 + return $tail;
443 + }
444 + return (string) preg_replace( '#^[ \t]*//[ \t]*xSpeed owner:[a-f0-9]*[^\r\n]*#i', '', $tail, 1 );
445 + }
446 +}
447 +
448 +if ( ! function_exists( 'xspeed_rewrite_wp_cache_define' ) ) {
449 + /**
450 + * Return rewritten source, or null when the source is ambiguous.
451 + *
452 + * @param string $source wp-config.php contents.
453 + * @param bool $enable Desired literal state.
454 + * @param string $marker Optional xSpeed ownership receipt.
455 + * @return string|null
456 + */
457 + function xspeed_rewrite_wp_cache_define( $source, $enable, $marker = '' ) {
458 + $parsed = xspeed_parse_wp_cache_defines( $source );
459 + if ( in_array( $parsed['state'], array( 'duplicate', 'dynamic', 'conditional' ), true ) ) {
460 + return null;
461 + }
462 + $suffix = '' !== $marker ? ' // xSpeed owner:' . preg_replace( '/[^a-f0-9]/i', '', $marker ) : '';
463 + $replacement = $enable ? "define( 'WP_CACHE', true );" . $suffix : '';
464 + if ( ! empty( $parsed['defines'] ) ) {
465 + $define = $parsed['defines'][0];
466 + /*
467 + * The parser's `end` is the `;` of the define, so the ownership
468 + * receipt WE wrote last time sits just past it and is not part of
469 + * what gets replaced. Rewriting therefore used to append a second
470 + * receipt to the same line, and a third, and a fourth: the enable
471 + * transaction runs on every admin_init, so the line grew by 26
472 + * bytes per wp-admin request. Disabling had the mirror bug — the
473 + * define went, the receipt comment stayed behind forever.
474 + *
475 + * Consume it here so a rewrite is idempotent and a removal is
476 + * complete. Only our own comment, only when it directly follows
477 + * the statement.
478 + */
479 + $tail = substr( $source, $define['end'] );
480 + $tail = xspeed_drop_receipt_comment( $tail );
481 + return substr( $source, 0, $define['start'] ) . $replacement . $tail;
482 + }
483 + if ( ! $enable ) {
484 + return $source;
485 + }
486 + $position = strpos( $source, '<?php' );
487 + if ( false === $position ) {
488 + return null;
489 + }
490 + $position += 5;
491 + return substr( $source, 0, $position ) . "\n" . $replacement . substr( $source, $position );
492 + }
493 +}
494 +
17 495 if ( ! function_exists( 'xspeed_strip_wp_cache_define' ) ) {
18 496 /**
19 497 * Remove every `define( 'WP_CACHE', … );` statement from wp-config source.
20 498 *
@@ -27,10 +505,10 @@
27 505 * confuses the next caching plugin the site installs — W3 Total Cache
28 506 * and WP Rocket both branch on it — and makes a "clean uninstall" not
29 507 * clean. (#9)
30 508 *
31 - * Matches the VALUE as `[^)]*`, the same shape the enable branch in
32 - * Cache::set_wp_cache_constant() already uses, so any spelling goes:
509 + * Uses the token parser above, so comments and string contents are ignored
510 + * while any real spelling is removed:
33 511 * true / TRUE / 1 / '1' / false / 0, with or without inner spaces.
34 512 * `false` is removed too — leaving `define( 'WP_CACHE', false );`
35 513 * behind is the same orphan problem, just quieter.
36 514 *
@@ -47,16 +525,20 @@
47 525 if ( ! is_string( $config ) || '' === $config ) {
48 526 return $config;
49 527 }
50 528
51 - $stripped = preg_replace(
52 - '/^[ \t]*define\(\s*[\'"]WP_CACHE[\'"]\s*,\s*[^)]*\)\s*;[ \t]*\r?\n?/mi',
53 - '',
54 - $config
55 - );
56 -
57 - // preg_replace() returns null on failure (catastrophic backtracking,
58 - // bad UTF-8). Returning that would blank wp-config.php — hand back
59 - // the untouched source instead and leave the constant in place.
60 - return is_string( $stripped ) ? $stripped : $config;
529 + $parsed = xspeed_parse_wp_cache_defines( $config );
530 + foreach ( array_reverse( $parsed['defines'] ) as $define ) {
531 + // A define inside someone's conditional is theirs, and cutting the
532 + // statement out of the block is how a `{ … }` loses its body.
533 + if ( ! empty( $define['conditional'] ) ) {
534 + continue;
535 + }
536 + // Take our ownership receipt comment with the statement it
537 + // annotates; leaving `// xSpeed owner:…` in an uninstalled site's
538 + // wp-config.php is the same orphan this function exists to stop.
539 + $tail = xspeed_drop_receipt_comment( substr( $config, $define['end'] ) );
540 + $config = substr( $config, 0, $define['start'] ) . $tail;
541 + }
542 + return $config;
61 543 }
62 544 }