Tests_WP_Customize_Manager   F
last analyzed

Complexity

Total Complexity 126

Size/Duplication

Total Lines 2636
Duplicated Lines 3.91 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
dl 103
loc 2636
rs 1.913
c 0
b 0
f 0
wmc 126
lcom 1
cbo 14

86 Methods

Rating   Name   Duplication   Size   Complexity  
A wpSetUpBeforeClass() 0 4 1
A setUp() 0 13 1
A tearDown() 0 6 1
A get_inactive_core_theme() 0 9 4
A instantiate() 0 4 1
B test_constructor() 0 27 1
B test_setup_theme_in_customize_admin() 0 39 3
B test_setup_theme_in_frontend() 0 28 2
A test_changeset_uuid() 0 5 1
A test_wp_loaded() 0 11 1
A test_find_changeset_post_id() 0 13 1
A test_changeset_post_id() 0 15 1
A test_changeset_data() 0 16 1
C test_import_theme_starter_content() 0 242 7
B test_customize_preview_init() 0 37 2
A test_filter_iframe_security_headers() 0 8 1
B test_add_state_query_params() 0 46 1
B test_save_changeset_post_without_theme_activation() 0 211 4
A filter_customize_changeset_save_data() 0 13 1
A return_illegal_error() 0 3 1
B test_save_changeset_post_with_theme_activation() 0 29 1
A create_test_manager() 0 18 1
B test_save_changeset_post_dumping_auto_draft_date() 0 29 1
B test_save_changeset_post_with_unchanged_values() 0 77 1
A test_save_changeset_post_with_varying_unfiltered_html_cap() 0 53 1
A register_scratchpad_setting() 0 7 1
A filter_sanitize_scratchpad() 0 3 1
A filter_customize_setting_to_log_current_user() 0 4 1
A test_is_cross_domain() 0 11 1
A test_get_allowed_urls() 0 11 1
A filter_customize_allowed_urls() 0 4 1
A test_doing_ajax() 0 12 2
A test_not_doing_ajax() 0 8 3
A test_unsanitized_post_values_from_input() 0 22 1
B test_unsanitized_post_values_with_changeset_and_stashed_theme_mods() 0 95 1
A test_post_value() 0 18 1
B test_invalid_post_value() 0 28 1
A filter_customize_sanitize_foo() 0 11 4
A filter_customize_validate_foo() 0 6 2
A test_post_value_validation_sanitization_order() 0 15 1
A filter_customize_sanitize_numeric() 0 3 1
A filter_customize_validate_numeric() 0 6 3
B test_validate_setting_values() 0 39 1
A test_late_validate_setting_values() 0 19 1
B test_validate_setting_values_args() 0 27 1
A late_validate_length() 0 7 2
A test_validate_setting_values_validation_sanitization_order() 0 12 1
A test_prepare_setting_validity_for_js() 0 18 2
A test_set_post_value() 0 23 1
A sanitize_foo_for_test_set_post_value() 0 3 1
A capture_customize_post_value_set_actions() 0 5 1
A test_add_dynamic_settings() 0 14 1
A test_has_published_pages() 0 12 2
A test_has_published_pages_when_nav_menus_created_posts() 0 16 2
A test_register_dynamic_settings() 0 18 1
A action_customize_register_for_dynamic_settings() 0 4 1
A filter_customize_dynamic_setting_args_for_test_dynamic_settings() 0 7 2
A filter_customize_dynamic_setting_class_for_test_dynamic_settings() 0 6 1
A test_get_document_title_template() 0 4 1
A test_preview_url() 0 8 1
B test_return_url() 0 31 1
A test_autofocus() 0 22 1
A test_nonces() 0 11 1
A filter_customize_refresh_nonces() 0 5 1
B test_customize_pane_settings() 0 28 1
A test_remove_frameless_preview_messenger_channel() 0 14 1
B test_customize_preview_settings() 0 28 1
A test_customize_loaded_components_filter() 0 23 1
B test_prepare_controls_stable_sorting() 0 29 2
A test_add_setting_return_instance() 0 17 1
A test_add_setting_honoring_dynamic() 0 16 1
A return_dynamic_customize_setting_class() 0 7 2
A return_dynamic_customize_setting_args() 0 6 2
B test_add_control_return_instance() 0 31 1
B test_get_previewable_devices() 0 31 1
A filtered_device_list() 0 8 1
A filter_customize_previewable_devices() 0 3 1
A test_prepare_controls_wp_list_sort_controls() 0 21 2
A test_sanitize_external_header_video_trim() 0 19 2
B test_save_changeset_post_with_varying_users() 8 113 4
A return_array_containing_widgets() 8 8 1
A return_array_containing_nav_menus() 8 8 1
A test_add_section_return_instance() 23 23 1
A test_add_panel_return_instance() 22 22 1
A test_prepare_controls_wp_list_sort_sections() 17 17 2
A test_prepare_controls_wp_list_sort_panels() 17 17 2

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 Tests_WP_Customize_Manager 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 Tests_WP_Customize_Manager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * WP_Customize_Manager tests.
4
 *
5
 * @package WordPress
6
 */
7
8
/**
9
 * Tests for the WP_Customize_Manager class.
10
 *
11
 * @group customize
12
 */
13
class Tests_WP_Customize_Manager extends WP_UnitTestCase {
14
15
	/**
16
	 * Customize manager instance re-instantiated with each test.
17
	 *
18
	 * @var WP_Customize_Manager
19
	 */
20
	public $manager;
21
22
	/**
23
	 * Symbol.
24
	 *
25
	 * @var stdClass
26
	 */
27
	public $undefined;
28
29
	/**
30
	 * Admin user ID.
31
	 *
32
	 * @var int
33
	 */
34
	protected static $admin_user_id;
35
36
	/**
37
	 * Subscriber user ID.
38
	 *
39
	 * @var int
40
	 */
41
	protected static $subscriber_user_id;
42
43
	/**
44
	 * Set up before class.
45
	 *
46
	 * @param WP_UnitTest_Factory $factory Factory.
47
	 */
48
	public static function wpSetUpBeforeClass( $factory ) {
49
		self::$subscriber_user_id = $factory->user->create( array( 'role' => 'subscriber' ) );
50
		self::$admin_user_id = $factory->user->create( array( 'role' => 'administrator' ) );
51
	}
52
53
	/**
54
	 * Set up test.
55
	 */
56
	function setUp() {
57
		parent::setUp();
58
		require_once( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
59
		$this->manager = $this->instantiate();
60
		$this->undefined = new stdClass();
61
62
		$orig_file = DIR_TESTDATA . '/images/canola.jpg';
63
		$this->test_file = '/tmp/canola.jpg';
64
		copy( $orig_file, $this->test_file );
65
		$orig_file2 = DIR_TESTDATA . '/images/waffles.jpg';
66
		$this->test_file2 = '/tmp/waffles.jpg';
67
		copy( $orig_file2, $this->test_file2 );
68
	}
69
70
	/**
71
	 * Tear down test.
72
	 */
73
	function tearDown() {
74
		$this->manager = null;
75
		unset( $GLOBALS['wp_customize'] );
76
		$_REQUEST = array();
77
		parent::tearDown();
78
	}
79
80
	/**
81
	 * Get a core theme that is not the same as the current theme.
82
	 *
83
	 * @throws Exception If an inactive core Twenty* theme cannot be found.
84
	 * @return string Theme slug (stylesheet).
85
	 */
86
	function get_inactive_core_theme() {
87
		$stylesheet = get_stylesheet();
88
		foreach ( wp_get_themes() as $theme ) {
89
			if ( $theme->stylesheet !== $stylesheet && 0 === strpos( $theme->stylesheet, 'twenty' ) ) {
90
				return $theme->stylesheet;
91
			}
92
		}
93
		throw new Exception( 'Unable to find inactive twenty* theme.' );
94
	}
95
96
	/**
97
	 * Instantiate class, set global $wp_customize, and return instance.
98
	 *
99
	 * @return WP_Customize_Manager
100
	 */
101
	function instantiate() {
102
		$GLOBALS['wp_customize'] = new WP_Customize_Manager();
103
		return $GLOBALS['wp_customize'];
104
	}
105
106
	/**
107
	 * Test WP_Customize_Manager::__construct().
108
	 *
109
	 * @covers WP_Customize_Manager::__construct()
110
	 */
111
	function test_constructor() {
112
		$uuid = wp_generate_uuid4();
113
		$theme = 'twentyfifteen';
114
		$messenger_channel = 'preview-123';
115
		$wp_customize = new WP_Customize_Manager( array(
116
			'changeset_uuid' => $uuid,
117
			'theme' => $theme,
118
			'messenger_channel' => $messenger_channel,
119
		) );
120
		$this->assertEquals( $uuid, $wp_customize->changeset_uuid() );
121
		$this->assertEquals( $theme, $wp_customize->get_stylesheet() );
122
		$this->assertEquals( $messenger_channel, $wp_customize->get_messenger_channel() );
123
124
		$theme = 'twentyfourteen';
125
		$messenger_channel = 'preview-456';
126
		$_REQUEST['theme'] = $theme;
127
		$_REQUEST['customize_messenger_channel'] = $messenger_channel;
128
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
129
		$this->assertEquals( $theme, $wp_customize->get_stylesheet() );
130
		$this->assertEquals( $messenger_channel, $wp_customize->get_messenger_channel() );
131
132
		$theme = 'twentyfourteen';
133
		$_REQUEST['customize_theme'] = $theme;
134
		$wp_customize = new WP_Customize_Manager();
135
		$this->assertEquals( $theme, $wp_customize->get_stylesheet() );
136
		$this->assertNotEmpty( $wp_customize->changeset_uuid() );
137
	}
138
139
	/**
140
	 * Test WP_Customize_Manager::setup_theme() for admin screen.
141
	 *
142
	 * @covers WP_Customize_Manager::setup_theme()
143
	 */
144
	function test_setup_theme_in_customize_admin() {
145
		global $pagenow, $wp_customize;
146
		$pagenow = 'customize.php';
147
		set_current_screen( 'customize' );
148
149
		// Unauthorized.
150
		$exception = null;
151
		$wp_customize = new WP_Customize_Manager();
152
		wp_set_current_user( self::$subscriber_user_id );
153
		try {
154
			$wp_customize->setup_theme();
155
		} catch ( Exception $e ) {
156
			$exception = $e;
157
		}
158
		$this->assertInstanceOf( 'WPDieException', $exception );
159
		$this->assertContains( 'you are not allowed to customize this site', $exception->getMessage() );
160
161
		// Bad changeset.
162
		$exception = null;
163
		wp_set_current_user( self::$admin_user_id );
164
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => 'bad' ) );
165
		try {
166
			$wp_customize->setup_theme();
167
		} catch ( Exception $e ) {
168
			$exception = $e;
169
		}
170
		$this->assertInstanceOf( 'WPDieException', $exception );
171
		$this->assertContains( 'Invalid changeset UUID', $exception->getMessage() );
172
173
		update_option( 'fresh_site', 0 );
174
		$wp_customize = new WP_Customize_Manager();
175
		$wp_customize->setup_theme();
176
		$this->assertFalse( has_action( 'after_setup_theme', array( $wp_customize, 'import_theme_starter_content' ) ) );
177
178
		// Make sure that starter content import gets queued on a fresh site.
179
		update_option( 'fresh_site', 1 );
180
		$wp_customize->setup_theme();
181
		$this->assertEquals( 100, has_action( 'after_setup_theme', array( $wp_customize, 'import_theme_starter_content' ) ) );
182
	}
183
184
	/**
185
	 * Test WP_Customize_Manager::setup_theme() for frontend.
186
	 *
187
	 * @covers WP_Customize_Manager::setup_theme()
188
	 */
189
	function test_setup_theme_in_frontend() {
190
		global $wp_customize, $pagenow, $show_admin_bar;
191
		$pagenow = 'front';
192
		set_current_screen( 'front' );
193
194
		wp_set_current_user( 0 );
195
		$exception = null;
196
		$wp_customize = new WP_Customize_Manager();
197
		wp_set_current_user( self::$subscriber_user_id );
198
		try {
199
			$wp_customize->setup_theme();
200
		} catch ( Exception $e ) {
201
			$exception = $e;
202
		}
203
		$this->assertInstanceOf( 'WPDieException', $exception );
204
		$this->assertContains( 'Non-existent changeset UUID', $exception->getMessage() );
205
206
		wp_set_current_user( self::$admin_user_id );
207
		$wp_customize = new WP_Customize_Manager( array( 'messenger_channel' => 'preview-1' ) );
208
		$wp_customize->setup_theme();
209
		$this->assertFalse( $show_admin_bar );
210
211
		show_admin_bar( true );
212
		wp_set_current_user( self::$admin_user_id );
213
		$wp_customize = new WP_Customize_Manager( array( 'messenger_channel' => null ) );
214
		$wp_customize->setup_theme();
215
		$this->assertTrue( $show_admin_bar );
216
	}
217
218
	/**
219
	 * Test WP_Customize_Manager::changeset_uuid().
220
	 *
221
	 * @ticket 30937
222
	 * @covers WP_Customize_Manager::changeset_uuid()
223
	 */
224
	function test_changeset_uuid() {
225
		$uuid = wp_generate_uuid4();
226
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
227
		$this->assertEquals( $uuid, $wp_customize->changeset_uuid() );
228
	}
229
230
	/**
231
	 * Test WP_Customize_Manager::wp_loaded().
232
	 *
233
	 * Ensure that post values are previewed even without being in preview.
234
	 *
235
	 * @ticket 30937
236
	 * @covers WP_Customize_Manager::wp_loaded()
237
	 */
238
	function test_wp_loaded() {
239
		wp_set_current_user( self::$admin_user_id );
240
		$wp_customize = new WP_Customize_Manager();
241
		$title = 'Hello World';
242
		$wp_customize->set_post_value( 'blogname', $title );
243
		$this->assertNotEquals( $title, get_option( 'blogname' ) );
244
		$wp_customize->wp_loaded();
245
		$this->assertFalse( $wp_customize->is_preview() );
246
		$this->assertEquals( $title, $wp_customize->get_setting( 'blogname' )->value() );
247
		$this->assertEquals( $title, get_option( 'blogname' ) );
248
	}
249
250
	/**
251
	 * Test WP_Customize_Manager::find_changeset_post_id().
252
	 *
253
	 * @ticket 30937
254
	 * @covers WP_Customize_Manager::find_changeset_post_id()
255
	 */
256
	function test_find_changeset_post_id() {
257
		$uuid = wp_generate_uuid4();
258
		$post_id = $this->factory()->post->create( array(
259
			'post_name' => $uuid,
260
			'post_type' => 'customize_changeset',
261
			'post_status' => 'auto-draft',
262
			'post_content' => '{}',
263
		) );
264
265
		$wp_customize = new WP_Customize_Manager();
266
		$this->assertNull( $wp_customize->find_changeset_post_id( wp_generate_uuid4() ) );
267
		$this->assertEquals( $post_id, $wp_customize->find_changeset_post_id( $uuid ) );
268
	}
269
270
	/**
271
	 * Test WP_Customize_Manager::changeset_post_id().
272
	 *
273
	 * @ticket 30937
274
	 * @covers WP_Customize_Manager::changeset_post_id()
275
	 */
276
	function test_changeset_post_id() {
277
		$uuid = wp_generate_uuid4();
278
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
279
		$this->assertNull( $wp_customize->changeset_post_id() );
280
281
		$uuid = wp_generate_uuid4();
282
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
283
		$post_id = $this->factory()->post->create( array(
284
			'post_name' => $uuid,
285
			'post_type' => 'customize_changeset',
286
			'post_status' => 'auto-draft',
287
			'post_content' => '{}',
288
		) );
289
		$this->assertEquals( $post_id, $wp_customize->changeset_post_id() );
290
	}
291
292
	/**
293
	 * Test WP_Customize_Manager::changeset_data().
294
	 *
295
	 * @ticket 30937
296
	 * @covers WP_Customize_Manager::changeset_data()
297
	 */
298
	function test_changeset_data() {
299
		$uuid = wp_generate_uuid4();
300
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
301
		$this->assertEquals( array(), $wp_customize->changeset_data() );
302
303
		$uuid = wp_generate_uuid4();
304
		$data = array( 'blogname' => array( 'value' => 'Hello World' ) );
305
		$this->factory()->post->create( array(
306
			'post_name' => $uuid,
307
			'post_type' => 'customize_changeset',
308
			'post_status' => 'auto-draft',
309
			'post_content' => wp_json_encode( $data ),
310
		) );
311
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
312
		$this->assertEquals( $data, $wp_customize->changeset_data() );
313
	}
