PostPreview::run()   F
last analyzed

Complexity

Conditions 19
Paths 3600

Size

Total Lines 64
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 47
nc 3600
nop 0
dl 0
loc 64
rs 0.3499
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 = '&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;
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like $char_length can also be of type integer. However, the property $char_length is declared as type boolean. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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 = '&hellip;' ) {
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like $strip can also be of type string. However, the property $strip is declared as type boolean. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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 ')) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $last_p_tag of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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;
0 ignored issues
show
introduced by
The condition is_string($strip) is always false.
Loading history...
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 ) {
0 ignored issues
show
introduced by
The condition $allowable_tags is always false.
Loading history...
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);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $more of Timber\TextHelper::trim_characters(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

268
					$text = TextHelper::trim_characters($text, $chars, /** @scrutinizer ignore-type */ false);
Loading history...
Bug introduced by
$chars of type true is incompatible with the type integer expected by parameter $num_chars of Timber\TextHelper::trim_characters(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

268
					$text = TextHelper::trim_characters($text, /** @scrutinizer ignore-type */ $chars, false);
Loading history...
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 ) {
0 ignored issues
show
introduced by
The condition $allowable_tags is always false.
Loading history...
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 ) {
0 ignored issues
show
introduced by
The condition $allowable_tags is always false.
Loading history...
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));
0 ignored issues
show
Bug introduced by
$allowable_tags of type false is incompatible with the type null|string|string[] expected by parameter $allowed_tags of strip_tags(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

306
			$text = trim(strip_tags($text, /** @scrutinizer ignore-type */ $allowable_tags));
Loading history...
307
		}
308
		if ( strlen($text) ) {
309
			return $this->assemble($text, $readmore_matches, $trimmed);
310
		}
311
312
		return trim($text);
313
	}
314
315
}
316