Completed
Push — update/remove_generator ( 059eab...0fd250 )
by
unknown
46:40 queued 38:57
created

Milestone_Widget   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 748
Duplicated Lines 22.99 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 172
loc 748
rs 3.132
c 0
b 0
f 0
wmc 64
lcom 1
cbo 2

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 21 21 4
A enqueue_admin() 15 15 2
A enqueue_template() 0 16 3
B styles_template() 0 56 1
A sanitize_color_hex() 0 16 4
A localize_script() 0 12 4
A widget() 0 41 2
D get_widget_data() 136 195 17
C get_unit() 0 53 12
B get_interval_in_units() 0 18 7
A update() 0 3 1
A sanitize_range() 0 9 3
A sanitize_instance() 0 45 1
B form() 0 117 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Milestone_Widget often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Milestone_Widget, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Milestone Countdown Widget
4
 *
5
 * @package Jetpack.
6
 */
7
8
use Automattic\Jetpack\Assets;
9
10
/**
11
 * Class Milestone_Widget
12
 */
13
class Milestone_Widget extends WP_Widget {
14
	/**
15
	 * Holding array for widget configuration and localization.
16
	 *
17
	 * @var array
18
	 */
19
	private static $config_js = array();
20
21
	/**
22
	 * Available time units sorted in descending order.
23
	 *
24
	 * @var Array
25
	 */
26
	protected $available_units = array(
27
		'years',
28
		'months',
29
		'days',
30
		'hours',
31
		'minutes',
32
		'seconds',
33
	);
34
35
	/**
36
	 * Milestone_Widget constructor.
37
	 */
38 View Code Duplication
	public function __construct() {
39
		$widget = array(
40
			'classname'   => 'milestone-widget',
41
			'description' => __( 'Display a countdown to a certain date.', 'jetpack' ),
42
		);
43
44
		parent::__construct(
45
			'Milestone_Widget',
46
			/** This filter is documented in modules/widgets/facebook-likebox.php */
47
			apply_filters( 'jetpack_widget_name', __( 'Milestone', 'jetpack' ) ),
48
			$widget
49
		);
50
51
		add_action( 'wp_enqueue_scripts', array( __class__, 'enqueue_template' ) );
52
		add_action( 'admin_enqueue_scripts', array( __class__, 'enqueue_admin' ) );
53
		add_action( 'wp_footer', array( $this, 'localize_script' ) );
54
55
		if ( is_active_widget( false, false, $this->id_base, true ) || is_active_widget( false, false, 'monster', true ) || is_customize_preview() ) {
56
			add_action( 'wp_head', array( __class__, 'styles_template' ) );
57
		}
58
	}
59
60
	/**
61
	 * Enqueue admin assets.
62
	 *
63
	 * @param string $hook_suffix Hook suffix provided by WordPress.
64
	 */
65 View Code Duplication
	public static function enqueue_admin( $hook_suffix ) {
66
		if ( 'widgets.php' === $hook_suffix ) {
67
			wp_enqueue_style( 'milestone-admin', plugin_dir_url( __FILE__ ) . 'style-admin.css', array(), '20201113' );
68
			wp_enqueue_script(
69
				'milestone-admin-js',
70
				Assets::get_file_url_for_environment(
71
					'_inc/build/widgets/milestone/admin.min.js',
72
					'modules/widgets/milestone/admin.js'
73
				),
74
				array( 'jquery' ),
75
				'20201113',
76
				true
77
			);
78
		}
79
	}
80
81
	/**
82
	 * Enqueue the frontend JS.
83
	 */
84
	public static function enqueue_template() {
85
		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
86
			return;
87
		}
88
89
		wp_enqueue_script(
90
			'milestone',
91
			Assets::get_file_url_for_environment(
92
				'_inc/build/widgets/milestone/milestone.min.js',
93
				'modules/widgets/milestone/milestone.js'
94
			),
95
			array(),
96
			'20201113',
97
			true
98
		);
99
	}
100
101
	/**
102
	 * Output the frontend styling.
103
	 */
