class-fl-builder-ajax-layout.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. <?php
  2. /**
  3. * Handles the rendering of the layout for AJAX refreshes.
  4. *
  5. * @since 1.7
  6. */
  7. final class FLBuilderAJAXLayout {
  8. /**
  9. * An array with data for partial refreshes.
  10. *
  11. * @since 1.7
  12. * @access private
  13. * @var array $partial_refresh_data
  14. */
  15. static private $partial_refresh_data = null;
  16. /**
  17. * Renders the layout data to be passed back to the builder.
  18. *
  19. * @since 1.7
  20. * @param string $node_id The ID of a node to try and render instead of the entire layout.
  21. * @param string $old_node_id The ID of a node that has been replaced in the layout.
  22. * @return array
  23. */
  24. static public function render( $node_id = null, $old_node_id = null ) {
  25. do_action( 'fl_builder_before_render_ajax_layout' );
  26. // Update the node ID in the post data?
  27. if ( $node_id ) {
  28. FLBuilderModel::update_post_data( 'node_id', $node_id );
  29. }
  30. // Register scripts needed for shortcodes and widgets.
  31. self::register_scripts();
  32. // Dequeue scripts and styles to only capture those that are needed.
  33. self::dequeue_scripts_and_styles();
  34. // Get the partial refresh data.
  35. $partial_refresh_data = self::get_partial_refresh_data();
  36. // Render the markup.
  37. $html = self::render_html();
  38. // Render scripts and styles.
  39. $scripts_styles = self::render_scripts_and_styles();
  40. // Render the assets.
  41. $assets = self::render_assets();
  42. do_action( 'fl_builder_after_render_ajax_layout' );
  43. // Return the response.
  44. return apply_filters( 'fl_builder_ajax_layout_response', array(
  45. 'partial' => $partial_refresh_data['is_partial_refresh'],
  46. 'nodeId' => $partial_refresh_data['node_id'],
  47. 'nodeType' => $partial_refresh_data['node_type'],
  48. 'oldNodeId' => $old_node_id,
  49. 'html' => $html,
  50. 'scriptsStyles' => $scripts_styles,
  51. 'css' => $assets['css'],
  52. 'js' => $assets['js'],
  53. ) );
  54. }
  55. /**
  56. * Renders the layout data for a new row.
  57. *
  58. * @since 1.7
  59. * @param string $cols The type of column layout to use.
  60. * @param int $position The position of the new row in the layout.
  61. * @param string $template_id The ID of a row template to render.
  62. * @param string $template_type The type of template. Either "user" or "core".
  63. * @return array
  64. */
  65. static public function render_new_row( $cols = '1-col', $position = false, $template_id = null, $template_type = 'user' ) {
  66. // Add a row template?
  67. if ( null !== $template_id ) {
  68. if ( 'core' == $template_type ) {
  69. $template = FLBuilderModel::get_template( $template_id, 'row' );
  70. $row = FLBuilderModel::apply_node_template( $template_id, null, $position, $template );
  71. } else {
  72. $row = FLBuilderModel::apply_node_template( $template_id, null, $position );
  73. }
  74. // Return the response.
  75. return array(
  76. 'layout' => self::render( $row->node ),
  77. 'config' => FLBuilderUISettingsForms::get_node_js_config(),
  78. );
  79. } else {
  80. // Add the row.
  81. $row = FLBuilderModel::add_row( $cols, $position );
  82. do_action( 'fl_builder_before_render_ajax_layout_html' );
  83. // Render the row.
  84. ob_start();
  85. FLBuilder::render_row( $row );
  86. $html = ob_get_clean();
  87. do_action( 'fl_builder_after_render_ajax_layout_html' );
  88. // Return the response.
  89. return array(
  90. 'partial' => true,
  91. 'nodeType' => $row->type,
  92. 'html' => $html,
  93. 'js' => 'FLBuilder._renderLayoutComplete();',
  94. );
  95. }
  96. }
  97. /**
  98. * Renders the layout data for a copied row.
  99. *
  100. * @since 1.7
  101. * @param string $node_id The ID of a row to copy.
  102. * @param object $settings These settings will be used for the copy if present.
  103. * @param string $settings_id The ID of the node who's settings were passed.
  104. * @return array
  105. */
  106. static public function copy_row( $node_id, $settings = null, $settings_id = null ) {
  107. $row = FLBuilderModel::copy_row( $node_id, $settings, $settings_id );
  108. return self::render( $row->node );
  109. }
  110. /**
  111. * Renders the layout data for a new column group.
  112. *
  113. * @since 1.7
  114. * @param string $node_id The node ID of a row to add the new group to.
  115. * @param string $cols The type of column layout to use.
  116. * @param int $position The position of the new column group in the row.
  117. * @return array
  118. */
  119. static public function render_new_column_group( $node_id, $cols = '1-col', $position = false ) {
  120. // Add the group.
  121. $group = FLBuilderModel::add_col_group( $node_id, $cols, $position );
  122. do_action( 'fl_builder_before_render_ajax_layout_html' );
  123. // Render the group.
  124. ob_start();
  125. FLBuilder::render_column_group( $group );
  126. $html = ob_get_clean();
  127. do_action( 'fl_builder_after_render_ajax_layout_html' );
  128. // Return the response.
  129. return array(
  130. 'partial' => true,
  131. 'nodeType' => $group->type,
  132. 'html' => $html,
  133. 'js' => 'FLBuilder._renderLayoutComplete();',
  134. );
  135. }
  136. /**
  137. * Renders the layout data for a new column or columns.
  138. *
  139. * @since 1.7
  140. * @param string $node_id Node ID of the column to insert before or after.
  141. * @param string $insert Either before or after.
  142. * @param string $type The type of column(s) to insert.
  143. * @param boolean $nested Whether these columns are nested or not.
  144. * @return array
  145. */
  146. static public function render_new_columns( $node_id, $insert, $type, $nested ) {
  147. // Add the column(s).
  148. $group = FLBuilderModel::add_cols( $node_id, $insert, $type, $nested );
  149. // Return the response.
  150. return self::render( $group->node );
  151. }
  152. /**
  153. * Renders a new column template.
  154. *
  155. * @since 2.1
  156. * @param string $template_id The ID of a column template to render.
  157. * @param string $parent_id A column node ID.
  158. * @param int $position The new column position.
  159. * @param string $template_type The type of template. Either "user" or "core".
  160. * @return array
  161. */
  162. static public function render_new_col_template( $template_id, $parent_id = null, $position = false, $template_type = 'user' ) {
  163. if ( 'core' == $template_type ) {
  164. $template = FLBuilderModel::get_template( $template_id, 'column' );
  165. $column = FLBuilderModel::apply_node_template( $template_id, $parent_id, $position, $template );
  166. } else {
  167. $column = FLBuilderModel::apply_node_template( $template_id, $parent_id, $position );
  168. }
  169. // Get the new column parent.
  170. $parent = ! $parent_id ? null : FLBuilderModel::get_node( $parent_id );
  171. // Get the node to render.
  172. if ( ! $parent ) {
  173. $row = FLBuilderModel::get_col_parent( 'row', $column );
  174. $render_id = $row->node;
  175. } elseif ( 'row' == $parent->type ) {
  176. $group = FLBuilderModel::get_col_parent( 'column-group', $column );
  177. $render_id = $group->node;
  178. } elseif ( 'column-group' == $parent->type ) {
  179. $render_id = $parent->node;
  180. } else {
  181. $render_id = $column->node;
  182. }
  183. // Return the response.
  184. return array(
  185. 'layout' => self::render( $render_id ),
  186. 'config' => FLBuilderUISettingsForms::get_node_js_config(),
  187. );
  188. }
  189. /**
  190. * Renders the layout data for a copied column.
  191. *
  192. * @since 2.0
  193. * @param string $node_id The ID of a column to copy.
  194. * @param object $settings These settings will be used for the copy if present.
  195. * @param string $settings_id The ID of the node who's settings were passed.
  196. * @return array
  197. */
  198. static public function copy_col( $node_id, $settings = null, $settings_id = null ) {
  199. $col = FLBuilderModel::copy_col( $node_id, $settings, $settings_id );
  200. return self::render( $col->node );
  201. }
  202. /**
  203. * Renders the layout data for a new module.
  204. *
  205. * @since 1.7
  206. * @param string $parent_id A column node ID.
  207. * @param int $position The new module position.
  208. * @param string $type The type of module.
  209. * @param string $alias Module alias slug if this module is an alias.
  210. * @param string $template_id The ID of a module template to render.
  211. * @param string $template_type The type of template. Either "user" or "core".
  212. * @return array
  213. */
  214. static public function render_new_module( $parent_id, $position = false, $type = null, $alias = null, $template_id = null, $template_type = 'user' ) {
  215. // Add a module template?
  216. if ( null !== $template_id ) {
  217. if ( 'core' == $template_type ) {
  218. $template = FLBuilderModel::get_template( $template_id, 'module' );
  219. $module = FLBuilderModel::apply_node_template( $template_id, $parent_id, $position, $template );
  220. } else {
  221. $module = FLBuilderModel::apply_node_template( $template_id, $parent_id, $position );
  222. }
  223. } else {
  224. $defaults = FLBuilderModel::get_module_alias_settings( $alias );
  225. $module = FLBuilderModel::add_default_module( $parent_id, $type, $position, $defaults );
  226. }
  227. // Maybe render the module's parent for a partial refresh?
  228. if ( $module->partial_refresh ) {
  229. // Get the new module parent.
  230. $parent = ! $parent_id ? null : FLBuilderModel::get_node( $parent_id );
  231. // Get the node to render.
  232. if ( ! $parent ) {
  233. $row = FLBuilderModel::get_module_parent( 'row', $module );
  234. $render_id = $row->node;
  235. } elseif ( 'row' == $parent->type ) {
  236. $group = FLBuilderModel::get_module_parent( 'column-group', $module );
  237. $render_id = $group->node;
  238. } elseif ( 'column-group' == $parent->type ) {
  239. $render_id = $parent->node;
  240. } else {
  241. $render_id = $module->node;
  242. }
  243. } else {
  244. $render_id = null;
  245. }
  246. // Return the response.
  247. return array(
  248. 'type' => $module->settings->type,
  249. 'nodeId' => $module->node,
  250. 'parentId' => $module->parent,
  251. 'global' => FLBuilderModel::is_node_global( $module ),
  252. 'layout' => self::render( $render_id ),
  253. 'settings' => null === $template_id ? null : $module->settings,
  254. 'legacy' => FLBuilderUISettingsForms::pre_render_legacy_module_settings( $module->settings->type, $module->settings ),
  255. );
  256. }
  257. /**
  258. * Renders the layout data for a copied module.
  259. *
  260. * @since 1.7
  261. * @param string $node_id The ID of a module to copy.
  262. * @param object $settings These settings will be used for the copy if present.
  263. * @return array
  264. */
  265. static public function copy_module( $node_id, $settings = null ) {
  266. $module = FLBuilderModel::copy_module( $node_id, $settings );
  267. return self::render( $module->node );
  268. }
  269. /**
  270. * Returns an array of partial refresh data.
  271. *
  272. * @since 1.7
  273. * @access private
  274. * @return array
  275. */
  276. static private function get_partial_refresh_data() {
  277. // Get the data if it's not cached.
  278. if ( ! self::$partial_refresh_data ) {
  279. $post_data = FLBuilderModel::get_post_data();
  280. $partial_refresh = false;
  281. $node_type = null;
  282. // Check for partial refresh if we have a node ID.
  283. if ( isset( $post_data['node_id'] ) ) {
  284. // Get the node.
  285. $node_id = $post_data['node_id'];
  286. $node = FLBuilderModel::get_node( $post_data['node_id'] );
  287. // Check a module for partial refresh.
  288. if ( $node && 'module' == $node->type ) {
  289. $node = FLBuilderModel::get_module( $node_id );
  290. $node_type = 'module';
  291. $partial_refresh = $node->partial_refresh;
  292. } elseif ( $node ) {
  293. $node_type = $node->type;
  294. $partial_refresh = self::node_modules_support_partial_refresh( $node );
  295. }
  296. } else {
  297. $node_id = null;
  298. $node = null;
  299. $node_type = null;
  300. }
  301. // Cache the partial refresh data.
  302. self::$partial_refresh_data = array(
  303. 'is_partial_refresh' => $partial_refresh,
  304. 'node_id' => $node_id,
  305. 'node' => $node,
  306. 'node_type' => $node_type,
  307. );
  308. }
  309. // Return the data.
  310. return self::$partial_refresh_data;
  311. }
  312. /**
  313. * Checks to see if all modules in a node support partial refresh.
  314. *
  315. * @since 1.7
  316. * @access private
  317. * @param object $node The node to check.
  318. * @return bool
  319. */
  320. static private function node_modules_support_partial_refresh( $node ) {
  321. $nodes = FLBuilderModel::get_categorized_nodes();
  322. if ( 'row' == $node->type ) {
  323. $template_post_id = FLBuilderModel::is_node_global( $node );
  324. foreach ( $nodes['groups'] as $group ) {
  325. if ( $node->node == $group->parent || ( $template_post_id && $node->template_node_id == $group->parent ) ) {
  326. foreach ( $nodes['columns'] as $column ) {
  327. if ( $group->node == $column->parent ) {
  328. foreach ( $nodes['modules'] as $module ) {
  329. if ( $column->node == $module->parent ) {
  330. if ( ! $module->partial_refresh ) {
  331. return false;
  332. }
  333. }
  334. }
  335. }
  336. }
  337. }
  338. }
  339. } elseif ( 'column-group' == $node->type ) {
  340. foreach ( $nodes['columns'] as $column ) {
  341. if ( $node->node == $column->parent ) {
  342. foreach ( $nodes['modules'] as $module ) {
  343. if ( $column->node == $module->parent ) {
  344. if ( ! $module->partial_refresh ) {
  345. return false;
  346. }
  347. }
  348. }
  349. }
  350. }
  351. } elseif ( 'column' == $node->type ) {
  352. foreach ( $nodes['modules'] as $module ) {
  353. if ( $node->node == $module->parent ) {
  354. if ( ! $module->partial_refresh ) {
  355. return false;
  356. }
  357. }
  358. }
  359. }
  360. return true;
  361. }
  362. /**
  363. * Renders the html for the layout or node.
  364. *
  365. * @since 1.7
  366. * @access private
  367. * @return string
  368. */
  369. static private function render_html() {
  370. do_action( 'fl_builder_before_render_ajax_layout_html' );
  371. // Get the partial refresh data.
  372. $partial_refresh_data = self::get_partial_refresh_data();
  373. // Start the output buffer.
  374. ob_start();
  375. // Render a node?
  376. if ( $partial_refresh_data['is_partial_refresh'] ) {
  377. switch ( $partial_refresh_data['node']->type ) {
  378. case 'row':
  379. FLBuilder::render_row( $partial_refresh_data['node'] );
  380. break;
  381. case 'column-group':
  382. FLBuilder::render_column_group( $partial_refresh_data['node'] );
  383. break;
  384. case 'column':
  385. FLBuilder::render_column( $partial_refresh_data['node'] );
  386. break;
  387. case 'module':
  388. FLBuilder::render_module( $partial_refresh_data['node'] );
  389. break;
  390. }
  391. } else {
  392. FLBuilder::render_nodes();
  393. }
  394. // Get the rendered HTML.
  395. $html = ob_get_clean();
  396. /**
  397. * Use this filter to prevent the builder from rendering shortcodes.
  398. * It is useful if you don’t want shortcodes rendering while the builder UI is active.
  399. * @see fl_builder_render_shortcodes
  400. * @link https://kb.wpbeaverbuilder.com/article/117-plugin-filter-reference
  401. */
  402. if ( apply_filters( 'fl_builder_render_shortcodes', true ) ) {
  403. $html = apply_filters( 'fl_builder_before_render_shortcodes', $html );
  404. ob_start();
  405. echo do_shortcode( $html );
  406. $html = ob_get_clean();
  407. }
  408. do_action( 'fl_builder_after_render_ajax_layout_html' );
  409. // Return the rendered HTML.
  410. return $html;
  411. }
  412. /**
  413. * Renders the assets for the layout or a node.
  414. *
  415. * @since 1.7
  416. * @access private
  417. * @return array
  418. */
  419. static private function render_assets() {
  420. $partial_refresh_data = self::get_partial_refresh_data();
  421. $asset_info = FLBuilderModel::get_asset_info();
  422. $asset_ver = FLBuilderModel::get_asset_version();
  423. $assets = array(
  424. 'js' => '',
  425. 'css' => '',
  426. );
  427. // Ensure global assets are rendered.
  428. FLBuilder::clear_enqueued_global_assets();
  429. // Render the JS.
  430. if ( $partial_refresh_data['is_partial_refresh'] ) {
  431. if ( ! class_exists( 'FLJSMin' ) ) {
  432. include FL_BUILDER_DIR . 'classes/class-fl-jsmin.php';
  433. }
  434. switch ( $partial_refresh_data['node']->type ) {
  435. case 'row':
  436. $assets['js'] = FLBuilder::render_row_js( $partial_refresh_data['node'] );
  437. $assets['js'] .= FLBuilder::render_row_modules_js( $partial_refresh_data['node'] );
  438. break;
  439. case 'column':
  440. $assets['js'] = FLBuilder::render_column_modules_js( $partial_refresh_data['node'] );
  441. break;
  442. case 'module':
  443. $assets['js'] = FLBuilder::render_module_js( $partial_refresh_data['node'] );
  444. break;
  445. }
  446. $assets['js'] .= 'FLBuilder._renderLayoutComplete();';
  447. try {
  448. $min = FLJSMin::minify( $assets['js'] );
  449. } catch ( Exception $e ) {}
  450. if ( $min ) {
  451. $assets['js'] = $min;
  452. }
  453. } else {
  454. FLBuilder::render_js();
  455. $assets['js'] = $asset_info['js_url'] . '?ver=' . $asset_ver;
  456. }
  457. // Render the CSS.
  458. FLBuilder::render_css();
  459. $assets['css'] = $asset_info['css_url'] . '?ver=' . $asset_ver;
  460. // Return the assets.
  461. return $assets;
  462. }
  463. /**
  464. * Do the wp_enqueue_scripts action to register any scripts or
  465. * styles that might need to be registered for shortcodes or widgets.
  466. *
  467. * @since 1.7
  468. * @access private
  469. * @return void
  470. */
  471. static private function register_scripts() {
  472. // Running these isn't necessary and can cause performance issues.
  473. remove_action( 'wp_enqueue_scripts', 'FLBuilder::register_layout_styles_scripts' );
  474. remove_action( 'wp_enqueue_scripts', 'FLBuilder::enqueue_ui_styles_scripts' );
  475. remove_action( 'wp_enqueue_scripts', 'FLBuilder::enqueue_all_layouts_styles_scripts' );
  476. ob_start();
  477. do_action( 'wp_enqueue_scripts' );
  478. ob_end_clean();
  479. }
  480. /**
  481. * Dequeue scripts and styles so we can capture only those
  482. * enqueued by shortcodes or widgets.
  483. *
  484. * @since 1.7
  485. * @access private
  486. * @return void
  487. */
  488. static private function dequeue_scripts_and_styles() {
  489. global $wp_scripts;
  490. global $wp_styles;
  491. if ( isset( $wp_scripts ) ) {
  492. $wp_scripts->queue = array();
  493. }
  494. if ( isset( $wp_styles ) ) {
  495. $wp_styles->queue = array();
  496. }
  497. remove_action( 'wp_print_styles', 'print_emoji_styles' );
  498. }
  499. /**
  500. * Renders scripts and styles enqueued by shortcodes or widgets.
  501. *
  502. * @since 1.7
  503. * @access private
  504. * @return string
  505. */
  506. static private function render_scripts_and_styles() {
  507. global $wp_scripts;
  508. global $wp_styles;
  509. $partial_refresh_data = self::get_partial_refresh_data();
  510. $modules = array();
  511. $scripts_styles = '';
  512. // Enqueue module font styles.
  513. if ( ! $partial_refresh_data['is_partial_refresh'] ) {
  514. $modules = FLBuilderModel::get_all_modules();
  515. } elseif ( 'module' !== $partial_refresh_data['node']->type ) {
  516. $nodes = FLBuilderModel::get_nested_nodes( $partial_refresh_data['node'] );
  517. foreach ( $nodes as $node ) {
  518. if ( 'module' === $node->type && isset( FLBuilderModel::$modules[ $node->settings->type ] ) ) {
  519. $node->form = FLBuilderModel::$modules[ $node->settings->type ]->form;
  520. $modules[] = $node;
  521. }
  522. }
  523. } else {
  524. $modules = array( $partial_refresh_data['node'] );
  525. }
  526. foreach ( $modules as $module ) {
  527. FLBuilderFonts::add_fonts_for_module( $module );
  528. }
  529. FLBuilderFonts::enqueue_styles();
  530. // Start the output buffer.
  531. ob_start();
  532. // Print scripts and styles.
  533. if ( isset( $wp_scripts ) ) {
  534. $wp_scripts->done[] = 'jquery';
  535. wp_print_scripts( $wp_scripts->queue );
  536. }
  537. if ( isset( $wp_styles ) ) {
  538. wp_print_styles( $wp_styles->queue );
  539. }
  540. // Return the scripts and styles markup.
  541. return ob_get_clean();
  542. }
  543. }