Completed
Push — update/use-identity-crisis-pac... ( d14b6d...6e141b )
by
unknown
128:58 queued 119:18
created

ChangeEntry::getSubheading()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
2
/**
3
 * Class representing a change entry.
4
 *
5
 * @package automattic/jetpack-changelogger
6
 */
7
8
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
9
10
namespace Automattic\Jetpack\Changelog;
11
12
use DateTime;
13
use InvalidArgumentException;
14
use JsonSerializable;
15
16
/**
17
 * Class representing a change entry.
18
 */
19
class ChangeEntry implements JsonSerializable {
20
21
	/**
22
	 * Entry significance.
23
	 *
24
	 * @var string|null
25
	 */
26
	protected $significance = null;
27
28
	/**
29
	 * Entry timestamp.
30
	 *
31
	 * @var DateTime
32
	 */
33
	protected $timestamp;
34
35
	/**
36
	 * Subheading the entry is under.
37
	 *
38
	 * @var string
39
	 */
40
	protected $subheading = '';
41
42
	/**
43
	 * Author of the entry.
44
	 *
45
	 * @var string
46
	 */
47
	protected $author = '';
48
49
	/**
50
	 * Content of the entry.
51
	 *
52
	 * @var string
53
	 */
54
	protected $content = '';
55
56
	/**
57
	 * Constructor.
58
	 *
59
	 * @param array $data Data for entry fields. Keys correspond to the setters, e.g. key 'content' calls `setContent()`.
60
	 * @throws InvalidArgumentException If an argument is invalid.
61
	 */
62 View Code Duplication
	public function __construct( array $data = array() ) {
63
		$data = $data + array( 'timestamp' => 'now' );
64
		foreach ( $data as $k => $v ) {
65
			$func = array( $this, 'set' . ucfirst( $k ) );
66
			if ( is_callable( $func ) ) {
67
				$func( $v );
68
			} else {
69
				throw new InvalidArgumentException( __METHOD__ . ": Unrecognized data item \"$k\"." );
70
			}
71
		}
72
	}
73
74
	/**
75
	 * Compare two ChangeEntry objects.
76
	 *
77
	 * @param ChangeEntry $a First ChangeEntry.
78
	 * @param ChangeEntry $b Second ChangeEntry.
79
	 * @param array       $config Comparison configuration. Keys are:
80
	 *        - ordering: (string[]) Order in which to consider the fields. Field
81
	 *          names correspond to getters, e.g. 'significance' =>
82
	 *          `getSignificance()`. Default ordering is subheading, content.
83
	 *        - knownSubheadings: (string[]) Change entries with these headings will
84
	 *          compare, in this order, after those with no subheading and before any
85
	 *          other subheadings.
86
	 * @return int <0 if $a should come before $b, >0 if $b should come before $a, or 0 otherwise.
87
	 * @throws InvalidArgumentException If an argument is invalid.
88
	 */
89
	public static function compare( ChangeEntry $a, ChangeEntry $b, array $config = array() ) {
90
		$config += array(
91
			'ordering'         => array( 'subheading', 'content' ),
92
			'knownSubheadings' => array(),
93
		);
94
95
		foreach ( $config['ordering'] as $field ) {
96
			// First, check for a custom comparison function.
97
			$func = array( static::class, 'compare' . ucfirst( $field ) );
98
			if ( is_callable( $func ) ) {
99
				$ret = $func( $a, $b, $config );
100
			} else {
101
				// Otherwise, just use `strnatcasecmp()`.
102
				$func = 'get' . ucfirst( $field );
103
				if ( ! is_callable( array( $a, $func ) ) || ! is_callable( array( $b, $func ) ) ) {
104
					throw new InvalidArgumentException( __METHOD__ . ': Invalid field in ordering' );
105
				}
106
				$aa  = call_user_func( array( $a, $func ) );
107
				$bb  = call_user_func( array( $b, $func ) );
108
				$ret = strnatcasecmp( $aa, $bb );
109
			}
110
			if ( 0 !== $ret ) {
111
				return $ret;
112
			}
113
		}
114
115
		return 0;
116
	}
117
118
	/**
119
	 * Get the significance.
120
	 *
121
	 * @return string|null
122
	 */
123
	public function getSignificance() {
124
		return $this->significance;
125
	}
126
127
	/**
128
	 * Set the significance.
129
	 *
130
	 * While entries coming in from users should always have a significance, we allow null here
131
	 * because entries created programmatically, particularly when parsing an existing changelog,
132
	 * may not include significance information.
133
	 *
134
	 * @param string|null $significance 'patch', 'minor', or 'major'.
135
	 * @returns $this
136
	 * @throws InvalidArgumentException If an argument is invalid.
137
	 */
138
	public function setSignificance( $significance ) {
139
		if ( ! in_array( $significance, array( null, 'patch', 'minor', 'major' ), true ) ) {
140
			throw new InvalidArgumentException( __METHOD__ . ": Significance must be 'patch', 'minor', or 'major' (or null)" );
141
		}
142
		$this->significance = $significance;
143
		return $this;
144
	}
145
146
	/**
147
	 * Compare significance values.
148
	 *
149
	 * @param ChangeEntry $a First entry.
150
	 * @param ChangeEntry $b Second entry.
151
	 * @param array       $config Passed from `compare()`, but unused here.
152
	 * @return int
153
	 */
154
	protected static function compareSignificance( ChangeEntry $a, ChangeEntry $b, array $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
155
		static $values = array( 'major', 'minor', 'patch', null );
156
		$aa            = array_search( $a->getSignificance(), $values, true );
157
		$bb            = array_search( $b->getSignificance(), $values, true );
158
		return $aa - $bb;
159
	}
160
161
	/**
162
	 * Get the timestamp.
163
	 *
164
	 * @return DateTime
165
	 */
166
	public function getTimestamp() {
167
		return $this->timestamp;
168
	}
169
170
	/**
171
	 * Set the timestamp.
172
	 *
173
	 * @param DateTime|string $timestamp Timestamp to set.
174
	 * @returns $this
175
	 * @throws InvalidArgumentException If an argument is invalid.
176
	 */
177 View Code Duplication
	public function setTimestamp( $timestamp ) {
178
		if ( ! $timestamp instanceof DateTime ) {
179
			try {
180
				$timestamp = new DateTime( $timestamp );
181
			} catch ( \Exception $ex ) {
182
				throw new InvalidArgumentException( __METHOD__ . ': Invalid timestamp', 0, $ex );
183
			}
184
		}
185
		$this->timestamp = $timestamp;
186
		return $this;
187
	}
188
189
	/**
190
	 * Compare timestamps.
191
	 *
192
	 * @param ChangeEntry $a First entry.
193
	 * @param ChangeEntry $b Second entry.
194
	 * @param array       $config Passed from `compare()`, but unused here.
195
	 * @return int
196
	 */
197
	protected static function compareTimestamp( ChangeEntry $a, ChangeEntry $b, array $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
198
		$aa = $a->getTimestamp();
199
		$bb = $b->getTimestamp();
200
		return $aa < $bb ? -1 : ( $aa > $bb ? 1 : 0 );
201
	}
202
203
	/**
204
	 * Get the subheading.
205
	 *
206
	 * @return string
207
	 */
208
	public function getSubheading() {
209
		return $this->subheading;
210
	}
211
212
	/**
213
	 * Set the subheading.
214
	 *
215
	 * @param string $subheading Subheading to set.
216
	 * @returns $this
217
	 */
218
	public function setSubheading( $subheading ) {
219
		$this->subheading = (string) $subheading;
220
		return $this;
221
	}
222
223
	/**
224
	 * Compare subheadings.
225
	 *
226
	 * @param ChangeEntry $a First entry.
227
	 * @param ChangeEntry $b Second entry.
228
	 * @param array       $config Passed from `compare()`, used for 'knownSubheadings'.
229
	 * @return int
230
	 */
231
	protected static function compareSubheading( ChangeEntry $a, ChangeEntry $b, array $config ) {
232
		$aa = $a->getSubheading();
233
		$bb = $b->getSubheading();
234
235
		// If they're equal, just return that.
236
		$cmp = strnatcasecmp( $aa, $bb );
237
		if ( 0 === $cmp ) {
238
			return 0;
239
		}
240
241
		// Empty string comes first.
242
		if ( '' === $aa ) {
243
			return -1;
244
		}
245
		if ( '' === $bb ) {
246
			return 1;
247
		}
248
249
		// Search for known values.
250
		foreach ( $config['knownSubheadings'] as $v ) {
251
			if ( strnatcasecmp( $aa, $v ) === 0 ) {
252
				return -1;
253
			}
254
			if ( strnatcasecmp( $bb, $v ) === 0 ) {
255
				return 1;
256
			}
257
		}
258
259
		// Fallback.
260
		return $cmp;
261
	}
262
263
	/**
264
	 * Get the author.
265
	 *
266
	 * @return string
267
	 */
268
	public function getAuthor() {
269
		return $this->author;
270
	}
271
272
	/**
273
	 * Set the author.
274
	 *
275
	 * @param string $author Author to set.
276
	 * @returns $this
277
	 */
278
	public function setAuthor( $author ) {
279
		$this->author = (string) $author;
280
		return $this;
281
	}
282
283
	/**
284
	 * Get the content.
285
	 *
286
	 * @return string
287
	 */
288
	public function getContent() {
289
		return $this->content;
290
	}
291
292
	/**
293
	 * Set the content.
294
	 *
295
	 * @param string $content Content to set.
296
	 * @returns $this
297
	 */
298
	public function setContent( $content ) {
299
		$this->content = (string) $content;
300
		return $this;
301
	}
302
303
	/**
304
	 * Return data for serializing to JSON.
305
	 *
306
	 * @return array
307
	 */
308
	public function jsonSerialize() {
309
		return array(
310
			'__class__'    => static::class,
311
			'significance' => $this->significance,
312
			'timestamp'    => $this->timestamp->format( DateTime::ISO8601 ),
313
			'subheading'   => $this->subheading,
314
			'author'       => $this->author,
315
			'content'      => $this->content,
316
		);
317
	}
318
319
	/**
320
	 * Unserialize from JSON.
321
	 *
322
	 * @param array $data JSON data as returned by self::jsonSerialize().
323
	 * @return static
324
	 * @throws InvalidArgumentException If the data is invalid.
325
	 */
326
	public static function jsonUnserialize( $data ) {
327
		$data = (array) $data;
328
		if ( ! isset( $data['__class__'] ) ) {
329
			throw new InvalidArgumentException( 'Invalid data' );
330
		}
331
		$class = $data['__class__'];
332
		unset( $data['__class__'] );
333 View Code Duplication
		if ( ! class_exists( $class ) || ! is_a( $class, static::class, true ) ) {
334
			throw new InvalidArgumentException( "Cannot instantiate $class via " . static::class . '::' . __FUNCTION__ );
335
		}
336
		return new $class( $data );
337
	}
338
339
}
340