Completed
Push — add/gutenblocks ( e99351...631f0a )
by
unknown
104:38 queued 96:29
created

Quiz_Shortcode::block_editor_assets()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Quiz shortcode.
4
 *
5
 * Usage:
6
 *
7
 * [quiz]
8
 * [question]What's the right answer?[/question]
9
 * [wrong]This one?[explanation]Nope[/explanation][/wrong]
10
 * [answer]Yes, this is the one![explanation]Yay![/explanation][/answer]
11
 * [wrong]Maybe this one[explanation]Keep trying[/explanation][/wrong]
12
 * [wrong]How about this one?[explanation]Try again[/explanation][/wrong]
13
 * [/quiz]
14
 */
15
class Quiz_Shortcode {
16
17
	/**
18
	 * Parameters admitted by [quiz] shortcode.
19
	 *
20
	 * @since 4.5.0
21
	 *
22
	 * @var array
23
	 */
24
	private static $quiz_params = array();
25
26
	/**
27
	 * Whether the scripts were enqueued.
28
	 *
29
	 * @since 4.5.0
30
	 *
31
	 * @var bool
32
	 */
33
	private static $scripts_enqueued = false;
34
35
	/**
36
	 * In a8c training, store user currently logged in.
37
	 *
38
	 * @since 4.5.0
39
	 *
40
	 * @var null
41
	 */
42
	private static $username = null;
43
44
	/**
45
	 * Whether the noscript tag was already printed.
46
	 *
47
	 * @since 4.5.0
48
	 *
49
	 * @var bool
50
	 */
51
	private static $noscript_info_printed = false;
52
53
	/**
54
	 * Whether JavaScript is available.
55
	 *
56
	 * @since 4.5.0
57
	 *
58
	 * @var null
59
	 */
60
	private static $javascript_unavailable = null;
61
62
	/**
63
	 * Register all shortcodes.
64
	 *
65
	 * @since 4.5.0
66
	 */
67
	public static function init() {
68
		add_shortcode( 'quiz', array( __CLASS__, 'shortcode' ) );
69
		add_shortcode( 'question', array( __CLASS__, 'question_shortcode' ) );
70
		add_shortcode( 'answer', array( __CLASS__, 'answer_shortcode' ) );
71
		add_shortcode( 'wrong', array( __CLASS__, 'wrong_shortcode' ) );
72
		add_shortcode( 'explanation', array( __CLASS__, 'explanation_shortcode' ) );
73
74
		add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'block_editor_assets' ) );
75
		add_action( 'enqueue_block_assets', array( __CLASS__, 'enqueue_scripts' ) );
76
	}
77
78
	/**
79
	 * Enqueue assets needed by the quiz,
80
	 *
81
	 * @since 4.5.0
82
	 */
83
	public static function enqueue_scripts() {
84
		if ( ! is_admin() ) {
85
			wp_enqueue_style( 'quiz', plugins_url( 'css/quiz.css', __FILE__ ) );
86
			wp_enqueue_script( 'quiz', plugins_url( 'js/quiz.js', __FILE__ ), array( 'jquery' ), null, true );
87
		}
88
	}
89
90
	/**
91
	 * Enqueue editor assets like JS and CSS.
92
	 *
93
	 * @since 5.4
94
	 */
95
	function block_editor_assets() {
96
		wp_enqueue_style(
97
			'gutenpack-quiz-editor',
98
			plugins_url( 'modules/shortcodes/css/quiz.css', JETPACK__PLUGIN_FILE ),
99
			array( 'wp-edit-blocks' ),
100
			JETPACK__VERSION
101
		);
102
	}
103
104
	/**
105
	 * Check if this is a feed and thus JS is unavailable.
106
	 *
107
	 * @since 4.5.0
108
	 *
109
	 * @return bool|null
110
	 */
111
	private static function is_javascript_unavailable() {
112
		if ( ! is_null( self::$javascript_unavailable ) ) {
113
			return self::$javascript_unavailable;
114
		}
115
116
		if ( is_feed() ) {
117
			return self::$javascript_unavailable = true;
0 ignored issues
show
Documentation Bug introduced by
It seems like true of type boolean is incompatible with the declared type null of property $javascript_unavailable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
118
		}
119
120
		return self::$javascript_unavailable = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type null of property $javascript_unavailable.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
121
	}
122
123
	/**
124
	 * Display message when JS is not available.
125
	 *
126
	 * @since 4.5.0
127
	 *
128
	 * @return string
129
	 */
130
	private static function noscript_info() {
131
		if ( self::$noscript_info_printed ) {
132
			return '';
133
		}
134
		self::$noscript_info_printed = true;
135
		return '<noscript><div><i>' . esc_html__( 'Please view this post in your web browser to complete the quiz.', 'jetpack' ) . '</i></div></noscript>';
136
	}
137
138
	/**
139
	 * Check if we're in WordPress.com.
140
	 *
141
	 * @since 4.5.0
142
	 *
143
	 * @return bool
144
	 */
145
	public static function is_wpcom() {
146
		return defined( 'IS_WPCOM' ) && IS_WPCOM;
147
	}
148
149
	/**
150
	 * Parse shortcode arguments and render its output.
151
	 *
152
	 * @since 4.5.0
153
	 *
154
	 * @param array  $atts    Shortcode parameters.
155
	 * @param string $content Content enclosed by shortcode tags.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $content not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
156
	 *
157
	 * @return string
158
	 */