314
315
	/**
316
	 * Test WP_Customize_Manager::import_theme_starter_content().
317
	 *
318
	 * @covers WP_Customize_Manager::import_theme_starter_content()
319
	 * @covers WP_Customize_Manager::_save_starter_content_changeset()
320
	 */
321
	function test_import_theme_starter_content() {
322
		wp_set_current_user( self::$admin_user_id );
323
		register_nav_menu( 'top', 'Top' );
324
		add_theme_support( 'custom-logo' );
325
		add_theme_support( 'custom-header' );
326
		add_theme_support( 'custom-background' );
327
328
		$existing_canola_attachment_id = self::factory()->attachment->create_object( $this->test_file, 0, array(
329
			'post_mime_type' => 'image/jpeg',
330
			'post_type' => 'attachment',
331
			'post_name' => 'canola',
332
		) );
333
		$existing_published_home_page_id = $this->factory()->post->create( array(
334
			'post_name' => 'home',
335
			'post_type' => 'page',
336
			'post_status' => 'publish'
337
		) );
338
		$existing_auto_draft_about_page_id = $this->factory()->post->create( array(
339
			'post_name' => 'about',
340
			'post_type' => 'page',
341
			'post_status' => 'auto-draft'
342
		) );
343
344
		global $wp_customize;
345
		$wp_customize = new WP_Customize_Manager();
346
		$starter_content_config = array(
347
			'widgets' => array(
348
				'sidebar-1' => array(
349
					'text_business_info',
350
					'meta_custom' => array( 'meta', array(
351
						'title' => 'Pre-hydrated meta widget.',
352
					) ),
353
				),
354
			),
355
			'nav_menus' => array(
356
				'top' => array(
357
					'name'  => 'Menu Name',
358
					'items' => array(
359
						'link_home',
360
						'page_about',
361
						'page_blog',
362
						'link_email',
363
						'link_facebook',
364
						'link_custom' => array(
365
							'title' => 'Custom',
366
							'url' => 'https://custom.example.com/',
367
						),
368
					),
369
				),
370
			),
371
			'posts' => array(
372
				'home',
373
				'about' => array(
374
					'template' => 'sample-page-template.php',
375
				),
376
				'blog',
377
				'custom' => array(
378
					'post_type' => 'post',
379
					'post_title' => 'Custom',
380
					'thumbnail' => '{{waffles}}',
381
				),
382
				'unknown_cpt' => array(
383
					'post_type' => 'unknown_cpt',
384
					'post_title' => 'Unknown CPT',
385
				),
386
			),
387
			'attachments' => array(
388
				'waffles' => array(
389
					'post_title' => 'Waffles',
390
					'post_content' => 'Waffles Attachment Description',
391
					'post_excerpt' => 'Waffles Attachment Caption',
392
					'file' => $this->test_file2,
393
				),
394
				'canola' => array(
395
					'post_title' => 'Canola',
396
					'post_content' => 'Canola Attachment Description',
397
					'post_excerpt' => 'Canola Attachment Caption',
398
					'file' => $this->test_file,
399
				),
400
			),
401
			'options' => array(
402
				'blogname' => 'Starter Content Title',
403
				'blogdescription' => 'Starter Content Tagline',
404
				'show_on_front'  => 'page',
405
				'page_on_front'  => '{{home}}',
406
				'page_for_posts' => '{{blog}}',
407
			),
408
			'theme_mods' => array(
409
				'custom_logo' => '{{canola}}',
410
				'header_image' => '{{waffles}}',
411
				'background_image' => '{{waffles}}',
412
			),
413
		);
414
415
		update_option( 'posts_per_page', 1 ); // To check #39022.
416
		add_theme_support( 'starter-content', $starter_content_config );
417
		$this->assertEmpty( $wp_customize->unsanitized_post_values() );
418
		$wp_customize->import_theme_starter_content();
419
		$changeset_values = $wp_customize->unsanitized_post_values();
420
		$expected_setting_ids = array(
421
			'blogname',
422
			'blogdescription',
423
			'custom_logo',
424
			'header_image_data',
425
			'background_image',
426
			'widget_text[2]',
427
			'widget_meta[3]',
428
			'sidebars_widgets[sidebar-1]',
429
			'nav_menus_created_posts',
430
			'nav_menu[-1]',
431
			'nav_menu_item[-1]',
432
			'nav_menu_item[-2]',
433
			'nav_menu_item[-3]',
434
			'nav_menu_item[-4]',
435
			'nav_menu_item[-5]',
436
			'nav_menu_item[-6]',
437
			'nav_menu_locations[top]',
438
			'show_on_front',
439
			'page_on_front',
440
			'page_for_posts',
441
		);
442
		$this->assertEqualSets( $expected_setting_ids, array_keys( $changeset_values ) );
443
444
		foreach ( array( 'widget_text[2]', 'widget_meta[3]' ) as $setting_id ) {
445
			$this->assertInternalType( 'array', $changeset_values[ $setting_id ] );
446
			$instance_data = $wp_customize->widgets->sanitize_widget_instance( $changeset_values[ $setting_id ] );
447
			$this->assertInternalType( 'array', $instance_data );
448
			$this->assertArrayHasKey( 'title', $instance_data );
449
		}
450
451
		$this->assertEquals( array( 'text-2', 'meta-3' ), $changeset_values['sidebars_widgets[sidebar-1]'] );
452
453
		$posts_by_name = array();
454
		$this->assertCount( 7, $changeset_values['nav_menus_created_posts'] );
455
		$this->assertContains( $existing_published_home_page_id, $changeset_values['nav_menus_created_posts'], 'Expected reuse of non-auto-draft posts.' );
456
		$this->assertContains( $existing_canola_attachment_id, $changeset_values['nav_menus_created_posts'], 'Expected reuse of non-auto-draft attachment.' );
457
		$this->assertNotContains( $existing_auto_draft_about_page_id, $changeset_values['nav_menus_created_posts'], 'Expected non-reuse of auto-draft posts.' );
458
		foreach ( $changeset_values['nav_menus_created_posts'] as $post_id ) {
459
			$post = get_post( $post_id );
460
			if ( $post->ID === $existing_published_home_page_id ) {
461
				$this->assertEquals( 'publish', $post->post_status );
462
			} elseif ( $post->ID === $existing_canola_attachment_id ) {
463
				$this->assertEquals( 'inherit', $post->post_status );
464
			} else {
465
				$this->assertEquals( 'auto-draft', $post->post_status );
466
				$this->assertEmpty( $post->post_name );
467
			}
468
			$post_name = $post->post_name;
469
			if ( empty( $post_name ) ) {
470
				$post_name = get_post_meta( $post->ID, '_customize_draft_post_name', true );
471
			}
472
			$posts_by_name[ $post_name ] = $post->ID;
473
		}
474
		$this->assertEquals( array( 'waffles', 'canola', 'home', 'about', 'blog', 'custom', 'unknown-cpt' ), array_keys( $posts_by_name ) );
475
		$this->assertEquals( 'Custom', get_post( $posts_by_name['custom'] )->post_title );
476
		$this->assertEquals( 'sample-page-template.php', get_page_template_slug( $posts_by_name['about'] ) );
477
		$this->assertEquals( '', get_page_template_slug( $posts_by_name['blog'] ) );
478
		$this->assertEquals( $posts_by_name['waffles'], get_post_thumbnail_id( $posts_by_name['custom'] ) );
479
		$this->assertEquals( '', get_post_thumbnail_id( $posts_by_name['blog'] ) );
480
		$attachment_metadata = wp_get_attachment_metadata( $posts_by_name['waffles'] );
481
		$this->assertEquals( 'Waffles', get_post( $posts_by_name['waffles'] )->post_title );
482
		$this->assertEquals( 'waffles', get_post_meta( $posts_by_name['waffles'], '_customize_draft_post_name', true ) );
483
		$this->assertArrayHasKey( 'file', $attachment_metadata );
484
		$this->assertContains( 'waffles', $attachment_metadata['file'] );
485
486
		$this->assertEquals( 'page', $changeset_values['show_on_front'] );
487
		$this->assertEquals( $posts_by_name['home'], $changeset_values['page_on_front'] );
488
		$this->assertEquals( $posts_by_name['blog'], $changeset_values['page_for_posts'] );
489
490
		$this->assertEquals( -1, $changeset_values['nav_menu_locations[top]'] );
491
		$this->assertEquals( 0, $changeset_values['nav_menu_item[-1]']['object_id'] );
492
		$this->assertEquals( 'custom', $changeset_values['nav_menu_item[-1]']['type'] );
493
		$this->assertEquals( home_url( '/' ), $changeset_values['nav_menu_item[-1]']['url'] );
494
495
		$this->assertEmpty( $wp_customize->changeset_data() );
496
		$this->assertNull( $wp_customize->changeset_post_id() );
497
		$this->assertEquals( 1000, has_action( 'customize_register', array( $wp_customize, '_save_starter_content_changeset' ) ) );
498
		do_action( 'customize_register', $wp_customize ); // This will trigger the changeset save.
499
		$this->assertInternalType( 'int', $wp_customize->changeset_post_id() );
500
		$this->assertNotEmpty( $wp_customize->changeset_data() );
501
		foreach ( $wp_customize->changeset_data() as $setting_id => $setting_params ) {
0 ignored issues
show
Bug introduced by
The expression $wp_customize->changeset_data() of type array|object<WP_Error> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
502
			$this->assertArrayHasKey( 'starter_content', $setting_params );
503
			$this->assertTrue( $setting_params['starter_content'] );
504
		}
505
506
		// Ensure that re-importing doesn't cause auto-drafts to balloon.
507
		$wp_customize->import_theme_starter_content();
508
		$changeset_data = $wp_customize->changeset_data();
509
		$this->assertEqualSets( array_values( $posts_by_name ), $changeset_data['nav_menus_created_posts']['value'] ); // Auto-drafts should not get re-created and amended with each import.
510
511
		// Test that saving non-starter content on top of the changeset clears the starter_content flag.
512
		$wp_customize->save_changeset_post( array(
513
			'data' => array(
514
				'blogname' => array( 'value' => 'Starter Content Modified' ),
515
			),
516
		) );
517
		$changeset_data = $wp_customize->changeset_data();
518
		$this->assertArrayNotHasKey( 'starter_content', $changeset_data['blogname'] );
519
		$this->assertArrayHasKey( 'starter_content', $changeset_data['blogdescription'] );
520
521
		// Test that adding blogname starter content is ignored now that it is modified, but updating a non-modified starter content blog description passes.
522
		$previous_blogname = $changeset_data['blogname']['value'];
523
		$previous_blogdescription = $changeset_data['blogdescription']['value'];
524
		$wp_customize->import_theme_starter_content( array(
525
			'options' => array(
526
				'blogname' => 'Newer Starter Content Title',
527
				'blogdescription' => 'Newer Starter Content Description',
528
			),
529
		) );
530
		$changeset_data = $wp_customize->changeset_data();
531
		$this->assertEquals( $previous_blogname, $changeset_data['blogname']['value'] );
532
		$this->assertArrayNotHasKey( 'starter_content', $changeset_data['blogname'] );
533
		$this->assertNotEquals( $previous_blogdescription, $changeset_data['blogdescription']['value'] );
534
		$this->assertArrayHasKey( 'starter_content', $changeset_data['blogdescription'] );
535
536
		// Publish.
537
		$this->assertEmpty( get_custom_logo() );
538
		$this->assertEmpty( get_header_image() );
539
		$this->assertEmpty( get_background_image() );
540
		$this->assertEmpty( get_theme_mod( 'custom_logo' ) );
541
		$this->assertEmpty( get_theme_mod( 'header_image' ) );
542
		$this->assertEmpty( get_theme_mod( 'background_image' ) );
543
		$this->assertEquals( 'auto-draft', get_post( $posts_by_name['about'] )->post_status );
544
		$this->assertEquals( 'auto-draft', get_post( $posts_by_name['waffles'] )->post_status );
545
		$this->assertNotEquals( $changeset_data['blogname']['value'], get_option( 'blogname' ) );
546
		$r = $wp_customize->save_changeset_post( array( 'status' => 'publish' ) );
547
		$this->assertInternalType( 'array', $r );
548
		$this->assertEquals( 'publish', get_post( $posts_by_name['about'] )->post_status );
549
		$this->assertEquals( 'inherit', get_post( $posts_by_name['waffles'] )->post_status );
550
		$this->assertEquals( $changeset_data['blogname']['value'], get_option( 'blogname' ) );
551
		$this->assertNotEmpty( get_theme_mod( 'custom_logo' ) );
552
		$this->assertNotEmpty( get_theme_mod( 'header_image' ) );
553
		$this->assertNotEmpty( get_theme_mod( 'background_image' ) );
554
		$this->assertNotEmpty( get_custom_logo() );
555
		$this->assertNotEmpty( get_header_image() );
556
		$this->assertNotEmpty( get_background_image() );
557
		$this->assertContains( 'canola', get_custom_logo() );
558
		$this->assertContains( 'waffles', get_header_image() );
559
		$this->assertContains( 'waffles', get_background_image() );
560
		$this->assertEquals( 'waffles', get_post( $posts_by_name['waffles'] )->post_name );
561
		$this->assertEmpty( get_post_meta( $posts_by_name['waffles'], '_customize_draft_post_name', true ) );
562
	}
563
564
	/**
565
	 * Test WP_Customize_Manager::customize_preview_init().
566
	 *
567
	 * @ticket 30937
568
	 * @covers WP_Customize_Manager::customize_preview_init()
569
	 */
570
	function test_customize_preview_init() {
571
572
		// Test authorized admin user.
573
		wp_set_current_user( self::$admin_user_id );
574
		$did_action_customize_preview_init = did_action( 'customize_preview_init' );
575
		$wp_customize = new WP_Customize_Manager();
576
		$wp_customize->customize_preview_init();
577
		$this->assertEquals( $did_action_customize_preview_init + 1, did_action( 'customize_preview_init' ) );
578
579
		$this->assertEquals( 10, has_action( 'wp_head', 'wp_no_robots' ) );
580
		$this->assertEquals( 10, has_action( 'wp_head', array( $wp_customize, 'remove_frameless_preview_messenger_channel' ) ) );
581
		$this->assertEquals( 10, has_filter( 'wp_headers', array( $wp_customize, 'filter_iframe_security_headers' ) ) );
582
		$this->assertEquals( 10, has_filter( 'wp_redirect', array( $wp_customize, 'add_state_query_params' ) ) );
583
		$this->assertTrue( wp_script_is( 'customize-preview', 'enqueued' ) );
584
		$this->assertEquals( 10, has_action( 'wp_head', array( $wp_customize, 'customize_preview_loading_style' ) ) );
585
		$this->assertEquals( 20, has_action( 'wp_footer', array( $wp_customize, 'customize_preview_settings' ) ) );
586
587
		// Test unauthorized user outside preview (no messenger_channel).
588
		wp_set_current_user( self::$subscriber_user_id );
589
		$wp_customize = new WP_Customize_Manager();
590
		$wp_customize->register_controls();
591
		$this->assertNotEmpty( $wp_customize->controls() );
592
		$wp_customize->customize_preview_init();
593
		$this->assertEmpty( $wp_customize->controls() );
594
595
		// Test unauthorized user inside preview (with messenger_channel).
596
		wp_set_current_user( self::$subscriber_user_id );
597
		$wp_customize = new WP_Customize_Manager( array( 'messenger_channel' => 'preview-0' ) );
598
		$exception = null;
599
		try {
600
			$wp_customize->customize_preview_init();
601
		} catch ( WPDieException $e ) {
602
			$exception = $e;
603
		}
604
		$this->assertNotNull( $exception );
605
		$this->assertContains( 'Unauthorized', $exception->getMessage() );
606
	}
607
608
	/**
609
	 * Test WP_Customize_Manager::filter_iframe_security_headers().
610
	 *
611
	 * @ticket 30937
612
	 * @covers WP_Customize_Manager::filter_iframe_security_headers()
613
	 */
614
	function test_filter_iframe_security_headers() {
615
		$customize_url = admin_url( 'customize.php' );
616
		$wp_customize = new WP_Customize_Manager();
617
		$headers = $wp_customize->filter_iframe_security_headers( array() );
618
		$this->assertArrayHasKey( 'X-Frame-Options', $headers );
619
		$this->assertArrayHasKey( 'Content-Security-Policy', $headers );
620
		$this->assertEquals( "ALLOW-FROM $customize_url", $headers['X-Frame-Options'] );
621
	}
