selective-refresh-ajax.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. <?php
  2. /**
  3. * WP_Customize_Selective_Refresh Ajax tests.
  4. *
  5. * @package WordPress
  6. * @subpackage UnitTests
  7. */
  8. /**
  9. * Tests for the WP_Customize_Selective_Refresh class Ajax.
  10. *
  11. * Note that this is intentionally not extending WP_Ajax_UnitTestCase because it
  12. * is not admin ajax.
  13. *
  14. * @since 4.5.0
  15. * @group ajax
  16. */
  17. class Test_WP_Customize_Selective_Refresh_Ajax extends WP_UnitTestCase {
  18. /**
  19. * Manager.
  20. *
  21. * @var WP_Customize_Manager
  22. */
  23. public $wp_customize;
  24. /**
  25. * Component.
  26. *
  27. * @var WP_Customize_Selective_Refresh
  28. */
  29. public $selective_refresh;
  30. /**
  31. * Set up the test fixture.
  32. */
  33. function setUp() {
  34. parent::setUp();
  35. // Define wp_doing_ajax so that wp_die() will be used instead of die().
  36. add_filter( 'wp_doing_ajax', '__return_true' );
  37. add_filter( 'wp_die_ajax_handler', array( $this, 'get_wp_die_handler' ), 1, 1 );
  38. require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
  39. $GLOBALS['wp_customize'] = new WP_Customize_Manager();
  40. $this->wp_customize = $GLOBALS['wp_customize'];
  41. if ( isset( $this->wp_customize->selective_refresh ) ) {
  42. $this->selective_refresh = $this->wp_customize->selective_refresh;
  43. }
  44. }
  45. /**
  46. * Do Customizer boot actions.
  47. */
  48. function do_customize_boot_actions() {
  49. $_SERVER['REQUEST_METHOD'] = 'POST';
  50. do_action( 'setup_theme' );
  51. do_action( 'after_setup_theme' );
  52. do_action( 'init' );
  53. do_action( 'customize_register', $this->wp_customize );
  54. $this->wp_customize->customize_preview_init();
  55. do_action( 'wp', $GLOBALS['wp'] );
  56. }
  57. /**
  58. * Test WP_Customize_Selective_Refresh::handle_render_partials_request().
  59. *
  60. * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
  61. */
  62. function test_handle_render_partials_request_for_unauthenticated_user() {
  63. $_POST[ WP_Customize_Selective_Refresh::RENDER_QUERY_VAR ] = '1';
  64. // Check current_user_cannot_customize.
  65. ob_start();
  66. try {
  67. $this->selective_refresh->handle_render_partials_request();
  68. } catch ( WPDieException $e ) {
  69. unset( $e );
  70. }
  71. $output = json_decode( ob_get_clean(), true );
  72. $this->assertFalse( $output['success'] );
  73. $this->assertSame( 'expected_customize_preview', $output['data'] );
  74. // Check expected_customize_preview.
  75. wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
  76. $_REQUEST['nonce'] = wp_create_nonce( 'preview-customize_' . $this->wp_customize->theme()->get_stylesheet() );
  77. ob_start();
  78. try {
  79. $this->selective_refresh->handle_render_partials_request();
  80. } catch ( WPDieException $e ) {
  81. unset( $e );
  82. }
  83. $output = json_decode( ob_get_clean(), true );
  84. $this->assertFalse( $output['success'] );
  85. $this->assertSame( 'expected_customize_preview', $output['data'] );
  86. // Check missing_partials.
  87. $this->do_customize_boot_actions();
  88. ob_start();
  89. try {
  90. $this->selective_refresh->handle_render_partials_request();
  91. } catch ( WPDieException $e ) {
  92. unset( $e );
  93. }
  94. $output = json_decode( ob_get_clean(), true );
  95. $this->assertFalse( $output['success'] );
  96. $this->assertSame( 'missing_partials', $output['data'] );
  97. // Check missing_partials.
  98. $_POST['partials'] = 'bad';
  99. $this->do_customize_boot_actions();
  100. ob_start();
  101. try {
  102. $this->selective_refresh->handle_render_partials_request();
  103. } catch ( WPDieException $e ) {
  104. $this->assertSame( '', $e->getMessage() );
  105. }
  106. $output = json_decode( ob_get_clean(), true );
  107. $this->assertFalse( $output['success'] );
  108. $this->assertSame( 'malformed_partials', $output['data'] );
  109. }
  110. /**
  111. * Set the current user to be an admin, add the preview nonce, and set the query var.
  112. */
  113. function setup_valid_render_partials_request_environment() {
  114. wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
  115. $_REQUEST['nonce'] = wp_create_nonce( 'preview-customize_' . $this->wp_customize->theme()->get_stylesheet() );
  116. $_POST[ WP_Customize_Selective_Refresh::RENDER_QUERY_VAR ] = '1';
  117. $this->do_customize_boot_actions();
  118. }
  119. /**
  120. * Test WP_Customize_Selective_Refresh::handle_render_partials_request() for an unrecognized partial.
  121. *
  122. * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
  123. */
  124. function test_handle_render_partials_request_for_unrecognized_partial() {
  125. $this->setup_valid_render_partials_request_environment();
  126. $context_data = array();
  127. $placements = array( $context_data );
  128. $_POST['partials'] = wp_slash(
  129. wp_json_encode(
  130. array(
  131. 'foo' => $placements,
  132. )
  133. )
  134. );
  135. ob_start();
  136. try {
  137. $this->expected_partial_ids = array( 'foo' );
  138. add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
  139. add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
  140. add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
  141. $this->selective_refresh->handle_render_partials_request();
  142. } catch ( WPDieException $e ) {
  143. $this->assertSame( '', $e->getMessage() );
  144. }
  145. $output = json_decode( ob_get_clean(), true );
  146. $this->assertTrue( $output['success'] );
  147. $this->assertInternalType( 'array', $output['data'] );
  148. $this->assertArrayHasKey( 'contents', $output['data'] );
  149. $this->assertArrayHasKey( 'errors', $output['data'] );
  150. $this->assertArrayHasKey( 'foo', $output['data']['contents'] );
  151. $this->assertNull( $output['data']['contents']['foo'] );
  152. }
  153. /**
  154. * Test WP_Customize_Selective_Refresh::handle_render_partials_request() for a partial that does not render.
  155. *
  156. * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
  157. */
  158. function test_handle_render_partials_request_for_non_rendering_partial() {
  159. $this->setup_valid_render_partials_request_environment();
  160. wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
  161. $this->wp_customize->add_setting( 'home' );
  162. $this->wp_customize->selective_refresh->add_partial( 'foo', array( 'settings' => array( 'home' ) ) );
  163. $context_data = array();
  164. $placements = array( $context_data );
  165. $_POST['partials'] = wp_slash(
  166. wp_json_encode(
  167. array(
  168. 'foo' => $placements,
  169. )
  170. )
  171. );
  172. $count_customize_render_partials_before = has_action( 'customize_render_partials_before' );
  173. $count_customize_render_partials_after = has_action( 'customize_render_partials_after' );
  174. ob_start();
  175. try {
  176. $this->expected_partial_ids = array( 'foo' );
  177. add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
  178. add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
  179. add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
  180. $this->selective_refresh->handle_render_partials_request();
  181. } catch ( WPDieException $e ) {
  182. $this->assertSame( '', $e->getMessage() );
  183. }
  184. $this->assertEquals( $count_customize_render_partials_before + 1, has_action( 'customize_render_partials_before' ) );
  185. $this->assertEquals( $count_customize_render_partials_after + 1, has_action( 'customize_render_partials_after' ) );
  186. $output = json_decode( ob_get_clean(), true );
  187. $this->assertSame( array( false ), $output['data']['contents']['foo'] );
  188. }
  189. /**
  190. * Test WP_Customize_Selective_Refresh::handle_render_partials_request() for a partial the user doesn't have the capability to edit.
  191. *
  192. * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
  193. */
  194. function test_handle_rendering_disallowed_partial() {
  195. $this->setup_valid_render_partials_request_environment();
  196. wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
  197. $this->wp_customize->add_setting(
  198. 'secret_message',
  199. array(
  200. 'capability' => 'top_secret_clearance',
  201. )
  202. );
  203. $this->wp_customize->selective_refresh->add_partial( 'secret_message', array( 'settings' => 'secret_message' ) );
  204. $context_data = array();
  205. $placements = array( $context_data );
  206. $_POST['partials'] = wp_slash(
  207. wp_json_encode(
  208. array(
  209. 'secret_message' => $placements,
  210. )
  211. )
  212. );
  213. ob_start();
  214. try {
  215. $this->selective_refresh->handle_render_partials_request();
  216. } catch ( WPDieException $e ) {
  217. $this->assertSame( '', $e->getMessage() );
  218. }
  219. $output = json_decode( ob_get_clean(), true );
  220. $this->assertNull( $output['data']['contents']['secret_message'] );
  221. }
  222. /**
  223. * Test WP_Customize_Selective_Refresh::handle_render_partials_request() for a partial for which an associated setting does not exist.
  224. *
  225. * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
  226. */
  227. function test_handle_rendering_partial_with_missing_settings() {
  228. $this->setup_valid_render_partials_request_environment();
  229. wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
  230. $this->wp_customize->selective_refresh->add_partial( 'bar', array( 'settings' => 'bar' ) );
  231. $context_data = array();
  232. $placements = array( $context_data );
  233. $_POST['partials'] = wp_slash(
  234. wp_json_encode(
  235. array(
  236. 'bar' => $placements,
  237. )
  238. )
  239. );
  240. ob_start();
  241. try {
  242. $this->selective_refresh->handle_render_partials_request();
  243. } catch ( WPDieException $e ) {
  244. $this->assertSame( '', $e->getMessage() );
  245. }
  246. $output = json_decode( ob_get_clean(), true );
  247. $this->assertNull( $output['data']['contents']['bar'] );
  248. }
  249. /**
  250. * Get the rendered blogname.
  251. *
  252. * @param WP_Customize_Partial $partial Partial.
  253. * @param array $context Context data.
  254. * @return string
  255. */
  256. function render_callback_blogname( $partial, $context ) {
  257. $this->assertInternalType( 'array', $context );
  258. $this->assertInstanceOf( 'WP_Customize_Partial', $partial );
  259. return get_bloginfo( 'name', 'display' );
  260. }
  261. /**
  262. * Get the rendered blogdescription.
  263. *
  264. * @param WP_Customize_Partial $partial Partial.
  265. * @param array $context Context data.
  266. * @return string
  267. */
  268. function render_callback_blogdescription( $partial, $context ) {
  269. $this->assertInternalType( 'array', $context );
  270. $this->assertInstanceOf( 'WP_Customize_Partial', $partial );
  271. $x = get_bloginfo( 'description', 'display' );
  272. return $x;
  273. }
  274. /**
  275. * Test WP_Customize_Selective_Refresh::handle_render_partials_request() for a partial that does render.
  276. *
  277. * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
  278. */
  279. function test_handle_render_partials_request_with_single_valid_placement() {
  280. $this->setup_valid_render_partials_request_environment();
  281. $this->wp_customize->selective_refresh->add_partial(
  282. 'test_blogname',
  283. array(
  284. 'settings' => array( 'blogname' ),
  285. 'render_callback' => array( $this, 'render_callback_blogname' ),
  286. )
  287. );
  288. $context_data = array();
  289. $placements = array( $context_data );
  290. $_POST['partials'] = wp_slash(
  291. wp_json_encode(
  292. array(
  293. 'test_blogname' => $placements,
  294. )
  295. )
  296. );
  297. $count_customize_render_partials_before = has_action( 'customize_render_partials_before' );
  298. $count_customize_render_partials_after = has_action( 'customize_render_partials_after' );
  299. ob_start();
  300. try {
  301. $this->expected_partial_ids = array( 'test_blogname' );
  302. add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
  303. add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
  304. add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
  305. $this->selective_refresh->handle_render_partials_request();
  306. } catch ( WPDieException $e ) {
  307. $this->assertSame( '', $e->getMessage() );
  308. }
  309. $this->assertEquals( $count_customize_render_partials_before + 1, has_action( 'customize_render_partials_before' ) );
  310. $this->assertEquals( $count_customize_render_partials_after + 1, has_action( 'customize_render_partials_after' ) );
  311. $output = json_decode( ob_get_clean(), true );
  312. $this->assertSame( array( get_bloginfo( 'name', 'display' ) ), $output['data']['contents']['test_blogname'] );
  313. $this->assertArrayHasKey( 'setting_validities', $output['data'] );
  314. }
  315. /**
  316. * Filter customize_dynamic_partial_args.
  317. *
  318. * @param array $partial_args Partial args.
  319. * @param string $partial_id Partial ID.
  320. *
  321. * @return array|false Args.
  322. */
  323. function filter_customize_dynamic_partial_args( $partial_args, $partial_id ) {
  324. if ( 'test_dynamic_blogname' === $partial_id ) {
  325. $partial_args = array(
  326. 'settings' => array( 'blogname' ),
  327. 'render_callback' => array( $this, 'render_callback_blogname' ),
  328. );
  329. }
  330. return $partial_args;
  331. }
  332. /**
  333. * Filter customize_render_partials_response.
  334. *
  335. * @param array $response Response.
  336. * @param WP_Customize_Selective_Refresh $component Selective refresh component.
  337. * @param array $partial_placements Placements' context data for the partials rendered in the request.
  338. * The array is keyed by partial ID, with each item being an array of
  339. * the placements' context data.
  340. * @return array Response.
  341. */
  342. function filter_customize_render_partials_response( $response, $component, $partial_placements ) {
  343. $this->assertInternalType( 'array', $response );
  344. $this->assertInstanceOf( 'WP_Customize_Selective_Refresh', $component );
  345. if ( isset( $this->expected_partial_ids ) ) {
  346. $this->assertSameSets( $this->expected_partial_ids, array_keys( $partial_placements ) );
  347. }
  348. return $response;
  349. }
  350. /**
  351. * Expected partial IDs.
  352. *
  353. * @var array
  354. */
  355. protected $expected_partial_ids;
  356. /**
  357. * Handle 'customize_render_partials_before' action.
  358. *
  359. * @param WP_Customize_Selective_Refresh $component Selective refresh component.
  360. * @param array $partial_placements Partial IDs.
  361. */
  362. function handle_action_customize_render_partials_after( $component, $partial_placements ) {
  363. $this->assertInstanceOf( 'WP_Customize_Selective_Refresh', $component );
  364. if ( isset( $this->expected_partial_ids ) ) {
  365. $this->assertSameSets( $this->expected_partial_ids, array_keys( $partial_placements ) );
  366. }
  367. }
  368. /**
  369. * Handle 'customize_render_partials_after' action.
  370. *
  371. * @param WP_Customize_Selective_Refresh $component Selective refresh component.
  372. * @param array $partial_placements Partial IDs.
  373. */
  374. function handle_action_customize_render_partials_before( $component, $partial_placements ) {
  375. $this->assertInstanceOf( 'WP_Customize_Selective_Refresh', $component );
  376. if ( isset( $this->expected_partial_ids ) ) {
  377. $this->assertSameSets( $this->expected_partial_ids, array_keys( $partial_placements ) );
  378. }
  379. }
  380. /**
  381. * Test WP_Customize_Selective_Refresh::handle_render_partials_request()dynamic partials are recognized.
  382. *
  383. * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
  384. */
  385. function test_handle_render_partials_request_for_dynamic_partial() {
  386. $this->setup_valid_render_partials_request_environment();
  387. add_filter( 'customize_dynamic_partial_args', array( $this, 'filter_customize_dynamic_partial_args' ), 10, 2 );
  388. $context_data = array();
  389. $placements = array( $context_data );
  390. $_POST['partials'] = wp_slash(
  391. wp_json_encode(
  392. array(
  393. 'test_dynamic_blogname' => $placements,
  394. )
  395. )
  396. );
  397. $count_customize_render_partials_before = has_action( 'customize_render_partials_before' );
  398. $count_customize_render_partials_after = has_action( 'customize_render_partials_after' );
  399. ob_start();
  400. try {
  401. $this->expected_partial_ids = array( 'test_dynamic_blogname' );
  402. add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
  403. add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
  404. add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
  405. $this->selective_refresh->handle_render_partials_request();
  406. } catch ( WPDieException $e ) {
  407. $this->assertSame( '', $e->getMessage() );
  408. }
  409. $this->assertEquals( $count_customize_render_partials_before + 1, has_action( 'customize_render_partials_before' ) );
  410. $this->assertEquals( $count_customize_render_partials_after + 1, has_action( 'customize_render_partials_after' ) );
  411. $output = json_decode( ob_get_clean(), true );
  412. $this->assertSame( array( get_bloginfo( 'name', 'display' ) ), $output['data']['contents']['test_dynamic_blogname'] );
  413. }
  414. /**
  415. * Test WP_Customize_Selective_Refresh::handle_render_partials_request() to multiple partials can be requested at once.
  416. *
  417. * @see WP_Customize_Selective_Refresh::handle_render_partials_request()
  418. */
  419. function test_handle_render_partials_request_for_multiple_partials_placements() {
  420. $this->setup_valid_render_partials_request_environment();
  421. $this->wp_customize->selective_refresh->add_partial(
  422. 'test_blogname',
  423. array(
  424. 'settings' => array( 'blogname' ),
  425. 'render_callback' => array( $this, 'render_callback_blogname' ),
  426. )
  427. );
  428. $this->wp_customize->selective_refresh->add_partial(
  429. 'test_blogdescription',
  430. array(
  431. 'settings' => array( 'blogdescription' ),
  432. 'render_callback' => array( $this, 'render_callback_blogdescription' ),
  433. )
  434. );
  435. $placement_context_data = array();
  436. $_POST['partials'] = wp_slash(
  437. wp_json_encode(
  438. array(
  439. 'test_blogname' => array( $placement_context_data ),
  440. 'test_blogdescription' => array( $placement_context_data, $placement_context_data ),
  441. )
  442. )
  443. );
  444. $count_customize_render_partials_before = has_action( 'customize_render_partials_before' );
  445. $count_customize_render_partials_after = has_action( 'customize_render_partials_after' );
  446. ob_start();
  447. try {
  448. $this->expected_partial_ids = array( 'test_blogname', 'test_blogdescription' );
  449. add_filter( 'customize_render_partials_response', array( $this, 'filter_customize_render_partials_response' ), 10, 3 );
  450. add_action( 'customize_render_partials_before', array( $this, 'handle_action_customize_render_partials_before' ), 10, 2 );
  451. add_action( 'customize_render_partials_after', array( $this, 'handle_action_customize_render_partials_after' ), 10, 2 );
  452. $this->selective_refresh->handle_render_partials_request();
  453. } catch ( WPDieException $e ) {
  454. $this->assertSame( '', $e->getMessage() );
  455. }
  456. $this->assertEquals( $count_customize_render_partials_before + 1, has_action( 'customize_render_partials_before' ) );
  457. $this->assertEquals( $count_customize_render_partials_after + 1, has_action( 'customize_render_partials_after' ) );
  458. $output = json_decode( ob_get_clean(), true );
  459. $this->assertSame( array( get_bloginfo( 'name', 'display' ) ), $output['data']['contents']['test_blogname'] );
  460. $this->assertSame( array_fill( 0, 2, get_bloginfo( 'description', 'display' ) ), $output['data']['contents']['test_blogdescription'] );
  461. }
  462. /**
  463. * Tear down.
  464. */
  465. function tearDown() {
  466. $this->expected_partial_ids = null;
  467. $this->wp_customize = null;
  468. unset( $GLOBALS['wp_customize'] );
  469. unset( $GLOBALS['wp_scripts'] );
  470. parent::tearDown();
  471. }
  472. }