1
|
|
|
<?php |
2
|
|
|
|
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 = '…'; |
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; |
123
|
|
|
return $this; |
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; |
|
|
|
|
140
|
|
|
return $this; |
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 = '…' ) { |
155
|
|
|
$this->end = $end; |
156
|
|
|
return $this; |
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; |
178
|
|
|
return $this; |
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; |
195
|
|
|
return $this; |
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; |
|
|
|
|
213
|
|
|
return $this; |
214
|
|
|
} |
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 ) { |
222
|
|
|
$text = trim($text); |
223
|
|
|
$last = $text[strlen($text) - 1]; |
224
|
|
|
$last_p_tag = null; |
225
|
|
|
if ( $last != '.' && $trimmed ) { |
226
|
|
|
$text .= $this->end; |
227
|
|
|
} |
228
|
|
|
if ( !$this->strip ) { |
229
|
|
|
$last_p_tag = strrpos($text, '</p>'); |
230
|
|
|
if ( $last_p_tag !== false ) { |
231
|
|
|
$text = substr($text, 0, $last_p_tag); |
232
|
|
|
} |
233
|
|
|
if ( $last != '.' && $trimmed ) { |
234
|
|
|
$text .= $this->end.' '; |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
$read_more_class = apply_filters('timber/post/preview/read_more_class', "read-more"); |
238
|
|
|
if ( $this->readmore && !empty($readmore_matches) && !empty($readmore_matches[1]) ) { |
239
|
|
|
$text .= ' <a href="'.$this->post->link().'" class="'.$read_more_class.'">'.trim($readmore_matches[1]).'</a>'; |
240
|
|
|
} elseif ( $this->readmore ) { |
241
|
|
|
$text .= ' <a href="'.$this->post->link().'" class="'.$read_more_class.'">'.trim($this->readmore).'</a>'; |
242
|
|
|
} |
243
|
|
|
if ( !$this->strip && $last_p_tag && (strpos($text, '<p>') > -1 || strpos($text, '<p ')) ) { |
|
|
|
|
244
|
|
|
$text .= '</p>'; |
245
|
|
|
} |
246
|
|
|
return trim($text); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
protected function run() { |
250
|
|
|
$force = $this->force; |
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
|
|
|
} |
285
|
|
|
$trimmed = true; |
286
|
|
|
} |
287
|
|
|
$text = do_shortcode($text); |
288
|
|
|
} |
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
|
|
|
} |
300
|
|
|
$trimmed = true; |
301
|
|
|
} |
302
|
|
|
if ( !strlen(trim($text)) ) { |
303
|
|
|
return trim($text); |
304
|
|
|
} |
305
|
|
|
if ( $strip ) { |
306
|
|
|
$text = trim(strip_tags($text, $allowable_tags)); |
|
|
|
|
307
|
|
|
} |
308
|
|
|
if ( strlen($text) ) { |
309
|
|
|
return $this->assemble($text, $readmore_matches, $trimmed); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
return trim($text); |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
} |
316
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.