622
623
	/**
624
	 * Test WP_Customize_Manager::add_state_query_params().
625
	 *
626
	 * @ticket 30937
627
	 * @covers WP_Customize_Manager::add_state_query_params()
628
	 */
629
	function test_add_state_query_params() {
630
		$preview_theme = $this->get_inactive_core_theme();
631
632
		$uuid = wp_generate_uuid4();
633
		$messenger_channel = 'preview-0';
634
		$wp_customize = new WP_Customize_Manager( array(
635
			'changeset_uuid' => $uuid,
636
			'messenger_channel' => $messenger_channel,
637
		) );
638
		$url = $wp_customize->add_state_query_params( home_url( '/' ) );
639
		$parsed_url = wp_parse_url( $url );
640
		parse_str( $parsed_url['query'], $query_params );
641
		$this->assertArrayHasKey( 'customize_messenger_channel', $query_params );
642
		$this->assertArrayHasKey( 'customize_changeset_uuid', $query_params );
643
		$this->assertArrayNotHasKey( 'customize_theme', $query_params );
644
		$this->assertEquals( $uuid, $query_params['customize_changeset_uuid'] );
645
		$this->assertEquals( $messenger_channel, $query_params['customize_messenger_channel'] );
646
647
		$uuid = wp_generate_uuid4();
648
		$wp_customize = new WP_Customize_Manager( array(
649
			'changeset_uuid' => $uuid,
650
			'messenger_channel' => null,
651
			'theme' => $preview_theme,
652
		) );
653
		$url = $wp_customize->add_state_query_params( home_url( '/' ) );
654
		$parsed_url = wp_parse_url( $url );
655
		parse_str( $parsed_url['query'], $query_params );
656
		$this->assertArrayNotHasKey( 'customize_messenger_channel', $query_params );
657
		$this->assertArrayHasKey( 'customize_changeset_uuid', $query_params );
658
		$this->assertArrayHasKey( 'customize_theme', $query_params );
659
		$this->assertEquals( $uuid, $query_params['customize_changeset_uuid'] );
660
		$this->assertEquals( $preview_theme, $query_params['customize_theme'] );
661
662
		$uuid = wp_generate_uuid4();
663
		$wp_customize = new WP_Customize_Manager( array(
664
			'changeset_uuid' => $uuid,
665
			'messenger_channel' => null,
666
			'theme' => $preview_theme,
667
		) );
668
		$url = $wp_customize->add_state_query_params( 'http://not-allowed.example.com/?q=1' );
669
		$parsed_url = wp_parse_url( $url );
670
		parse_str( $parsed_url['query'], $query_params );
671
		$this->assertArrayNotHasKey( 'customize_messenger_channel', $query_params );
672
		$this->assertArrayNotHasKey( 'customize_changeset_uuid', $query_params );
673
		$this->assertArrayNotHasKey( 'customize_theme', $query_params );
674
	}
675
676
	/**
677
	 * Test WP_Customize_Manager::save_changeset_post().
678
	 *
679
	 * @ticket 30937
680
	 * @covers WP_Customize_Manager::save_changeset_post()
681
	 */
682
	function test_save_changeset_post_without_theme_activation() {
683
		global $wp_customize;
684
		wp_set_current_user( self::$admin_user_id );
685
686
		$did_action = array(
687
			'customize_save_validation_before' => did_action( 'customize_save_validation_before' ),
688
			'customize_save' => did_action( 'customize_save' ),
689
			'customize_save_after' => did_action( 'customize_save_after' ),
690
		);
691
		$uuid = wp_generate_uuid4();
692
693
		$wp_customize = $manager = new WP_Customize_Manager( array(
694
			'changeset_uuid' => $uuid,
695
		) );
696
		$wp_customize = $manager;
697
		$manager->register_controls();
698
		$manager->set_post_value( 'blogname', 'Changeset Title' );
699
		$manager->set_post_value( 'blogdescription', 'Changeset Tagline' );
700
701
		$pre_saved_data = array(
702
			'blogname' => array(
703
				'value' => 'Overridden Changeset Title',
704
			),
705
			'blogdescription' => array(
706
				'custom' => 'something',
707
			),
708
		);
709
		$date = ( gmdate( 'Y' ) + 1 ) . '-12-01 00:00:00';
710
		$r = $manager->save_changeset_post( array(
711
			'status' => 'auto-draft',
712
			'title' => 'Auto Draft',
713
			'date_gmt' => $date,
714
			'data' => $pre_saved_data,
715
		) );
716
		$this->assertInternalType( 'array', $r );
717
718
		$this->assertEquals( $did_action['customize_save_validation_before'] + 1, did_action( 'customize_save_validation_before' ) );
719
720
		$post_id = $manager->find_changeset_post_id( $uuid );
721
		$this->assertNotNull( $post_id );
722
		$saved_data = json_decode( get_post( $post_id )->post_content, true );
723
		$this->assertEquals( $manager->unsanitized_post_values(), wp_list_pluck( $saved_data, 'value' ) );
724
		$this->assertEquals( $pre_saved_data['blogname']['value'], $saved_data['blogname']['value'] );
725
		$this->assertEquals( $pre_saved_data['blogdescription']['custom'], $saved_data['blogdescription']['custom'] );
726
		foreach ( $saved_data as $setting_id => $setting_params ) {
0 ignored issues
show
Bug introduced by
The expression $saved_data of type boolean|integer|double|s...|array|object<stdClass> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
727
			$this->assertArrayHasKey( 'type', $setting_params );
728
			$this->assertEquals( 'option', $setting_params['type'] );
729
			$this->assertArrayHasKey( 'user_id', $setting_params );
730
			$this->assertEquals( self::$admin_user_id, $setting_params['user_id'] );
731
		}
732
		$this->assertEquals( 'Auto Draft', get_post( $post_id )->post_title );
733
		$this->assertEquals( 'auto-draft', get_post( $post_id )->post_status );
734
		$this->assertEquals( $date, get_post( $post_id )->post_date_gmt );
735
		$this->assertNotEquals( 'Changeset Title', get_option( 'blogname' ) );
736
		$this->assertArrayHasKey( 'setting_validities', $r );
737
738
		// Test saving with invalid settings, ensuring transaction blocked.
739
		$previous_saved_data = $saved_data;
740
		$manager->add_setting( 'foo_unauthorized', array(
741
			'capability' => 'do_not_allow',
742
		) );
743
		$manager->add_setting( 'baz_illegal', array(
744
			'validate_callback' => array( $this, 'return_illegal_error' ),
745
		) );
746
		$r = $manager->save_changeset_post( array(
747
			'status' => 'auto-draft',
748
			'data' => array(
749
				'blogname' => array(
750
					'value' => 'OK',
751
				),
752
				'foo_unauthorized' => array(
753
					'value' => 'No',
754
				),
755
				'bar_unknown' => array(
756
					'value' => 'No',
757
				),
758
				'baz_illegal' => array(
759
					'value' => 'No',
760
				),
761
			),
762
		) );
763
		$this->assertInstanceOf( 'WP_Error', $r );
764
		$this->assertEquals( 'transaction_fail', $r->get_error_code() );
765
		$this->assertInternalType( 'array', $r->get_error_data() );
766
		$this->assertArrayHasKey( 'setting_validities', $r->get_error_data() );
767
		$error_data = $r->get_error_data();
768
		$this->assertArrayHasKey( 'blogname', $error_data['setting_validities'] );
769
		$this->assertTrue( $error_data['setting_validities']['blogname'] );
770
		$this->assertArrayHasKey( 'foo_unauthorized', $error_data['setting_validities'] );
771
		$this->assertInstanceOf( 'WP_Error', $error_data['setting_validities']['foo_unauthorized'] );
772
		$this->assertEquals( 'unauthorized', $error_data['setting_validities']['foo_unauthorized']->get_error_code() );
773
		$this->assertArrayHasKey( 'bar_unknown', $error_data['setting_validities'] );
774
		$this->assertInstanceOf( 'WP_Error', $error_data['setting_validities']['bar_unknown'] );
775
		$this->assertEquals( 'unrecognized', $error_data['setting_validities']['bar_unknown']->get_error_code() );
776
		$this->assertArrayHasKey( 'baz_illegal', $error_data['setting_validities'] );
777
		$this->assertInstanceOf( 'WP_Error', $error_data['setting_validities']['baz_illegal'] );
778
		$this->assertEquals( 'illegal', $error_data['setting_validities']['baz_illegal']->get_error_code() );
779
780
		// Since transactional, ensure no changes have been made.
781
		$this->assertEquals( $previous_saved_data, json_decode( get_post( $post_id )->post_content, true ) );
782
783
		// Attempt a non-transactional/incremental update.
784
		$wp_customize = $manager = new WP_Customize_Manager( array(
785
			'changeset_uuid' => $uuid,
786
		) );
787
		$wp_customize = $manager;
788
		$manager->register_controls(); // That is, register settings.
789
		$r = $manager->save_changeset_post( array(
790
			'status' => null,
791
			'data' => array(
792
				'blogname' => array(
793
					'value' => 'Non-Transactional \o/ <script>unsanitized</script>',
794
				),
795
				'bar_unknown' => array(
796
					'value' => 'No',
797
				),
798
			),
799
		) );
800
		$this->assertInternalType( 'array', $r );
801
		$this->assertArrayHasKey( 'setting_validities', $r );
802
		$this->assertTrue( $r['setting_validities']['blogname'] );
803
		$this->assertInstanceOf( 'WP_Error', $r['setting_validities']['bar_unknown'] );
804
		$saved_data = json_decode( get_post( $post_id )->post_content, true );
805
		$this->assertNotEquals( $previous_saved_data, $saved_data );
806
		$this->assertEquals( 'Non-Transactional \o/ <script>unsanitized</script>', $saved_data['blogname']['value'] );
807
808
		// Ensure the filter applies.
809
		$customize_changeset_save_data_call_count = $this->customize_changeset_save_data_call_count;
810
		add_filter( 'customize_changeset_save_data', array( $this, 'filter_customize_changeset_save_data' ), 10, 2 );
811
		$manager->save_changeset_post( array(
812
			'status' => null,
813
			'data' => array(
814
				'blogname' => array(
815
					'value' => 'Filtered',
816
				),
817
			),
818
		) );
819
		$this->assertEquals( $customize_changeset_save_data_call_count + 1, $this->customize_changeset_save_data_call_count );
820
821
		// Publish the changeset: actions will be doubled since also trashed.
822
		$expected_actions = array(
823
			'wp_trash_post' => 1,
824
			'clean_post_cache' => 2,
825
			'transition_post_status' => 2,
826
			'publish_to_trash' => 1,
827
			'trash_customize_changeset' => 1,
828
			'edit_post' => 2,
829
			'save_post_customize_changeset' => 2,
830
			'save_post' => 2,
831
			'wp_insert_post' => 2,
832
			'trashed_post' => 1,
833
		);
834
		$action_counts = array();
835
		foreach ( array_keys( $expected_actions ) as $action_name ) {
836
			$action_counts[ $action_name ] = did_action( $action_name );
837
		}
838
839
		$wp_customize = $manager = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
840
		do_action( 'customize_register', $wp_customize );
841
		$manager->add_setting( 'scratchpad', array(
842
			'type' => 'option',
843
			'capability' => 'exist',
844
		) );
845
		$manager->get_setting( 'blogname' )->capability = 'exist';
846
		$original_capabilities = wp_list_pluck( $manager->settings(), 'capability' );
847
		wp_set_current_user( self::$subscriber_user_id );
848
		$r = $manager->save_changeset_post( array(
849
			'status' => 'publish',
850
			'data' => array(
851
				'blogname' => array(
852
					'value' => 'Do it live \o/',
853
				),
854
				'scratchpad' => array(
855
					'value' => '<script>console.info( "HELLO" )</script>',
856
				),
857
			),
858
		) );
859
		$this->assertInternalType( 'array', $r );
860
		$this->assertEquals( 'Do it live \o/', get_option( 'blogname' ) );
861
		$this->assertEquals( 'trash', get_post_status( $post_id ) ); // Auto-trashed.
862
		$this->assertEquals( $original_capabilities, wp_list_pluck( $manager->settings(), 'capability' ) );
863
		$this->assertContains( '<script>', get_post( $post_id )->post_content );
864
		$this->assertEquals( $manager->changeset_uuid(), get_post( $post_id )->post_name, 'Expected that the "__trashed" suffix to not be added.' );
865
		wp_set_current_user( self::$admin_user_id );
866
		$this->assertEquals( 'publish', get_post_meta( $post_id, '_wp_trash_meta_status', true ) );
867
		$this->assertTrue( is_numeric( get_post_meta( $post_id, '_wp_trash_meta_time', true ) ) );
868
869
		foreach ( array_keys( $expected_actions ) as $action_name ) {
870
			$this->assertEquals( $expected_actions[ $action_name ] + $action_counts[ $action_name ], did_action( $action_name ), "Action: $action_name" );
871
		}
872
873
		// Test revisions.
874
		add_post_type_support( 'customize_changeset', 'revisions' );
875
		$uuid = wp_generate_uuid4();
876
		$wp_customize = $manager = new WP_Customize_Manager( array( 'changeset_uuid' => $uuid ) );
877
		do_action( 'customize_register', $manager );
878
879
		$manager->set_post_value( 'blogname', 'Hello Surface' );
880
		$manager->save_changeset_post( array( 'status' => 'auto-draft' ) );
881
882
		$manager->set_post_value( 'blogname', 'Hello World' );
883
		$manager->save_changeset_post( array( 'status' => 'draft' ) );
884
		$this->assertTrue( wp_revisions_enabled( get_post( $manager->changeset_post_id() ) ) );
0 ignored issues
show
Bug introduced by
It seems like get_post($manager->changeset_post_id()) targeting get_post() can also be of type array or null; however, wp_revisions_enabled() does only seem to accept object<WP_Post>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
885
886
		$manager->set_post_value( 'blogname', 'Hello Solar System' );
887
		$manager->save_changeset_post( array( 'status' => 'draft' ) );
888
889
		$manager->set_post_value( 'blogname', 'Hello Galaxy' );
890
		$manager->save_changeset_post( array( 'status' => 'draft' ) );
891
		$this->assertCount( 3, wp_get_post_revisions( $manager->changeset_post_id() ) );
892
	}
893
894
	/**
895
	 * Call count for customize_changeset_save_data filter.
896
	 *
897
	 * @var int
898
	 */
899
	protected $customize_changeset_save_data_call_count = 0;
900
901
	/**
902
	 * Filter customize_changeset_save_data.
903
	 *
904
	 * @param array $data    Data.
905
	 * @param array $context Context.
906
	 * @returns array Data.
907
	 */
908
	function filter_customize_changeset_save_data( $data, $context ) {
909
		$this->customize_changeset_save_data_call_count += 1;
910
		$this->assertInternalType( 'array', $data );
911
		$this->assertInternalType( 'array', $context );
912
		$this->assertArrayHasKey( 'uuid', $context );
913
		$this->assertArrayHasKey( 'title', $context );
914
		$this->assertArrayHasKey( 'status', $context );
915
		$this->assertArrayHasKey( 'date_gmt', $context );
916
		$this->assertArrayHasKey( 'post_id', $context );
917
		$this->assertArrayHasKey( 'previous_data', $context );
918
		$this->assertArrayHasKey( 'manager', $context );
919
		return $data;
920
	}
921
922
	/**
923
	 * Return illegal error.
924
	 *
925
	 * @return WP_Error Error.
926
	 */
927
	function return_illegal_error() {
928
		return new WP_Error( 'illegal' );
929
	}
930
931
	/**
932
	 * Test WP_Customize_Manager::save_changeset_post().
933
	 *
934
	 * @ticket 30937
935
	 * @covers WP_Customize_Manager::save_changeset_post()
936
	 * @covers WP_Customize_Manager::update_stashed_theme_mod_settings()
937
	 */