159
	public static function shortcode( $atts, $content = null ) {
160
161
		// There's nothing to do if there's nothing enclosed.
162
		if ( null == $content ) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $content of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
163
			return '';
164
		}
165
166
		$id = '';
167
168
		if ( self::is_javascript_unavailable() ) {
169
			// in an e-mail print the question and the info sentence once per question, too
170
			self::$noscript_info_printed = false;
171
		} else {
172
173
			if ( ! self::$scripts_enqueued ) {
174
				// lazy enqueue cannot use the wp_enqueue_scripts action anymore
175
				self::enqueue_scripts();
176
				self::$scripts_enqueued = true;
177
			}
178
179
			$default_atts = self::is_wpcom()
180
				? array(
181
					'trackid' => '',
182
					'a8ctraining' => '',
183
				)
184
				: array(
185
					'trackid' => '',
186
				);
187
188
189
			self::$quiz_params = shortcode_atts( $default_atts, $atts );
190
191
			if ( ! empty( self::$quiz_params[ 'trackid' ] ) ) {
192
				$id .= ' data-trackid="' . esc_attr( self::$quiz_params[ 'trackid' ] ) . '"';
193
			}
194
			if ( self::is_wpcom() && ! empty( self::$quiz_params[ 'a8ctraining' ] ) ) {
195
				if ( is_null( self::$username ) ) {
196
					self::$username = wp_get_current_user()->user_login;
197
				}
198
				$id .= ' data-a8ctraining="'. esc_attr( self::$quiz_params[ 'a8ctraining' ] ) . '" data-username="' . esc_attr( self::$username ) . '"';
199
			}
200
		}
201
202
		$quiz = self::do_shortcode( $content );
203
		return '<div class="jetpack-quiz quiz"' . $id . '>' . $quiz . '</div>';
204
	}
205
206
	/**
207
	 * Strip line breaks, restrict allowed HTML to a few whitelisted tags and execute nested shortcodes.
208
	 *
209
	 * @since 4.5.0
210
	 *
211
	 * @param string $content
212
	 *
213
	 * @return mixed|string
214
	 */
215
	private static function do_shortcode( $content ) {
216
		// strip autoinserted line breaks
217
		$content = preg_replace( '#(<(?:br /|/?p)>\n?)*(\[/?[a-z]+\])(<(?:br /|/?p)>\n?)*#', '$2', $content );
218
219
		// Add internal parameter so it's only rendered when it has it
220
		$content = preg_replace( '/\[(question|answer|wrong|explanation)\]/i', '[$1 quiz_item="true"]', $content );
221
		$content = do_shortcode( $content );
222
		$content = wp_kses( $content, array(
223
			'tt' => array(),
224
			'pre' => array(),
225
			'strong' => array(),
226
			'i' => array(),
227
			'br' => array(),
228
			'img' => array( 'src' => true),
229
			'div' => array( 'class' => true, 'data-correct' => 1, 'data-track-id' => 1, 'data-a8ctraining' => 1, 'data-username' => 1 ),
230
		) );
231
		return $content;
232
	}
233
234
	/**
235
	 * Render question.
236
	 *
237
	 * @since 4.5.0
238
	 *
239
	 * @param array $atts
240
	 * @param null  $content
241
	 *
242
	 * @return string
243
	 */
244
	public static function question_shortcode( $atts, $content = null ) {
245
		return isset( $atts['quiz_item'] )
246
			? '<div class="jetpack-quiz-question question">' . self::do_shortcode( $content ) . '</div>'
247
			: '';
248
	}
249
250
	/**
251
	 * Render correct answer.
252
	 *
253
	 * @since 4.5.0
254
	 *
255
	 * @param array $atts
256
	 * @param null  $content
257
	 *
258
	 * @return string
259
	 */
260 View Code Duplication
	public static function answer_shortcode( $atts, $content = null ) {
261
		if ( self::is_javascript_unavailable() ) {
262
			return self::noscript_info();
263
		}
264
265
		return isset( $atts['quiz_item'] )
266
			? '<div class="jetpack-quiz-answer answer" data-correct="1">' . self::do_shortcode( $content ) . '</div>'
267
			: '';
268
	}
269
270
	/**
271
	 * Render wrong response.
272
	 *
273
	 * @since 4.5.0
274
	 *
275
	 * @param array $atts
276
	 * @param null  $content
277
	 *
278
	 * @return string
279
	 */
280 View Code Duplication
	public static function wrong_shortcode( $atts, $content = null ) {
281
		if ( self::is_javascript_unavailable() ) {
282
			return self::noscript_info();
283
		}
284
285
		return isset( $atts['quiz_item'] )
286
			? '<div class="jetpack-quiz-answer answer">' . self::do_shortcode( $content ) . '</div>'
287
			: '';
288
	}
289
290
	/**
291
	 * Render explanation for wrong or right answer.
292
	 *
293
	 * @since 4.5.0
294
	 *
295
	 * @param array $atts
296
	 * @param null  $content
297
	 *
298
	 * @return string
299
	 */
300 View Code Duplication
	public static function explanation_shortcode( $atts, $content = null ) {
301
		if ( self::is_javascript_unavailable() ) {
302
			return self::noscript_info();
303
		}
304
305
		return isset( $atts['quiz_item'] )
306
			? '<div class="jetpack-quiz-explanation explanation">' . self::do_shortcode( $content ) . '</div>'
307
			: '';
308
	}
309
}
310
311
Quiz_Shortcode::init();
312