Timber - Version 1.9.0

Version Description

Timber now requires PHP 5.6 or greater. While Timber may work on PHP 5.5 and older versions; support will no longer be maintained in future versions.

Changes for Theme Developers - Adds support for roles on the user object. Example: {{ post.author.roles }} which returns an array of roles #1898 (thanks @palmiak) - Adds support for capabilities on the user object. Example: {{post.author.can("moderate_comments")}} which returns true or false #1898 (thanks @palmiak)

Fixes and improvements * Fix an error with handling args for nav menus #1865 (thanks @palmiak) * Allowed tags won't be stripped when automatically generating an excerpt #1886 (thanks @davefx) * Fix for JPG/WEBP conversion for some older PHP installs #1854

Download this release

Release Info

Developer jarednova
Plugin Icon 128x128 Timber
Version 1.9.0
Comparing to
See all releases

Code changes from version 1.8.4 to 1.9.0

README.md CHANGED
@@ -7,6 +7,7 @@ By
7
  [Lukas Gächter](https://github.com/gchtr) ([@lgaechter](https://twitter.com/lgaechter)),
8
  [Pascal Knecht](https://github.com/pascalknecht) ([@pascalknecht](https://twitter.com/revenwo)),
9
  [Maciej Palmowski](https://github.com/palmiak) ([@palmiak_fp](https://twitter.com/palmiak_fp)),
 
10
  [Upstatement](https://twitter.com/upstatement) and [hundreds of other GitHub contributors](https://github.com/timber/timber/graphs/contributors)
11
 
12
  [![Build Status](https://img.shields.io/travis/timber/timber/master.svg?style=flat-square)](https://travis-ci.org/timber/timber)
@@ -95,6 +96,7 @@ Timber is great for any WordPress developer who cares about writing good, mainta
95
  * [**Pine**](https://github.com/azeemhassni/pine) A CLI _installer_ for Timber
96
  * [**Timber CLI**](https://github.com/nclud/wp-timber-cli) A CLI for Timber
97
  * [**Timber Commented Include**](https://github.com/djboris88/timber-commented-include) Debug output via HTML comments before and after each include statement in Twig
 
98
  * [**Timber Dump Extension**](https://github.com/nlemoine/timber-dump-extension) Debug output with nice formatting
99
  * [**Timber Photon**](https://github.com/slimndap/TimberPhoton) Plug-in to use JetPack's free Photon image manipulation and CDN with Timber
100
  * [**Timber Sugar**](https://github.com/timber/sugar) A catch-all for goodies to use w Timber
7
  [Lukas Gächter](https://github.com/gchtr) ([@lgaechter](https://twitter.com/lgaechter)),
8
  [Pascal Knecht](https://github.com/pascalknecht) ([@pascalknecht](https://twitter.com/revenwo)),
9
  [Maciej Palmowski](https://github.com/palmiak) ([@palmiak_fp](https://twitter.com/palmiak_fp)),
10
+ [Coby Tamayo](https://github.com/acobster) ([@cobytamayo](https://keybase.io/acobster)),
11
  [Upstatement](https://twitter.com/upstatement) and [hundreds of other GitHub contributors](https://github.com/timber/timber/graphs/contributors)
12
 
13
  [![Build Status](https://img.shields.io/travis/timber/timber/master.svg?style=flat-square)](https://travis-ci.org/timber/timber)
96
  * [**Pine**](https://github.com/azeemhassni/pine) A CLI _installer_ for Timber
97
  * [**Timber CLI**](https://github.com/nclud/wp-timber-cli) A CLI for Timber
98
  * [**Timber Commented Include**](https://github.com/djboris88/timber-commented-include) Debug output via HTML comments before and after each include statement in Twig
99
+ * [**Timber Debugger**](https://github.com/djboris88/timber-debugger) Package that provides extra debugging options for Timber
100
  * [**Timber Dump Extension**](https://github.com/nlemoine/timber-dump-extension) Debug output with nice formatting
101
  * [**Timber Photon**](https://github.com/slimndap/TimberPhoton) Plug-in to use JetPack's free Photon image manipulation and CDN with Timber
102
  * [**Timber Sugar**](https://github.com/timber/sugar) A catch-all for goodies to use w Timber
lib/Comment.php CHANGED
@@ -79,16 +79,16 @@ class Comment extends Core implements CoreInterface {
79
  * <h3>Comments by...</h3>
80
  * <ol>
81
  * {% for comment in post.comments %}
82
- * <li>{{comment.author.name}}, who is a {{comment.author.role}}</li>
83
  * {% endfor %}
84
  * </ol>
85
  * ```
86
  * ```html
87
  * <h3>Comments by...</h3>
88
  * <ol>
89
- * <li>Jared Novack, who is a contributor</li>
90
- * <li>Katie Ricci, who is a subscriber</li>
91
- * <li>Rebecca Pearl, who is a author</li>
92
  * </ol>
93
  * ```
94
  * @return User
@@ -130,13 +130,13 @@ class Comment extends Core implements CoreInterface {
130
  }
131
 
132
  $email = $this->avatar_email();
133
-
134
  $args = array('size' => $size, 'default' => $default);
135
  $args = apply_filters('pre_get_avatar_data', $args, $email);
136
  if ( isset($args['url']) ) {
137
  return $args['url'];
138
  }
139
-
140
  $email_hash = '';
141
  if ( !empty($email) ) {
142
  $email_hash = md5(strtolower(trim($email)));
@@ -206,7 +206,7 @@ class Comment extends Core implements CoreInterface {
206
  * @return boolean
207
  */
208
  public function approved() {
209
- return Helper::is_true($this->comment_approved);
210
  }
211
 
212
  /**
79
  * <h3>Comments by...</h3>
80
  * <ol>
81
  * {% for comment in post.comments %}
82
+ * <li>{{comment.author.name}}, who has the following roles: {{comment.author.roles|join(', ')}}</li>
83
  * {% endfor %}
84
  * </ol>
85
  * ```
86
  * ```html
87
  * <h3>Comments by...</h3>
88
  * <ol>
89
+ * <li>Jared Novack, who is a contributor</li>
90
+ * <li>Katie Ricci, who is a subscriber</li>
91
+ * <li>Rebecca Pearl, who is a author</li>
92
  * </ol>
93
  * ```
94
  * @return User
130
  }
131
 
132
  $email = $this->avatar_email();
133
+
134
  $args = array('size' => $size, 'default' => $default);
135
  $args = apply_filters('pre_get_avatar_data', $args, $email);
136
  if ( isset($args['url']) ) {
137
  return $args['url'];
138
  }
139
+
140
  $email_hash = '';
141
  if ( !empty($email) ) {
142
  $email_hash = md5(strtolower(trim($email)));
206
  * @return boolean
207
  */
208
  public function approved() {
209
+ return Helper::is_true($this->comment_approved);
210
  }
211
 
212
  /**
lib/Helper.php CHANGED
@@ -480,4 +480,26 @@ class Helper {
480
  Helper::warn('TimberHelper::get_current_url() is deprecated and will be removed in future versions, use Timber\URLHelper::get_current_url()');
481
  return URLHelper::get_current_url();
482
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  }
480
  Helper::warn('TimberHelper::get_current_url() is deprecated and will be removed in future versions, use Timber\URLHelper::get_current_url()');
481
  return URLHelper::get_current_url();
482
  }
483
+
484
+ /**
485
+ * Converts a WP object (WP_Post, WP_Term) into his
486
+ * equivalent Timber class (Timber\Post, Timber\Term).
487
+ *
488
+ * If no match is found the function will return the inital argument.
489
+ *
490
+ * @param mix $obj WP Object
491
+ * @return mix Instance of equivalent Timber object, or the argument if no match is found
492
+ */
493
+ public static function convert_wp_object( $obj ) {
494
+ if ( $obj instanceof \WP_Post ) {
495
+ $class = \Timber\PostGetter::get_post_class($obj->post_type);
496
+ return new $class($obj->ID);
497
+ } elseif ( $obj instanceof \WP_Term ) {
498
+ return new \Timber\Term($obj->term_id);
499
+ } elseif ( $obj instanceof \WP_User ) {
500
+ return new \Timber\User($obj->ID);
501
+ }
502
+
503
+ return $obj;
504
+ }
505
  }
lib/ImageHelper.php CHANGED
@@ -307,7 +307,11 @@ class ImageHelper {
307
  */
308
  protected static function process_delete_generated_files( $filename, $ext, $dir, $search_pattern, $match_pattern = null ) {
309
  $searcher = '/'.$filename.$search_pattern;
310
- foreach ( glob($dir.$searcher) as $found_file ) {
 
 
 
 
311
  $pattern = '/'.preg_quote($dir, '/').'\/'.preg_quote($filename, '/').$match_pattern.preg_quote($ext, '/').'/';
312
  $match = preg_match($pattern, $found_file);
313
  if ( !$match_pattern || $match ) {
307
  */
308
  protected static function process_delete_generated_files( $filename, $ext, $dir, $search_pattern, $match_pattern = null ) {
309
  $searcher = '/'.$filename.$search_pattern;
310
+ $files = glob($dir.$searcher);
311
+ if ( $files === false || empty($files) ) {
312
+ return;
313
+ }
314
+ foreach ( $files as $found_file ) {
315
  $pattern = '/'.preg_quote($dir, '/').'\/'.preg_quote($filename, '/').$match_pattern.preg_quote($ext, '/').'/';
316
  $match = preg_match($pattern, $found_file);
317
  if ( !$match_pattern || $match ) {
lib/Menu.php CHANGED
@@ -105,7 +105,7 @@ class Menu extends Core {
105
  *
106
  * @see wp_nav_menu()
107
  */
108
- $default_args = array(
109
  'menu' => '',
110
  'container' => 'div',
111
  'container_class' => '',
@@ -130,7 +130,10 @@ class Menu extends Core {
130
  *
131
  * @see wp_nav_menu()
132
  */
133
- $menu = apply_filters( 'wp_nav_menu_objects', $menu, $default_args );
 
 
 
134
 
135
  $menu = self::order_children($menu);
136
  $menu = self::strip_to_depth_limit($menu);
105
  *
106
  * @see wp_nav_menu()
107
  */
108
+ $default_args_array = array(
109
  'menu' => '',
110
  'container' => 'div',
111
  'container_class' => '',
130
  *
131
  * @see wp_nav_menu()
132
  */
133
+ $default_args_array = apply_filters( 'wp_nav_menu_args', $default_args_array );
134
+ $default_args_obj = (object) $default_args_array;
135
+
136
+ $menu = apply_filters( 'wp_nav_menu_objects', $menu, $default_args_obj );
137
 
138
  $menu = self::order_children($menu);
139
  $menu = self::strip_to_depth_limit($menu);
lib/Post.php CHANGED
@@ -209,7 +209,7 @@ class Post extends Core implements CoreInterface {
209
  }
210
 
211
  /**
212
- * Determined whether or not an admin/editor is looking at the post in "preview mode" via the
213
  * WordPress admin
214
  * @internal
215
  * @return bool
@@ -404,7 +404,30 @@ class Post extends Core implements CoreInterface {
404
  }
405
 
406
  /**
407
- * @return PostPreview
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  */
409
  public function preview() {
410
  return new PostPreview($this);
@@ -811,7 +834,7 @@ class Post extends Core implements CoreInterface {
811
  */
812
  public function field_object( $field_name ) {
813
  $value = apply_filters('timber/post/meta_object_field', null, $this->ID, $field_name, $this);
814
- $value = $this->convert($value, __CLASS__);
815
  return $value;
816
  }
817
 
@@ -834,7 +857,7 @@ class Post extends Core implements CoreInterface {
834
  }
835
  }
836
  $value = apply_filters('timber_post_get_meta_field', $value, $this->ID, $field_name, $this);
837
- $value = $this->convert($value, __CLASS__);
838
  return $value;
839
  }
840
 
@@ -1367,18 +1390,16 @@ class Post extends Core implements CoreInterface {
1367
  * @param array|WP_Post $data
1368
  * @param string $class
1369
  */
1370
- public function convert( $data, $class = '\Timber\Post' ) {
1371
- if ( $data instanceof WP_Post ) {
1372
- $data = new $class($data);
1373
  } else if ( is_array($data) ) {
1374
  $func = __FUNCTION__;
1375
  foreach ( $data as &$ele ) {
1376
- if ( gettype($ele) === 'array' ) {
1377
- $ele = $this->$func($ele, $class);
1378
- } else {
1379
- if ( $ele instanceof WP_Post ) {
1380
- $ele = new $class($ele);
1381
- }
1382
  }
1383
  }
1384
  }
209
  }
210
 
211
  /**
212
+ * Determine whether or not an admin/editor is looking at the post in "preview mode" via the
213
  * WordPress admin
214
  * @internal
215
  * @return bool
404
  }
405
 
406
  /**
407
+ * Gets a preview/excerpt of your post.
408
+ *
409
+ * If you have text defined in the excerpt textarea of your post, it will use that. Otherwise it
410
+ * will pull from the post_content. If there's a `<!-- more -->` tag, it will use that to mark
411
+ * where to pull through.
412
+ *
413
+ * This method returns a `Timber\PostPreview` object, which is a **chainable object**. This
414
+ * means that you can change the output of the preview by **adding more methods**. Refer to the
415
+ * [documentation of the `Timber\PostPreview` class](https://timber.github.io/docs/reference/timber-postpreview/)
416
+ * to get an overview of all the available methods.
417
+ *
418
+ * @example
419
+ * ```twig
420
+ * {# Use default preview #}
421
+ * <p>{{ post.preview }}</p>
422
+ *
423
+ * {# Change the post preview text #}
424
+ * <p>{{ post.preview.read_more('Continue Reading') }}</p>
425
+ *
426
+ * {# Additionally restrict the length to 50 words #}
427
+ * <p>{{ post.preview.length(50).read_more('Continue Reading') }}</p>
428
+ * ```
429
+ * @see \Timber\PostPreview
430
+ * @return \Timber\PostPreview
431
  */
432
  public function preview() {
433
  return new PostPreview($this);
834
  */
835
  public function field_object( $field_name ) {
836
  $value = apply_filters('timber/post/meta_object_field', null, $this->ID, $field_name, $this);
837
+ $value = $this->convert($value);
838
  return $value;
839
  }
840
 
857
  }
858
  }
859
  $value = apply_filters('timber_post_get_meta_field', $value, $this->ID, $field_name, $this);
860
+ $value = $this->convert($value);
861
  return $value;
862
  }
863
 
1390
  * @param array|WP_Post $data
1391
  * @param string $class
1392
  */
1393
+ public function convert( $data ) {
1394
+ if ( is_object($data) ) {
1395
+ $data = Helper::convert_wp_object($data);
1396
  } else if ( is_array($data) ) {
1397
  $func = __FUNCTION__;
1398
  foreach ( $data as &$ele ) {
1399
+ if ( is_array($ele) ) {
1400
+ $ele = $this->$func($ele);
1401
+ } else if ( is_object($ele) ) {
1402
+ $ele = Helper::convert_wp_object($ele);
 
 
1403
  }
1404
  }
1405
  }
lib/PostPreview.php CHANGED
@@ -3,34 +3,120 @@
3
  namespace Timber;
4
 
5
  /**
6
- * An object that lets a user easily modify the post preview to their
7
- * liking
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  * @since 1.0.4
9
- */
 
10
  class PostPreview {
11
-
 
 
 
 
12
  protected $post;
 
 
 
 
 
 
13
  protected $end = '&hellip;';
 
 
 
 
 
 
14
  protected $force = false;
 
 
 
 
 
 
15
  protected $length = 50;
 
 
 
 
 
 
16
  protected $char_length = false;
 
 
 
 
 
 
17
  protected $readmore = 'Read More';
 
 
 
 
 
 
18
  protected $strip = true;
 
 
 
 
 
 
19
  protected $destroy_tags = array('script', 'style');
20
 
21
  /**
22
- * @param Post $post
 
 
 
23
  */
24
  public function __construct( $post ) {
25
  $this->post = $post;
26
  }
27
 
 
 
 
 
 
 
28
  public function __toString() {
29
  return $this->run();
30
  }
31
 
32
  /**
33
- * @param integer $length (in words) of the target preview
 
 
 
 
 
 
 
 
34
  */
35
  public function length( $length = 50 ) {
36
  $this->length = $length;
@@ -38,7 +124,16 @@ class PostPreview {
38
  }
39
 
40
  /**
41
- * @param integer $char_length (in characters) of the target preview
 
 
 
 
 
 
 
 
 
42
  */
43
  public function chars( $char_length = false ) {
44
  $this->char_length = $char_length;
@@ -46,7 +141,15 @@ class PostPreview {
46
  }
47
 
48
  /**
49
- * @param string $end how should the text in the preview end
 
 
 
 
 
 
 
 
50
  */
51
  public function end( $end = '&hellip;' ) {
52
  $this->end = $end;
@@ -54,7 +157,21 @@ class PostPreview {
54
  }
55
 
56
  /**
57
- * @param boolean $force If the editor wrote a manual excerpt longer than the set length, should it be "forced" to the size specified?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  */
59
  public function force( $force = true ) {
60
  $this->force = $force;
@@ -62,7 +179,16 @@ class PostPreview {
62
  }
63
 
64
  /**
65
- * @param string $readmore What the text displays as to the reader inside of the <a> tag
 
 
 
 
 
 
 
 
 
66
  */
67
  public function read_more( $readmore = 'Read More' ) {
68
  $this->readmore = $readmore;
@@ -70,7 +196,17 @@ class PostPreview {
70
  }
71
 
72
  /**
73
- * @param boolean|string $strip strip the tags or what? You can also provide a list of allowed tags (e.g. '<p><a>')
 
 
 
 
 
 
 
 
 
 
74
  */
75
  public function strip( $strip = true ) {
76
  $this->strip = $strip;
@@ -79,7 +215,7 @@ class PostPreview {
79
 
80
  /**
81
  * @param string $text
82
- * @param array|booelan $readmore_matches
83
  * @param boolean $trimmed was the text trimmed?
84
  */
85
  protected function assemble( $text, $readmore_matches, $trimmed ) {
@@ -115,25 +251,34 @@ class PostPreview {
115
  $len = $this->length;
116
  $chars = $this->char_length;
117
  $strip = $this->strip;
 
118
  $readmore_matches = array();
119
  $text = '';
120
  $trimmed = false;
121
  if ( isset($this->post->post_excerpt) && strlen($this->post->post_excerpt) ) {
 
122
  if ( $this->force ) {
123
- $text = TextHelper::trim_words($this->post->post_excerpt, $len, false);
 
 
 
 
 
124
  if ( $chars !== false ) {
125
- $text = TextHelper::trim_characters($this->post->post_excerpt, $chars, false);
126
  }
127
  $trimmed = true;
128
- } else {
129
- $text = $this->post->post_excerpt;
130
- }
131
  }
132
  if ( !strlen($text) && preg_match('/<!--\s?more(.*?)?-->/', $this->post->post_content, $readmore_matches) ) {
133
  $pieces = explode($readmore_matches[0], $this->post->post_content);
134
  $text = $pieces[0];
135
  if ( $force ) {
136
- $text = TextHelper::trim_words($text, $len, false);
 
 
 
 
137
  if ( $chars !== false ) {
138
  $text = TextHelper::trim_characters($text, $chars, false);
139
  }
@@ -144,7 +289,11 @@ class PostPreview {
144
  if ( !strlen($text) ) {
145
  $text = $this->post->content();
146
  $text = TextHelper::remove_tags($text, $this->destroy_tags);
147
- $text = TextHelper::trim_words($text, $len, false);
 
 
 
 
148
  if ( $chars !== false ) {
149
  $text = TextHelper::trim_characters($text, $chars, false);
150
  }
@@ -154,7 +303,6 @@ class PostPreview {
154
  return trim($text);
155
  }
156
  if ( $strip ) {
157
- $allowable_tags = (is_string($strip)) ? $strip : null;
158
  $text = trim(strip_tags($text, $allowable_tags));
159
  }
160
  if ( strlen($text) ) {
3
  namespace Timber;
4
 
5
  /**
6
+ * The PostPreview class lets a user modify a post preview/excerpt to their liking.
7
+ *
8
+ * It’s designed to be used through the `Timber\Post::preview()` method. The public methods of this
9
+ * class all return the object itself, which means that this is a **chainable object**. You can
10
+ * change the output of the preview by **adding more methods**.
11
+ *
12
+ * By default, the preview will
13
+ *
14
+ * - have a length of 50 words, which will be forced, even if a longer excerpt is set on the post.
15
+ * - be stripped of all HTML tags.
16
+ * - have an ellipsis (…) as the end of the text.
17
+ * - have a "Read More" link appended.
18
+ *
19
+ * @example
20
+ * ```twig
21
+ * {# Use default preview #}
22
+ * <p>{{ post.preview }}</p>
23
+ *
24
+ * {# Change the post preview text #}
25
+ * <p>{{ post.preview.read_more('Continue Reading') }}</p>
26
+ *
27
+ * {# Additionally restrict the length to 50 words #}
28
+ * <p>{{ post.preview.length(50).read_more('Continue Reading') }}</p>
29
+ * ```
30
  * @since 1.0.4
31
+ * @see \Timber\Post::preview()
32
+ */
33
  class PostPreview {
34
+ /**
35
+ * Post.
36
+ *
37
+ * @var \Timber\Post
38
+ */
39
  protected $post;
40
+
41
+ /**
42
+ * Preview end.
43
+ *
44
+ * @var string
45
+ */
46
  protected $end = '&hellip;';
47
+
48
+ /**
49
+ * Force length.
50
+ *
51
+ * @var bool
52
+ */
53
  protected $force = false;
54
+
55
+ /**
56
+ * Length in words.
57
+ *
58
+ * @var int
59
+ */
60
  protected $length = 50;
61
+
62
+ /**
63
+ * Length in characters.
64
+ *
65
+ * @var bool
66
+ */
67
  protected $char_length = false;
68
+
69
+ /**
70
+ * Read more text.
71
+ *
72
+ * @var string
73
+ */
74
  protected $readmore = 'Read More';
75
+
76
+ /**
77
+ * HTML tag stripping behavior.
78
+ *
79
+ * @var bool
80
+ */
81
  protected $strip = true;
82
+
83
+ /**
84
+ * Destroy tags.
85
+ *
86
+ * @var array List of tags that should always be destroyed.
87
+ */
88
  protected $destroy_tags = array('script', 'style');
89
 
90
  /**
91
+ * PostPreview constructor.
92
+ *
93
+ * @api
94
+ * @param \Timber\Post $post The post to pull the preview from.
95
  */
96
  public function __construct( $post ) {
97
  $this->post = $post;
98
  }
99
 
100
+ /**
101
+ * Returns the resulting preview.
102
+ *
103
+ * @api
104
+ * @return string
105
+ */
106
  public function __toString() {
107
  return $this->run();
108
  }
109
 
110
  /**
111
+ * Restricts the length of the preview to a certain amount of words.
112
+ *
113
+ * @api
114
+ * @example
115
+ * ```twig
116
+ * <p>{{ post.preview.length(50) }}</p>
117
+ * ```
118
+ * @param int $length The maximum amount of words (not letters) for the preview. Default `50`.
119
+ * @return \Timber\PostPreview
120
  */
121
  public function length( $length = 50 ) {
122
  $this->length = $length;
124
  }
125
 
126
  /**
127
+ * Restricts the length of the preview to a certain amount of characters.
128
+ *
129
+ * @api
130
+ * @example
131
+ * ```twig
132
+ * <p>{{ post.preview.chars(180) }}</p>
133
+ * ```
134
+ * @param int|bool $char_length The maximum amount of characters for the preview. Default
135
+ * `false`.
136
+ * @return \Timber\PostPreview
137
  */
138
  public function chars( $char_length = false ) {
139
  $this->char_length = $char_length;
141
  }
142
 
143
  /**
144
+ * Defines the text to end the preview with.
145
+ *
146
+ * @api
147
+ * @example
148
+ * ```twig
149
+ * <p>{{ post.preview.end('… and much more!') }}</p>
150
+ * ```
151
+ * @param string $end The text for the end of the preview. Default `…`.
152
+ * @return \Timber\PostPreview
153
  */
154
  public function end( $end = '&hellip;' ) {
155
  $this->end = $end;
157
  }
158
 
159
  /**
160
+ * Forces preview lengths.
161
+ *
162
+ * What happens if your custom post excerpt is longer than the length requested? By default, it
163
+ * will use the full `post_excerpt`. However, you can set this to `true` to *force* your excerpt
164
+ * to be of the desired length.
165
+ *
166
+ * @api
167
+ * @example
168
+ * ```twig
169
+ * <p>{{ post.preview.length(20).force }}</p>
170
+ * ```
171
+ * @param bool $force Whether the length of the preview should be forced to the requested
172
+ * length, even if an editor wrote a manual excerpt that is longer than the
173
+ * set length. Default `true`.
174
+ * @return \Timber\PostPreview
175
  */
176
  public function force( $force = true ) {
177
  $this->force = $force;
179
  }
180
 
181
  /**
182
+ * Defines the text to be used for the "Read More" link.
183
+ *
184
+ * Set this to `false` to not add a "Read More" link.
185
+ *
186
+ * @api
187
+ * ```twig
188
+ * <p>{{ post.preview.read_more('Learn more') }}</p>
189
+ * ```
190
+ * @param string $readmore Text for the link. Default 'Read More'.
191
+ * @return \Timber\PostPreview
192
  */
193
  public function read_more( $readmore = 'Read More' ) {
194
  $this->readmore = $readmore;
196
  }
197
 
198
  /**
199
+ * Defines how HTML tags should be stripped from the preview.
200
+ *
201
+ * @api
202
+ * ```twig
203
+ * {# Strips all HTML tags, except for bold or emphasized text #}
204
+ * <p>{{ post.preview.length('50').strip('<strong><em>') }}</p>
205
+ * ```
206
+ * @param bool|string $strip Whether or how HTML tags in the preview should be stripped. Use
207
+ * `true` to strip all tags, `false` for no stripping, or a string for
208
+ * a list of allowed tags (e.g. '<p><a>'). Default `true`.
209
+ * @return \Timber\PostPreview
210
  */
211
  public function strip( $strip = true ) {
212
  $this->strip = $strip;
215
 
216
  /**
217
  * @param string $text
218
+ * @param array|bool $readmore_matches
219
  * @param boolean $trimmed was the text trimmed?
220
  */
221
  protected function assemble( $text, $readmore_matches, $trimmed ) {
251
  $len = $this->length;
252
  $chars = $this->char_length;
253
  $strip = $this->strip;
254
+ $allowable_tags = ( $strip && is_string($strip)) ? $strip : false;
255
  $readmore_matches = array();
256
  $text = '';
257
  $trimmed = false;
258
  if ( isset($this->post->post_excerpt) && strlen($this->post->post_excerpt) ) {
259
+ $text = $this->post->post_excerpt;
260
  if ( $this->force ) {
261
+
262
+ if ( $allowable_tags ) {
263
+ $text = TextHelper::trim_words($text, $len, false, strtr($allowable_tags, '<>', ' '));
264
+ } else {
265
+ $text = TextHelper::trim_words($text, $len, false);
266
+ }
267
  if ( $chars !== false ) {
268
+ $text = TextHelper::trim_characters($text, $chars, false);
269
  }
270
  $trimmed = true;
271
+ }
 
 
272
  }
273
  if ( !strlen($text) && preg_match('/<!--\s?more(.*?)?-->/', $this->post->post_content, $readmore_matches) ) {
274
  $pieces = explode($readmore_matches[0], $this->post->post_content);
275
  $text = $pieces[0];
276
  if ( $force ) {
277
+ if ( $allowable_tags ) {
278
+ $text = TextHelper::trim_words($text, $len, false, strtr($allowable_tags, '<>', ' '));
279
+ } else {
280
+ $text = TextHelper::trim_words($text, $len, false);
281
+ }
282
  if ( $chars !== false ) {
283
  $text = TextHelper::trim_characters($text, $chars, false);
284
  }
289
  if ( !strlen($text) ) {
290
  $text = $this->post->content();
291
  $text = TextHelper::remove_tags($text, $this->destroy_tags);
292
+ if ( $allowable_tags ) {
293
+ $text = TextHelper::trim_words($text, $len, false, strtr($allowable_tags, '<>', ' '));
294
+ } else {
295
+ $text = TextHelper::trim_words($text, $len, false);
296
+ }
297
  if ( $chars !== false ) {
298
  $text = TextHelper::trim_characters($text, $chars, false);
299
  }
303
  return trim($text);
304
  }
305
  if ( $strip ) {
 
306
  $text = trim(strip_tags($text, $allowable_tags));
307
  }
308
  if ( strlen($text) ) {
lib/TextHelper.php CHANGED
@@ -18,13 +18,10 @@ class TextHelper {
18
  * @author @CROSP
19
  * @param string $text Text to trim.
20
  * @param int $num_chars Number of characters. Default is 60.
21
- * @param string|null $more Optional. What to append if $text needs to be trimmed. Default '&hellip;'.
22
  * @return string trimmed text.
23
  */
24
- public static function trim_characters( $text, $num_chars = 60, $more = null ) {
25
- if ( $more === null ) {
26
- $more = __('&hellip;');
27
- }
28
  $text = wp_strip_all_tags($text);
29
  $text = mb_strimwidth($text, 0, $num_chars, $more);
30
  return $text;
@@ -44,10 +41,10 @@ class TextHelper {
44
  $more = __('&hellip;');
45
  }
46
  $original_text = $text;
47
- $allowed_tag_string = '';
48
- foreach ( explode(' ', apply_filters('timber/trim_words/allowed_tags', $allowed_tags)) as $tag ) {
49
- $allowed_tag_string .= '<'.$tag.'>';
50
- }
51
  $text = strip_tags($text, $allowed_tag_string);
52
  /* 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. */
53
  if ( 'characters' == _x('words', 'word count: words or characters?') && preg_match('/^utf\-?8$/i', get_option('blog_charset')) ) {
18
  * @author @CROSP
19
  * @param string $text Text to trim.
20
  * @param int $num_chars Number of characters. Default is 60.
21
+ * @param string $more What to append if $text needs to be trimmed. Defaults to '&hellip;'.
22
  * @return string trimmed text.
23
  */
24
+ public static function trim_characters( $text, $num_chars = 60, $more = '&hellip;' ) {
 
 
 
25
  $text = wp_strip_all_tags($text);
26
  $text = mb_strimwidth($text, 0, $num_chars, $more);
27
  return $text;
41
  $more = __('&hellip;');
42
  }
43
  $original_text = $text;
44
+
45
+ $allowed_tags_array = explode(' ', apply_filters('timber/trim_words/allowed_tags', $allowed_tags));
46
+ $allowed_tags_array = array_filter($allowed_tags_array, function($value) { return $value !== ''; });
47
+ $allowed_tag_string = '<'.implode('><', $allowed_tags_array).'>';
48
  $text = strip_tags($text, $allowed_tag_string);
49
  /* 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. */
50
  if ( 'characters' == _x('words', 'word count: words or characters?') && preg_match('/^utf\-?8$/i', get_option('blog_charset')) ) {
lib/Timber.php CHANGED
@@ -35,7 +35,7 @@ use Timber\Loader;
35
  */
36
  class Timber {
37
 
38
- public static $version = '1.8.4';
39
  public static $locations;
40
  public static $dirname = 'views';
41
  public static $twig_cache = false;
35
  */
36
  class Timber {
37
 
38
+ public static $version = '1.9.0';
39
  public static $locations;
40
  public static $dirname = 'views';
41
  public static $twig_cache = false;
lib/Twig.php CHANGED
@@ -225,7 +225,7 @@ class Twig {
225
  return apply_filters_ref_array($tag, $args);
226
  } ));
227
 
228
-
229
  $twig = apply_filters('timber/twig', $twig);
230
  /**
231
  * get_twig is deprecated, use timber/twig
@@ -358,14 +358,14 @@ class Twig {
358
  * @param string $second_delimiter
359
  * @return string
360
  */
361
- public function add_list_separators( $arr, $first_delimiter = ',', $second_delimiter = 'and' ) {
362
  $length = count($arr);
363
  $list = '';
364
  foreach ( $arr as $index => $item ) {
365
  if ( $index < $length - 2 ) {
366
  $delimiter = $first_delimiter.' ';
367
  } elseif ( $index == $length - 2 ) {
368
- $delimiter = ' '.$second_delimiter.' ';
369
  } else {
370
  $delimiter = '';
371
  }
225
  return apply_filters_ref_array($tag, $args);
226
  } ));
227
 
228
+
229
  $twig = apply_filters('timber/twig', $twig);
230
  /**
231
  * get_twig is deprecated, use timber/twig
358
  * @param string $second_delimiter
359
  * @return string
360
  */
361
+ public function add_list_separators( $arr, $first_delimiter = ',', $second_delimiter = ' and' ) {
362
  $length = count($arr);
363
  $list = '';
364
  foreach ( $arr as $index => $item ) {
365
  if ( $index < $length - 2 ) {
366
  $delimiter = $first_delimiter.' ';
367
  } elseif ( $index == $length - 2 ) {
368
+ $delimiter = $second_delimiter.' ';
369
  } else {
370
  $delimiter = '';
371
  }
lib/User.php CHANGED
@@ -65,6 +65,16 @@ class User extends Core implements CoreInterface {
65
  public $id;
66
  public $user_nicename;
67
 
 
 
 
 
 
 
 
 
 
 
68
  /**
69
  * @param object|int|bool $uid
70
  */
@@ -141,6 +151,10 @@ class User extends Core implements CoreInterface {
141
  } else {
142
  $this->import($data);
143
  }
 
 
 
 
144
  }
145
  unset($this->user_pass);
146
  $this->id = $this->ID;
@@ -229,4 +243,98 @@ class User extends Core implements CoreInterface {
229
  public function slug() {
230
  return $this->user_nicename;
231
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  }
65
  public $id;
66
  public $user_nicename;
67
 
68
+ /**
69
+ * The roles the user is part of.
70
+ *
71
+ * @api
72
+ * @since 1.8.5
73
+ *
74
+ * @var array
75
+ */
76
+ protected $roles;
77
+
78
  /**
79
  * @param object|int|bool $uid
80
  */
151
  } else {
152
  $this->import($data);
153
  }
154
+
155
+ if ( isset($data->roles) ) {
156
+ $this->roles = $this->get_roles($data->roles);
157
+ }
158
  }
159
  unset($this->user_pass);
160
  $this->id = $this->ID;
243
  public function slug() {
244
  return $this->user_nicename;
245
  }
246
+
247
+ /**
248
+ * Creates an associative array with user role slugs and their translated names.
249
+ *
250
+ * @internal
251
+ * @since 1.8.5
252
+ * @param array $roles user roles.
253
+ * @return array|null
254
+ */
255
+ protected function get_roles( $roles ) {
256
+ if ( empty($roles) ) {
257
+ // @codeCoverageIgnoreStart
258
+ return null;
259
+ // @codeCoverageIgnoreEnd
260
+ }
261
+
262
+ $wp_roles = wp_roles();
263
+ $names = $wp_roles->get_names();
264
+
265
+ $values = array();
266
+
267
+ foreach ( $roles as $role ) {
268
+ $name = $role;
269
+ if ( isset($names[ $role ]) ) {
270
+ $name = translate_user_role($names[ $role ]);
271
+ }
272
+ $values[ $role ] = $name;
273
+ }
274
+
275
+ return $values;
276
+ }
277
+
278
+ /**
279
+ * Gets the user roles.
280
+ * Roles shouldn’t be used to check whether a user has a capability. Use roles only for
281
+ * displaying purposes. For example, if you want to display the name of the subscription a user
282
+ * has on the site behind a paywall.
283
+ *
284
+ * If you want to check for capabilities, use `{{ user.can('capability') }}`. If you only want
285
+ * to check whether a user is logged in, you can use `{% if user %}`.
286
+ *
287
+ * @api
288
+ * @since 1.8.5
289
+ * @example
290
+ * ```twig
291
+ * <h2>Role name</h2>
292
+ * {% for role in post.author.roles %}
293
+ * {{ role }}
294
+ * {% endfor %}
295
+ * ```
296
+ * ```twig
297
+ * <h2>Role name</h2>
298
+ * {{ post.author.roles|join(', ') }}
299
+ * ```
300
+ * ```twig
301
+ * {% for slug, name in post.author.roles %}
302
+ * {{ slug }}
303
+ * {% endfor %}
304
+ * ```
305
+ *
306
+ * @return array|null
307
+ */
308
+ public function roles() {
309
+ return $this->roles;
310
+ }
311
+
312
+ /**
313
+ * Checks whether a user has a capability.
314
+ *
315
+ * Don’t use role slugs for capability checks. While checking against a role in place of a
316
+ * capability is supported in part, this practice is discouraged as it may produce unreliable
317
+ * results. This includes cases where you want to check whether a user is registered. If you
318
+ * want to check whether a user is a Subscriber, use `{{ user.can('read') }}`. If you only want
319
+ * to check whether a user is logged in, you can use `{% if user %}`.
320
+ *
321
+ * @api
322
+ * @since 1.8.5
323
+ *
324
+ * @param string $capability The capability to check.
325
+ *
326
+ * @example
327
+ * Give moderation users another CSS class to style them differently.
328
+ *
329
+ * ```twig
330
+ * <span class="comment-author {{ comment.author.can('moderate_comments') ? 'comment-author--is-moderator }}">
331
+ * {{ comment.author.name }}
332
+ * </span>
333
+ * ```
334
+ *
335
+ * @return bool Whether the user has the capability.
336
+ */
337
+ public function can( $capability ) {
338
+ return user_can($this->ID, $capability);
339
+ }
340
  }
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: jarednova, connorjburton, lggorman
3
  Tags: template engine, templates, twig
4
  Requires at least: 4.7.9
5
- Tested up to: 5.0.2
6
- Stable tag: 1.8.4
7
- Requires PHP: 5.3
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -36,6 +36,18 @@ _Twig is the template language powering Timber; if you need a little background
36
  **Changes for Theme Developers**
37
  - Please add bullet points here with your PR. The heading for this section will get the correct version number once released.
38
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  = 1.8.4 =
40
  **Fixes and improvements**
41
  * Resolve potential pagination issue #1642 (thanks @gchtr)
2
  Contributors: jarednova, connorjburton, lggorman
3
  Tags: template engine, templates, twig
4
  Requires at least: 4.7.9
5
+ Tested up to: 5.0.3
6
+ Stable tag: 1.9.0
7
+ Requires PHP: 5.6
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
36
  **Changes for Theme Developers**
37
  - Please add bullet points here with your PR. The heading for this section will get the correct version number once released.
38
 
39
+ = 1.9.0 =
40
+ Timber now requires PHP 5.6 or greater. While Timber may work on PHP 5.5 and older versions; support will no longer be maintained in future versions.
41
+
42
+ **Changes for Theme Developers**
43
+ - Adds support for roles on the user object. Example: `{{ post.author.roles }}` which returns an array of roles #1898 (thanks @palmiak)
44
+ - Adds support for capabilities on the user object. Example: `{{post.author.can("moderate_comments")}}` which returns true or false #1898 (thanks @palmiak)
45
+
46
+ **Fixes and improvements**
47
+ * Fix an error with handling args for nav menus #1865 (thanks @palmiak)
48
+ * Allowed tags won't be stripped when automatically generating an excerpt #1886 (thanks @davefx)
49
+ * Fix for JPG/WEBP conversion for some older PHP installs #1854
50
+
51
  = 1.8.4 =
52
  **Fixes and improvements**
53
  * Resolve potential pagination issue #1642 (thanks @gchtr)
timber.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: Timber
4
  Description: The WordPress Timber Library allows you to write themes using the power of Twig templates.
5
  Plugin URI: http://timber.upstatement.com
6
  Author: Jared Novack + Upstatement
7
- Version: 1.8.4
8
  Author URI: http://upstatement.com/
9
  */
10
  // we look for Composer files first in the plugins dir.
4
  Description: The WordPress Timber Library allows you to write themes using the power of Twig templates.
5
  Plugin URI: http://timber.upstatement.com
6
  Author: Jared Novack + Upstatement
7
+ Version: 1.9.0
8
  Author URI: http://upstatement.com/
9
  */
10
  // we look for Composer files first in the plugins dir.
vendor/autoload.php CHANGED
@@ -4,4 +4,4 @@
4
 
5
  require_once __DIR__ . '/composer' . '/autoload_real.php';
6
 
7
- return ComposerAutoloaderInit2d3b2487a3de8d7edb3d42365706054f::getLoader();
4
 
5
  require_once __DIR__ . '/composer' . '/autoload_real.php';
6
 
7
+ return ComposerAutoloaderInit47041c9f634303a394d98c7e6b876c3d::getLoader();
vendor/composer/autoload_real.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
- class ComposerAutoloaderInit2d3b2487a3de8d7edb3d42365706054f
6
  {
7
  private static $loader;
8
 
@@ -19,9 +19,9 @@ class ComposerAutoloaderInit2d3b2487a3de8d7edb3d42365706054f
19
  return self::$loader;
20
  }
21
 
22
- spl_autoload_register(array('ComposerAutoloaderInit2d3b2487a3de8d7edb3d42365706054f', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
- spl_autoload_unregister(array('ComposerAutoloaderInit2d3b2487a3de8d7edb3d42365706054f', 'loadClassLoader'));
25
 
26
  $map = require __DIR__ . '/autoload_namespaces.php';
27
  foreach ($map as $namespace => $path) {
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
+ class ComposerAutoloaderInit47041c9f634303a394d98c7e6b876c3d
6
  {
7
  private static $loader;
8
 
19
  return self::$loader;
20
  }
21
 
22
+ spl_autoload_register(array('ComposerAutoloaderInit47041c9f634303a394d98c7e6b876c3d', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit47041c9f634303a394d98c7e6b876c3d', 'loadClassLoader'));
25
 
26
  $map = require __DIR__ . '/autoload_namespaces.php';
27
  foreach ($map as $namespace => $path) {