938
	function test_save_changeset_post_with_theme_activation() {
939
		global $wp_customize;
940
		wp_set_current_user( self::$admin_user_id );
941
942
		$preview_theme = $this->get_inactive_core_theme();
943
		$stashed_theme_mods = array(
944
			$preview_theme => array(
945
				'background_color' => array(
946
					'value' => '#123456',
947
				),
948
			),
949
		);
950
		update_option( 'customize_stashed_theme_mods', $stashed_theme_mods );
951
		$uuid = wp_generate_uuid4();
952
		$manager = new WP_Customize_Manager( array(
953
			'changeset_uuid' => $uuid,
954
			'theme' => $preview_theme,
955
		) );
956
		$wp_customize = $manager;
957
		do_action( 'customize_register', $manager );
958
959
		$manager->set_post_value( 'blogname', 'Hello Preview Theme' );
960
		$post_values = $manager->unsanitized_post_values();
961
		$manager->save_changeset_post( array( 'status' => 'publish' ) ); // Activate.
962
963
		$this->assertEquals( '#123456', $post_values['background_color'] );
964
		$this->assertEquals( $preview_theme, get_stylesheet() );
965
		$this->assertEquals( 'Hello Preview Theme', get_option( 'blogname' ) );
966
	}
967
968
	/**
969
	 * Test saving changesets with varying users and capabilities.
970
	 *
971
	 * @ticket 38705
972
	 * @covers WP_Customize_Manager::save_changeset_post()
973
	 */
974
	function test_save_changeset_post_with_varying_users() {
975
		global $wp_customize;
976
977
		add_theme_support( 'custom-background' );
978
		wp_set_current_user( self::$admin_user_id );
979
		$other_admin_user_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
980
981
		$uuid = wp_generate_uuid4();
982
		$wp_customize = $this->create_test_manager( $uuid );
983
		$r = $wp_customize->save_changeset_post( array(
984
			'status' => 'auto-draft',
985
			'data' => array(
986
				'blogname' => array(
987
					'value' => 'Admin 1 Title',
988
				),
989
				'scratchpad' => array(
990
					'value' => 'Admin 1 Scratch',
991
				),
992
				'background_color' => array(
993
					'value' => '#000000',
994
				),
995
			),
996
		) );
997
		$this->assertInternalType( 'array', $r );
998
		$this->assertEquals(
999
			array_fill_keys( array( 'blogname', 'scratchpad', 'background_color' ), true ),
1000
			$r['setting_validities']
1001
		);
1002
		$post_id = $wp_customize->find_changeset_post_id( $uuid );
1003
		$data = json_decode( get_post( $post_id )->post_content, true );
1004
		$this->assertEquals( self::$admin_user_id, $data['blogname']['user_id'] );
1005
		$this->assertEquals( self::$admin_user_id, $data['scratchpad']['user_id'] );
1006
		$this->assertEquals( self::$admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
1007
1008
		// Attempt to save just one setting under a different user.
1009
		wp_set_current_user( $other_admin_user_id );
1010
		$wp_customize = $this->create_test_manager( $uuid );
1011
		$r = $wp_customize->save_changeset_post( array(
1012
			'status' => 'auto-draft',
1013
			'data' => array(
1014
				'blogname' => array(
1015
					'value' => 'Admin 2 Title',
1016
				),
1017
				'background_color' => array(
1018
					'value' => '#FFFFFF',
1019
				),
1020
			),
1021
		) );
1022
		$this->assertInternalType( 'array', $r );
1023
		$this->assertEquals(
1024
			array_fill_keys( array( 'blogname', 'background_color' ), true ),
1025
			$r['setting_validities']
1026
		);
1027
		$data = json_decode( get_post( $post_id )->post_content, true );
1028
		$this->assertEquals( 'Admin 2 Title', $data['blogname']['value'] );
1029
		$this->assertEquals( $other_admin_user_id, $data['blogname']['user_id'] );
1030
		$this->assertEquals( 'Admin 1 Scratch', $data['scratchpad']['value'] );
1031
		$this->assertEquals( self::$admin_user_id, $data['scratchpad']['user_id'] );
1032
		$this->assertEquals( '#FFFFFF', $data[ $this->manager->get_stylesheet() . '::background_color' ]['value'] );
1033
		$this->assertEquals( $other_admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
1034
1035
		// Attempt to save now as under-privileged user.
1036
		$wp_customize = $this->create_test_manager( $uuid );
1037
		$r = $wp_customize->save_changeset_post( array(
1038
			'status' => 'auto-draft',
1039
			'data' => array(
1040
				'blogname' => array(
1041
					'value' => 'Admin 2 Title', // Identical to what is already in the changeset so will be skipped.
1042
				),
1043
				'scratchpad' => array(
1044
					'value' => 'Subscriber Scratch',
1045
				),
1046
			),
1047
			'user_id' => self::$subscriber_user_id,
1048
		) );
1049
		$this->assertInternalType( 'array', $r );
1050
		$this->assertEquals(
1051
			array_fill_keys( array( 'scratchpad', 'blogname' ), true ),
1052
			$r['setting_validities']
1053
		);
1054
		$data = json_decode( get_post( $post_id )->post_content, true );
1055
		$this->assertEquals( $other_admin_user_id, $data['blogname']['user_id'], 'Expected setting to be untouched.' );
1056
		$this->assertEquals( self::$subscriber_user_id, $data['scratchpad']['user_id'] );
1057
		$this->assertEquals( $other_admin_user_id, $data[ $this->manager->get_stylesheet() . '::background_color' ]['user_id'] );
1058
1059
		// Manually update the changeset so that the user_id context is not included.
1060
		$data = json_decode( get_post( $post_id )->post_content, true );
1061
		$data['blogdescription']['value'] = 'Programmatically-supplied Tagline';
1062
		wp_update_post( wp_slash( array( 'ID' => $post_id, 'post_content' => wp_json_encode( $data ) ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_slash(array('ID' => $...wp_json_encode($data))) targeting wp_slash() can also be of type string; however, wp_update_post() does only seem to accept array|object, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1063
1064
		// Ensure the modifying user set as the current user when each is saved, simulating WP Cron envronment.
1065
		wp_set_current_user( 0 );
1066
		$save_counts = array();
1067 View Code Duplication
		foreach ( array_keys( $data ) as $setting_id ) {
1068
			$setting_id = preg_replace( '/^.+::/', '', $setting_id );
1069
			$save_counts[ $setting_id ] = did_action( sprintf( 'customize_save_%s', $setting_id ) );
1070
		}
1071
		$this->filtered_setting_current_user_ids = array();
1072
		foreach ( $wp_customize->settings() as $setting ) {
1073
			add_filter( sprintf( 'customize_sanitize_%s', $setting->id ), array( $this, 'filter_customize_setting_to_log_current_user' ), 10, 2 );
1074
		}
1075
		wp_update_post( array( 'ID' => $post_id, 'post_status' => 'publish' ) );
1076 View Code Duplication
		foreach ( array_keys( $data ) as $setting_id ) {
1077
			$setting_id = preg_replace( '/^.+::/', '', $setting_id );
1078
			$this->assertEquals( $save_counts[ $setting_id ] + 1, did_action( sprintf( 'customize_save_%s', $setting_id ) ), $setting_id );
1079
		}
1080
		$this->assertEqualSets( array( 'blogname', 'blogdescription', 'background_color', 'scratchpad' ), array_keys( $this->filtered_setting_current_user_ids ) );
1081
		$this->assertEquals( $other_admin_user_id, $this->filtered_setting_current_user_ids['blogname'] );
1082
		$this->assertEquals( 0, $this->filtered_setting_current_user_ids['blogdescription'] );
1083
		$this->assertEquals( self::$subscriber_user_id, $this->filtered_setting_current_user_ids['scratchpad'] );
1084
		$this->assertEquals( $other_admin_user_id, $this->filtered_setting_current_user_ids['background_color'] );
1085
		$this->assertEquals( 'Subscriber Scratch', get_option( 'scratchpad' ) );
1086
	}
1087
1088
	/**
1089
	 * Create test manager.
1090
	 *
1091
	 * @param string $uuid Changeset UUID.
1092
	 * @return WP_Customize_Manager Manager.
1093
	 */
1094
	protected function create_test_manager( $uuid ) {
1095
		$manager = new WP_Customize_Manager( array(
1096
			'changeset_uuid' => $uuid,
1097
		) );
1098
		do_action( 'customize_register', $manager );
1099
		$manager->add_setting( 'blogfounded', array(
1100
			'type' => 'option',
1101
		) );
1102
		$manager->add_setting( 'blogterminated', array(
1103
			'type' => 'option',
1104
			'capability' => 'do_not_allow',
1105
		) );
1106
		$manager->add_setting( 'scratchpad', array(
1107
			'type' => 'option',
1108
			'capability' => 'exist',
1109
		) );
1110
		return $manager;
1111
	}
1112
1113
	/**
1114
	 * Test that updating an auto-draft changeset bumps its post_date to keep it from getting garbage collected by wp_delete_auto_drafts().
1115
	 *
1116
	 * @ticket 31089
1117
	 * @see wp_delete_auto_drafts()
1118
	 * @covers WP_Customize_Manager::save_changeset_post()
1119
	 */
1120
	function test_save_changeset_post_dumping_auto_draft_date() {
1121
		global $wp_customize;
1122
		wp_set_current_user( self::$admin_user_id );
1123
1124
		$uuid = wp_generate_uuid4();
1125
		$changeset_post_id = wp_insert_post( array(
1126
			'post_type' => 'customize_changeset',
1127
			'post_content' => '{}',
1128
			'post_name' => $uuid,
1129
			'post_status' => 'auto-draft',
1130
			'post_date' => gmdate( 'Y-m-d H:i:s', strtotime( '-3 days' ) ),
1131
		) );
1132
1133
		$post = get_post( $changeset_post_id );
1134
		$original_post_date = $post->post_date;
1135
1136
		$wp_customize = $this->create_test_manager( $uuid );
1137
		$wp_customize->save_changeset_post( array(
1138
			'status' => 'auto-draft',
1139
			'data' => array(
1140
				'blogname' => array(
1141
					'value' => 'Admin 1 Title',
1142
				),
1143
			),
1144
		) );
1145
1146
		$post = get_post( $changeset_post_id );
1147
		$this->assertNotEquals( $post->post_date, $original_post_date );
1148
	}
1149
1150
	/**
1151
	 * Test writing changesets when user supplies unchanged values.
1152
	 *
1153
	 * @ticket 38865
1154
	 * @covers WP_Customize_Manager::save_changeset_post()
1155
	 */
1156
	function test_save_changeset_post_with_unchanged_values() {
1157
		global $wp_customize;
1158
1159
		add_theme_support( 'custom-background' );
1160
		wp_set_current_user( self::$admin_user_id );
1161
		$other_admin_user_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
1162
1163
		$uuid = wp_generate_uuid4();
1164
		$wp_customize = $this->create_test_manager( $uuid );
1165
		$wp_customize->save_changeset_post( array(
1166
			'status' => 'auto-draft',
1167
			'data' => array(
1168
				'blogname' => array(
1169
					'value' => 'Admin 1 Title',
1170
				),
1171
				'blogdescription' => array(
1172
					'value' => 'Admin 1 Tagline',
1173
				),
1174
				'blogfounded' => array(
1175
					'value' => '2016',
1176
				),
1177
				'scratchpad' => array(
1178
					'value' => 'Admin 1 Scratch',
1179
				),
1180
			),
1181
		) );
1182
1183
		// Make sure that setting properties of unknown and unauthorized settings are rejected.
1184
		$data = get_post( $wp_customize->changeset_post_id() )->post_content;
1185
		$r = $wp_customize->save_changeset_post( array(
1186
			'data' => array(
1187
				'unknownsetting' => array(
1188
					'custom' => 'prop',
1189
				),
1190
				'blogterminated' => array(
1191
					'custom' => 'prop',
1192
				),
1193
			),
1194
		) );
1195
		$this->assertInstanceOf( 'WP_Error', $r['setting_validities']['unknownsetting'] );
1196
		$this->assertEquals( 'unrecognized', $r['setting_validities']['unknownsetting']->get_error_code() );
1197
		$this->assertInstanceOf( 'WP_Error', $r['setting_validities']['blogterminated'] );
1198
		$this->assertEquals( 'unauthorized', $r['setting_validities']['blogterminated']->get_error_code() );
1199
		$this->assertEquals( $data, get_post( $wp_customize->changeset_post_id() )->post_content );
1200
1201
		// Test submitting data with changed and unchanged settings, creating a new instance so that the post_values are cleared.
1202
		wp_set_current_user( $other_admin_user_id );
1203
		$wp_customize = $this->create_test_manager( $uuid );
1204
		$r = $wp_customize->save_changeset_post( array(
1205
			'status' => 'auto-draft',
1206
			'data' => array(
1207
				'blogname' => array(
1208
					'value' => 'Admin 1 Title', // Unchanged value.
1209
				),
1210
				'blogdescription' => array(
1211
					'value' => 'Admin 1 Tagline Changed', // Changed value.
1212
				),
1213
				'blogfounded' => array(
1214
					'extra' => 'blogfounded_param', // New param.
1215
				),
1216
				'scratchpad' => array(
1217
					'value' => 'Admin 1 Scratch', // Unchanged value.
1218
					'extra' => 'background_scratchpad2', // New param.
1219
				),
1220
			),
1221
		) );
1222
1223
		// Note that blogfounded is not included among setting_validities because no value was supplied and it is not unrecognized/unauthorized.
1224
		$this->assertEquals( array_fill_keys( array( 'blogname', 'blogdescription', 'scratchpad' ), true ), $r['setting_validities'], 'Expected blogname even though unchanged.' );
1225
1226
		$data = json_decode( get_post( $wp_customize->changeset_post_id() )->post_content, true );
1227
1228
		$this->assertEquals( self::$admin_user_id, $data['blogname']['user_id'], 'Expected unchanged user_id since value was unchanged.' );
1229
		$this->assertEquals( $other_admin_user_id, $data['blogdescription']['user_id'] );
1230
		$this->assertEquals( $other_admin_user_id, $data['blogfounded']['user_id'] );
1231
		$this->assertEquals( $other_admin_user_id, $data['scratchpad']['user_id'] );
1232
	}
1233
1234
	/**
1235
	 * Test writing changesets and publishing with users who can unfiltered_html and those who cannot.
1236
	 *
1237
	 * @ticket 38705
1238
	 * @covers WP_Customize_Manager::save_changeset_post()
1239
	 */
1240
	function test_save_changeset_post_with_varying_unfiltered_html_cap() {
1241
		global $wp_customize;
1242
		grant_super_admin( self::$admin_user_id );
1243
		$this->assertTrue( user_can( self::$admin_user_id, 'unfiltered_html' ) );
1244
		$this->assertFalse( user_can( self::$subscriber_user_id, 'unfiltered_html' ) );
1245
		wp_set_current_user( 0 );
1246
		add_action( 'customize_register', array( $this, 'register_scratchpad_setting' ) );
1247
1248
		// Attempt scratchpad with user who has unfiltered_html.
1249
		update_option( 'scratchpad', '' );
1250
		$wp_customize = new WP_Customize_Manager();
1251
		do_action( 'customize_register', $wp_customize );
1252
		$wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
1253
		$wp_customize->save_changeset_post( array(
1254
			'status' => 'auto-draft',
1255
			'user_id' => self::$admin_user_id,
1256
		) );
1257
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $wp_customize->changeset_uuid() ) );
1258
		do_action( 'customize_register', $wp_customize );
1259
		$wp_customize->save_changeset_post( array( 'status' => 'publish' ) );
1260
		$this->assertEquals( 'Unfiltered<script>evil</script>', get_option( 'scratchpad' ) );
1261
1262
		// Attempt scratchpad with user who doesn't have unfiltered_html.
1263
		update_option( 'scratchpad', '' );
1264
		$wp_customize = new WP_Customize_Manager();
1265
		do_action( 'customize_register', $wp_customize );
1266
		$wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
1267
		$wp_customize->save_changeset_post( array(
1268
			'status' => 'auto-draft',
1269
			'user_id' => self::$subscriber_user_id,
1270
		) );
1271
		$wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $wp_customize->changeset_uuid() ) );
1272
		do_action( 'customize_register', $wp_customize );
1273
		$wp_customize->save_changeset_post( array( 'status' => 'publish' ) );
1274
		$this->assertEquals( 'Unfilteredevil', get_option( 'scratchpad' ) );
1275
1276
		// Attempt publishing scratchpad as anonymous user when changeset was set by privileged user.
1277
		update_option( 'scratchpad', '' );
1278
		$wp_customize = new WP_Customize_Manager();
1279
		do_action( 'customize_register', $wp_customize );
1280
		$wp_customize->set_post_value( 'scratchpad', 'Unfiltered<script>evil</script>' );
1281
		$wp_customize->save_changeset_post( array(
1282
			'status' => 'auto-draft',
1283
			'user_id' => self::$admin_user_id,
1284
		) );
1285
		$changeset_post_id = $wp_customize->changeset_post_id();
1286
		wp_set_current_user( 0 );
1287
		$wp_customize = null;
1288
		unset( $GLOBALS['wp_actions']['customize_register'] );
1289
		$this->assertEquals( 'Unfilteredevil', apply_filters( 'content_save_pre', 'Unfiltered<script>evil</script>' ) );
1290
		wp_publish_post( $changeset_post_id ); // @todo If wp_update_post() is used here, then kses will corrupt the post_content.
1291
		$this->assertEquals( 'Unfiltered<script>evil</script>', get_option( 'scratchpad' ) );
1292
	}
1293
1294
	/**
1295
	 * Register scratchpad setting.
1296
	 *
1297
	 * @param WP_Customize_Manager $wp_customize Manager.
1298
	 */
1299
	function register_scratchpad_setting( WP_Customize_Manager $wp_customize ) {
1300
		$wp_customize->add_setting( 'scratchpad', array(
1301
			'type' => 'option',
1302
			'capability' => 'exist',
1303
			'sanitize_callback' => array( $this, 'filter_sanitize_scratchpad' ),
1304
		) );
1305
	}
1306
1307
	/**
1308
	 * Sanitize scratchpad as if it is post_content so kses filters apply.
1309
	 *
1310
	 * @param string $value Value.
1311
	 * @return string Value.
1312
	 */
1313
	function filter_sanitize_scratchpad( $value ) {
1314
		return apply_filters( 'content_save_pre', $value );
1315
	}
1316
1317
	/**
1318
	 * Current user when settings are filtered.
1319
	 *
1320
	 * @var array
1321
	 */
1322
	protected $filtered_setting_current_user_ids = array();
1323
1324
	/**
1325
	 * Filter setting to capture the current user when the filter applies.
1326
	 *
1327
	 * @param mixed                $value   Setting value.
1328
	 * @param WP_Customize_Setting $setting Setting.
1329
	 * @return mixed Value.
1330
	 */
1331
	function filter_customize_setting_to_log_current_user( $value, $setting ) {
1332
		$this->filtered_setting_current_user_ids[ $setting->id ] = get_current_user_id();
1333
		return $value;
1334
	}
1335
1336
	/**
1337
	 * Test WP_Customize_Manager::is_cross_domain().
1338
	 *
1339
	 * @ticket 30937
1340
	 * @covers WP_Customize_Manager::is_cross_domain()
1341
	 */
1342
	function test_is_cross_domain() {
1343
		$wp_customize = new WP_Customize_Manager();
1344
1345
		update_option( 'home', 'http://example.com' );
1346
		update_option( 'siteurl', 'http://example.com' );
1347
		$this->assertFalse( $wp_customize->is_cross_domain() );
1348
1349
		update_option( 'home', 'http://example.com' );
1350
		update_option( 'siteurl', 'https://admin.example.com' );
1351
		$this->assertTrue( $wp_customize->is_cross_domain() );
1352
	}
1353
1354
	/**
1355
	 * Test WP_Customize_Manager::get_allowed_urls().
1356
	 *
1357
	 * @ticket 30937
1358
	 * @covers WP_Customize_Manager::get_allowed_urls()
1359
	 */
1360
	function test_get_allowed_urls() {
1361
		$wp_customize = new WP_Customize_Manager();
1362
		$this->assertFalse( is_ssl() );
1363
		$this->assertFalse( $wp_customize->is_cross_domain() );
1364
		$allowed = $wp_customize->get_allowed_urls();
1365
		$this->assertEquals( $allowed, array( home_url( '/', 'http' ) ) );
1366
1367
		add_filter( 'customize_allowed_urls', array( $this, 'filter_customize_allowed_urls' ) );
1368
		$allowed = $wp_customize->get_allowed_urls();
1369
		$this->assertEqualSets( $allowed, array( 'http://headless.example.com/', home_url( '/', 'http' ) ) );
1370
	}
1371
1372
	/**
1373
	 * Callback for customize_allowed_urls filter.
1374
	 *
1375
	 * @param array $urls URLs.
1376
	 * @return array URLs.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1377
	 */
1378
	function filter_customize_allowed_urls( $urls ) {
1379
		$urls[] = 'http://headless.example.com/';
1380
		return $urls;
1381
	}
1382
1383
	/**
1384
	 * Test WP_Customize_Manager::doing_ajax().
1385
	 *
1386
	 * @group ajax
1387
	 */
1388
	function test_doing_ajax() {
1389
		if ( ! defined( 'DOING_AJAX' ) ) {
1390
			define( 'DOING_AJAX', true );
1391
		}
1392
1393
		$manager = $this->manager;
1394
		$this->assertTrue( $manager->doing_ajax() );
1395
1396
		$_REQUEST['action'] = 'customize_save';
1397
		$this->assertTrue( $manager->doing_ajax( 'customize_save' ) );
1398
		$this->assertFalse( $manager->doing_ajax( 'update-widget' ) );
1399
	}
1400
1401
	/**
1402
	 * Test ! WP_Customize_Manager::doing_ajax().
1403
	 */
1404
	function test_not_doing_ajax() {
1405
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
1406
			$this->markTestSkipped( 'Cannot test when DOING_AJAX' );
1407
		}
1408
1409
		$manager = $this->manager;
1410
		$this->assertFalse( $manager->doing_ajax() );
1411
	}
