Issues (311)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

lib/timber-helper.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * As the name suggests these are helpers for Timber (and you!) when developing. You can find additional (mainly internally-focused helpers) in TimberURLHelper
5
 */
6
class TimberHelper {
7
8
	/**
9
	 * A utility for a one-stop shop for Transients
10
	 * @api
11
	 * @example
12
	 * ```php
13
	 * $favorites = Timber::transient('user-'.$uid.'-favorites', function() use ($uid) {
14
	 *  	//some expensive query here that's doing something you want to store to a transient
15
	 *  	return $favorites;
16
	 * }, 600);
17
	 * Timber::context['favorites'] = $favorites;
18
	 * Timber::render('single.twig', $context);
19
	 * ```
20
	 *
21
	 * @param string  $slug           Unique identifier for transient
22
	 * @param callable $callback      Callback that generates the data that's to be cached
23
	 * @param int     $transient_time (optional) Expiration of transients in seconds
24
	 * @param int     $lock_timeout   (optional) How long (in seconds) to lock the transient to prevent race conditions
25
	 * @param bool    $force          (optional) Force callback to be executed when transient is locked
26
	 * @return mixed
27
	 */
28
	public static function transient( $slug, $callback, $transient_time = 0, $lock_timeout = 5, $force = false ) {
29
		$slug = apply_filters( 'timber/transient/slug', $slug );
30
31
		$enable_transients = ( $transient_time === false || ( defined( 'WP_DISABLE_TRANSIENTS' ) && WP_DISABLE_TRANSIENTS ) ) ? false : true;
32
		$data = $enable_transients ? get_transient( $slug ) : false;
33
34
		if ( false === $data ) {
35
36
			if ( $enable_transients && self::_is_transient_locked( $slug ) ) {
37
38
				$force = apply_filters( 'timber_force_transients', $force );
39
				$force = apply_filters( 'timber_force_transient_' . $slug, $force );
40
41
				if ( !$force ) {
42
					//the server is currently executing the process.
43
					//We're just gonna dump these users. Sorry!
44
					return false;
45
				}
46
47
				$enable_transients = false;
48
			}
49
50
			// lock timeout shouldn't be higher than 5 seconds, unless
51
			// remote calls with high timeouts are made here
52
			if ( $enable_transients )
53
				self::_lock_transient( $slug, $lock_timeout );
54
55
			$data = $callback();
56
57
			if ( $enable_transients ) {
58
				set_transient( $slug, $data, $transient_time );
59
				self::_unlock_transient( $slug );
60
			}
61
62
		}
63
64
		return $data;
65
66
	}
67
68
	/**
69
	 * @internal
70
	 * @param string $slug
71
	 * @param integer $lock_timeout
72
	 */
73
	static function _lock_transient( $slug, $lock_timeout ) {
74
		set_transient( $slug . '_lock', true, $lock_timeout );
75
	}
76
77
	/**
78
	 * @internal
79
	 * @param string $slug
80
	 */
81
	static function _unlock_transient( $slug ) {
82
		delete_transient( $slug . '_lock', true );
83
	}
84
85
	/**
86
	 * @internal
87
	 * @param string $slug
88
	 */
89
	static function _is_transient_locked( $slug ) {
90
		return (bool)get_transient( $slug . '_lock' );
91
	}
92
93
	/* These are for measuring page render time */
94
95
	/**
96
	 * For measuring time, this will start a timer
97
	 * @api
98
	 * @return float
99
	 */
100
	public static function start_timer() {
101
		$time = microtime();
102
		$time = explode( ' ', $time );
103
		$time = $time[1] + $time[0];
104
		return $time;
105
	}
106
107
	/**
108
	 * For stopping time and getting the data
109
	 * @example
110
	 * ```php
111
	 * $start = TimberHelper::start_timer();
112
	 * // do some stuff that takes awhile
113
	 * echo TimberHelper::stop_timer( $start );
114
	 * ```
115
	 * @param int     $start
116
	 * @return string
117
	 */
118
	public static function stop_timer( $start ) {
119
		$time = microtime();
120
		$time = explode( ' ', $time );
121
		$time = $time[1] + $time[0];
122
		$finish = $time;
123
		$total_time = round( ( $finish - $start ), 4 );
124
		return $total_time . ' seconds.';
125
	}
126
127
	/* Function Utilities
128
	======================== */
129
130
	/**
131
	 * Calls a function with an output buffer. This is useful if you have a function that outputs text that you want to capture and use within a twig template.
132
	 * @example
133
	 * ```php
134
	 * function the_form() {
135
	 *     echo '<form action="form.php"><input type="text" /><input type="submit /></form>';
136
	 * }
137
	 *
138
	 * $context = Timber::get_context();
139
	 * $context['post'] = new TimberPost();
140
	 * $context['my_form'] = TimberHelper::ob_function('the_form');
141
	 * Timber::render('single-form.twig', $context);
142
	 * ```
143
	 * ```twig
144
	 * <h1>{{ post.title }}</h1>
145
	 * {{ my_form }}
146
	 * ```
147
	 * ```html
148
	 * <h1>Apply to my contest!</h1>
149
	 * <form action="form.php"><input type="text" /><input type="submit /></form>
150
	 * ```
151
	 * @api
152
	 * @param callback $function
153
	 * @param array   $args
154
	 * @return string
155
	 */
156
	public static function ob_function( $function, $args = array( null ) ) {
157
		ob_start();
158
		call_user_func_array( $function, $args );
159
		$data = ob_get_contents();
160
		ob_end_clean();
161
		return $data;
162
	}
163
164
	/**
165
	 *
166
	 *
167
	 * @param string  $function_name
168
	 * @param integer[]   $defaults
169
	 * @param bool    $return_output_buffer
170
	 * @return TimberFunctionWrapper
171
	 */
172
	public static function function_wrapper( $function_name, $defaults = array(), $return_output_buffer = false ) {
173
		return new TimberFunctionWrapper( $function_name, $defaults, $return_output_buffer );
174
	}
175
176
	/**
177
	 *
178
	 *
179
	 * @param mixed $arg that you want to error_log
180
	 * @return void
181
	 */
182
	public static function error_log( $arg ) {
183
		if ( !WP_DEBUG ) {
184
			return;
185
		}
186
		if ( is_object( $arg ) || is_array( $arg ) ) {
187
			$arg = print_r( $arg, true );
188
		}
189
		return error_log( $arg );
190
	}
191
192
	/**
193
	 *
194
	 *
195
	 * @param string  $separator
196
	 * @param string  $seplocation
197
	 * @return string
198
	 */
199
	public static function get_wp_title( $separator = ' ', $seplocation = 'left' ) {
200
		$separator = apply_filters( 'timber_wp_title_seperator', $separator );
201
		return trim( wp_title( $separator, false, $seplocation ) );
202
	}
203
204
	/* Text Utilities
205
	======================== */
206
207
	/**
208
	 *
209
	 *
210
	 * @param string  $text
211
	 * @param int     $num_words
212
	 * @param string|null|false  $more text to appear in "Read more...". Null to use default, false to hide
213
	 * @param string  $allowed_tags
214
	 * @return string
215
	 */
216
	public static function trim_words( $text, $num_words = 55, $more = null, $allowed_tags = 'p a span b i br blockquote' ) {
217
		if ( null === $more ) {
218
			$more = __( '&hellip;' );
219
		}
220
		$original_text = $text;
221
		$allowed_tag_string = '';
222
		foreach ( explode( ' ', apply_filters( 'timber/trim_words/allowed_tags', $allowed_tags ) ) as $tag ) {
223
			$allowed_tag_string .= '<' . $tag . '>';
224
		}
225
		$text = strip_tags( $text, $allowed_tag_string );
226
		/* translators: If your word count is based on single characters (East Asian characters), enter 'characters'. Otherwise, enter 'words'. Do not translate into your own language. */
227
		if ( 'characters' == _x( 'words', 'word count: words or characters?' ) && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ) ) {
228
			$text = trim( preg_replace( "/[\n\r\t ]+/", ' ', $text ), ' ' );
229
			preg_match_all( '/./u', $text, $words_array );
230
			$words_array = array_slice( $words_array[0], 0, $num_words + 1 );
231
			$sep = '';
232
		} else {
233
			$words_array = preg_split( "/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY );
234
			$sep = ' ';
235
		}
236
		if ( count( $words_array ) > $num_words ) {
237
			array_pop( $words_array );
238
			$text = implode( $sep, $words_array );
239
			$text = $text . $more;
240
		} else {
241
			$text = implode( $sep, $words_array );
242
		}
243
		$text = self::close_tags( $text );
244
		return apply_filters( 'wp_trim_words', $text, $num_words, $more, $original_text );
245
	}
246
247
	/**
248
	 *
249
	 *
250
	 * @param string  $html
251
	 * @return string
252
	 */
253
	public static function close_tags( $html ) {
254
		//put all opened tags into an array
255
		preg_match_all( '#<([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result );
256
		$openedtags = $result[1];
257
		//put all closed tags into an array
258
		preg_match_all( '#</([a-z]+)>#iU', $html, $result );
259
		$closedtags = $result[1];
260
		$len_opened = count( $openedtags );
261
		// all tags are closed
262
		if ( count( $closedtags ) == $len_opened ) {
263
			return $html;
264
		}
265
		$openedtags = array_reverse( $openedtags );
266
		// close tags
267
		for ( $i = 0; $i < $len_opened; $i++ ) {
268
			if ( !in_array( $openedtags[$i], $closedtags ) ) {
269
				$html .= '</' . $openedtags[$i] . '>';
270
			} else {
271
				unset( $closedtags[array_search( $openedtags[$i], $closedtags )] );
272
			}
273
		}
274
		$html = str_replace(array('</br>','</hr>','</wbr>'), '', $html);
275
		$html = str_replace(array('<br>','<hr>','<wbr>'), array('<br />','<hr />','<wbr />'), $html);
276
		return $html;
277
	}
278
279
	/* WordPress Query Utilities
280
	======================== */
281
282
	/**
283
	 * @param string  $key
284
	 * @param string  $value
285
	 * @return array|int
286
	 * @deprecated 0.20.0
287
	 */
288
	public static function get_posts_by_meta( $key, $value ) {
289
		global $wpdb;
290
		$query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s", $key, $value );
291
		$results = $wpdb->get_col( $query );
292
		$pids = array();
293
		foreach ( $results as $result ) {
294
			if ( get_post( $result ) ) {
295
				$pids[] = $result;
296
			}
297
		}
298
		if ( count( $pids ) ) {
299
			return $pids;
300
		}
301
		return 0;
302
	}
303
304
	/**
305
	 *
306
	 *
307
	 * @param string  $key
308
	 * @param string  $value
309
	 * @return int
310
	 * @deprecated 0.20.0
311
	 */
312
	public static function get_post_by_meta( $key, $value ) {
313
		global $wpdb;
314
		$query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s ORDER BY post_id", $key, $value );
315
		$results = $wpdb->get_col( $query );
316
		foreach ( $results as $result ) {
317
			if ( $result && get_post( $result ) ) {
318
				return $result;
319
			}
320
		}
321
		return 0;
322
	}
323
324
	/**
325
	 *
326
	 * @deprecated 0.21.8
327
	 * @param int     $ttid
328
	 * @return mixed
329
	 */
330
	public static function get_term_id_by_term_taxonomy_id( $ttid ) {
331
		global $wpdb;
332
		$query = $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %s", $ttid );
333
		return $wpdb->get_var( $query );
334
	}
335
336
	/* Object Utilities
337
	======================== */
338
339
	/**
340
	 *
341
	 *
342
	 * @param array   $array
343
	 * @param string  $prop
344
	 * @return void
345
	 */
346
	public static function osort( &$array, $prop ) {
347
		usort( $array, function ( $a, $b ) use ( $prop ) {
348
				return $a->$prop > $b->$prop ? 1 : -1;
349
			} );
350
	}
351
352
	/**
353
	 *
354
	 *
355
	 * @param array   $arr
356
	 * @return bool
357
	 */
358
	public static function is_array_assoc( $arr ) {
359
		if ( !is_array( $arr ) ) {
360
			return false;
361
		}
362
		return (bool)count( array_filter( array_keys( $arr ), 'is_string' ) );
363
	}
364
365
	/**
366
	 *
367
	 *
368
	 * @param array   $array
369
	 * @return stdClass
370
	 */
371
	public static function array_to_object( $array ) {
372
		$obj = new stdClass;
373
		foreach ( $array as $k => $v ) {
374
			if ( is_array( $v ) ) {
375
				$obj->{$k} = self::array_to_object( $v ); //RECURSION
376
			} else {
377
				$obj->{$k} = $v;
378
			}
379
		}
380
		return $obj;
381
	}
382
383
	/**
384
	 *
385
	 *
386
	 * @param array   $array
387
	 * @param string  $key
388
	 * @param mixed   $value
389
	 * @return bool|int
390
	 */
391
	public static function get_object_index_by_property( $array, $key, $value ) {
392
		if ( is_array( $array ) ) {
393
			$i = 0;
394
			foreach ( $array as $arr ) {
395
				if ( is_array( $arr ) ) {
396
					if ( $arr[$key] == $value ) {
397
						return $i;
398
					}
399
				} else {
400
					if ( $arr->$key == $value ) {
401
						return $i;
402
					}
403
				}
404
				$i++;
405
			}
406
		}
407
		return false;
408
	}
409
410
	/**
411
	 *
412
	 *
413
	 * @param array   $array
414
	 * @param string  $key
415
	 * @param mixed   $value
416
	 * @return array|null
417
	 * @throws Exception
418
	 */
419
	public static function get_object_by_property( $array, $key, $value ) {
420
		if ( is_array( $array ) ) {
421
			foreach ( $array as $arr ) {
422
				if ( $arr->$key == $value ) {
423
					return $arr;
424
				}
425
			}
426
		} else {
427
			throw new InvalidArgumentException( '$array is not an array, got:' );
428
			TimberHelper::error_log( $array );
0 ignored issues
show
\TimberHelper::error_log($array); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
429
		}
430
	}
431
432
	/**
433
	 *
434
	 *
435
	 * @param array   $array
436
	 * @param int     $len
437
	 * @return array
438
	 */
439
	public static function array_truncate( $array, $len ) {
440
		if ( sizeof( $array ) > $len ) {
441
			$array = array_splice( $array, 0, $len );
442
		}
443
		return $array;
444
	}
445
446
	/* Bool Utilities
447
	======================== */
448
449
	/**
450
	 *
451
	 *
452
	 * @param mixed   $value
453
	 * @return bool
454
	 */
455
	public static function is_true( $value ) {
456
		if ( isset( $value ) ) {
457
			if (is_string($value)) {
458
				$value = strtolower($value);
459
			}
460
			if ( ($value == 'true' || $value === 1 || $value === '1' || $value == true) && $value !== false && $value !== 'false') {
461
				return true;
462
			}
463
		}
464
		return false;
465
	}
466
467
	/**
468
	 *
469
	 *
470
	 * @param int     $i
471
	 * @return bool
472
	 */
473
	public static function iseven( $i ) {
474
		return ( $i % 2 ) == 0;
475
	}
476
477
	/**
478
	 *
479
	 *
480
	 * @param int     $i
481
	 * @return bool
482
	 */
483
	public static function isodd( $i ) {
484
		return ( $i % 2 ) != 0;
485
	}
486
487
	/* Links, Forms, Etc. Utilities
488
	======================== */
489
490
	/**
491
	 *
492
	 * Gets the comment form for use on a single article page
493
	 * @deprecated 0.21.8 use `{{ function('comment_form') }}` instead
494
	 * @param int     $post_id which post_id should the form be tied to?
495
	 * @param array   $args this $args thing is a fucking mess, [fix at some point](http://codex.wordpress.org/Function_Reference/comment_form)
496
	 * @return string
497
	 */
498
	public static function get_comment_form( $post_id = null, $args = array() ) {
499
		return self::ob_function( 'comment_form', array( $args, $post_id ) );
500
	}
501
502
	/**
503
	 *
504
	 *
505
	 * @param string  $args
506
	 * @return array
507
	 */
508
	public static function paginate_links( $args = '' ) {
509
		$defaults = array(
510
			'base' => '%_%', // http://example.com/all_posts.php%_% : %_% is replaced by format (below)
511
			'format' => '?page=%#%', // ?page=%#% : %#% is replaced by the page number
512
			'total' => 1,
513
			'current' => 0,
514
			'show_all' => false,
515
			'prev_next' => false,
516
			'prev_text' => __( '&laquo; Previous' ),
517
			'next_text' => __( 'Next &raquo;' ),
518
			'end_size' => 1,
519
			'mid_size' => 2,
520
			'type' => 'array',
521
			'add_args' => false, // array of query args to add
522
			'add_fragment' => ''
523
		);
524
		$args = wp_parse_args( $args, $defaults );
525
		// Who knows what else people pass in $args
526
		$args['total'] = intval( (int)$args['total'] );
527
		if ( $args['total'] < 2 ) {
528
			return array();
529
		}
530
		$args['current'] = (int)$args['current'];
531
		$args['end_size'] = 0 < (int)$args['end_size'] ? (int)$args['end_size'] : 1; // Out of bounds?  Make it the default.
532
		$args['mid_size'] = 0 <= (int)$args['mid_size'] ? (int)$args['mid_size'] : 2;
533
		$args['add_args'] = is_array( $args['add_args'] ) ? $args['add_args'] : false;
534
		$page_links = array();
535
		$dots = false;
536
		for ( $n = 1; $n <= $args['total']; $n++ ) {
537
			$n_display = number_format_i18n( $n );
538
			if ( $n == $args['current'] ) {
539
				$page_links[] = array(
540
					'class' => 'page-number page-numbers current',
541
					'title' => $n_display,
542
					'text' => $n_display,
543
					'name' => $n_display,
544
					'current' => true
545
				);
546
				$dots = true;
547
			} else {
548
				if ( $args['show_all'] || ( $n <= $args['end_size'] || ( $args['current'] && $n >= $args['current'] - $args['mid_size'] && $n <= $args['current'] + $args['mid_size'] ) || $n > $args['total'] - $args['end_size'] ) ) {
549
					$link = str_replace( '%_%', 1 == $n ? '' : $args['format'], $args['base'] );
550
					$link = str_replace( '%#%', $n, $link );
551
					$link = trailingslashit( $link ) . ltrim( $args['add_fragment'], '/' );
552
					if ( $args['add_args'] ) {
553
						$link = rtrim( add_query_arg( $args['add_args'], $link ), '/' );
554
					}
555
					$link = str_replace(' ', '+', $link);
556
					$link = untrailingslashit( $link );
557
					$page_links[] = array(
558
						'class' => 'page-number page-numbers',
559
						'link' => esc_url( apply_filters( 'paginate_links', $link ) ),
560
						'title' => $n_display,
561
						'name' => $n_display,
562
						'current' => $args['current'] == $n
563
					);
564
					$dots = true;
565
				} elseif ( $dots && !$args['show_all'] ) {
566
					$page_links[] = array(
567
						'class' => 'dots',
568
						'title' => __( '&hellip;' )
569
					);
570
					$dots = false;
571
				}
572
			}
573
		}
574
		return $page_links;
575
	}
576
577
	/**
578
	 * @deprecated 0.18.0
579
	 */
580
	static function get_current_url() {
581
		return TimberURLHelper::get_current_url();
582
	}
583
584
	/**
585
	 * @deprecated 0.18.0
586
	 */
587
	static function is_url( $url ) {
588
		return TimberURLHelper::is_url( $url );
589
	}
590
591
	/**
592
	 * @deprecated 0.18.0
593
	 */
594
	static function get_path_base() {
595
		return TimberURLHelper::get_path_base();
596
	}
597
598
	/**
599
	 * @deprecated 0.18.0
600
	 */
601
	static function get_rel_url( $url, $force = false ) {
602
		return TimberURLHelper::get_rel_url( $url, $force );
603
	}
604
605
	/**
606
	 * @deprecated 0.18.0
607
	 */
608
	static function is_local( $url ) {
609
		return TimberURLHelper::is_local( $url );
610
	}
611
612
	/**
613
	 * @deprecated 0.18.0
614
	 */
615
	static function get_full_path( $src ) {
616
		return TimberURLHelper::get_full_path( $src );
617
	}
618
619
	/**
620
	 * @deprecated 0.18.0
621
	 */
622
	static function get_rel_path( $src ) {
623
		return TimberURLHelper::get_rel_path( $src );
624
	}
625
626
	/**
627
	 * @deprecated 0.18.0
628
	 */
629
	static function remove_double_slashes( $url ) {
630
		return TimberURLHelper::remove_double_slashes( $url );
631
	}
632
633
	/**
634
	 * @deprecated 0.18.0
635
	 */
636
	static function prepend_to_url( $url, $path ) {
637
		return TimberURLHelper::prepend_to_url( $url, $path );
638
	}
639
640
	/**
641
	 * @deprecated 0.18.0
642
	 */
643
	static function preslashit( $path ) {
644
		return TimberURLHelper::preslashit( $path );
645
	}
646
647
	/**
648
	 * @deprecated 0.18.0
649
	 */
650
	static function is_external( $url ) {
651
		return TimberURLHelper::is_external( $url );
652
	}
653
654
	/**
655
	 * @deprecated 0.18.0
656
	 */
657
	static function download_url( $url, $timeout = 300 ) {
658
		return TimberURLHelper::download_url( $url, $timeout );
659
	}
660
661
	/**
662
	 * @deprecated 0.18.0
663
	 */
664
	static function get_params( $i = -1 ) {
665
		return TimberURLHelper::get_params( $i );
666
	}
667
668
}
669