104
	public static function styles_template() {
105
		global $themecolors;
106
		$colors = wp_parse_args(
107
			$themecolors,
108
			array(
0 ignored issues
show
Documentation introduced by
array('bg' => 'ffffff', ...c', 'text' => '333333') is of type array<string,string,{"bg...ring","text":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
109
				'bg'     => 'ffffff',
110
				'border' => 'cccccc',
111
				'text'   => '333333',
112
			)
113
		);
114
		?>
115
<style>
116
.milestone-widget {
117
	margin-bottom: 1em;
118
}
119
.milestone-content {
120
	line-height: 2;
121
	margin-top: 5px;
122
	max-width: 100%;
123
	padding: 0;
124
	text-align: center;
125
}
126
.milestone-header {
127
	background-color: <?php echo self::sanitize_color_hex( $colors['text'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
128
	color: <?php echo self::sanitize_color_hex( $colors['bg'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
129
	line-height: 1.3;
130
	margin: 0;
131
	padding: .8em;
132
}
133
.milestone-header .event,
134
.milestone-header .date {
135
	display: block;
136
}
137
.milestone-header .event {
138
	font-size: 120%;
139
}
140
.milestone-countdown .difference {
141
	display: block;
142
	font-size: 500%;
143
	font-weight: bold;
144
	line-height: 1.2;
145
}
146
.milestone-countdown,
147
.milestone-message {
148
	background-color: <?php echo self::sanitize_color_hex( $colors['bg'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
149
	border: 1px solid <?php echo self::sanitize_color_hex( $colors['border'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
150
	border-top: 0;
151
	color: <?php echo self::sanitize_color_hex( $colors['text'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>;
152
	padding-bottom: 1em;
153
}
154
.milestone-message {
155
	padding-top: 1em
156
}
157
</style>
158
		<?php
159
	}
160
161
	/**
162
	 * Ensure that a string representing a color in hexadecimal
163
	 * notation is safe for use in css and database saves.
164
	 *
165
	 * @param string $hex Hexademical code to sanitize.
166
	 * @param string $prefix Prefix for the hex code.
167
	 *
168
	 * @return string Color in hexadecimal notation on success - the string "transparent" otherwise.
169
	 */
170
	public static function sanitize_color_hex( $hex, $prefix = '#' ) {
171
		$hex = trim( $hex );
172
173
		/* Strip recognized prefixes. */
174
		if ( 0 === strpos( $hex, '#' ) ) {
175
			$hex = substr( $hex, 1 );
176
		} elseif ( 0 === strpos( $hex, '%23' ) ) {
177
			$hex = substr( $hex, 3 );
178
		}
179
180
		if ( 0 !== preg_match( '/^[0-9a-fA-F]{6}$/', $hex ) ) {
181
			return $prefix . $hex;
182
		}
183
184
		return 'transparent';
185
	}
186
187
	/**
188
	 * Localize Front-end Script.
189
	 *
190
	 * Print the javascript configuration array only if the
191
	 * current template has an instance of the widget that
192
	 * is still counting down. In all other cases, this
193
	 * function will dequeue milestone.js.
194
	 *
195
	 * Hooks into the "wp_footer" action.
196
	 */
197
	public function localize_script() {
198
		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
199
			return;
200
		}
201
202
		if ( empty( self::$config_js['instances'] ) ) {
203
			wp_dequeue_script( 'milestone' );
204
			return;
205
		}
206
		self::$config_js['api_root'] = esc_url_raw( rest_url() );
207
		wp_localize_script( 'milestone', 'MilestoneConfig', self::$config_js );
208
	}
209
210
	/**
211
	 * Widget
212
	 *
213
	 * @param array $args Widget args.
214
	 * @param array $instance Widget instance.
215
	 */
216
	public function widget( $args, $instance ) {
217
		echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
218
219
		/** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
220
		$title = apply_filters( 'widget_title', $instance['title'] );
221
		if ( ! empty( $title ) ) {
222
			echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
223
		}
224
225
		$data   = $this->get_widget_data( $instance );
226
		$config = array(
227
			'id'      => $args['widget_id'],
228
			'message' => $data['message'],
229
			'refresh' => $data['refresh'],
230
		);
231
232
		/*
233
		 * Sidebars may be configured to not expose the `widget_id`. Example: `twentytwenty` footer areas.
234
		 *
235
		 * We need our own unique identifier.
236
		 */
237
		$config['content_id'] = $args['widget_id'] . '-content';
238
239
		self::$config_js['instances'][] = $config;
240
241
		echo sprintf( '<div id="%s" class="milestone-content">', esc_html( $config['content_id'] ) );
242
243
		echo '<div class="milestone-header">';
244
		echo '<strong class="event">' . esc_html( $instance['event'] ) . '</strong>';
245
		echo '<span class="date">' . esc_html( date_i18n( get_option( 'date_format' ), $data['milestone'] ) ) . '</span>';
246
		echo '</div>';
247
248
		echo wp_kses_post( $data['message'] );
249
250
		echo '</div><!--milestone-content-->';
251
252
		echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
253
254
		/** This action is documented in modules/widgets/gravatar-profile.php */
255
		do_action( 'jetpack_stats_extra', 'widget_view', 'milestone' );
256
	}
257
258
	/**
259
	 * Getter for the widget data.
260
	 *
261
	 * @param array $instance Widget instance.
262
	 *
263
	 * @return array
264
	 */
265
	public function get_widget_data( $instance ) {
266
		$data = array();
267
268
		$instance = $this->sanitize_instance( $instance );
269
270
		$milestone = mktime( $instance['hour'], $instance['min'], 0, $instance['month'], $instance['day'], $instance['year'] );
271
		$now       = (int) current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
272
		$type      = $instance['type'];
273
274
		if ( 'since' === $type ) {
275
			$diff = (int) floor( $now - $milestone );
276
		} else {
277
			$diff = (int) floor( $milestone - $now );
278
		}
279
280
		$data['diff'] = $diff;
281
		$data['unit'] = $this->get_unit( $diff, $instance['unit'] );
282
283
		// Setting the refresh counter to equal the number of seconds it takes to flip a unit.
284
		$refresh_intervals = array(
285
			0, // should be YEAR_IN_SECONDS, but doing setTimeout for a year doesn't seem to be logical.
286
			0, // same goes for MONTH_IN_SECONDS.
287
			DAY_IN_SECONDS,
288
			HOUR_IN_SECONDS,
289
			MINUTE_IN_SECONDS,
290
			1,
291
		);
292
293
		$data['refresh']   = $refresh_intervals[ array_search( $data['unit'], $this->available_units, true ) ];
294
		$data['milestone'] = $milestone;
295
296
		if ( ( 1 > $diff ) && ( 'until' === $type ) ) {
297
			$data['message'] = '<div class="milestone-message">' . $instance['message'] . '</div>';
298
			$data['refresh'] = 0; // No need to refresh, the milestone has been reached.
299
		} else {
300
			$interval_text = $this->get_interval_in_units( $diff, $data['unit'] );
301
			$interval      = (int) $interval_text;
302
303
			if ( 'since' === $type ) {
304
305 View Code Duplication
				switch ( $data['unit'] ) {
306
					case 'years':
307
						$data['message'] = sprintf(
308
							/* translators: %s is the number of year(s). */
309
							_n(
310
								'<span class="difference">%s</span> <span class="label">year ago.</span>',
311
								'<span class="difference">%s</span> <span class="label">years ago.</span>',
312
								$interval,
313
								'jetpack'
314
							),
315
							$interval_text
316
						);
317
						break;
318
					case 'months':
319
						$data['message'] = sprintf(
320
							/* translators: %s is the number of month(s). */
321
							_n(
322
								'<span class="difference">%s</span> <span class="label">month ago.</span>',
323
								'<span class="difference">%s</span> <span class="label">months ago.</span>',
324
								$interval,
325
								'jetpack'
326
							),
327
							$interval_text
328
						);
329
						break;
330
					case 'days':
331
						$data['message'] = sprintf(
332
							/* translators: %s is the number of days(s). */
333
							_n(
334
								'<span class="difference">%s</span> <span class="label">day ago.</span>',
335
								'<span class="difference">%s</span> <span class="label">days ago.</span>',
336
								$interval,
337
								'jetpack'
338
							),
339
							$interval_text
340
						);
341
						break;
342
					case 'hours':
343
						$data['message'] = sprintf(
344
							/* translators: %s is the number of hours(s). */
345
							_n(
346
								'<span class="difference">%s</span> <span class="label">hour ago.</span>',
347
								'<span class="difference">%s</span> <span class="label">hours ago.</span>',
348
								$interval,
349
								'jetpack'
350
							),
351
							$interval_text
352
						);
353
						break;
354
					case 'minutes':
355
						$data['message'] = sprintf(
356
							/* translators: %s is the number of minutes(s). */
357
							_n(
358
								'<span class="difference">%s</span> <span class="label">minute ago.</span>',
359
								'<span class="difference">%s</span> <span class="label">minutes ago.</span>',
360
								$interval,
361
								'jetpack'
362
							),
363
							$interval_text
364
						);
365
						break;
366
					case 'seconds':
367
						$data['message'] = sprintf(
368
							/* translators: %s is the number of second(s). */
369
							_n(
370
								'<span class="difference">%s</span> <span class="label">second ago.</span>',
371
								'<span class="difference">%s</span> <span class="label">seconds ago.</span>',
372
								$interval,
373
								'jetpack'
374
							),
375
							$interval_text
376
						);
377
						break;
378
				}
379
			} else {
380 View Code Duplication
				switch ( $this->get_unit( $diff, $instance['unit'] ) ) {
381
					case 'years':
382
						$data['message'] = sprintf(
383
							/* translators: %s is the number of year(s). */
384
							_n(
385
								'<span class="difference">%s</span> <span class="label">year to go.</span>',
386
								'<span class="difference">%s</span> <span class="label">years to go.</span>',
387
								$interval,
388
								'jetpack'
389
							),
390
							$interval_text
391
						);
392
						break;
393
					case 'months':
394
						$data['message'] = sprintf(
395
							/* translators: %s is the number of month(s). */
396
							_n(
397
								'<span class="difference">%s</span> <span class="label">month to go.</span>',
398
								'<span class="difference">%s</span> <span class="label">months to go.</span>',
399
								$interval,
400
								'jetpack'
401
							),
402
							$interval_text
403
						);
404
						break;
405
					case 'days':
406
						$data['message'] = sprintf(
407
							/* translators: %s is the number of days(s). */
408
							_n(
409
								'<span class="difference">%s</span> <span class="label">day to go.</span>',
410
								'<span class="difference">%s</span> <span class="label">days to go.</span>',
411
								$interval,
412
								'jetpack'
413
							),
414
							$interval_text
415
						);
416
						break;
417
					case 'hours':
418
						$data['message'] = sprintf(
419
							/* translators: %s is the number of hour(s). */
420
							_n(
421
								'<span class="difference">%s</span> <span class="label">hour to go.</span>',
422
								'<span class="difference">%s</span> <span class="label">hours to go.</span>',
423
								$interval,
424
								'jetpack'
425
							),
426
							$interval_text
427
						);
428
						break;
429
					case 'minutes':
430
						$data['message'] = sprintf(
431
							/* translators: %s is the number of minute(s). */
432
							_n(
433
								'<span class="difference">%s</span> <span class="label">minute to go.</span>',
434
								'<span class="difference">%s</span> <span class="label">minutes to go.</span>',
435
								$interval,
436
								'jetpack'
437
							),
438
							$interval_text
439
						);
440
						break;
441
					case 'seconds':
442
						$data['message'] = sprintf(
443
							/* translators: %s is the number of second(s). */
444
							_n(
445
								'<span class="difference">%s</span> <span class="label">second to go.</span>',
446
								'<span class="difference">%s</span> <span class="label">seconds to go.</span>',
447
								$interval,
448
								'jetpack'
449
							),
450
							$interval_text
451
						);
452
						break;
453
				}
454
			}
455
			$data['message'] = '<div class="milestone-countdown">' . $data['message'] . '</div>';
456
		}
457
458
		return $data;
459
	}
460
461
	/**
462
	 * Return the largest possible time unit that the difference will be displayed in.
463
	 *
464
	 * @param Integer $seconds the interval in seconds.
465
	 * @param String  $maximum_unit the maximum unit that will be used. Optional.
466
	 * @return String $calculated_unit
467
	 */
468
	protected function get_unit( $seconds, $maximum_unit = 'automatic' ) {
469
		$unit = '';
0 ignored issues
show
Unused Code introduced by
$unit is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
470
471
		if ( $seconds >= YEAR_IN_SECONDS * 2 ) {
472
			// more than 2 years - show in years, one decimal point.
473
			$unit = 'years';
474
475
		} elseif ( $seconds >= YEAR_IN_SECONDS ) {
476
			if ( 'years' === $maximum_unit ) {
477
				$unit = 'years';
478
			} else {
479
				// automatic mode - showing months even if it's between one and two years.
480
				$unit = 'months';
481
			}
482
		} elseif ( $seconds >= MONTH_IN_SECONDS * 3 ) {
483
			// fewer than 2 years - show in months.
484
			$unit = 'months';
485
486
		} elseif ( $seconds >= MONTH_IN_SECONDS ) {
487
			if ( 'months' === $maximum_unit ) {
488
				$unit = 'months';
489
			} else {
490
				// automatic mode - showing days even if it's between one and three months.
491
				$unit = 'days';
492
			}
493
		} elseif ( $seconds >= DAY_IN_SECONDS - 1 ) {
494
			// fewer than a month - show in days.
495
			$unit = 'days';
496
497
		} elseif ( $seconds >= HOUR_IN_SECONDS - 1 ) {
498
			// less than 1 day - show in hours.
499
			$unit = 'hours';
500
501
		} elseif ( $seconds >= MINUTE_IN_SECONDS - 1 ) {
502
			// less than 1 hour - show in minutes.
503
			$unit = 'minutes';
504
505
		} else {
506
			// less than 1 minute - show in seconds.
507
			$unit = 'seconds';
508
		}
509
510
		$maximum_unit_index = array_search( $maximum_unit, $this->available_units, true );
511
		$unit_index         = array_search( $unit, $this->available_units, true );
512
513
		if (
514
			false === $maximum_unit_index // the maximum unit parameter is automatic.
515
			|| $unit_index > $maximum_unit_index // there is not enough seconds for even one maximum time unit.
516
		) {
517
			return $unit;
518
		}
519
		return $maximum_unit;
520
	}
521
522
	/**
523
	 * Returns a time difference value in specified units.
524
	 *
525
	 * @param int    $seconds Number of seconds.
526
	 * @param string $units Unit.
527
	 * @return int $time_in_units.
528
	 */
529
	protected function get_interval_in_units( $seconds, $units ) {
530
		switch ( $units ) {
531
			case 'years':
532
				$years    = $seconds / YEAR_IN_SECONDS;
533
				$decimals = abs( round( $years, 1 ) - round( $years ) ) > 0 ? 1 : 0;
534
				return number_format_i18n( $years, $decimals );
535
			case 'months':
536
				return (int) ( $seconds / 60 / 60 / 24 / 30 );
537
			case 'days':
538
				return (int) ( $seconds / 60 / 60 / 24 + 1 );
539
			case 'hours':
540
				return (int) ( $seconds / 60 / 60 );
541
			case 'minutes':
542
				return (int) ( $seconds / 60 + 1 );
543
			default:
544
				return $seconds;
545
		}
546
	}
547
548
	/**
549
	 * Update widget.
550
	 *
551
	 * @param array $new_instance New instance of the widget being saved.
552
	 * @param array $old_instance Previous instance being saved over.
553
	 *
554
	 * @return array
555
	 */
556
	public function update( $new_instance, $old_instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
557
		return $this->sanitize_instance( $new_instance );
558
	}
559
560
	/**
561
	 * Make sure that a number is within a certain range.
562
	 * If the number is too small it will become the possible lowest value.
563
	 * If the number is too large it will become the possible highest value.
564
	 *
565
	 * @param int $n The number to check.
566
	 * @param int $floor The lowest possible value.
567
	 * @param int $ceil The highest possible value.
568
	 */
569
	public function sanitize_range( $n, $floor, $ceil ) {
570
		$n = (int) $n;
571
		if ( $n < $floor ) {
572
			$n = $floor;
573
		} elseif ( $n > $ceil ) {
574
			$n = $ceil;
575
		}
576
		return $n;
577
	}
578
579
	/**
580
	 * Sanitize an instance of this widget.
581
	 *
582
	 * Date ranges match the documentation for mktime in the php manual.
583
	 *
584
	 * @see https://php.net/manual/en/function.mktime.php#refsect1-function.mktime-parameters
585
	 *
586
	 * @uses Milestone_Widget::sanitize_range().
587
	 *
588
	 * @param array $dirty Unsantized data for the widget.
589
	 *
590
	 * @return array Santized data.
591
	 */
592
	public function sanitize_instance( $dirty ) {
593
		$now = (int) current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
594
595
		$dirty = wp_parse_args(
596
			$dirty,
597
			array(
0 ignored issues
show
Documentation introduced by
array('title' => '', 'ev...hour' => 0, 'min' => 0) is of type array<string,?,{"title":...eger","min":"integer"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
598
				'title'   => '',
599
				'event'   => __( 'The Big Day', 'jetpack' ),
600
				'unit'    => 'automatic',
601
				'type'    => 'until',
602
				'message' => __( 'The big day is here.', 'jetpack' ),
603
				'day'     => gmdate( 'd', $now ),
604
				'month'   => gmdate( 'm', $now ),
605
				'year'    => gmdate( 'Y', $now ),
606
				'hour'    => 0,
607
				'min'     => 0,
608
			)
609
		);
610
611
		$allowed_tags = array(
612
			'a'      => array(
613
				'title'  => array(),
614
				'href'   => array(),
615
				'target' => array(),
616
			),
617
			'em'     => array( 'title' => array() ),
618
			'strong' => array( 'title' => array() ),
619
		);
620
621
		$clean = array(
622
			'title'   => trim( wp_strip_all_tags( stripslashes( $dirty['title'] ) ) ),
623
			'event'   => trim( wp_strip_all_tags( stripslashes( $dirty['event'] ) ) ),
624
			'unit'    => $dirty['unit'],
625
			'type'    => $dirty['type'],
626
			'message' => wp_kses( $dirty['message'], $allowed_tags ),
627
			'year'    => $this->sanitize_range( $dirty['year'], 1901, 2037 ),
628
			'month'   => $this->sanitize_range( $dirty['month'], 1, 12 ),
629
			'hour'    => $this->sanitize_range( $dirty['hour'], 0, 23 ),
630
			'min'     => zeroise( $this->sanitize_range( $dirty['min'], 0, 59 ), 2 ),
631
		);
632
633
		$clean['day'] = $this->sanitize_range( $dirty['day'], 1, gmdate( 't', mktime( 0, 0, 0, $clean['month'], 1, $clean['year'] ) ) );
634
635
		return $clean;
636
	}
637
638
	/**
639
	 * Form
640
	 *
641
	 * @param array $instance Widget instance.
642
	 */
643
	public function form( $instance ) {
644
		$instance = $this->sanitize_instance( $instance );
645
646
		$units = array(
647
			'automatic' => _x( 'Automatic', 'Milestone widget: mode in which the date unit is determined automatically', 'jetpack' ),
648
			'years'     => _x( 'Years', 'Milestone widget: mode in which the date unit is set to years', 'jetpack' ),
649
			'months'    => _x( 'Months', 'Milestone widget: mode in which the date unit is set to months', 'jetpack' ),
650
			'days'      => _x( 'Days', 'Milestone widget: mode in which the date unit is set to days', 'jetpack' ),
651
			'hours'     => _x( 'Hours', 'Milestone widget: mode in which the date unit is set to hours', 'jetpack' ),
652
		);
653
		?>
654
		<div class="milestone-widget">
655
			<p>
656
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title', 'jetpack' ); ?></label>
657
				<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
658
			</p>
659
660
			<p>
661
				<label for="<?php echo esc_attr( $this->get_field_id( 'event' ) ); ?>"><?php esc_html_e( 'Description', 'jetpack' ); ?></label>
662
				<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'event' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'event' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['event'] ); ?>" />
663
			</p>
664
665
			<fieldset class="jp-ms-data-time">
666
				<legend><?php esc_html_e( 'Date', 'jetpack' ); ?></legend>
667
668
				<label for="<?php echo esc_attr( $this->get_field_id( 'month' ) ); ?>" class="assistive-text"><?php esc_html_e( 'Month', 'jetpack' ); ?></label>
669
				<select id="<?php echo esc_attr( $this->get_field_id( 'month' ) ); ?>" class="month" name="<?php echo esc_attr( $this->get_field_name( 'month' ) ); ?>">
670
					<?php
671
								global $wp_locale;
672
					for ( $i = 1; $i < 13; $i++ ) {
673
						$monthnum = zeroise( $i, 2 );
674
						printf(
675
							'<option value="%s" %s>%s-%s</option>',
676
							esc_attr( $monthnum ),
677
							selected( $i, $instance['month'], false ),
678
							esc_attr( $monthnum ),
679
							esc_attr( $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) ) )
680
						);
681
					}
682
					?>
683
				</select>
684
685
				<label for="<?php echo esc_attr( $this->get_field_id( 'day' ) ); ?>" class="assistive-text"><?php esc_html_e( 'Day', 'jetpack' ); ?></label>
686
				<input id="<?php echo esc_attr( $this->get_field_id( 'day' ) ); ?>" class="day" name="<?php echo esc_attr( $this->get_field_name( 'day' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['day'] ); ?>">,
687
688
				<label for="<?php echo esc_attr( $this->get_field_id( 'year' ) ); ?>" class="assistive-text"><?php esc_html_e( 'Year', 'jetpack' ); ?></label>
689
				<input id="<?php echo esc_attr( $this->get_field_id( 'year' ) ); ?>" class="year" name="<?php echo esc_attr( $this->get_field_name( 'year' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['year'] ); ?>">
690
			</fieldset>
691
692
			<fieldset class="jp-ms-data-time">
693
				<legend><?php esc_html_e( 'Time', 'jetpack' ); ?></legend>
694
695
				<label for="<?php echo esc_attr( $this->get_field_id( 'hour' ) ); ?>" class="assistive-text"><?php esc_html_e( 'Hour', 'jetpack' ); ?></label>
696
				<input id="<?php echo esc_attr( $this->get_field_id( 'hour' ) ); ?>" class="hour" name="<?php echo esc_attr( $this->get_field_name( 'hour' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['hour'] ); ?>">
697
698
				<label for="<?php echo esc_attr( $this->get_field_id( 'min' ) ); ?>" class="assistive-text"><?php esc_html_e( 'Minutes', 'jetpack' ); ?></label>
699
700
				<span class="time-separator">:</span>
701
702
				<input id="<?php echo esc_attr( $this->get_field_id( 'min' ) ); ?>" class="minutes" name="<?php echo esc_attr( $this->get_field_name( 'min' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['min'] ); ?>">
703
			</fieldset>
704
705
			<fieldset class="jp-ms-data-unit">
706
				<legend><?php esc_html_e( 'Time Unit', 'jetpack' ); ?></legend>
707
708
				<label for="<?php echo esc_attr( $this->get_field_id( 'unit' ) ); ?>" class="assistive-text">
709
					<?php esc_html_e( 'Time Unit', 'jetpack' ); ?>
710
				</label>
711
712
				<select id="<?php echo esc_attr( $this->get_field_id( 'unit' ) ); ?>" class="unit" name="<?php echo esc_attr( $this->get_field_name( 'unit' ) ); ?>">
713
					<?php
714
					foreach ( $units as $key => $unit ) {
715
						printf(
716
							'<option value="%s" %s>%s</option>',
717
							esc_attr( $key ),
718
							selected( $key, $instance['unit'], false ),
719
							esc_html( $unit )
720
						);
721
					}
722
					?>
723
				</select>
724
			</fieldset>
725
726
			<ul class="milestone-type">
727
				<li>
728
					<label>
729
						<input
730
								<?php checked( $instance['type'], 'until' ); ?>
731
								name="<?php echo esc_attr( $this->get_field_name( 'type' ) ); ?>"
732
								type="radio"
733
								value="until"
734
						/>
735
						<?php esc_html_e( 'Until your milestone', 'jetpack' ); ?>
736
					</label>
737
				</li>
738
739
				<li>
740
					<label>
741
						<input
742
								<?php checked( $instance['type'], 'since' ); ?>
743
								name="<?php echo esc_attr( $this->get_field_name( 'type' ) ); ?>"
744
								type="radio"
745
								value="since"
746
						/>
747
						<?php esc_html_e( 'Since your milestone', 'jetpack' ); ?>
748
					</label>
749
				</li>
750
			</ul>
751
752
			<p class="milestone-message-wrapper">
753
				<label for="<?php echo esc_attr( $this->get_field_id( 'message' ) ); ?>"><?php esc_html_e( 'Milestone Reached Message', 'jetpack' ); ?></label>
754
				<textarea id="<?php echo esc_attr( $this->get_field_id( 'message' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'message' ) ); ?>" class="widefat" rows="3"><?php echo esc_textarea( $instance['message'] ); ?></textarea>
755
			</p>
756
		</div>
757
758
		<?php
759
	}
760
}
761