1412
1413
	/**
1414
	 * Test WP_Customize_Manager::unsanitized_post_values().
1415
	 *
1416
	 * @ticket 30988
1417
	 */
1418
	function test_unsanitized_post_values_from_input() {
1419
		wp_set_current_user( self::$admin_user_id );
1420
		$manager = $this->manager;
1421
1422
		$customized = array(
1423
			'foo' => 'bar',
1424
			'baz[quux]' => 123,
1425
		);
1426
		$_POST['customized'] = wp_slash( wp_json_encode( $customized ) );
0 ignored issues
show
Security Bug introduced by
It seems like wp_json_encode($customized) targeting wp_json_encode() can also be of type false; however, wp_slash() does only seem to accept string|array, did you maybe forget to handle an error condition?
Loading history...
1427
		$post_values = $manager->unsanitized_post_values();
1428
		$this->assertEquals( $customized, $post_values );
1429
		$this->assertEmpty( $manager->unsanitized_post_values( array( 'exclude_post_data' => true ) ) );
1430
1431
		$manager->set_post_value( 'foo', 'BAR' );
1432
		$post_values = $manager->unsanitized_post_values();
1433
		$this->assertEquals( 'BAR', $post_values['foo'] );
1434
		$this->assertEmpty( $manager->unsanitized_post_values( array( 'exclude_post_data' => true ) ) );
1435
1436
		// If user is unprivileged, the post data is ignored.
1437
		wp_set_current_user( 0 );
1438
		$this->assertEmpty( $manager->unsanitized_post_values() );
1439
	}
1440
1441
	/**
1442
	 * Test WP_Customize_Manager::unsanitized_post_values().
1443
	 *
1444
	 * @ticket 30937
1445
	 * @covers WP_Customize_Manager::unsanitized_post_values()
1446
	 */
1447
	function test_unsanitized_post_values_with_changeset_and_stashed_theme_mods() {
1448
		wp_set_current_user( self::$admin_user_id );
1449
1450
		$preview_theme = $this->get_inactive_core_theme();
1451
		$stashed_theme_mods = array(
1452
			$preview_theme => array(
1453
				'background_color' => array(
1454
					'value' => '#000000',
1455
				),
1456
			),
1457
		);
1458
		$stashed_theme_mods[ get_stylesheet() ] = array(
1459
			'background_color' => array(
1460
				'value' => '#FFFFFF',
1461
			),
1462
		);
1463
		update_option( 'customize_stashed_theme_mods', $stashed_theme_mods );
1464
1465
		$post_values = array(
1466
			'blogdescription' => 'Post Input Tagline',
1467
		);
1468
		$_POST['customized'] = wp_slash( wp_json_encode( $post_values ) );
0 ignored issues
show
Security Bug introduced by
It seems like wp_json_encode($post_values) targeting wp_json_encode() can also be of type false; however, wp_slash() does only seem to accept string|array, did you maybe forget to handle an error condition?
Loading history...
1469
1470
		$uuid = wp_generate_uuid4();
1471
		$changeset_data = array(
1472
			'blogname' => array(
1473
				'value' => 'Changeset Title',
1474
			),
1475
			'blogdescription' => array(
1476
				'value' => 'Changeset Tagline',
1477
			),
1478
		);
1479
		$this->factory()->post->create( array(
1480
			'post_type' => 'customize_changeset',
1481
			'post_status' => 'auto-draft',
1482
			'post_name' => $uuid,
1483
			'post_content' => wp_json_encode( $changeset_data ),
1484
		) );
1485
1486
		$manager = new WP_Customize_Manager( array(
1487
			'changeset_uuid' => $uuid,
1488
		) );
1489
		$this->assertTrue( $manager->is_theme_active() );
1490
1491
		$this->assertArrayNotHasKey( 'background_color', $manager->unsanitized_post_values() );
1492
1493
		$this->assertEquals(
1494
			array(
1495
				'blogname' => 'Changeset Title',
1496
				'blogdescription' => 'Post Input Tagline',
1497
			),
1498
			$manager->unsanitized_post_values()
1499
		);
1500
		$this->assertEquals(
1501
			array(
1502
				'blogdescription' => 'Post Input Tagline',
1503
			),
1504
			$manager->unsanitized_post_values( array( 'exclude_changeset' => true ) )
1505
		);
1506
1507
		$manager->set_post_value( 'blogdescription', 'Post Override Tagline' );
1508
		$this->assertEquals(
1509
			array(
1510
				'blogname' => 'Changeset Title',
1511
				'blogdescription' => 'Post Override Tagline',
1512
			),
1513
			$manager->unsanitized_post_values()
1514
		);
1515
1516
		$this->assertEquals(
1517
			array(
1518
				'blogname' => 'Changeset Title',
1519
				'blogdescription' => 'Changeset Tagline',
1520
			),
1521
			$manager->unsanitized_post_values( array( 'exclude_post_data' => true ) )
1522
		);
1523
1524
		$this->assertEmpty( $manager->unsanitized_post_values( array( 'exclude_post_data' => true, 'exclude_changeset' => true ) ) );
1525
1526
		// Test unstashing theme mods.
1527
		$manager = new WP_Customize_Manager( array(
1528
			'changeset_uuid' => $uuid,
1529
			'theme' => $preview_theme,
1530
		) );
1531
		$this->assertFalse( $manager->is_theme_active() );
1532
		$values = $manager->unsanitized_post_values( array( 'exclude_post_data' => true, 'exclude_changeset' => true ) );
1533
		$this->assertNotEmpty( $values );
1534
		$this->assertArrayHasKey( 'background_color', $values );
1535
		$this->assertEquals( '#000000', $values['background_color'] );
1536
1537
		$values = $manager->unsanitized_post_values( array( 'exclude_post_data' => false, 'exclude_changeset' => false ) );
1538
		$this->assertArrayHasKey( 'background_color', $values );
1539
		$this->assertArrayHasKey( 'blogname', $values );
1540
		$this->assertArrayHasKey( 'blogdescription', $values );
1541
	}
1542
1543
	/**
1544
	 * Test the WP_Customize_Manager::post_value() method.
1545
	 *
1546
	 * @ticket 30988
1547
	 */
1548
	function test_post_value() {
1549
		wp_set_current_user( self::$admin_user_id );
1550
		$posted_settings = array(
1551
			'foo' => 'OOF',
1552
		);
1553
		$_POST['customized'] = wp_slash( wp_json_encode( $posted_settings ) );
0 ignored issues
show
Security Bug introduced by
It seems like wp_json_encode($posted_settings) targeting wp_json_encode() can also be of type false; however, wp_slash() does only seem to accept string|array, did you maybe forget to handle an error condition?
Loading history...
1554
1555
		$manager = $this->manager;
1556
1557
		$manager->add_setting( 'foo', array( 'default' => 'foo_default' ) );
1558
		$foo_setting = $manager->get_setting( 'foo' );
1559
		$this->assertEquals( 'foo_default', $manager->get_setting( 'foo' )->value(), 'Expected non-previewed setting to return default when value() method called.' );
1560
		$this->assertEquals( $posted_settings['foo'], $manager->post_value( $foo_setting, 'post_value_foo_default' ), 'Expected post_value($foo_setting) to return value supplied in $_POST[customized][foo]' );
0 ignored issues
show
Bug introduced by
It seems like $foo_setting defined by $manager->get_setting('foo') on line 1558 can be null; however, WP_Customize_Manager::post_value() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1561
1562
		$manager->add_setting( 'bar', array( 'default' => 'bar_default' ) );
1563
		$bar_setting = $manager->get_setting( 'bar' );
1564
		$this->assertEquals( 'post_value_bar_default', $manager->post_value( $bar_setting, 'post_value_bar_default' ), 'Expected post_value($bar_setting, $default) to return $default since no value supplied in $_POST[customized][bar]' );
0 ignored issues
show
Bug introduced by
It seems like $bar_setting defined by $manager->get_setting('bar') on line 1563 can be null; however, WP_Customize_Manager::post_value() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1565
	}
1566
1567
	/**
1568
	 * Test the WP_Customize_Manager::post_value() method for a setting value that fails validation.
1569
	 *
1570
	 * @ticket 34893
1571
	 */
1572
	function test_invalid_post_value() {
1573
		wp_set_current_user( self::$admin_user_id );
1574
		$default_value = 'foo_default';
1575
		$setting = $this->manager->add_setting( 'foo', array(
1576
			'validate_callback' => array( $this, 'filter_customize_validate_foo' ),
1577
			'sanitize_callback' => array( $this, 'filter_customize_sanitize_foo' ),
1578
		) );
1579
		$this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
1580
		$this->assertEquals( $default_value, $setting->post_value( $default_value ) );
1581
1582
		$post_value = 'bar';
1583
		$this->manager->set_post_value( 'foo', $post_value );
1584
		$this->assertEquals( strtoupper( $post_value ), $this->manager->post_value( $setting, $default_value ) );
1585
		$this->assertEquals( strtoupper( $post_value ), $setting->post_value( $default_value ) );
1586
1587
		$this->manager->set_post_value( 'foo', 'return_wp_error_in_sanitize' );
1588
		$this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
1589
		$this->assertEquals( $default_value, $setting->post_value( $default_value ) );
1590
1591
		$this->manager->set_post_value( 'foo', 'return_null_in_sanitize' );
1592
		$this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
1593
		$this->assertEquals( $default_value, $setting->post_value( $default_value ) );
1594
1595
		$post_value = '<script>evil</script>';
1596
		$this->manager->set_post_value( 'foo', $post_value );
1597
		$this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
1598
		$this->assertEquals( $default_value, $setting->post_value( $default_value ) );
1599
	}
1600
1601
	/**
1602
	 * Filter customize_validate callback.
1603
	 *
1604
	 * @param mixed $value Value.
1605
	 * @return string|WP_Error
0 ignored issues
show
Documentation introduced by
Should the return type not be null|object|integer|double|array|boolean|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
1606
	 */
1607
	function filter_customize_sanitize_foo( $value ) {
1608
		if ( 'return_null_in_sanitize' === $value ) {
1609
			$value = null;
1610
		} elseif ( is_string( $value ) ) {
1611
			$value = strtoupper( $value );
1612
			if ( false !== stripos( $value, 'return_wp_error_in_sanitize' ) ) {
1613
				$value = new WP_Error( 'invalid_value_in_sanitize', __( 'Invalid value.' ), array( 'source' => 'filter_customize_sanitize_foo' ) );
1614
			}
1615
		}
1616
		return $value;
1617
	}
1618
1619
	/**
1620
	 * Filter customize_validate callback.
1621
	 *
1622
	 * @param WP_Error $validity Validity.
1623
	 * @param mixed    $value    Value.
1624
	 * @return WP_Error
1625
	 */
1626
	function filter_customize_validate_foo( $validity, $value ) {
1627
		if ( false !== stripos( $value, '<script' ) ) {
1628
			$validity->add( 'invalid_value_in_validate', __( 'Invalid value.' ), array( 'source' => 'filter_customize_validate_foo' ) );
1629
		}
1630
		return $validity;
1631
	}
1632
1633
	/**
1634
	 * Test the WP_Customize_Manager::post_value() method to make sure that the validation and sanitization are done in the right order.
1635
	 *
1636
	 * @ticket 37247
1637
	 */
1638
	function test_post_value_validation_sanitization_order() {
1639
		wp_set_current_user( self::$admin_user_id );
1640
		$default_value = '0';
1641
		$setting = $this->manager->add_setting( 'numeric', array(
1642
			'validate_callback' => array( $this, 'filter_customize_validate_numeric' ),
1643
			'sanitize_callback' => array( $this, 'filter_customize_sanitize_numeric' ),
1644
		) );
1645
		$this->assertEquals( $default_value, $this->manager->post_value( $setting, $default_value ) );
1646
		$this->assertEquals( $default_value, $setting->post_value( $default_value ) );
1647
1648
		$post_value = '42';
1649
		$this->manager->set_post_value( 'numeric', $post_value );
1650
		$this->assertEquals( $post_value, $this->manager->post_value( $setting, $default_value ) );
1651
		$this->assertEquals( $post_value, $setting->post_value( $default_value ) );
1652
	}
1653
1654
	/**
1655
	 * Filter customize_validate callback for a numeric value.
1656
	 *
1657
	 * @param mixed $value Value.
1658
	 * @return string|WP_Error
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1659
	 */
1660
	function filter_customize_sanitize_numeric( $value ) {
1661
		return absint( $value );
1662
	}
1663
1664
	/**
1665
	 * Filter customize_validate callback for a numeric value.
1666
	 *
1667
	 * @param WP_Error $validity Validity.
1668
	 * @param mixed    $value    Value.
1669
	 * @return WP_Error
1670
	 */
1671
	function filter_customize_validate_numeric( $validity, $value ) {
1672
		if ( ! is_string( $value ) || ! is_numeric( $value ) ) {
1673
			$validity->add( 'invalid_value_in_validate', __( 'Invalid value.' ), array( 'source' => 'filter_customize_validate_numeric' ) );
1674
		}
1675
		return $validity;
1676
	}
1677
1678
	/**
1679
	 * Test WP_Customize_Manager::validate_setting_values().
1680
	 *
1681
	 * @see WP_Customize_Manager::validate_setting_values()
1682
	 */
1683
	function test_validate_setting_values() {
1684
		wp_set_current_user( self::$admin_user_id );
1685
		$setting = $this->manager->add_setting( 'foo', array(
1686
			'validate_callback' => array( $this, 'filter_customize_validate_foo' ),
1687
			'sanitize_callback' => array( $this, 'filter_customize_sanitize_foo' ),
1688
		) );
1689
1690
		$post_value = 'bar';
1691
		$this->manager->set_post_value( 'foo', $post_value );
1692
		$validities = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
1693
		$this->assertCount( 1, $validities );
1694
		$this->assertEquals( array( 'foo' => true ), $validities );
1695
1696
		$this->manager->set_post_value( 'foo', 'return_wp_error_in_sanitize' );
1697
		$invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
1698
		$this->assertCount( 1, $invalid_settings );
1699
		$this->assertArrayHasKey( $setting->id, $invalid_settings );
1700
		$this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] );
1701
		$error = $invalid_settings[ $setting->id ];
1702
		$this->assertEquals( 'invalid_value_in_sanitize', $error->get_error_code() );
1703
		$this->assertEquals( array( 'source' => 'filter_customize_sanitize_foo' ), $error->get_error_data() );
1704
1705
		$this->manager->set_post_value( 'foo', 'return_null_in_sanitize' );
1706
		$invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
1707
		$this->assertCount( 1, $invalid_settings );
1708
		$this->assertArrayHasKey( $setting->id, $invalid_settings );
1709
		$this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] );
1710
		$this->assertNull( $invalid_settings[ $setting->id ]->get_error_data() );
1711
1712
		$post_value = '<script>evil</script>';
1713
		$this->manager->set_post_value( 'foo', $post_value );
1714
		$invalid_settings = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
1715
		$this->assertCount( 1, $invalid_settings );
1716
		$this->assertArrayHasKey( $setting->id, $invalid_settings );
1717
		$this->assertInstanceOf( 'WP_Error', $invalid_settings[ $setting->id ] );
1718
		$error = $invalid_settings[ $setting->id ];
1719
		$this->assertEquals( 'invalid_value_in_validate', $error->get_error_code() );
1720
		$this->assertEquals( array( 'source' => 'filter_customize_validate_foo' ), $error->get_error_data() );
1721
	}
1722
1723
	/**
1724
	 * Test WP_Customize_Manager::validate_setting_values().
1725
	 *
1726
	 * @ticket 37638
1727
	 * @covers WP_Customize_Manager::validate_setting_values()
1728
	 */
1729
	function test_late_validate_setting_values() {
1730
		$setting = new Test_Setting_Without_Applying_Validate_Filter( $this->manager, 'required' );
1731
		$this->manager->add_setting( $setting );
1732
1733
		$this->assertInstanceOf( 'WP_Error', $setting->validate( '' ) );
1734
		$setting_validities = $this->manager->validate_setting_values( array( $setting->id => '' ) );
1735
		$this->assertInstanceOf( 'WP_Error', $setting_validities[ $setting->id ] );
1736
1737
		$this->assertTrue( $setting->validate( 'ok' ) );
1738
		$setting_validities = $this->manager->validate_setting_values( array( $setting->id => 'ok' ) );
1739
		$this->assertTrue( $setting_validities[ $setting->id ] );
1740
1741
		add_filter( "customize_validate_{$setting->id}", array( $this, 'late_validate_length' ), 10, 3 );
1742
		$this->assertTrue( $setting->validate( 'bad' ) );
1743
		$setting_validities = $this->manager->validate_setting_values( array( $setting->id => 'bad' ) );
1744
		$validity = $setting_validities[ $setting->id ];
1745
		$this->assertInstanceOf( 'WP_Error', $validity );
1746
		$this->assertEquals( 'minlength', $validity->get_error_code() );
1747
	}
1748
1749
	/**
1750
	 * Test WP_Customize_Manager::validate_setting_values().
1751
	 *
1752
	 * @ticket 30937
1753
	 * @covers WP_Customize_Manager::validate_setting_values()
1754
	 */
1755
	function test_validate_setting_values_args() {
1756
		wp_set_current_user( self::$admin_user_id );
1757
		$this->manager->register_controls();
1758
1759
		$validities = $this->manager->validate_setting_values( array( 'unknown' => 'X' ) );
1760
		$this->assertEmpty( $validities );
1761
1762
		$validities = $this->manager->validate_setting_values( array( 'unknown' => 'X' ), array( 'validate_existence' => false ) );
1763
		$this->assertEmpty( $validities );
1764
1765
		$validities = $this->manager->validate_setting_values( array( 'unknown' => 'X' ), array( 'validate_existence' => true ) );
1766
		$this->assertNotEmpty( $validities );
1767
		$this->assertArrayHasKey( 'unknown', $validities );
1768
		$error = $validities['unknown'];
1769
		$this->assertInstanceOf( 'WP_Error', $error );
1770
		$this->assertEquals( 'unrecognized', $error->get_error_code() );
1771
1772
		$this->manager->get_setting( 'blogname' )->capability = 'do_not_allow';
1773
		$validities = $this->manager->validate_setting_values( array( 'blogname' => 'X' ), array( 'validate_capability' => false ) );
1774
		$this->assertArrayHasKey( 'blogname', $validities );
1775
		$this->assertTrue( $validities['blogname'] );
1776
		$validities = $this->manager->validate_setting_values( array( 'blogname' => 'X' ), array( 'validate_capability' => true ) );
1777
		$this->assertArrayHasKey( 'blogname', $validities );
1778
		$error = $validities['blogname'];
1779
		$this->assertInstanceOf( 'WP_Error', $error );
1780
		$this->assertEquals( 'unauthorized', $error->get_error_code() );
1781
	}
1782
1783
	/**
1784
	 * Add a length constraint to a setting.
1785
	 *
1786
	 * Adds minimum-length error code if the length is less than 10.
1787
	 *
1788
	 * @param WP_Error             $validity Validity.
1789
	 * @param mixed                $value    Value.
1790
	 * @param WP_Customize_Setting $setting  Setting.
1791
	 * @return WP_Error Validity.
1792
	 */
1793
	function late_validate_length( $validity, $value, $setting ) {
1794
		$this->assertInstanceOf( 'WP_Customize_Setting', $setting );
1795
		if ( strlen( $value ) < 10 ) {
1796
			$validity->add( 'minlength', '' );
1797
		}
1798
		return $validity;
1799
	}
1800
1801
	/**
1802
	 * Test the WP_Customize_Manager::validate_setting_values() method to make sure that the validation and sanitization are done in the right order.
1803
	 *
1804
	 * @ticket 37247
1805
	 */
1806
	function test_validate_setting_values_validation_sanitization_order() {
1807
		wp_set_current_user( self::$admin_user_id );
1808
		$setting = $this->manager->add_setting( 'numeric', array(
1809
			'validate_callback' => array( $this, 'filter_customize_validate_numeric' ),
1810
			'sanitize_callback' => array( $this, 'filter_customize_sanitize_numeric' ),
1811
		) );
1812
		$post_value = '42';
1813
		$this->manager->set_post_value( 'numeric', $post_value );
1814
		$validities = $this->manager->validate_setting_values( $this->manager->unsanitized_post_values() );
1815
		$this->assertCount( 1, $validities );
1816
		$this->assertEquals( array( 'numeric' => true ), $validities );
1817
	}
1818
1819
	/**
1820
	 * Test WP_Customize_Manager::prepare_setting_validity_for_js().
1821
	 *
1822
	 * @see WP_Customize_Manager::prepare_setting_validity_for_js()
1823
	 */
1824
	function test_prepare_setting_validity_for_js() {
1825
		$this->assertTrue( $this->manager->prepare_setting_validity_for_js( true ) );
1826
		$error = new WP_Error();
1827
		$error->add( 'bad_letter', 'Bad letter', 'A' );
1828
		$error->add( 'bad_letter', 'Bad letra', 123 );
1829
		$error->add( 'bad_number', 'Bad number', array( 'number' => 123 ) );
1830
		$validity = $this->manager->prepare_setting_validity_for_js( $error );
1831
		$this->assertInternalType( 'array', $validity );
1832
		foreach ( $error->errors as $code => $messages ) {
1833
			$this->assertArrayHasKey( $code, $validity );
1834
			$this->assertInternalType( 'array', $validity[ $code ] );
1835
			$this->assertEquals( join( ' ', $messages ), $validity[ $code ]['message'] );
1836
			$this->assertArrayHasKey( 'data', $validity[ $code ] );
1837
			$this->assertEquals( $validity[ $code ]['data'], $error->get_error_data( $code ) );
1838
		}
1839
		$this->assertArrayHasKey( 'number', $validity['bad_number']['data'] );
1840
		$this->assertEquals( 123, $validity['bad_number']['data']['number'] );
1841
	}
1842
1843
	/**
1844
	 * Test WP_Customize_Manager::set_post_value().
1845
	 *
1846
	 * @see WP_Customize_Manager::set_post_value()
1847
	 */
1848
	function test_set_post_value() {
1849
		wp_set_current_user( self::$admin_user_id );
1850
		$this->manager->add_setting( 'foo', array(
1851
			'sanitize_callback' => array( $this, 'sanitize_foo_for_test_set_post_value' ),
1852
		) );
1853
		$setting = $this->manager->get_setting( 'foo' );
1854
1855
		$this->assertEmpty( $this->captured_customize_post_value_set_actions );
1856
		add_action( 'customize_post_value_set', array( $this, 'capture_customize_post_value_set_actions' ), 10, 3 );
1857
		add_action( 'customize_post_value_set_foo', array( $this, 'capture_customize_post_value_set_actions' ), 10, 2 );
1858
		$this->manager->set_post_value( $setting->id, '123abc' );
1859
		$this->assertCount( 2, $this->captured_customize_post_value_set_actions );
1860
		$this->assertEquals( 'customize_post_value_set_foo', $this->captured_customize_post_value_set_actions[0]['action'] );
1861
		$this->assertEquals( 'customize_post_value_set', $this->captured_customize_post_value_set_actions[1]['action'] );
1862
		$this->assertEquals( array( '123abc', $this->manager ), $this->captured_customize_post_value_set_actions[0]['args'] );
1863
		$this->assertEquals( array( $setting->id, '123abc', $this->manager ), $this->captured_customize_post_value_set_actions[1]['args'] );
1864
1865
		$unsanitized = $this->manager->unsanitized_post_values();
1866
		$this->assertArrayHasKey( $setting->id, $unsanitized );
1867
1868
		$this->assertEquals( '123abc', $unsanitized[ $setting->id ] );
1869
		$this->assertEquals( 123, $setting->post_value() );
1870
	}
1871
1872
	/**
1873
	 * Sanitize a value for Tests_WP_Customize_Manager::test_set_post_value().
1874
	 *
1875
	 * @see Tests_WP_Customize_Manager::test_set_post_value()
1876
	 *
1877
	 * @param mixed $value Value.
1878
	 * @return int Value.
1879
	 */
1880
	function sanitize_foo_for_test_set_post_value( $value ) {
1881
		return intval( $value );
1882
	}
1883
1884
	/**
1885
	 * Store data coming from customize_post_value_set action calls.
1886
	 *
1887
	 * @see Tests_WP_Customize_Manager::capture_customize_post_value_set_actions()
1888
	 * @var array
1889
	 */
1890
	protected $captured_customize_post_value_set_actions = array();
1891
1892
	/**
1893
	 * Capture the actions fired when calling WP_Customize_Manager::set_post_value().
1894
	 *
1895
	 * @see Tests_WP_Customize_Manager::test_set_post_value()
1896
	 */
1897
	function capture_customize_post_value_set_actions() {
1898
		$action = current_action();
1899
		$args = func_get_args();
1900
		$this->captured_customize_post_value_set_actions[] = compact( 'action', 'args' );
1901
	}
1902
1903
	/**
1904
	 * Test the WP_Customize_Manager::add_dynamic_settings() method.
1905
	 *
1906
	 * @ticket 30936
1907
	 */
1908
	function test_add_dynamic_settings() {
1909
		$manager = $this->manager;
1910
		$setting_ids = array( 'foo', 'bar' );
1911
		$manager->add_setting( 'foo', array( 'default' => 'foo_default' ) );
1912
		$this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected there to not be a bar setting up front.' );
1913
		$manager->add_dynamic_settings( $setting_ids );
1914
		$this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected the bar setting to remain absent since filters not added.' );
1915
1916
		$this->action_customize_register_for_dynamic_settings();
1917
		$manager->add_dynamic_settings( $setting_ids );
1918
		$this->assertNotEmpty( $manager->get_setting( 'bar' ), 'Expected bar setting to be created since filters were added.' );
1919
		$this->assertEquals( 'foo_default', $manager->get_setting( 'foo' )->default, 'Expected static foo setting to not get overridden by dynamic setting.' );
1920
		$this->assertEquals( 'dynamic_bar_default', $manager->get_setting( 'bar' )->default, 'Expected dynamic setting bar to have default providd by filter.' );
1921
	}
1922
1923
	/**
1924
	 * Test WP_Customize_Manager::has_published_pages().
1925
	 *
1926
	 * @ticket 38013
1927
	 * @covers WP_Customize_Manager::has_published_pages()
1928
	 */
1929
	function test_has_published_pages() {
1930
		foreach ( get_pages() as $page ) {
0 ignored issues
show
Bug introduced by
The expression get_pages() of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1931
			wp_delete_post( $page->ID, true );
1932
		}
1933
		$this->assertFalse( $this->manager->has_published_pages() );
1934
1935
		$this->factory()->post->create( array( 'post_type' => 'page', 'post_status' => 'private' ) );
1936
		$this->assertFalse( $this->manager->has_published_pages() );
1937
1938
		$this->factory()->post->create( array( 'post_type' => 'page', 'post_status' => 'publish' ) );
1939
		$this->assertTrue( $this->manager->has_published_pages() );
1940
	}
1941
1942
	/**
1943
	 * Ensure that page stubs created via nav menus will cause has_published_pages to return true.
1944
	 *
1945
	 * @ticket 38013
1946
	 * @covers WP_Customize_Manager::has_published_pages()
1947
	 */
1948
	function test_has_published_pages_when_nav_menus_created_posts() {
1949
		foreach ( get_pages() as $page ) {
0 ignored issues
show
Bug introduced by
The expression get_pages() of type array|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1950
			wp_delete_post( $page->ID, true );
1951
		}
1952
		$this->assertFalse( $this->manager->has_published_pages() );
1953
1954
		wp_set_current_user( self::$admin_user_id );
1955
		$this->manager->nav_menus->customize_register();
1956
		$setting_id = 'nav_menus_created_posts';
1957
		$setting = $this->manager->get_setting( $setting_id );
1958
		$this->assertInstanceOf( 'WP_Customize_Filter_Setting', $setting );
1959
		$auto_draft_page = $this->factory()->post->create( array( 'post_type' => 'page', 'post_status' => 'auto-draft' ) );
1960
		$this->manager->set_post_value( $setting_id, array( $auto_draft_page ) );
1961
		$setting->preview();
1962
		$this->assertTrue( $this->manager->has_published_pages() );
1963
	}
1964
1965
	/**
1966
	 * Test the WP_Customize_Manager::register_dynamic_settings() method.
1967
	 *
1968
	 * This is similar to test_add_dynamic_settings, except the settings are passed via $_POST['customized'].
1969
	 *
1970
	 * @ticket 30936
1971
	 */
1972
	function test_register_dynamic_settings() {
1973
		wp_set_current_user( self::$admin_user_id );
1974
		$posted_settings = array(
1975
			'foo' => 'OOF',
1976
			'bar' => 'RAB',
1977
		);
1978
		$_POST['customized'] = wp_slash( wp_json_encode( $posted_settings ) );
0 ignored issues
show
Security Bug introduced by
It seems like wp_json_encode($posted_settings) targeting wp_json_encode() can also be of type false; however, wp_slash() does only seem to accept string|array, did you maybe forget to handle an error condition?
Loading history...
1979
1980
		add_action( 'customize_register', array( $this, 'action_customize_register_for_dynamic_settings' ) );
1981
1982
		$manager = $this->manager;
1983
		$manager->add_setting( 'foo', array( 'default' => 'foo_default' ) );
1984
1985
		$this->assertEmpty( $manager->get_setting( 'bar' ), 'Expected dynamic setting "bar" to not be registered.' );
1986
		do_action( 'customize_register', $manager );
1987
		$this->assertNotEmpty( $manager->get_setting( 'bar' ), 'Expected dynamic setting "bar" to be automatically registered after customize_register action.' );
1988
		$this->assertEmpty( $manager->get_setting( 'baz' ), 'Expected unrecognized dynamic setting "baz" to remain unregistered.' );
1989
	}
1990
1991
	/**
1992
	 * In lieu of closures, callback for customize_register action added in test_register_dynamic_settings().
1993
	 */
1994
	function action_customize_register_for_dynamic_settings() {
1995
		add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_customize_dynamic_setting_args_for_test_dynamic_settings' ), 10, 2 );
1996
		add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_customize_dynamic_setting_class_for_test_dynamic_settings' ), 10, 3 );
1997
	}
1998
1999
	/**
2000
	 * In lieu of closures, callback for customize_dynamic_setting_args filter added for test_register_dynamic_settings().
2001
	 *
2002
	 * @param array  $setting_args Setting args.
2003
	 * @param string $setting_id   Setting ID.
2004
	 * @return array
2005
	 */
2006
	function filter_customize_dynamic_setting_args_for_test_dynamic_settings( $setting_args, $setting_id ) {
2007
		$this->assertInternalType( 'string', $setting_id );
2008
		if ( in_array( $setting_id, array( 'foo', 'bar' ) ) ) {
2009
			$setting_args = array( 'default' => "dynamic_{$setting_id}_default" );
2010
		}
2011
		return $setting_args;
2012
	}
2013
2014
	/**
2015
	 * In lieu of closures, callback for customize_dynamic_setting_class filter added for test_register_dynamic_settings().
2016
	 *
2017
	 * @param string $setting_class Setting class.
2018
	 * @param string $setting_id    Setting ID.
2019
	 * @param array  $setting_args  Setting args.
2020
	 * @return string
2021
	 */
2022
	function filter_customize_dynamic_setting_class_for_test_dynamic_settings( $setting_class, $setting_id, $setting_args ) {
2023
		$this->assertEquals( 'WP_Customize_Setting', $setting_class );
2024
		$this->assertInternalType( 'string', $setting_id );
2025
		$this->assertInternalType( 'array', $setting_args );
2026
		return $setting_class;
2027
	}
2028
2029
	/**
2030
	 * Test get_document_title_template() method.
2031
	 *
2032
	 * @see WP_Customize_Manager::get_document_title_template()
2033
	 */
2034
	function test_get_document_title_template() {
2035
		$tpl = $this->manager->get_document_title_template();
2036
		$this->assertContains( '%s', $tpl );
2037
	}
2038
2039
	/**
2040
	 * Test get_preview_url()/set_preview_url methods.
2041
	 *
2042
	 * @see WP_Customize_Manager::get_preview_url()
2043
	 * @see WP_Customize_Manager::set_preview_url()
2044
	 */
2045
	function test_preview_url() {
2046
		$this->assertEquals( home_url( '/' ), $this->manager->get_preview_url() );
2047
		$preview_url = home_url( '/foo/bar/baz/' );
2048
		$this->manager->set_preview_url( $preview_url );
2049
		$this->assertEquals( $preview_url, $this->manager->get_preview_url() );
2050
		$this->manager->set_preview_url( 'http://illegalsite.example.com/food/' );
2051
		$this->assertEquals( home_url( '/' ), $this->manager->get_preview_url() );
2052
	}
2053
2054
	/**
2055
	 * Test get_return_url()/set_return_url() methods.
2056
	 *
2057
	 * @see WP_Customize_Manager::get_return_url()
2058
	 * @see WP_Customize_Manager::set_return_url()
2059
	 */
2060
	function test_return_url() {
2061
		wp_set_current_user( self::factory()->user->create( array( 'role' => 'author' ) ) );
2062
		$this->assertEquals( home_url( '/' ), $this->manager->get_return_url() );
2063
2064
		wp_set_current_user( self::$admin_user_id );
2065
		$this->assertTrue( current_user_can( 'edit_theme_options' ) );
2066
		$this->assertEquals( home_url( '/' ), $this->manager->get_return_url() );
2067
2068
		$preview_url = home_url( '/foo/' );
2069
		$this->manager->set_preview_url( $preview_url );
2070
		$this->assertEquals( $preview_url, $this->manager->get_return_url() );
2071
2072
		$_SERVER['HTTP_REFERER'] = wp_slash( admin_url( 'customize.php' ) );
2073
		$this->assertEquals( $preview_url, $this->manager->get_return_url() );
2074
2075
		// See #35355.
2076
		$_SERVER['HTTP_REFERER'] = wp_slash( admin_url( 'wp-login.php' ) );
2077
		$this->assertEquals( $preview_url, $this->manager->get_return_url() );
2078
2079
		$url = home_url( '/referred/' );
2080
		$_SERVER['HTTP_REFERER'] = wp_slash( $url );
2081
		$this->assertEquals( $url, $this->manager->get_return_url() );
2082
2083
		$url = 'http://badreferer.example.com/';
2084
		$_SERVER['HTTP_REFERER'] = wp_slash( $url );
2085
		$this->assertNotEquals( $url, $this->manager->get_return_url() );
2086
		$this->assertEquals( $preview_url, $this->manager->get_return_url() );
2087
2088
		$this->manager->set_return_url( admin_url( 'edit.php?trashed=1' ) );
2089
		$this->assertEquals( admin_url( 'edit.php' ), $this->manager->get_return_url() );
2090
	}
2091
2092
	/**
2093
	 * Test get_autofocus()/set_autofocus() methods.
2094
	 *
2095
	 * @see WP_Customize_Manager::get_autofocus()
2096
	 * @see WP_Customize_Manager::set_autofocus()
2097
	 */
2098
	function test_autofocus() {
2099
		$this->assertEmpty( $this->manager->get_autofocus() );
2100
2101
		$this->manager->set_autofocus( array( 'unrecognized' => 'food' ) );
2102
		$this->assertEmpty( $this->manager->get_autofocus() );
2103
2104
		$autofocus = array( 'control' => 'blogname' );
2105
		$this->manager->set_autofocus( $autofocus );
2106
		$this->assertEquals( $autofocus, $this->manager->get_autofocus() );
2107
2108
		$autofocus = array( 'section' => 'colors' );
2109
		$this->manager->set_autofocus( $autofocus );
2110
		$this->assertEquals( $autofocus, $this->manager->get_autofocus() );
2111
2112
		$autofocus = array( 'panel' => 'widgets' );
2113
		$this->manager->set_autofocus( $autofocus );
2114
		$this->assertEquals( $autofocus, $this->manager->get_autofocus() );
2115
2116
		$autofocus = array( 'control' => array( 'blogname', 'blogdescription' ) );
2117
		$this->manager->set_autofocus( $autofocus );
2118
		$this->assertEmpty( $this->manager->get_autofocus() );
2119
	}
2120
2121
	/**
2122
	 * Test get_nonces() method.
2123
	 *
2124
	 * @see WP_Customize_Manager::get_nonces()
2125
	 */
2126
	function test_nonces() {
2127
		$nonces = $this->manager->get_nonces();
2128
		$this->assertInternalType( 'array', $nonces );
2129
		$this->assertArrayHasKey( 'save', $nonces );
2130
		$this->assertArrayHasKey( 'preview', $nonces );
2131
2132
		add_filter( 'customize_refresh_nonces', array( $this, 'filter_customize_refresh_nonces' ), 10, 2 );
2133
		$nonces = $this->manager->get_nonces();
2134
		$this->assertArrayHasKey( 'foo', $nonces );
2135
		$this->assertEquals( wp_create_nonce( 'foo' ), $nonces['foo'] );
2136
	}
2137
2138
	/**
2139
	 * Filter for customize_refresh_nonces.
2140
	 *
2141
	 * @param array                $nonces  Nonces.
2142
	 * @param WP_Customize_Manager $manager Manager.
2143
	 * @return array Nonces.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2144
	 */
2145
	function filter_customize_refresh_nonces( $nonces, $manager ) {
2146
		$this->assertInstanceOf( 'WP_Customize_Manager', $manager );
2147
		$nonces['foo'] = wp_create_nonce( 'foo' );
2148
		return $nonces;
2149
	}
2150
2151
	/**
2152
	 * Test customize_pane_settings() method.
2153
	 *
2154
	 * @see WP_Customize_Manager::customize_pane_settings()
2155
	 */
2156
	function test_customize_pane_settings() {
2157
		wp_set_current_user( self::$admin_user_id );
2158
		$this->manager->register_controls();
2159
		$this->manager->prepare_controls();
2160
		$autofocus = array( 'control' => 'blogname' );
2161
		$this->manager->set_autofocus( $autofocus );
2162
2163
		ob_start();
2164
		$this->manager->customize_pane_settings();
2165
		$content = ob_get_clean();
2166
2167
		$this->assertContains( 'var _wpCustomizeSettings =', $content );
2168
		$this->assertContains( '"blogname"', $content );
2169
		$this->assertContains( '"type":"option"', $content );
2170
		$this->assertContains( '_wpCustomizeSettings.controls', $content );
2171
		$this->assertContains( '_wpCustomizeSettings.settings', $content );
2172
		$this->assertContains( '</script>', $content );
2173
2174
		$this->assertNotEmpty( preg_match( '#var _wpCustomizeSettings\s*=\s*({.*?});\s*\n#', $content, $matches ) );
2175
		$json = $matches[1];
2176
		$data = json_decode( $json, true );
2177
		$this->assertNotEmpty( $data );
2178
2179
		$this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices', 'changeset', 'timeouts' ), array_keys( $data ) );
2180
		$this->assertEquals( $autofocus, $data['autofocus'] );
2181
		$this->assertArrayHasKey( 'save', $data['nonce'] );
2182
		$this->assertArrayHasKey( 'preview', $data['nonce'] );
2183
	}
2184
2185
	/**
2186
	 * Test remove_frameless_preview_messenger_channel.
2187
	 *
2188
	 * @ticket 38867
2189
	 * @covers WP_Customize_Manager::remove_frameless_preview_messenger_channel()
2190
	 */
2191
	function test_remove_frameless_preview_messenger_channel() {
2192
		wp_set_current_user( self::$admin_user_id );
2193
		$manager = new WP_Customize_Manager( array( 'messenger_channel' => null ) );
2194
		ob_start();
2195
		$manager->remove_frameless_preview_messenger_channel();
0 ignored issues
show
Unused Code introduced by
The call to the method WP_Customize_Manager::re...iew_messenger_channel() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
2196
		$output = ob_get_clean();
2197
		$this->assertEmpty( $output );
2198
2199
		$manager = new WP_Customize_Manager( array( 'messenger_channel' => 'preview-0' ) );
2200
		ob_start();
2201
		$manager->remove_frameless_preview_messenger_channel();
0 ignored issues
show
Unused Code introduced by
The call to the method WP_Customize_Manager::re...iew_messenger_channel() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
2202
		$output = ob_get_clean();
2203
		$this->assertContains( '<script>', $output );
2204
	}
2205
2206
	/**
2207
	 * Test customize_preview_settings() method.
2208
	 *
2209
	 * @see WP_Customize_Manager::customize_preview_settings()
2210
	 */
2211
	function test_customize_preview_settings() {
2212
		wp_set_current_user( self::$admin_user_id );
2213
		$this->manager->register_controls();
2214
		$this->manager->prepare_controls();
2215
		$this->manager->set_post_value( 'foo', 'bar' );
2216
		$_POST['customize_messenger_channel'] = 'preview-0';
2217
2218
		ob_start();
2219
		$this->manager->customize_preview_settings();
2220
		$content = ob_get_clean();
2221
2222
		$this->assertEquals( 1, preg_match( '/var _wpCustomizeSettings = ({.+});/', $content, $matches ) );
2223
		$settings = json_decode( $matches[1], true );
2224
2225
		$this->assertArrayHasKey( 'theme', $settings );
2226
		$this->assertArrayHasKey( 'url', $settings );
2227
		$this->assertArrayHasKey( 'channel', $settings );
2228
		$this->assertArrayHasKey( 'activePanels', $settings );
2229
		$this->assertArrayHasKey( 'activeSections', $settings );
2230
		$this->assertArrayHasKey( 'activeControls', $settings );
2231
		$this->assertArrayHasKey( 'settingValidities', $settings );
2232
		$this->assertArrayHasKey( 'nonce', $settings );
2233
		$this->assertArrayHasKey( '_dirty', $settings );
2234
		$this->assertArrayHasKey( 'timeouts', $settings );
2235
		$this->assertArrayHasKey( 'changeset', $settings );
2236
2237
		$this->assertArrayHasKey( 'preview', $settings['nonce'] );
2238
	}
2239
2240
	/**
2241
	 * @ticket 33552
2242
	 */
2243
	function test_customize_loaded_components_filter() {
2244
		$manager = new WP_Customize_Manager();
2245
		$this->assertInstanceOf( 'WP_Customize_Widgets', $manager->widgets );
2246
		$this->assertInstanceOf( 'WP_Customize_Nav_Menus', $manager->nav_menus );
2247
2248
		add_filter( 'customize_loaded_components', array( $this, 'return_array_containing_widgets' ), 10, 2 );
2249
		$manager = new WP_Customize_Manager();
2250
		$this->assertInstanceOf( 'WP_Customize_Widgets', $manager->widgets );
2251
		$this->assertEmpty( $manager->nav_menus );
2252
		remove_all_filters( 'customize_loaded_components' );
2253
2254
		add_filter( 'customize_loaded_components', array( $this, 'return_array_containing_nav_menus' ), 10, 2 );
2255
		$manager = new WP_Customize_Manager();
2256
		$this->assertInstanceOf( 'WP_Customize_Nav_Menus', $manager->nav_menus );
2257
		$this->assertEmpty( $manager->widgets );
2258
		remove_all_filters( 'customize_loaded_components' );
2259
2260
		add_filter( 'customize_loaded_components', '__return_empty_array' );
2261
		$manager = new WP_Customize_Manager();
2262
		$this->assertEmpty( $manager->widgets );
2263
		$this->assertEmpty( $manager->nav_menus );
2264
		remove_all_filters( 'customize_loaded_components' );
2265
	}
2266
2267
	/**
2268
	 * @see Tests_WP_Customize_Manager::test_customize_loaded_components_filter()
2269
	 *
2270
	 * @param array                $components         Components.
2271
	 * @param WP_Customize_Manager $customize_manager  Manager.
2272
	 *
2273
	 * @return array Components.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2274
	 */
2275 View Code Duplication
	function return_array_containing_widgets( $components, $customize_manager ) {
2276
		$this->assertInternalType( 'array', $components );
2277
		$this->assertContains( 'widgets', $components );
2278
		$this->assertContains( 'nav_menus', $components );
2279
		$this->assertInternalType( 'array', $components );
2280
		$this->assertInstanceOf( 'WP_Customize_Manager', $customize_manager );
2281
		return array( 'widgets' );
2282
	}
2283
2284
	/**
2285
	 * @see Tests_WP_Customize_Manager::test_customize_loaded_components_filter()
2286
	 *
2287
	 * @param array                $components         Components.
2288
	 * @param WP_Customize_Manager $customize_manager  Manager.
2289
	 *
2290
	 * @return array Components.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2291
	 */
2292 View Code Duplication
	function return_array_containing_nav_menus( $components, $customize_manager ) {
2293
		$this->assertInternalType( 'array', $components );
2294
		$this->assertContains( 'widgets', $components );
2295
		$this->assertContains( 'nav_menus', $components );
2296
		$this->assertInternalType( 'array', $components );
2297
		$this->assertInstanceOf( 'WP_Customize_Manager', $customize_manager );
2298
		return array( 'nav_menus' );
2299
	}
2300
2301
	/**
2302
	 * @ticket 30225
2303
	 * @ticket 34594
2304
	 */
2305
	function test_prepare_controls_stable_sorting() {
2306
		$manager = new WP_Customize_Manager();
2307
		$manager->register_controls();
2308
		$section_id = 'foo-section';
2309
		wp_set_current_user( self::$admin_user_id );
2310
		$manager->add_section( $section_id, array(
2311
			'title'      => 'Section',
2312
			'priority'   => 1,
2313
		) );
2314
2315
		$added_control_ids = array();
2316
		$count = 9;
2317
		for ( $i = 0; $i < $count; $i += 1 ) {
2318
			$id = 'sort-test-' . $i;
2319
			$added_control_ids[] = $id;
2320
			$manager->add_setting( $id );
2321
			$control = new WP_Customize_Control( $manager, $id, array(
2322
				'section' => $section_id,
2323
				'priority' => 1,
2324
				'setting' => $id,
2325
			) );
2326
			$manager->add_control( $control );
2327
		}
2328
2329
		$manager->prepare_controls();
2330
2331
		$sorted_control_ids = wp_list_pluck( $manager->get_section( $section_id )->controls, 'id' );
2332
		$this->assertEquals( $added_control_ids, $sorted_control_ids );
2333
	}
2334
2335
	/**
2336
	 * @ticket 34596
2337
	 */
2338 View Code Duplication
	function test_add_section_return_instance() {
2339
		$manager = new WP_Customize_Manager();
2340
		wp_set_current_user( self::$admin_user_id );
2341
2342
		$section_id = 'foo-section';
2343
		$result_section = $manager->add_section( $section_id, array(
2344
			'title'    => 'Section',
2345
			'priority' => 1,
2346
		) );
2347
2348
		$this->assertInstanceOf( 'WP_Customize_Section', $result_section );
2349
		$this->assertEquals( $section_id, $result_section->id );
2350
2351
		$section = new WP_Customize_Section( $manager, $section_id, array(
2352
			'title'    => 'Section 2',
2353
			'priority' => 2,
2354
		) );
2355
		$result_section = $manager->add_section( $section );
2356
2357
		$this->assertInstanceOf( 'WP_Customize_Section', $result_section );
2358
		$this->assertEquals( $section_id, $result_section->id );
2359
		$this->assertEquals( $section, $result_section );
2360
	}
2361
2362
	/**
2363
	 * @ticket 34596
2364
	 */
2365
	function test_add_setting_return_instance() {
2366
		$manager = new WP_Customize_Manager();
2367
		wp_set_current_user( self::$admin_user_id );
2368
2369
		$setting_id = 'foo-setting';
2370
		$result_setting = $manager->add_setting( $setting_id );
2371
2372
		$this->assertInstanceOf( 'WP_Customize_Setting', $result_setting );
2373
		$this->assertEquals( $setting_id, $result_setting->id );
2374
2375
		$setting = new WP_Customize_Setting( $manager, $setting_id );
2376
		$result_setting = $manager->add_setting( $setting );
2377
2378
		$this->assertInstanceOf( 'WP_Customize_Setting', $result_setting );
2379
		$this->assertEquals( $setting, $result_setting );
2380
		$this->assertEquals( $setting_id, $result_setting->id );
2381
	}
2382
2383
	/**
2384
	 * @ticket 34597
2385
	 */
2386
	function test_add_setting_honoring_dynamic() {
2387
		$manager = new WP_Customize_Manager();
2388
2389
		$setting_id = 'dynamic';
2390
		$setting = $manager->add_setting( $setting_id );
2391
		$this->assertEquals( 'WP_Customize_Setting', get_class( $setting ) );
2392
		$this->assertObjectNotHasAttribute( 'custom', $setting );
2393
		$manager->remove_setting( $setting_id );
2394
2395
		add_filter( 'customize_dynamic_setting_class', array( $this, 'return_dynamic_customize_setting_class' ), 10, 3 );
2396
		add_filter( 'customize_dynamic_setting_args', array( $this, 'return_dynamic_customize_setting_args' ), 10, 2 );
2397
		$setting = $manager->add_setting( $setting_id );
2398
		$this->assertEquals( 'Test_Dynamic_Customize_Setting', get_class( $setting ) );
2399
		$this->assertObjectHasAttribute( 'custom', $setting );
2400
		$this->assertEquals( 'foo', $setting->custom );
0 ignored issues
show
Bug introduced by
The property custom does not seem to exist in WP_Customize_Setting.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2401
	}
2402
2403
	/**
2404
	 * Return 'Test_Dynamic_Customize_Setting' in 'customize_dynamic_setting_class.
2405
	 *
2406
	 * @param string $class Setting class.
2407
	 * @param array  $args  Setting args.
2408
	 * @param string $id    Setting ID.
2409
	 * @return string       Setting class.
2410
	 */
2411
	function return_dynamic_customize_setting_class( $class, $id, $args ) {
2412
		unset( $args );
2413
		if ( 0 === strpos( $id, 'dynamic' ) ) {
2414
			$class = 'Test_Dynamic_Customize_Setting';
2415
		}
2416
		return $class;
2417
	}
2418
2419
	/**
2420
	 * Return 'Test_Dynamic_Customize_Setting' in 'customize_dynamic_setting_class.
2421
	 *
2422
	 * @param array  $args Setting args.
2423
	 * @param string $id   Setting ID.
2424
	 * @return string      Setting args.
0 ignored issues
show
Documentation introduced by
Should the return type not be array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
2425
	 */
2426
	function return_dynamic_customize_setting_args( $args, $id ) {
2427
		if ( 0 === strpos( $id, 'dynamic' ) ) {
2428
			$args['custom'] = 'foo';
2429
		}
2430
		return $args;
2431
	}
2432
2433
	/**
2434
	 * @ticket 34596
2435
	 */
2436 View Code Duplication
	function test_add_panel_return_instance() {
2437
		$manager = new WP_Customize_Manager();
2438
		wp_set_current_user( self::$admin_user_id );
2439
2440
		$panel_id = 'foo-panel';
2441
		$result_panel = $manager->add_panel( $panel_id, array(
2442
			'title'    => 'Test Panel',
2443
			'priority' => 2,
2444
		) );
2445
2446
		$this->assertInstanceOf( 'WP_Customize_Panel', $result_panel );
2447
		$this->assertEquals( $panel_id, $result_panel->id );
2448
2449
		$panel = new WP_Customize_Panel( $manager, $panel_id, array(
2450
			'title' => 'Test Panel 2',
2451
		) );
2452
		$result_panel = $manager->add_panel( $panel );
2453
2454
		$this->assertInstanceOf( 'WP_Customize_Panel', $result_panel );
2455
		$this->assertEquals( $panel, $result_panel );
2456
		$this->assertEquals( $panel_id, $result_panel->id );
2457
	}
2458
2459
	/**
2460
	 * @ticket 34596
2461
	 */
2462
	function test_add_control_return_instance() {
2463
		$manager = new WP_Customize_Manager();
2464
		$section_id = 'foo-section';
2465
		wp_set_current_user( self::$admin_user_id );
2466
		$manager->add_section( $section_id, array(
2467
			'title'    => 'Section',
2468
			'priority' => 1,
2469
		) );
2470
2471
		$control_id = 'foo-control';
2472
		$manager->add_setting( $control_id );
2473
2474
		$result_control = $manager->add_control( $control_id, array(
2475
			'section'  => $section_id,
2476
			'priority' => 1,
2477
			'setting'  => $control_id,
2478
		) );
2479
		$this->assertInstanceOf( 'WP_Customize_Control', $result_control );
2480
		$this->assertEquals( $control_id, $result_control->id );
2481
2482
		$control = new WP_Customize_Control( $manager, $control_id, array(
2483
			'section'  => $section_id,
2484
			'priority' => 1,
2485
			'setting'  => $control_id,
2486
		) );
2487
		$result_control = $manager->add_control( $control );
2488
2489
		$this->assertInstanceOf( 'WP_Customize_Control', $result_control );
2490
		$this->assertEquals( $control, $result_control );
2491
		$this->assertEquals( $control_id, $result_control->id );
2492
	}
2493
2494
2495
	/**
2496
	 * Testing the return values both with and without filter.
2497
	 *
2498
	 * @ticket 31195
2499
	 */
2500
	function test_get_previewable_devices() {
2501
2502
		// Setup the instance.
2503
		$manager = new WP_Customize_Manager();
2504
2505
		// The default devices list.
2506
		$default_devices = array(
2507
			'desktop' => array(
2508
				'label'   => __( 'Enter desktop preview mode' ),
2509
				'default' => true,
2510
			),
2511
			'tablet'  => array(
2512
				'label' => __( 'Enter tablet preview mode' ),
2513
			),
2514
			'mobile'  => array(
2515
				'label' => __( 'Enter mobile preview mode' ),
2516
			),
2517
		);
2518
2519
		// Control test.
2520
		$devices = $manager->get_previewable_devices();
2521
		$this->assertSame( $default_devices, $devices );
2522
2523
		// Adding the filter.
2524
		add_filter( 'customize_previewable_devices', array( $this, 'filter_customize_previewable_devices' ) );
2525
		$devices = $manager->get_previewable_devices();
2526
		$this->assertSame( $this->filtered_device_list(), $devices );
2527
2528
		// Clean up.
2529
		remove_filter( 'customize_previewable_devices', array( $this, 'filter_customize_previewable_devices' ) );
2530
	}
2531
2532
	/**
2533
	 * Helper method for test_get_previewable_devices.
2534
	 *
2535
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array<string,string|boolean>>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2536
	 */
2537
	function filtered_device_list() {
2538
		return array(
2539
			'custom-device' => array(
2540
				'label' => __( 'Enter custom-device preview mode' ),
2541
				'default' => true,
2542
			),
2543
		);
2544
	}
2545
2546
	/**
2547
	 * Callback for the customize_previewable_devices filter.
2548
	 *
2549
	 * @param array $devices The list of devices.
2550
	 *
2551
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array<string,string|boolean>>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2552
	 */
2553
	function filter_customize_previewable_devices( $devices ) {
2554
		return $this->filtered_device_list();
2555
	}
2556
2557
	/**
2558
	 * @ticket 37128
2559
	 */
2560
	function test_prepare_controls_wp_list_sort_controls() {
2561
		wp_set_current_user( self::$admin_user_id );
2562
2563
		$controls = array( 'foo' => 2, 'bar' => 4, 'foobar' => 3, 'key' => 1 );
2564
		$controls_sorted = array( 'key', 'foo', 'foobar', 'bar' );
2565
2566
		$this->manager->add_section( 'foosection', array() );
2567
2568
		foreach ( $controls as $control_id => $priority ) {
2569
			$this->manager->add_setting( $control_id );
2570
			$this->manager->add_control( $control_id, array(
2571
				'priority' => $priority,
2572
				'section'  => 'foosection',
2573
			) );
2574
		}
2575
2576
		$this->manager->prepare_controls();
2577
2578
		$result = $this->manager->controls();
2579
		$this->assertEquals( $controls_sorted, array_keys( $result ) );
2580
	}
2581
2582
	/**
2583
	 * @ticket 37128
2584
	 */
2585 View Code Duplication
	function test_prepare_controls_wp_list_sort_sections() {
2586
		wp_set_current_user( self::$admin_user_id );
2587
2588
		$sections = array( 'foo' => 2, 'bar' => 4, 'foobar' => 3, 'key' => 1 );
2589
		$sections_sorted = array( 'key', 'foo', 'foobar', 'bar' );
2590
2591
		foreach ( $sections as $section_id => $priority ) {
2592
			$this->manager->add_section( $section_id, array(
2593
				'priority' => $priority,
2594
			) );
2595
		}
2596
2597
		$this->manager->prepare_controls();
2598
2599
		$result = $this->manager->sections();
2600
		$this->assertEquals( $sections_sorted, array_keys( $result ) );
2601
	}
2602
2603
	/**
2604
	 * @ticket 37128
2605
	 */
2606 View Code Duplication
	function test_prepare_controls_wp_list_sort_panels() {
2607
		wp_set_current_user( self::$admin_user_id );
2608
2609
		$panels = array( 'foo' => 2, 'bar' => 4, 'foobar' => 3, 'key' => 1 );
2610
		$panels_sorted = array( 'key', 'foo', 'foobar', 'bar' );
2611
2612
		foreach ( $panels as $panel_id => $priority ) {
2613
			$this->manager->add_panel( $panel_id, array(
2614
				'priority' => $priority,
2615
			) );
2616
		}
2617
2618
		$this->manager->prepare_controls();
2619
2620
		$result = $this->manager->panels();
2621
		$this->assertEquals( $panels_sorted, array_keys( $result ) );
2622
	}
2623
2624
	/**
2625
	 * Verify sanitization of external header video URL will trim the whitespaces in the beginning and end of the URL.
2626
	 *
2627
	 * @ticket 39125
2628
	 */
2629
	function test_sanitize_external_header_video_trim() {
2630
		$this->manager->register_controls();
2631
		$setting = $this->manager->get_setting( 'external_header_video' );
2632
		$video_url = 'https://www.youtube.com/watch?v=KiS8rZBeIO0';
2633
2634
		$whitespaces = array(
2635
			' ',  // space
2636
			"\t", // horizontal tab
2637
			"\n", // line feed
2638
			"\r", // carriage return,
2639
			"\f", // form feed,
2640
			"\v", // vertical tab
2641
		);
2642
2643
		foreach ( $whitespaces as $whitespace  ) {
2644
			$sanitized = $setting->sanitize( $whitespace . $video_url . $whitespace );
2645
			$this->assertEquals( $video_url, $sanitized );
2646
		}
2647
	}
2648
}
2649
2650
require_once ABSPATH . WPINC . '/class-wp-customize-setting.php';
2651
2652
/**
2653
 * Class Test_Dynamic_Customize_Setting
2654
 *
2655
 * @see Tests_WP_Customize_Manager::test_add_setting_honoring_dynamic()
2656
 */
2657
class Test_Dynamic_Customize_Setting extends WP_Customize_Setting {
2658
	public $type = 'dynamic';
2659
	public $custom;
2660
}
2661
2662
/**
2663
 * Class Test_Setting_Without_Applying_Validate_Filter.
2664
 *
2665
 * @see Tests_WP_Customize_Manager::test_late_validate_setting_values()
2666
 */
2667
class Test_Setting_Without_Applying_Validate_Filter extends WP_Customize_Setting {
2668
2669
	/**
2670
	 * Validates an input.
2671
	 *
2672
	 * @param mixed $value Value to validate.
2673
	 * @return true|WP_Error True if the input was validated, otherwise WP_Error.
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2674
	 */
2675
	public function validate( $value ) {
2676
		if ( empty( $value ) ) {
2677
			return new WP_Error( 'empty_value', __( 'You must supply a value' ) );
2678
		}
2679
		return true;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return true; (boolean) is incompatible with the return type of the parent method WP_Customize_Setting::validate of type true|WP_Error.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2680
	}
2681
2682
}
2683