PluginProbe ʕ •ᴥ•ʔ
Page Builder by SiteOrigin / 2.34.3
Page Builder by SiteOrigin v2.34.3
2.34.3 2.34.2 2.29.5 2.29.6 2.29.7 2.29.8 2.29.9 2.3 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.31.2 2.31.3 2.31.4 2.31.5 2.31.6 2.31.7 2.31.8 2.32.0 2.32.1 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.34.0 2.34.1 2.4 2.4.1 2.4.10 2.4.11 2.4.12 2.4.13 2.4.14 2.4.15 2.4.16 2.4.17 2.4.18 2.4.19 2.4.2 2.4.20 2.4.21 2.4.22 2.4.23 2.4.24 2.4.25 2.4.3 2.4.4 2.4.5 2.4.6 2.4.8 2.4.9 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.14 2.5.15 2.5.16 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.6.6 2.6.7 2.6.8 2.6.9 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0 2.8.1 2.8.2 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 trunk 2.10.0 2.10.1 2.10.10 2.10.11 2.10.12 2.10.13 2.10.14 2.10.15 2.10.16 2.10.17 2.10.2 2.10.3 2.10.4 2.10.5 2.10.6 2.10.7 2.10.8 2.10.9 2.11.0 2.11.1 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.12.0 2.12.1 2.12.2 2.12.3 2.12.4 2.12.5 2.12.6 2.13.0 2.13.1 2.13.2 2.14.0 2.14.1 2.14.2 2.14.3 2.15.0 2.15.1 2.15.2 2.15.3 2.16.0 2.16.1 2.16.10 2.16.11 2.16.12 2.16.13 2.16.14 2.16.15 2.16.16 2.16.17 2.16.18 2.16.19 2.16.2 2.16.3 2.16.4 2.16.5 2.16.6 2.16.7 2.16.8 2.16.9 2.17.0 2.18.0 2.18.1 2.18.2 2.18.3 2.18.4 2.19.0 2.20.0 2.20.1 2.20.2 2.20.3 2.20.4 2.20.5 2.20.6 2.21.0 2.21.1 2.22.0 2.22.1 2.23.0 2.24.0 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.26.1 2.26.2 2.27.0 2.27.1 2.28.0 2.29.0 2.29.1 2.29.10 2.29.11 2.29.12 2.29.13 2.29.14 2.29.15 2.29.16 2.29.17 2.29.18 2.29.19 2.29.2 2.29.20 2.29.21 2.29.22 2.29.3 2.29.4
siteorigin-panels / inc / admin.php
siteorigin-panels / inc Last commit date
data 3 years ago installer 3 months ago widgets 3 months ago admin-dashboard.php 2 years ago admin-layouts.php 10 months ago admin-widget-dialog.php 1 year ago admin-widgets-bundle.php 11 months ago admin.php 1 month ago compatibility.php 1 month ago css-builder.php 3 years ago functions.php 3 years ago home.php 3 years ago live-editor.php 2 years ago post-content-filters.php 2 years ago renderer-legacy.php 3 months ago renderer.php 1 month ago revisions.php 3 years ago settings.php 3 months ago sidebars-emulator.php 2 years ago styles-admin.php 11 months ago styles.php 11 months ago widget-shortcode.php 2 years ago
admin.php
2052 lines
1 <?php
2
3 /**
4 * Class SiteOrigin_Panels_Admin
5 *
6 * Handles all the admin and database interactions.
7 */
8 class SiteOrigin_Panels_Admin {
9 /**
10 * @var bool Store that we're in the save post action, to prevent infinite loops.
11 */
12 private $in_save_post;
13
14 public function __construct() {
15 add_action( 'plugin_action_links_siteorigin-panels/siteorigin-panels.php', array(
16 $this,
17 'plugin_action_links',
18 ) );
19
20 add_action( 'plugins_loaded', array( $this, 'admin_init_widget_count' ) );
21
22 add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
23 add_action( 'admin_init', array( $this, 'save_home_page' ) );
24 add_action( 'save_post', array( $this, 'save_post' ) );
25
26 add_action( 'after_switch_theme', array( $this, 'update_home_on_theme_change' ) );
27
28 // Enqueuing admin scripts.
29 add_action( 'admin_print_scripts-post-new.php', array( $this, 'enqueue_admin_scripts' ) );
30 add_action( 'admin_print_scripts-post.php', array( $this, 'enqueue_admin_scripts' ) );
31 add_action( 'admin_print_scripts-appearance_page_so_panels_home_page', array(
32 $this,
33 'enqueue_admin_scripts',
34 ) );
35 add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) );
36 add_action( 'admin_print_scripts-edit.php', array( $this, 'footer_column_css' ) );
37
38 // Enqueue the admin styles.
39 add_action( 'admin_print_styles-post-new.php', array( $this, 'enqueue_admin_styles' ) );
40 add_action( 'admin_print_styles-post.php', array( $this, 'enqueue_admin_styles' ) );
41 add_action( 'admin_print_styles-appearance_page_so_panels_home_page', array( $this, 'enqueue_admin_styles' ) );
42 add_action( 'admin_print_styles-widgets.php', array( $this, 'enqueue_admin_styles' ) );
43
44 // The help tab.
45 add_action( 'load-page.php', array( $this, 'add_help_tab' ), 12 );
46 add_action( 'load-post-new.php', array( $this, 'add_help_tab' ), 12 );
47 add_action( 'load-appearance_page_so_panels_home_page', array( $this, 'add_help_tab' ), 12 );
48
49 add_action( 'customize_controls_print_scripts', array( $this, 'js_templates' ) );
50
51 // Register all the admin actions.
52 add_action( 'wp_ajax_so_panels_builder_content', array( $this, 'action_builder_content' ) );
53 add_action( 'wp_ajax_so_panels_builder_content_json', array( $this, 'action_builder_content_json' ) );
54 add_action( 'wp_ajax_so_panels_widget_form', array( $this, 'action_widget_form' ) );
55 add_action( 'wp_ajax_so_panels_live_editor_preview', array( $this, 'action_live_editor_preview' ) );
56 add_action( 'wp_ajax_so_panels_layout_block_preview', array( $this, 'layout_block_preview' ) );
57
58 // Initialize the additional admin classes.
59 SiteOrigin_Panels_Admin_Widget_Dialog::single();
60 SiteOrigin_Panels_Admin_Widgets_Bundle::single();
61 SiteOrigin_Panels_Admin_Layouts::single();
62
63 // Check to make sure we have all the correct markup.
64 SiteOrigin_Panels_Admin_Dashboard::single();
65
66 // Load the Installer if it's not already active.
67 if ( ! class_exists( 'SiteOrigin_Installer' ) ) {
68 include plugin_dir_path( __FILE__ ) . 'installer/siteorigin-installer.php';
69 }
70
71 $this->in_save_post = false;
72
73 // Block editor specific actions.
74 if ( function_exists( 'register_block_type' ) ) {
75 add_action( 'admin_notices', array( $this, 'admin_notices' ) );
76 add_filter( 'gutenberg_can_edit_post_type', array( $this, 'show_classic_editor_for_panels' ), 10, 2 );
77 add_filter( 'use_block_editor_for_post_type', array( $this, 'show_classic_editor_for_panels' ), 10, 2 );
78 add_action( 'admin_print_scripts-edit.php', array( $this, 'add_panels_add_new_button' ) );
79
80 if ( siteorigin_panels_setting( 'admin-post-state' ) ) {
81 add_filter( 'display_post_states', array( $this, 'add_panels_post_state' ), 10, 2 );
82 }
83 }
84
85 // Inline Saving.
86 add_filter( 'heartbeat_received', array( $this, 'inline_saving_heartbeat_received' ), 10, 2 );
87
88 // Classic editor notice.
89 add_filter( 'so_panels_show_classic_admin_notice', array( $this, 'maybe_hide_admin_notice' ), 9 );
90 add_action( 'wp_ajax_so_panels_dismiss_post_notice', array( $this, 'dismiss_admin_post_notice' ) );
91 }
92
93 /**
94 * @return SiteOrigin_Panels_Admin
95 */
96 public static function single() {
97 static $single;
98
99 return empty( $single ) ? $single = new self() : $single;
100 }
101
102 /**
103 * Do some general admin initialization
104 */
105 public function admin_init_widget_count() {
106 if ( siteorigin_panels_setting( 'admin-widget-count' ) ) {
107 // Add the custom columns.
108 $post_types = siteorigin_panels_setting( 'post-types' );
109
110 if ( ! empty( $post_types ) ) {
111 foreach ( $post_types as $post_type ) {
112 add_filter( 'manage_' . $post_type . 's_columns', array( $this, 'add_custom_column' ) );
113 add_action( 'manage_' . $post_type . 's_custom_column', array( $this, 'display_custom_column' ), 10, 2 );
114 }
115 }
116 }
117 }
118
119 /**
120 * Check if this is an admin page.
121 *
122 * @return mixed|void
123 */
124 public static function is_admin() {
125 $screen = get_current_screen();
126 $is_panels_page = ( $screen->base == 'post' && in_array( $screen->id, siteorigin_panels_setting( 'post-types' ) ) ) ||
127 in_array( $screen->base, array( 'appearance_page_so_panels_home_page', 'widgets', 'customize' ) ) ||
128 self::is_block_editor();
129
130 return apply_filters( 'siteorigin_panels_is_admin_page', $is_panels_page );
131 }
132
133 /**
134 * Check if the current page is Gutenberg or the Block Ediotr
135 *
136 * @return bool
137 */
138 public static function is_block_editor() {
139 // This is for the Gutenberg plugin.
140 $is_gutenberg_page = function_exists( 'is_gutenberg_page' ) && is_gutenberg_page();
141 // This is for WP 5 with the integrated block editor.
142 $is_block_editor = false;
143
144 if ( function_exists( 'get_current_screen' ) ) {
145 $current_screen = get_current_screen();
146
147 if ( $current_screen && method_exists( $current_screen, 'is_block_editor' ) ) {
148 $is_block_editor = $current_screen->is_block_editor();
149 }
150 }
151
152 return $is_gutenberg_page || $is_block_editor;
153 }
154
155 /**
156 * Add action links to the plugin list for Page Builder.
157 *
158 * @return array
159 */
160 public function plugin_action_links( $links ) {
161 if ( ! is_array( $links ) ) {
162 return $links;
163 }
164
165 unset( $links['edit'] );
166 $links[] = '<a href="' . esc_url( admin_url( 'options-general.php?page=siteorigin_panels' ) ) . '">' . __( 'Settings', 'siteorigin-panels' ) . '</a>';
167 $links[] = '<a href="http://siteorigin.com/threads/plugin-page-builder/">' . __( 'Support', 'siteorigin-panels' ) . '</a>';
168
169 if ( SiteOrigin_Panels::display_premium_teaser() ) {
170 $links[] = '<a href="' . esc_url( SiteOrigin_Panels::premium_url() ) . '" style="color: #3db634" target="_blank" rel="noopener noreferrer">' . __( 'Addons', 'siteorigin-panels' ) . '</a>';
171 }
172
173 return $links;
174 }
175
176 /**
177 * Callback to register the Page Builder Metaboxes
178 */
179 public function add_meta_boxes() {
180 foreach ( siteorigin_panels_setting( 'post-types' ) as $type ) {
181 add_meta_box(
182 'so-panels-panels',
183 __( 'Page Builder', 'siteorigin-panels' ),
184 array( $this, 'render_meta_boxes' ),
185 (string) $type,
186 'advanced',
187 'high',
188 array(
189 // Ideally when we have panels data for a page we would set this to false and it would cause the
190 // editor to fall back to classic editor, but that's not the case so we just declare it as a `__back_compat_meta_box`.
191 '__back_compat_meta_box' => true,
192 '__block_editor_compatible_meta_box' => false,
193 )
194 );
195 }
196 }
197
198 /**
199 * Render a panel metabox.
200 */
201 public function render_meta_boxes( $post ) {
202 $panels_data = $this->get_current_admin_panels_data();
203 $preview_url = SiteOrigin_Panels::preview_url();
204
205 if ( apply_filters( 'siteorigin_panels_add_preview_content', true ) ) {
206 $preview_content = apply_filters( 'siteorigin_panels_add_preview_content', true ) ? $this->generate_panels_preview( $post->ID, $panels_data ) : '';
207 }
208
209 $builder_id = uniqid();
210 $builder_type = apply_filters( 'siteorigin_panels_post_builder_type', 'editor_attached', $post, $panels_data );
211 $builder_supports = apply_filters( 'siteorigin_panels_builder_supports', array(), $post, $panels_data );
212 include plugin_dir_path( __FILE__ ) . '../tpl/metabox-panels.php';
213 }
214
215 /**
216 * Save the panels data
217 *
218 * @action save_post
219 */
220 public function save_post( $post_id ) {
221 // Check that everything is valid with this save.
222 if (
223 $this->in_save_post ||
224 ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ||
225 empty( $_POST['_sopanels_nonce'] ) ||
226 ! wp_verify_nonce( $_POST['_sopanels_nonce'], 'save' ) ||
227 ! current_user_can( 'edit_post', $post_id ) ||
228 ! isset( $_POST['panels_data'] )
229 ) {
230 return;
231 }
232 $this->in_save_post = true;
233 // Get post from db as it might have been changed and saved by other plugins.
234 $post = get_post( $post_id );
235 $old_panels_data = get_post_meta( $post_id, 'panels_data', true );
236 $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
237
238 $panels_data['widgets'] = $this->process_raw_widgets(
239 ! empty( $panels_data['widgets'] ) ? $panels_data['widgets'] : array(),
240 ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
241 false
242 );
243
244 if ( siteorigin_panels_setting( 'sidebars-emulator' ) ) {
245 $sidebars_emulator = SiteOrigin_Panels_Sidebars_Emulator::single();
246 $panels_data['widgets'] = $sidebars_emulator->generate_sidebar_widget_ids( $panels_data['widgets'], $post_id );
247 }
248
249 $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
250 $panels_data = apply_filters( 'siteorigin_panels_data_pre_save', $panels_data, $post, $post_id );
251
252 if ( ! empty( $panels_data['widgets'] ) || ! empty( $panels_data['grids'] ) ) {
253 // Use `update_metadata` instead of `update_post_meta` to prevent saving to parent post when it's a revision, e.g. preview.
254 update_metadata( 'post', $post_id, 'panels_data', map_deep( $panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) ) );
255
256 if ( siteorigin_panels_setting( 'copy-content' ) ) {
257 // Store a version of the HTML in post_content
258 $post_parent_id = wp_is_post_revision( $post_id );
259 $layout_id = ( ! empty( $post_parent_id ) ) ? $post_parent_id : $post_id;
260
261 SiteOrigin_Panels_Post_Content_Filters::add_filters();
262 $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] = true;
263 $post_content = SiteOrigin_Panels::renderer()->render( $layout_id, false, $panels_data );
264 $post_css = SiteOrigin_Panels::renderer()->generate_css( $layout_id, $panels_data );
265 SiteOrigin_Panels_Post_Content_Filters::remove_filters();
266 unset( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] );
267
268 // Update the post_content.
269 $post->post_content = $post_content;
270
271 if ( siteorigin_panels_setting( 'copy-styles' ) ) {
272 $post->post_content .= "\n\n";
273 $post->post_content .= '<style type="text/css" class="panels-style" data-panels-style-for-post="' . (int) $layout_id . '">';
274 $post->post_content .= '@import url(' . esc_url( SiteOrigin_Panels::front_css_url() ) . '); ';
275 $post->post_content .= $post_css;
276 $post->post_content .= '</style>';
277 }
278 $copy_content_update_method = apply_filters(
279 'siteorigin_panels_copy_content_update_method',
280 'wp_update_post',
281 $post,
282 $post_id,
283 $panels_data
284 );
285
286 if ( $copy_content_update_method === 'direct_db' ) {
287 $this->copy_content_update_post_direct_db( $post );
288 } else {
289 // Prevent slug modification during content-only update.
290 // wp_update_post triggers the full wp_insert_post pipeline,
291 // where filters from other plugins (e.g. WPML) can corrupt
292 // the post slug. Lock it to the current value.
293 $slug_lock = static function( $override, $slug, $id ) use ( $post ) {
294 return $id === $post->ID ? $slug : $override;
295 };
296 add_filter( 'pre_wp_unique_post_slug', $slug_lock, 1, 3 );
297
298 $copy_content_update_args = array(
299 'ID' => $post->ID,
300 'post_content' => $post->post_content,
301 );
302 wp_update_post( $copy_content_update_args, false, true );
303
304 remove_filter( 'pre_wp_unique_post_slug', $slug_lock, 1 );
305 }
306 }
307 } else {
308 // There are no widgets or rows, so delete the panels data.
309 delete_post_meta( $post_id, 'panels_data' );
310 }
311
312 // If this is a Live Editor Quick Edit, setup redirection.
313 if (
314 siteorigin_panels_setting( 'live-editor-quick-link-close-after' ) &&
315 ! empty( $_POST['_wp_http_referer'] ) &&
316 strpos( $_POST['_wp_http_referer'], 'so_live_editor' ) !== false
317 ) {
318 add_filter( 'redirect_post_location', array( $this, 'live_editor_redirect_after' ), 10, 2 );
319 }
320
321 $this->in_save_post = false;
322 }
323
324 /**
325 * Update post content directly in the posts table and clear cache.
326 *
327 * @param WP_Post $post Post to update.
328 *
329 * @return void
330 */
331 private function copy_content_update_post_direct_db( $post ) {
332 global $wpdb;
333
334 $wpdb->update(
335 $wpdb->posts,
336 array(
337 'post_content' => $post->post_content,
338 'post_modified' => current_time( 'mysql' ),
339 'post_modified_gmt' => current_time( 'mysql', true ),
340 ),
341 array(
342 'ID' => $post->ID,
343 ),
344 array( '%s', '%s', '%s' ),
345 array( '%d' )
346 );
347 clean_post_cache( $post->ID );
348 }
349
350 /*
351 * Handles Live Editor Quick Link redirection after editing.
352 */
353 public function live_editor_redirect_after( $location, $post_id ) {
354 return get_permalink( $post_id );
355 }
356
357 /**
358 * Enqueue the panels admin scripts
359 *
360 * @param string $prefix
361 * @param bool $force Should we force the enqueues
362 *
363 * @action admin_print_scripts-post-new.php
364 * @action admin_print_scripts-post.php
365 * @action admin_print_scripts-appearance_page_so_panels_home_page
366 */
367 public function enqueue_admin_scripts( $prefix = '', $force = false ) {
368 $screen = get_current_screen();
369
370 if ( $force || self::is_admin() ) {
371 wp_register_script(
372 'wp-color-picker-alpha',
373 esc_url( siteorigin_panels_url( 'js/lib/wp-color-picker-alpha' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ) ),
374 array( 'wp-color-picker' ),
375 '3.0.2',
376 true
377 );
378
379 if (
380 ! wp_script_is( 'select2', 'registered' ) &&
381 ! wp_script_is( 'select2', 'enqueued' )
382 ) {
383 wp_register_script(
384 'select2',
385 esc_url( siteorigin_panels_url( 'js/lib/select2' . SITEORIGIN_PANELS_JS_SUFFIX . '.js'
386 ) ),
387 array( 'jquery' ),
388 '4.1.0-rc.0'
389 );
390
391 wp_enqueue_style(
392 'select2',
393 siteorigin_panels_url( 'css/lib/select2' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' ),
394 array(),
395 '4.1.0-rc.0'
396 );
397 }
398
399 // Media is required for row styles.
400 wp_enqueue_media();
401 wp_enqueue_script(
402 'so-panels-admin',
403 esc_url( siteorigin_panels_url( 'js/siteorigin-panels' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ) ),
404 array(
405 'jquery',
406 'jquery-ui-resizable',
407 'jquery-ui-sortable',
408 'jquery-ui-draggable',
409 'jquery-ui-slider',
410 'wp-color-picker-alpha',
411 'underscore',
412 'backbone',
413 'plupload',
414 'plupload-all',
415 ),
416 SITEORIGIN_PANELS_VERSION,
417 true
418 );
419 add_action( 'admin_footer', array( $this, 'js_templates' ) );
420
421 $widgets = $this->get_widgets();
422 $directory_enabled = get_user_meta( get_current_user_id(), 'so_panels_directory_enabled', true );
423
424 // This is the widget we'll use for default text.
425 if ( ! empty( $widgets[ 'SiteOrigin_Widget_Editor_Widget' ] ) ) {
426 $text_widget = 'SiteOrigin_Widget_Editor_Widget';
427 } elseif ( ! empty( $widgets[ 'WP_Widget_Text' ] ) ) {
428 $text_widget = 'WP_Widget_Text';
429 } else {
430 $text_widget = false;
431 }
432 $text_widget = apply_filters( 'siteorigin_panels_text_widget_class', $text_widget );
433
434 $user = wp_get_current_user();
435
436 $tabs = apply_filters( 'siteorigin_panels_widget_dialog_tabs', array() );
437 $tabs = array_map( function ( $tab ) {
438 $tab['title'] = esc_html( $tab['title'] );
439 $tab['filter']['groups'] = self::escape_text_recursive( $tab['filter']['groups'] );
440 return $tab;
441 }, $tabs );
442
443 $contextual_actions = $this->get_contextual_menu_actions();
444
445 $load_on_attach = siteorigin_panels_setting( 'load-on-attach' ) || isset( $_GET['siteorigin-page-builder'] );
446 wp_localize_script( 'so-panels-admin', 'panelsOptions', array(
447 'user' => ! empty( $user ) ? $user->ID : 0,
448 'ajaxurl' => esc_url( wp_nonce_url( admin_url( 'admin-ajax.php' ), 'panels_action', '_panelsnonce' ) ),
449 'widgets' => $widgets,
450 'text_widget' => $text_widget,
451 'widget_dialog_tabs' => $tabs,
452 'row_layouts' => apply_filters( 'siteorigin_panels_row_layouts', array() ),
453 'directory_enabled' => ! empty( $directory_enabled ),
454 'copy_content' => siteorigin_panels_setting( 'copy-content' ),
455 'cache' => array(),
456 'instant_open' => siteorigin_panels_setting( 'instant-open-widgets' ),
457 'add_media' => esc_html__( 'Choose Media', 'siteorigin-panels' ),
458 'add_media_done' => esc_html__( 'Done', 'siteorigin-panels' ),
459 'default_columns' => apply_filters( 'siteorigin_panels_default_row_columns', array(
460 array(
461 'weight' => 0.5,
462 ),
463 array(
464 'weight' => 0.5,
465 ),
466 ) ),
467
468 // Settings for the contextual menu.
469 'contextual' => array(
470 // Developers can change which widgets are displayed by default using this filter.
471 'default_widgets' => apply_filters( 'siteorigin_panels_contextual_default_widgets', array(
472 'SiteOrigin_Widget_Editor_Widget',
473 'SiteOrigin_Widget_Button_Widget',
474 'SiteOrigin_Widget_Image_Widget',
475 'SiteOrigin_Panels_Widgets_Layout',
476 ) ),
477 'actions' => $contextual_actions,
478 ),
479
480 // General localization messages
481 'loc' => array(
482 'missing_widget' => array(
483 'title' => esc_html__( 'Missing Widget', 'siteorigin-panels' ),
484 'description' => esc_html__( "Page Builder doesn't know about this widget.", 'siteorigin-panels' ),
485 ),
486 'time' => array(
487 // TRANSLATORS: Number of seconds since.
488 'seconds' => esc_html__( '%d seconds', 'siteorigin-panels' ),
489 // TRANSLATORS: Number of minutes since.
490 'minutes' => esc_html__( '%d minutes', 'siteorigin-panels' ),
491 // TRANSLATORS: Number of hours since.
492 'hours' => esc_html__( '%d hours', 'siteorigin-panels' ),
493
494 // TRANSLATORS: A single second since.
495 'second' => esc_html__( '%d second', 'siteorigin-panels' ),
496 // TRANSLATORS: A single minute since.
497 'minute' => esc_html__( '%d minute', 'siteorigin-panels' ),
498 // TRANSLATORS: A single hour since.
499 'hour' => esc_html__( '%d hour', 'siteorigin-panels' ),
500
501 // TRANSLATORS: Time ago - eg. "1 minute before".
502 'ago' => esc_html__( '%s before', 'siteorigin-panels' ),
503 'now' => esc_html__( 'Now', 'siteorigin-panels' ),
504 ),
505 'history' => array(
506 // History messages.
507 'current' => esc_html__( 'Current', 'siteorigin-panels' ),
508 'revert' => esc_html__( 'Original', 'siteorigin-panels' ),
509 'restore' => esc_html__( 'Version restored', 'siteorigin-panels' ),
510 'back_to_editor' => esc_html__( 'Converted to editor', 'siteorigin-panels' ),
511
512 // Widgets.
513 // TRANSLATORS: Message displayed in the history when a widget is deleted.
514 'widget_deleted' => esc_html__( 'Widget deleted', 'siteorigin-panels' ),
515 // TRANSLATORS: Message displayed in the history when a widget is added.
516 'widget_added' => esc_html__( 'Widget added', 'siteorigin-panels' ),
517 // TRANSLATORS: Message displayed in the history when a widget is edited.
518 'widget_edited' => esc_html__( 'Widget edited', 'siteorigin-panels' ),
519 // TRANSLATORS: Message displayed in the history when a widget is duplicated.
520 'widget_duplicated' => esc_html__( 'Widget duplicated', 'siteorigin-panels' ),
521 // TRANSLATORS: Message displayed in the history when a widget position is changed.
522 'widget_moved' => esc_html__( 'Widget moved', 'siteorigin-panels' ),
523
524 // Rows
525 // TRANSLATORS: Message displayed in the history when a row is deleted.
526 'row_deleted' => esc_html__( 'Row deleted', 'siteorigin-panels' ),
527 // TRANSLATORS: Message displayed in the history when a row is added.
528 'row_added' => esc_html__( 'Row added', 'siteorigin-panels' ),
529 // TRANSLATORS: Message displayed in the history when a row is edited.
530 'row_edited' => esc_html__( 'Row edited', 'siteorigin-panels' ),
531 // TRANSLATORS: Message displayed in the history when a row position is changed.
532 'row_moved' => esc_html__( 'Row moved', 'siteorigin-panels' ),
533 // TRANSLATORS: Message displayed in the history when a row is duplicated.
534 'row_duplicated' => esc_html__( 'Row duplicated', 'siteorigin-panels' ),
535 // TRANSLATORS: Message displayed in the history when a row is pasted.
536 'row_pasted' => esc_html__( 'Row pasted', 'siteorigin-panels' ),
537
538 // Cells.
539 'cell_resized' => esc_html__( 'Column resized', 'siteorigin-panels' ),
540
541 // Prebuilt.
542 'prebuilt_loaded' => esc_html__( 'Prebuilt layout loaded', 'siteorigin-panels' ),
543 ),
544
545 // General localization.
546 'prebuilt_loading' => esc_html__( 'Loading prebuilt layout', 'siteorigin-panels' ),
547 'confirm_use_builder' => esc_html__( "Would you like to copy this editor's existing content to Page Builder?", 'siteorigin-panels' ),
548 'confirm_stop_builder' => esc_html__( 'Would you like to clear your Page Builder content and revert to using the standard visual editor?', 'siteorigin-panels' ),
549 // TRANSLATORS: This is the title for a widget called "Layout Builder".
550 'layout_widget' => esc_html__( 'Layout Builder Widget', 'siteorigin-panels' ),
551 // TRANSLATORS: A standard confirmation message
552 'dropdown_confirm' => esc_html__( 'Are you sure?', 'siteorigin-panels' ),
553 // TRANSLATORS: When a layout file is ready to be inserted. %s is the filename.
554 'ready_to_insert' => esc_html__( '%s is ready to insert.', 'siteorigin-panels' ),
555
556 // Everything for the contextual menu.
557 'contextual' => array(
558 'add_widget_below' => esc_html__( 'Add Widget Below', 'siteorigin-panels' ),
559 'add_widget_cell' => esc_html__( 'Add Widget to Column', 'siteorigin-panels' ),
560 'search_widgets' => esc_html__( 'Search Widgets', 'siteorigin-panels' ),
561
562 'add_row' => esc_html__( 'Add Row', 'siteorigin-panels' ),
563 'column' => esc_html__( 'Column', 'siteorigin-panels' ),
564
565 'cell_actions' => esc_html__( 'Column Actions', 'siteorigin-panels' ),
566 'cell_paste_widget' => esc_html__( 'Paste Widget', 'siteorigin-panels' ),
567
568 'widget_actions' => esc_html__( 'Widget Actions', 'siteorigin-panels' ),
569 'widget_edit' => esc_html__( 'Edit Widget', 'siteorigin-panels' ),
570 'widget_duplicate' => esc_html__( 'Duplicate Widget', 'siteorigin-panels' ),
571 'widget_delete' => esc_html__( 'Delete Widget', 'siteorigin-panels' ),
572 'widget_copy' => esc_html__( 'Copy Widget', 'siteorigin-panels' ),
573 'widget_paste' => esc_html__( 'Paste Widget Below', 'siteorigin-panels' ),
574
575 'row_actions' => esc_html__( 'Row Actions', 'siteorigin-panels' ),
576 'row_edit' => esc_html__( 'Edit Row', 'siteorigin-panels' ),
577 'row_duplicate' => esc_html__( 'Duplicate Row', 'siteorigin-panels' ),
578 'row_delete' => esc_html__( 'Delete Row', 'siteorigin-panels' ),
579 'row_copy' => esc_html__( 'Copy Row', 'siteorigin-panels' ),
580 'row_paste' => esc_html__( 'Paste Row', 'siteorigin-panels' ),
581 ),
582 'draft' => esc_html__( 'Draft', 'siteorigin-panels' ),
583 'untitled' => esc_html__( 'Untitled', 'siteorigin-panels' ),
584 'row' => array(
585 'add' => esc_html__( 'New Row', 'siteorigin-panels' ),
586 'edit' => esc_html__( 'Row', 'siteorigin-panels' ),
587 'cellInput' => esc_html__( 'Adjust column size of column %s.', 'siteorigin-panels' ),
588 'direction' => esc_html__( 'Change column direction to the %s', 'siteorigin-panels' ),
589 // TRANSLATORS: Used by the Column Preset Direction button aria-label.
590 'left' => esc_html__( 'left', 'siteorigin-panels' ),
591 // TRANSLATORS: Used by the Column Preset Direction button aria-label.
592 'right' => esc_html__( 'right', 'siteorigin-panels' ),
593 ),
594 'welcomeMessage' => array(
595 'addingDisabled' => esc_html__( 'Hmmm... Adding layout elements is not enabled. Please check if Page Builder has been configured to allow adding elements.', 'siteorigin-panels' ),
596 'oneEnabled' => esc_html__( 'Add a {{%= items[0] %}} to get started.', 'siteorigin-panels' ),
597 'twoEnabled' => esc_html__( 'Add a {{%= items[0] %}} or {{%= items[1] %}} to get started.', 'siteorigin-panels' ),
598 'threeEnabled' => esc_html__( 'Add a {{%= items[0] %}}, {{%= items[1] %}} or {{%= items[2] %}} to get started.', 'siteorigin-panels' ),
599 'addWidgetButton' => "<a href='#' class='so-tool-button so-widget-add'>" . esc_html__( 'Widget', 'siteorigin-panels' ) . '</a>',
600 'addRowButton' => "<a href='#' class='so-tool-button so-row-add'>" . esc_html__( 'Row', 'siteorigin-panels' ) . '</a>',
601 'addPrebuiltButton' => "<a href='#' class='so-tool-button so-prebuilt-add'>" . esc_html__( 'Prebuilt Layout', 'siteorigin-panels' ) . '</a>',
602 'docsMessage' => sprintf(
603 esc_html__( 'Read our %s if you need help.', 'siteorigin-panels' ),
604 "<a href='https://siteorigin.com/page-builder/documentation/' target='_blank' rel='noopener noreferrer'>" . esc_html__( 'documentation', 'siteorigin-panels' ) . '</a>'
605 ),
606 ),
607 ),
608 'plupload' => array(
609 'max_file_size' => wp_max_upload_size() . 'b',
610 'url' => esc_url( wp_nonce_url(
611 admin_url( 'admin-ajax.php' ), 'panels_action', '_panelsnonce'
612 ) ),
613 'flash_swf_url' => esc_url( includes_url( 'js/plupload/plupload.flash.swf' ) ),
614 'silverlight_xap_url' => esc_url( includes_url( 'js/plupload/plupload.silverlight.xap' ) ),
615 'filter_title' => esc_html__( 'Page Builder layouts', 'siteorigin-panels' ),
616 'error_message' => esc_html__( 'Error uploading or importing file.', 'siteorigin-panels' ),
617 ),
618 'wpColorPickerOptions' => apply_filters( 'siteorigin_panels_wpcolorpicker_options', array() ),
619 'prebuiltDefaultScreenshot' => esc_url( siteorigin_panels_url( 'css/images/prebuilt-default.png' ) ),
620 'loadOnAttach' => $load_on_attach,
621 'siteoriginWidgetRegex' => str_replace( '*+', '*', get_shortcode_regex( array( 'siteorigin_widget' ) ) ),
622 'forms' => array(
623 'loadingFailed' => esc_html__( 'Unknown error. Failed to load the form. Please check your internet connection, contact your web site administrator, or try again later.', 'siteorigin-panels' ),
624 ),
625 'row_color' => array(
626 'migrations' => apply_filters( 'siteorigin_panels_admin_row_colors_migration', array(
627 1 => esc_html__( 'soft-blue', 'siteorigin-panels' ),
628 2 => esc_html__( 'soft-red', 'siteorigin-panels' ),
629 3 => esc_html__( 'grayish-violet', 'siteorigin-panels' ),
630 4 => esc_html__( 'lime-green', 'siteorigin-panels' ),
631 5 => esc_html__( 'desaturated-yellow', 'siteorigin-panels' ),
632 ) ),
633 'default' => apply_filters( 'siteorigin_panels_admin_row_colors_default', esc_html__( 'soft-blue', 'siteorigin-panels' ) ),
634 ),
635 ) );
636
637 $js_widgets = array();
638
639 if ( $screen->base != 'widgets' ) {
640 // Render all the widget forms. A lot of widgets use this as a chance to enqueue their scripts.
641 $original_post = isset( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; // Make sure widgets don't change the global post.
642 global $wp_widget_factory;
643
644 foreach ( $wp_widget_factory->widgets as $widget_obj ) {
645 ob_start();
646 $return = $widget_obj->form( array() );
647 // These are the new widgets in WP 4.8 which are largely JS based. They only enqueue their own
648 // scripts on the 'widgets' screen.
649 if ( $this->is_core_js_widget( $widget_obj ) && method_exists( $widget_obj, 'enqueue_admin_scripts' ) ) {
650 $widget_obj->enqueue_admin_scripts();
651 }
652 do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, array() ) );
653 ob_end_clean();
654
655 // Need to render templates for new WP 4.8 widgets when not on the 'widgets' screen or in the customizer.
656 if ( $this->is_core_js_widget( $widget_obj ) ) {
657 $js_widgets[] = $widget_obj;
658 }
659 }
660 $GLOBALS['post'] = $original_post;
661 }
662
663 // This gives panels a chance to enqueue scripts too, without having to check the screen ID.
664 if ( $screen->base != 'widgets' && $screen->base != 'customize' ) {
665 foreach ( $js_widgets as $js_widget ) {
666 $js_widget->render_control_template_scripts();
667 }
668 do_action( 'siteorigin_panel_enqueue_admin_scripts' );
669 do_action( 'sidebar_admin_setup' );
670 }
671 }
672 }
673
674 /**
675 * Prepare contextual menu actions for the builder UI.
676 *
677 * @return array
678 */
679 private function get_contextual_menu_actions(): array {
680 $contextual_actions = apply_filters(
681 'siteorigin_panels_contextual_menu_actions',
682 array(
683 'row' => array(),
684 'widget' => array(),
685 )
686 );
687
688 $contextual_actions = is_array( $contextual_actions ) ? $contextual_actions : array();
689 $allowed_contexts = array( 'row', 'widget', 'cell', 'builder' );
690 $contextual_actions = array_intersect_key( $contextual_actions, array_flip( $allowed_contexts ) );
691 foreach ( $allowed_contexts as $context ) {
692 if ( empty( $contextual_actions[ $context ] ) || ! is_array( $contextual_actions[ $context ] ) ) {
693 $contextual_actions[ $context ] = array();
694 continue;
695 }
696
697 foreach ( $contextual_actions[ $context ] as $action_id => $action ) {
698 if ( empty( $action['title'] ) ) {
699 unset( $contextual_actions[ $context ][ $action_id ] );
700 continue;
701 }
702
703 $contextual_actions[ $context ][ $action_id ] = array(
704 'title' => sanitize_text_field( $action['title'] ),
705 'confirm' => ! empty( $action['confirm'] ),
706 'priority' => isset( $action['priority'] ) ? (int) $action['priority'] : 50,
707 );
708 }
709 }
710
711 return $contextual_actions;
712 }
713
714 /**
715 * Enqueue the admin panel styles.
716 *
717 * @param string $prefix
718 * @param bool $force Should we force the enqueue.
719 *
720 * @action admin_print_styles-post-new.php
721 * @action admin_print_styles-post.php
722 */
723 public function enqueue_admin_styles( $prefix = '', $force = false ) {
724 if ( $force || self::is_admin() ) {
725 wp_enqueue_style(
726 'so-panels-admin',
727 esc_url( siteorigin_panels_url( 'css/admin' . SITEORIGIN_PANELS_CSS_SUFFIX . '.css' ) ),
728 array( 'wp-color-picker' ),
729 SITEORIGIN_PANELS_VERSION
730 );
731 do_action( 'siteorigin_panel_enqueue_admin_styles' );
732
733 $row_colors = SiteOrigin_Panels_Admin::get_row_colors();
734 $row_colors_css = '';
735
736 foreach ( $row_colors as $id => $color ) {
737 $name = ! empty( $color['name'] ) ? sanitize_title( $color['name'] ) : $id;
738 $row_colors_css .= '
739 .siteorigin-panels-builder .so-rows-container .so-row-color-' . $name . '.so-row-color {
740 background-color: ' . $color['active'] . ';
741 border: 1px solid ' . $color['inactive'] . ';
742 }
743 .siteorigin-panels-builder .so-rows-container .so-row-color-' . $name . '.so-row-color.so-row-color-selected:before {
744 background: ' . $color['active'] . ';
745 }
746
747 .siteorigin-panels-builder .so-rows-container .so-row-container.so-row-color-' . $name . ' .so-cells .cell .cell-wrapper {
748 background-color: ' . $color['inactive'] . ';
749 }
750 .siteorigin-panels-builder .so-rows-container .so-row-container.so-row-color-' . $name . ' .so-cells .cell.cell-selected .cell-wrapper {
751 background-color: ' . $color['active'] . ';
752 }
753
754 .siteorigin-panels-builder .so-rows-container .so-row-container.so-row-color-' . $name . ' .so-cells .cell .resize-handle {
755 background-color: ' . $color['cell_divider'] . ';
756 }
757 .siteorigin-panels-builder .so-rows-container .so-row-container.so-row-color-' . $name . ' .so-cells .cell .resize-handle:hover {
758 background-color: ' . $color['cell_divider_hover'] . ';
759 }';
760 }
761
762 if ( ! empty( $row_colors_css ) ) {
763 wp_add_inline_style( 'so-panels-admin', $row_colors_css );
764 }
765 }
766 }
767
768 /**
769 * Add a help tab to pages that include a Page Builder interface.
770 */
771 public function add_help_tab( $prefix ) {
772 $screen = get_current_screen();
773
774 if (
775 ( $screen->base == 'post' && ( in_array( $screen->id, siteorigin_panels_setting( 'post-types' ) ) || $screen->id == '' ) )
776 || ( $screen->id == 'appearance_page_so_panels_home_page' )
777 ) {
778 $screen->add_help_tab( array(
779 'id' => 'panels-help-tab', // Unique id for the tab.
780 'title' => __( 'Page Builder', 'siteorigin-panels' ), // Unique visible title for the tab.
781 'callback' => array( $this, 'help_tab_content' ),
782 ) );
783 }
784 }
785
786 /**
787 * Display the content for the help tab.
788 */
789 public function help_tab_content() {
790 include plugin_dir_path( __FILE__ ) . '../tpl/help.php';
791 }
792
793 /**
794 * Get the Page Builder data for the current admin page.
795 *
796 * @return array
797 */
798 public function get_current_admin_panels_data() {
799 $screen = get_current_screen();
800
801 // Localize the panels with the panels data.
802 if ( $screen->base == 'appearance_page_so_panels_home_page' ) {
803 $home_page_id = get_option( 'page_on_front' );
804
805 if ( empty( $home_page_id ) ) {
806 $home_page_id = get_option( 'siteorigin_panels_home_page_id' );
807 }
808
809 $panels_data = ! empty( $home_page_id ) ? get_post_meta( $home_page_id, 'panels_data', true ) : null;
810
811 if ( is_null( $panels_data ) ) {
812 // Load the default layout.
813 $layouts = apply_filters( 'siteorigin_panels_prebuilt_layouts', array() );
814
815 $home_name = siteorigin_panels_setting( 'home-page-default' ) ? siteorigin_panels_setting( 'home-page-default' ) : 'home';
816 $panels_data = ! empty( $layouts[ $home_name ] ) ? $layouts[ $home_name ] : current( $layouts );
817 } elseif ( empty( $panels_data ) ) {
818 // The current page_on_front isn't using Page Builder.
819 return false;
820 }
821
822 $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, 'home' );
823 } else {
824 global $post;
825
826 if ( ! empty( $post ) ) {
827 $panels_data = get_post_meta( $post->ID, 'panels_data', true );
828 $panels_data = apply_filters( 'siteorigin_panels_data', $panels_data, $post->ID );
829 }
830 }
831
832 if ( empty( $panels_data ) ) {
833 $panels_data = array();
834 }
835
836 return $panels_data;
837 }
838
839 /**
840 * Save home page.
841 */
842 public function save_home_page() {
843 if ( ! isset( $_POST['_sopanels_home_nonce'] ) || ! wp_verify_nonce( $_POST['_sopanels_home_nonce'], 'save' ) ) {
844 return;
845 }
846
847 if ( ! current_user_can( 'edit_theme_options' ) ) {
848 return;
849 }
850
851 if ( ! isset( $_POST['panels_data'] ) ) {
852 return;
853 }
854
855 // Check that the home page ID is set and the home page exists.
856 $page_id = get_option( 'page_on_front' );
857
858 if ( empty( $page_id ) ) {
859 $page_id = get_option( 'siteorigin_panels_home_page_id' );
860 }
861
862 $post_content = wp_unslash( $_POST['post_content'] );
863
864 if ( ! $page_id || get_post_meta( $page_id, 'panels_data', true ) == '' ) {
865 // Lets create a new page.
866 $page_id = wp_insert_post( array(
867 // TRANSLATORS: This is the default name given to a user's home page.
868 'post_title' => __( 'Home Page', 'siteorigin-panels' ),
869 'post_status' => ! empty( $_POST['siteorigin_panels_home_enabled'] ) ? 'publish' : 'draft',
870 'post_type' => 'page',
871 'post_content' => $post_content,
872 'comment_status' => 'closed',
873 ) );
874 update_option( 'page_on_front', $page_id );
875 update_option( 'siteorigin_panels_home_page_id', $page_id );
876
877 // Action triggered when creating a new home page through the custom home page interface.
878 do_action( 'siteorigin_panels_create_home_page', $page_id );
879 } else {
880 // `wp_insert_post` does it's own sanitization, but it seems `wp_update_post` doesn't.
881 $post_content = sanitize_post_field( 'post_content', $post_content, $page_id, 'db' );
882
883 // Update the post with changed content to save revision if necessary.
884 wp_update_post( array( 'ID' => $page_id, 'post_content' => $post_content ) );
885 }
886
887 $page = get_post( $page_id );
888
889 // Save the updated page data.
890 $old_panels_data = get_post_meta( $page_id, 'panels_data', true );
891 $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
892 $panels_data['widgets'] = $this->process_raw_widgets(
893 $panels_data['widgets'],
894 ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
895 false
896 );
897
898 if ( siteorigin_panels_setting( 'sidebars-emulator' ) ) {
899 $sidebars_emulator = SiteOrigin_Panels_Sidebars_Emulator::single();
900 $panels_data['widgets'] = $sidebars_emulator->generate_sidebar_widget_ids( $panels_data['widgets'], $page_id );
901 }
902
903 $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
904 $panels_data = apply_filters( 'siteorigin_panels_data_pre_save', $panels_data, $page, $page_id );
905
906 update_post_meta( $page_id, 'panels_data', map_deep( $panels_data, array( 'SiteOrigin_Panels_Admin', 'double_slash_string' ) ) );
907
908 $template = get_post_meta( $page_id, '_wp_page_template', true );
909 $home_template = siteorigin_panels_setting( 'home-template' );
910
911 if ( ( $template == '' || $template == 'default' ) && ! empty( $home_template ) ) {
912 // Set the home page template.
913 update_post_meta( $page_id, '_wp_page_template', $home_template );
914 }
915
916 if ( ! empty( $_POST['siteorigin_panels_home_enabled'] ) ) {
917 update_option( 'show_on_front', 'page' );
918 update_option( 'page_on_front', $page_id );
919 update_option( 'siteorigin_panels_home_page_id', $page_id );
920 wp_publish_post( $page_id );
921 } else {
922 // We're disabling this home page.
923 update_option( 'show_on_front', 'posts' );
924
925 // Change the post status to draft.
926 $post = get_post( $page_id );
927
928 if ( $post->post_status != 'draft' ) {
929 global $wpdb;
930
931 $wpdb->update( $wpdb->posts, array( 'post_status' => 'draft' ), array( 'ID' => $post->ID ) );
932 clean_post_cache( $post->ID );
933
934 $old_status = $post->post_status;
935 $post->post_status = 'draft';
936 wp_transition_post_status( 'draft', $old_status, $post );
937
938 do_action( 'edit_post', $post->ID, $post );
939 do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
940 do_action( 'save_post', $post->ID, $post, true );
941 do_action( 'wp_insert_post', $post->ID, $post, true );
942 }
943 }
944 }
945
946 /**
947 * After the theme is switched, change the template on the home page if the theme supports home page functionality.
948 */
949 public function update_home_on_theme_change() {
950 $page_id = get_option( 'page_on_front' );
951
952 if ( empty( $page_id ) ) {
953 $page_id = get_option( 'siteorigin_panels_home_page_id' );
954 }
955
956 if ( siteorigin_panels_setting( 'home-page' ) && siteorigin_panels_setting( 'home-template' ) && $page_id && get_post_meta( $page_id, 'panels_data', true ) !== '' ) {
957 // Lets update the home page to use the home template that this theme supports.
958 update_post_meta( $page_id, '_wp_page_template', siteorigin_panels_setting( 'home-template' ) );
959 }
960 }
961
962 /**
963 * @return array|mixed|void
964 */
965 public function get_widgets() {
966 global $wp_widget_factory;
967 $widgets = get_transient( 'siteorigin_panels_widgets' );
968
969 if ( $widgets !== false ) {
970 return $widgets;
971 }
972
973 $widgets = array();
974
975 foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) {
976 $class = preg_match( '/[0-9a-f]{32}/', $class ) ? get_class( $widget_obj ) : $class;
977 $widgets[ $class ] = array(
978 'class' => $class,
979 'title' => ! empty( $widget_obj->name ) ? $widget_obj->name : __( 'Untitled Widget', 'siteorigin-panels' ),
980 'description' => ! empty( $widget_obj->widget_options['description'] ) ? $widget_obj->widget_options['description'] : '',
981 'installed' => true,
982 'groups' => array(),
983 );
984
985 // Get Page Builder specific widget options.
986 if ( isset( $widget_obj->widget_options['panels_title'] ) ) {
987 $widgets[ $class ]['panels_title'] = $widget_obj->widget_options['panels_title'];
988 }
989
990 if ( isset( $widget_obj->widget_options['panels_title_check_sub_fields'] ) ) {
991 $widgets[ $class ]['panels_title_check_sub_fields'] = $widget_obj->widget_options['panels_title_check_sub_fields'];
992 }
993
994 if ( isset( $widget_obj->widget_options['panels_groups'] ) ) {
995 $widgets[ $class ]['groups'] = $widget_obj->widget_options['panels_groups'];
996 }
997
998 if ( isset( $widget_obj->widget_options['panels_icon'] ) ) {
999 $widgets[ $class ]['icon'] = $widget_obj->widget_options['panels_icon'];
1000 }
1001 }
1002
1003 // Other plugins can manipulate the list of widgets. Possibly to add recommended widgets.
1004 $widgets = apply_filters( 'siteorigin_panels_widgets', $widgets );
1005
1006 // Exclude these temporarily, as they won't work until we have a reliable way to enqueue their admin form scripts.
1007 $to_exclude = array(
1008 'Jetpack_Gallery_Widget',
1009 'WPCOM_Widget_GooglePlus_Badge',
1010 'Jetpack_Widget_Social_Icons',
1011 'Jetpack_Twitter_Timeline_Widget',
1012 );
1013
1014 foreach ( $to_exclude as $widget_class ) {
1015 if ( in_array( $widget_class, $widgets ) ) {
1016 unset( $widgets[ $widget_class ] );
1017 }
1018 }
1019
1020 // Sort the widgets alphabetically.
1021 uasort( $widgets, array( $this, 'widgets_sorter' ) );
1022
1023 set_transient( 'siteorigin_panels_widgets', $widgets, 10 * 60 );
1024
1025 return $widgets;
1026 }
1027
1028 /**
1029 * Sorts widgets for get_widgets function by title.
1030 *
1031 * @return int
1032 */
1033 public function widgets_sorter( $a, $b ) {
1034 if ( empty( $a['title'] ) ) {
1035 return - 1;
1036 }
1037
1038 if ( empty( $b['title'] ) ) {
1039 return 1;
1040 }
1041
1042 return $a['title'] > $b['title'] ? 1 : - 1;
1043 }
1044
1045 /**
1046 * Process raw widgets that have come from the Page Builder front end.
1047 *
1048 * @param array $widgets An array of widgets from panels_data.
1049 * @param array $old_widgets
1050 * @param bool $escape_classes Should the class names be escaped.
1051 * @param bool $force
1052 *
1053 * @return array
1054 */
1055 public function process_raw_widgets( $widgets, $old_widgets = array(), $escape_classes = false, $force = false ) {
1056 if ( empty( $widgets ) || ! is_array( $widgets ) ) {
1057 return array();
1058 }
1059
1060 $old_widgets_by_id = array();
1061
1062 if ( ! empty( $old_widgets ) ) {
1063 foreach ( $old_widgets as $widget ) {
1064 if ( ! empty( $widget[ 'panels_info' ][ 'widget_id' ] ) ) {
1065 $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ] = $widget;
1066 unset( $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ][ 'panels_info' ] );
1067 }
1068 }
1069 }
1070
1071 foreach ( $widgets as $i => & $widget ) {
1072 if ( ! is_array( $widget ) ) {
1073 continue;
1074 }
1075
1076 if ( is_array( $widget ) ) {
1077 $info = (array) ( is_array( $widget['panels_info'] ) ? $widget['panels_info'] : $widget['info'] );
1078 } else {
1079 $info = array();
1080 }
1081 unset( $widget['info'] );
1082
1083 $info[ 'class' ] = apply_filters( 'siteorigin_panels_widget_class', $info[ 'class' ] );
1084
1085 if ( ! empty( $info['raw'] ) || $force ) {
1086 $the_widget = SiteOrigin_Panels::get_widget_instance( $info['class'] );
1087
1088 if ( ! empty( $the_widget ) &&
1089 method_exists( $the_widget, 'update' ) ) {
1090 if (
1091 ! empty( $old_widgets_by_id ) &&
1092 ! empty( $widget[ 'panels_info' ][ 'widget_id' ] ) &&
1093 ! empty( $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ] )
1094 ) {
1095 $old_widget = $old_widgets_by_id[ $widget[ 'panels_info' ][ 'widget_id' ] ];
1096 } else {
1097 $old_widget = $widget;
1098 }
1099
1100 /** @var WP_Widget $the_widget */
1101 $the_widget = SiteOrigin_Panels::get_widget_instance( $info['class'] );
1102 $instance = $the_widget->update( $widget, $old_widget );
1103 $instance = apply_filters( 'widget_update_callback', $instance, $widget, $old_widget, $the_widget );
1104
1105 $widget = $instance;
1106
1107 unset( $info['raw'] );
1108 }
1109 }
1110
1111 if ( $escape_classes ) {
1112 // Escaping for namespaced widgets.
1113 $info[ 'class' ] = preg_replace( '/\\\\+/', '\\\\\\\\', $info['class'] );
1114 }
1115
1116 $widget['panels_info'] = $info;
1117 }
1118
1119 return $widgets;
1120 }
1121
1122 private function column_sizes_round( $size ) {
1123 if ( is_array( $size ) ) {
1124 return array_map( array( $this, 'column_sizes_round' ), $size );
1125 }
1126 return round( $size , 2);
1127 }
1128
1129 /**
1130 * Add all the footer JS templates.
1131 */
1132 public function js_templates() {
1133 $column_sizes = apply_filters( 'siteorigin_panels_column_sizes', array(
1134 2 => array(
1135 array( 50, 50 ),
1136 array( 25, 75 ),
1137 array( 61.8, 38.2 ),
1138 ),
1139 3 => array(
1140 array( 33, 33, 33 ),
1141 array( 25, 50, 25 ),
1142 ),
1143 4 => array(
1144 array( 25, 25, 25, 25 ),
1145 array( 10, 40, 40, 10 ),
1146 ),
1147 5 => array(
1148 array( 20, 20, 20, 20, 20 ),
1149 array( 10, 15, 30, 15, 30 ),
1150 ),
1151 ) );
1152
1153 // Prevent extra long column sizes.
1154 if ( ! empty( $column_sizes ) ) {
1155 $column_sizes = array_map( array( $this, 'column_sizes_round' ), $column_sizes );
1156 }
1157
1158 include plugin_dir_path( __FILE__ ) . '../tpl/js-templates.php';
1159 }
1160
1161 public static function get_row_colors() {
1162 $row_colors = apply_filters( 'siteorigin_panels_admin_row_colors', array(
1163 1 => array(
1164 'name' => __( 'Soft Blue', 'siteorigin-panels' ),
1165 'inactive' => '#cde2ec',
1166 'active' => '#a4cadd',
1167 'cell_divider' => '#e7f1f6',
1168 'cell_divider_hover' => '#dcebf2',
1169 ),
1170 2 => array(
1171 'name' => __( 'Soft Red', 'siteorigin-panels' ),
1172 'inactive' => '#f2c2be',
1173 'active' => '#e9968f',
1174 'cell_divider' => '#f8dedc',
1175 'cell_divider_hover' => '#f5d2cf',
1176 ),
1177 3 => array(
1178 'name' => __( 'Grayish Violet', 'siteorigin-panels' ),
1179 'inactive' => '#d5ccdf',
1180 'active' => '#b9aac9',
1181 'cell_divider' => '#e7e2ed',
1182 'cell_divider_hover' => '#dfd9e7',
1183 ),
1184 4 => array(
1185 'name' => __( 'Lime Green', 'siteorigin-panels' ),
1186 'inactive' => '#cae7cd',
1187 'active' => '#a3d6a9',
1188 'cell_divider' => '#e3f2e4',
1189 'cell_divider_hover' => '#d8edda',
1190 ),
1191 5 => array(
1192 'name' => __( 'Desaturated Yellow', 'siteorigin-panels' ),
1193 'inactive' => '#e2dcb1',
1194 'active' => '#d3ca88',
1195 'cell_divider' => '#ece8cb',
1196 'cell_divider_hover' => '#e8e3c0',
1197 ),
1198 ) );
1199
1200 // Ensure all of the colors are valid.
1201 foreach ( $row_colors as $id => $color ) {
1202 unset( $name );
1203
1204 if (
1205 ! empty( $color['inactive'] ) &&
1206 ! empty( $color['active'] ) &&
1207 ! empty( $color['cell_divider'] ) &&
1208 ! empty( $color['cell_divider_hover'] )
1209 ) {
1210 // If color has a name set, store it and re-apply later.
1211 if ( ! empty( $color['name'] ) ) {
1212 $name = $color['name'];
1213 unset( $color['name'] );
1214 }
1215
1216 $valid_row_colors[ $id ] = array_map( 'sanitize_hex_color', $color );
1217
1218 if ( ! empty( $name ) ) {
1219 $valid_row_colors[ $id ]['name'] = $name;
1220 }
1221 }
1222 }
1223
1224 return ! empty( $valid_row_colors ) ? $valid_row_colors : array();
1225 }
1226
1227 /**
1228 * Render a widget form with all the Page Builder specific fields.
1229 *
1230 * @param string $widget_class The class of the widget
1231 * @param array $instance Widget values
1232 * @param bool $raw
1233 * @param string $widget_number
1234 *
1235 * @return mixed|string The form
1236 */
1237 public function render_form( $widget_class, $instance = array(), $raw = false, $widget_number = '{$id}' ) {
1238 $the_widget = SiteOrigin_Panels::get_widget_instance( $widget_class );
1239 // This is a chance for plugins to replace missing widgets
1240 $the_widget = apply_filters( 'siteorigin_panels_widget_object', $the_widget, $widget_class );
1241
1242 if ( empty( $the_widget ) || ! is_a( $the_widget, 'WP_Widget' ) ) {
1243 $widgets = $this->get_widgets();
1244
1245 if ( ! empty( $widgets[ $widget_class ] ) && ! empty( $widgets[ $widget_class ]['plugin'] ) ) {
1246 // We know about this widget, show a form about installing it.
1247 $install_url = siteorigin_panels_plugin_activation_install_url( $widgets[ $widget_class ]['plugin']['slug'], $widgets[ $widget_class ]['plugin']['name'] );
1248 $form =
1249 '<div class="panels-missing-widget-form">' .
1250 '<p>' .
1251 preg_replace(
1252 array(
1253 '/1\{ *(.*?) *\}/',
1254 '/2\{ *(.*?) *\}/',
1255 ),
1256 array(
1257 '<a href="' . $install_url . '" target="_blank" rel="noopener noreferrer">$1</a>',
1258 '<strong>$1</strong>',
1259 ),
1260 sprintf(
1261 __( 'You need to install 1{%1$s} to use the widget 2{%2$s}.', 'siteorigin-panels' ),
1262 $widgets[ $widget_class ]['plugin']['name'],
1263 $widget_class
1264 )
1265 ) .
1266 '</p>' .
1267 '<p>' . __( "Save and reload this page to start using the widget after you've installed it.", 'siteorigin-panels' ) . '</p>' .
1268 '</div>';
1269 } else {
1270 // This widget is missing, so show a missing widgets form.
1271 $form =
1272 '<div class="panels-missing-widget-form"><p>' .
1273 preg_replace(
1274 array(
1275 '/1\{ *(.*?) *\}/',
1276 '/2\{ *(.*?) *\}/',
1277 ),
1278 array(
1279 '<strong>$1</strong>',
1280 '<a href="https://siteorigin.com/thread/" target="_blank" rel="noopener noreferrer">$1</a>',
1281 ),
1282 sprintf(
1283 __( 'The widget 1{%1$s} is not available. Please try locate and install the missing plugin. Post on the 2{support forums} if you need help.', 'siteorigin-panels' ),
1284 esc_html( $widget_class )
1285 )
1286 ) .
1287 '</p></div>';
1288 }
1289
1290 // Allow other themes and plugins to change the missing widget form.
1291 return apply_filters( 'siteorigin_panels_missing_widget_form', $form, $widget_class, $instance );
1292 }
1293
1294 if ( $raw ) {
1295 $instance = $the_widget->update( $instance, $instance );
1296 }
1297
1298 $the_widget->id = 'temp';
1299 $the_widget->number = $widget_number;
1300
1301 do_action( 'siteorigin_panels_before_widget_form', $the_widget, $instance );
1302
1303 ob_start();
1304
1305 if ( $this->is_core_js_widget( $the_widget ) ) {
1306 ?><div class="widget-content"><?php
1307 }
1308 $return = $the_widget->form( $instance );
1309 do_action_ref_array( 'in_widget_form', array( &$the_widget, &$return, $instance ) );
1310
1311 if ( $this->is_core_js_widget( $the_widget ) ) {
1312 ?>
1313 </div>
1314 <input type="hidden" name="id_base" class="id_base" value="<?php echo esc_attr( $the_widget->id_base ); ?>" />
1315 <?php
1316 }
1317 $form = ob_get_clean();
1318
1319 // Convert the widget field naming into ones that Page Builder uses.
1320 $exp = preg_quote( $the_widget->get_field_name( '____' ) );
1321 $exp = str_replace( '____', '(.*?)', $exp );
1322 $form = preg_replace( '/' . $exp . '/', 'widgets[' . preg_replace( '/\$(\d)/', '\\\$$1', $widget_number ) . '][$1]', $form );
1323
1324 $form = apply_filters( 'siteorigin_panels_widget_form', $form, $widget_class, $instance );
1325
1326 // Add all the information fields.
1327 return $form;
1328 }
1329
1330 /**
1331 * Checks whether a widget is considered to be a JS widget. I.e. it needs to have scripts and/or styles enqueued for
1332 * it's admin form to work.
1333 *
1334 * Can remove the whitelist of core widgets when all widgets are following a similar pattern.
1335 *
1336 * @param $widget The widget to be tested.
1337 *
1338 * @return bool Whether or not the widget is considered a JS widget.
1339 */
1340 public function is_core_js_widget( $widget ) {
1341 $js_widgets = apply_filters(
1342 'siteorigin_panels_core_js_widgets',
1343 array(
1344 'WP_Widget_Custom_HTML',
1345 'WP_Widget_Media_Audio',
1346 'WP_Widget_Media_Gallery',
1347 'WP_Widget_Media_Image',
1348 'WP_Widget_Media_Video',
1349 'WP_Widget_Text',
1350 )
1351 );
1352
1353 $is_js_widget = in_array( get_class( $widget ), $js_widgets ) &&
1354 // Need to check this for `WP_Widget_Text` which was not a JS widget before 4.8
1355 method_exists( $widget, 'render_control_template_scripts' );
1356
1357 return $is_js_widget;
1358 }
1359
1360 public function generate_panels_preview( $post_id, $panels_data ) {
1361 $GLOBALS[ 'SITEORIGIN_PANELS_PREVIEW_RENDER' ] = true;
1362 $return = SiteOrigin_Panels::renderer()->render( (int) $post_id, false, $panels_data );
1363
1364 unset( $GLOBALS[ 'SITEORIGIN_PANELS_PREVIEW_RENDER' ] );
1365
1366 return $return;
1367 }
1368
1369 /**
1370 * Get builder content based on the submitted panels_data.
1371 */
1372 public function action_builder_content() {
1373 header( 'content-type: text/html' );
1374
1375 if ( ! wp_verify_nonce( $_GET['_panelsnonce'], 'panels_action' ) ) {
1376 wp_die();
1377 }
1378
1379 if ( ! current_user_can( 'edit_post', $_POST['post_id'] ) ) {
1380 wp_die();
1381 }
1382
1383 if ( empty( $_POST['post_id'] ) || empty( $_POST['panels_data'] ) ) {
1384 wp_die();
1385 }
1386
1387 $old_panels_data = get_post_meta( $_POST['post_id'], 'panels_data', true );
1388 $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
1389 if ( json_last_error() !== JSON_ERROR_NONE ) {
1390 wp_die();
1391 }
1392
1393 $panels_data['widgets'] = $this->process_raw_widgets(
1394 $panels_data['widgets'],
1395 ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
1396 false
1397 );
1398 $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1399
1400 // Create a version of the builder data for post content.
1401 SiteOrigin_Panels_Post_Content_Filters::add_filters();
1402 $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] = true;
1403 echo SiteOrigin_Panels::renderer()->render( (int) $_POST['post_id'], false, $panels_data );
1404 SiteOrigin_Panels_Post_Content_Filters::remove_filters();
1405 unset( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] );
1406
1407 wp_die();
1408 }
1409
1410 /**
1411 * Get builder content based on the submitted panels_data.
1412 */
1413 public function action_builder_content_json() {
1414 header( 'content-type: application/json' );
1415 $return = array( 'post_content' => '', 'preview' => '', 'sanitized_panels_data' => '' );
1416
1417 if ( ! wp_verify_nonce( $_GET['_panelsnonce'], 'panels_action' ) ) {
1418 wp_die();
1419 }
1420
1421 if ( ! empty( $_POST['post_id'] ) ) {
1422 // This is a post so ensure the user is able to edit it.
1423 if ( ! current_user_can( 'edit_post', $_POST['post_id'] ) ) {
1424 wp_die();
1425 }
1426 $old_panels_data = get_post_meta( $_POST['post_id'], 'panels_data', true );
1427 } else {
1428 // This isn't a post, add default data to skip post speciifc checks.
1429 $old_panels_data = array();
1430 $_POST['post_id'] = 0;
1431 }
1432
1433 if ( empty( $_POST['panels_data'] ) ) {
1434 echo wp_json_encode( $return );
1435 wp_die();
1436 }
1437
1438 $panels_data = json_decode( wp_unslash( $_POST['panels_data'] ), true );
1439 if ( json_last_error() !== JSON_ERROR_NONE ) {
1440 echo wp_json_encode( $return );
1441 wp_die();
1442 }
1443
1444 $panels_data['widgets'] = $this->process_raw_widgets(
1445 $panels_data['widgets'],
1446 ! empty( $old_panels_data['widgets'] ) ? $old_panels_data['widgets'] : false,
1447 false
1448 );
1449 $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1450 $return['sanitized_panels_data'] = $panels_data;
1451
1452 // Create a version of the builder data for post content.
1453 SiteOrigin_Panels_Post_Content_Filters::add_filters();
1454 $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] = true;
1455 $return['post_content'] = SiteOrigin_Panels::renderer()->render( (int) $_POST['post_id'], false, $panels_data );
1456 SiteOrigin_Panels_Post_Content_Filters::remove_filters();
1457 unset( $GLOBALS[ 'SITEORIGIN_PANELS_POST_CONTENT_RENDER' ] );
1458
1459 $return['preview'] = $this->generate_panels_preview( (int) $_POST['post_id'], $panels_data );
1460
1461 echo wp_json_encode( $return );
1462
1463 wp_die();
1464 }
1465
1466 /**
1467 * Display a widget form with the provided data.
1468 */
1469 public function action_widget_form() {
1470 if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'panels_action' ) ) {
1471 wp_die(
1472 __( 'The supplied nonce is invalid.', 'siteorigin-panels' ),
1473 __( 'Invalid nonce.', 'siteorigin-panels' ),
1474 403
1475 );
1476 }
1477
1478 if ( empty( $_REQUEST['widget'] ) ) {
1479 wp_die(
1480 __( 'Please specify the type of widget form to be rendered.', 'siteorigin-panels' ),
1481 __( 'Missing widget type.', 'siteorigin-panels' ),
1482 400
1483 );
1484 }
1485
1486 $request = array_map( 'stripslashes_deep', $_REQUEST );
1487
1488 $widget_class = sanitize_text_field( $request['widget'] );
1489 $widget_class = apply_filters( 'siteorigin_panels_widget_class', $widget_class );
1490 $instance = ! empty( $request['instance'] ) ? json_decode( $request['instance'], true ) : array();
1491 $form = $this->render_form( $widget_class, $instance, $_REQUEST['raw'] == 'true' );
1492 $form = apply_filters( 'siteorigin_panels_ajax_widget_form', $form, $widget_class, $instance );
1493
1494 echo $form;
1495 wp_die();
1496 }
1497
1498 /**
1499 * Preview in the live editor when there is no public view of the item.
1500 */
1501 public function action_live_editor_preview() {
1502 if ( empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'live-editor-preview' ) ) {
1503 wp_die();
1504 }
1505
1506 include plugin_dir_path( __FILE__ ) . '../tpl/live-editor-preview.php';
1507
1508 exit();
1509 }
1510
1511 /**
1512 * Preview in the Block Editor.
1513 */
1514 public function layout_block_preview() {
1515 if ( empty( $_POST['panelsData'] ) || empty( $_REQUEST['_panelsnonce'] ) || ! wp_verify_nonce( $_REQUEST['_panelsnonce'], 'layout-block-preview' ) ) {
1516 wp_die();
1517 }
1518
1519 $panels_data = json_decode( wp_unslash( $_POST['panelsData'] ), true );
1520 if ( json_last_error() !== JSON_ERROR_NONE ) {
1521 wp_die();
1522 }
1523
1524 $builder_id = 'gbp' . uniqid();
1525 $panels_data['widgets'] = SiteOrigin_Panels_Admin::single()->process_raw_widgets( $panels_data['widgets'], false, true, true );
1526 $panels_data = SiteOrigin_Panels_Styles_Admin::single()->sanitize_all( $panels_data );
1527 $sowb_active = class_exists( 'SiteOrigin_Widgets_Bundle' );
1528
1529 if ( $sowb_active ) {
1530 // We need this to get our widgets bundle to add it's styles inline for previews.
1531 add_filter( 'siteorigin_widgets_is_preview', '__return_true' );
1532 }
1533 $rendered_layout = SiteOrigin_Panels::renderer()->render( $builder_id, true, $panels_data, $layout_data );
1534 ob_start();
1535
1536 // Need to explicitly call `siteorigin_widget_print_styles` because Gutenberg previews don't render a full version of the front end,
1537 // so neither the `wp_head` nor the `wp_footer` actions are called, which usually trigger `siteorigin_widget_print_styles`.
1538 if ( $sowb_active ) {
1539 siteorigin_widget_print_styles();
1540 }
1541 ?>
1542 <style>@import url('<?php echo esc_url( SiteOrigin_Panels::front_css_url() ); ?>');</style>
1543 <?php
1544 echo SiteOrigin_Panels_Renderer::single()->print_inline_css( true );
1545 $rendered_layout .= ob_get_clean();
1546 echo $rendered_layout;
1547 wp_die();
1548 }
1549
1550 /**
1551 * Add a column that indicates if a column is powered by Page Builder.
1552 *
1553 * @return array
1554 */
1555 public function add_custom_column( $columns ) {
1556 $index = array_search( 'comments', array_keys( $columns ) );
1557
1558 if ( empty( $index ) ) {
1559 $columns = array_merge(
1560 $columns,
1561 array( 'panels' => __( 'Page Builder', 'siteorigin-panels' ) )
1562 );
1563 } else {
1564 $columns = array_slice( $columns, 0, $index, true ) +
1565 array( 'panels' => __( 'Page Builder', 'siteorigin-panels' ) ) +
1566 array_slice( $columns, $index, count( $columns ) - 1, true );
1567 }
1568
1569 return $columns;
1570 }
1571
1572 public function display_custom_column( $column, $post_id ) {
1573 if ( $column != 'panels' ) {
1574 return;
1575 }
1576
1577 $panels_data = get_post_meta( $post_id, 'panels_data', true );
1578
1579 if ( ! empty( $panels_data['widgets'] ) ) {
1580 $widgets_count = count( $panels_data['widgets'] );
1581 printf( _n( '%s Widget', '%s Widgets', $widgets_count, 'siteorigin-panels' ), $widgets_count );
1582 } else {
1583 echo '';
1584 }
1585 }
1586
1587 public function footer_column_css() {
1588 if ( siteorigin_panels_setting( 'admin-widget-count' ) ) {
1589 $screen = get_current_screen();
1590 $post_types = siteorigin_panels_setting( 'post-types' );
1591
1592 if (
1593 $screen->base == 'edit' &&
1594 is_array( $post_types ) &&
1595 in_array( $screen->post_type, $post_types )
1596 ) {
1597 ?><style type="text/css">.column-panels{ width: 10% }</style><?php
1598 }
1599 }
1600 }
1601
1602 /**
1603 * Add double slashes to strings
1604 *
1605 * @return string
1606 */
1607 public static function double_slash_string( $value ) {
1608 return is_string( $value ) ? addcslashes( $value, '\\' ) : $value;
1609 }
1610
1611 public function get_layout_directories() {
1612 }
1613
1614 /**
1615 * Display links for various SiteOrigin Premium Addons.
1616 */
1617 public static function display_footer_premium_link() {
1618 $links = array(
1619 array(
1620 'text' => __( 'Get the row, column, and widget %link%.', 'siteorigin-panels' ),
1621 'url' => SiteOrigin_Panels::premium_url( 'plugin/animations' ),
1622 'anchor' => __( 'Animations Addon', 'siteorigin-panels' ),
1623 ),
1624 array(
1625 'text' => __( 'Get the %link%. Build custom post types with reusable Page Builder layouts.', 'siteorigin-panels' ),
1626 'url' => SiteOrigin_Panels::premium_url( 'plugin/cpt-builder' ),
1627 'anchor' => __( 'CPT Builder Addon', 'siteorigin-panels' ),
1628 ),
1629 array(
1630 'text' => __( 'Get the %link%. Add beautiful and customizable text overlays with animations to your images.', 'siteorigin-panels' ),
1631 'url' => SiteOrigin_Panels::premium_url( 'plugin/image-overlay' ),
1632 'anchor' => __( 'Image Overlay Addon', 'siteorigin-panels' ),
1633 ),
1634 array(
1635 'text' => __( 'Get a %link% for the SiteOrigin Image, Masonry, and Slider Widgets.', 'siteorigin-panels' ),
1636 'url' => SiteOrigin_Panels::premium_url( 'plugin/lightbox' ),
1637 'anchor' => __( 'Lightbox Addon', 'siteorigin-panels' ),
1638 ),
1639 array(
1640 'text' => __( 'Link an entire Page Builder row, column, or widget with the %link%.', 'siteorigin-panels' ),
1641 'url' => SiteOrigin_Panels::premium_url( 'plugin/link-overlay' ),
1642 'anchor' => __( 'Link Overlay Addon', 'siteorigin-panels' ),
1643 ),
1644 array(
1645 'text' => __( 'Get the %link%. Create a widget once, use it everywhere. Update it and the changes reflect in all instances of the widget.', 'siteorigin-panels' ),
1646 'url' => SiteOrigin_Panels::premium_url( 'plugin/mirror-widgets' ),
1647 'anchor' => __( 'Mirror Widgets Addon', 'siteorigin-panels' ),
1648 ),
1649 array(
1650 'text' => __( 'Upload multiple image frames at once to Widgets Bundle Slider and Image Grid type widgets with %link%.', 'siteorigin-panels' ),
1651 'url' => SiteOrigin_Panels::premium_url( 'plugin/multiple-media' ),
1652 'anchor' => __( 'SiteOrigin Premium', 'siteorigin-panels' ),
1653 ),
1654 array(
1655 'text' => __( 'Add parallax background images to your slider type widgets with %link%.', 'siteorigin-panels' ),
1656 'url' => SiteOrigin_Panels::premium_url( 'plugin/parallax-sliders' ),
1657 'anchor' => __( 'SiteOrigin Premium', 'siteorigin-panels' ),
1658 ),
1659 array(
1660 'text' => __( 'Hide rows and widgets based for logged-in or logged-out users with the %link%.', 'siteorigin-panels' ),
1661 'url' => SiteOrigin_Panels::premium_url( 'plugin/toggle-visibility' ),
1662 'anchor' => __( 'Toggle Visibility Addon', 'siteorigin-panels' ),
1663 ),
1664 array(
1665 'text' => __( 'Show or hide rows and widgets between a selected date range with the %link%.', 'siteorigin-panels' ),
1666 'url' => SiteOrigin_Panels::premium_url( 'plugin/toggle-visibility' ),
1667 'anchor' => __( 'Toggle Visibility Addon', 'siteorigin-panels' ),
1668 ),
1669 array(
1670 'text' => __( 'Hide rows and widgets on specific devices with the %link%.', 'siteorigin-panels' ),
1671 'url' => SiteOrigin_Panels::premium_url( 'plugin/toggle-visibility' ),
1672 'anchor' => __( 'Toggle Visibility Addon', 'siteorigin-panels' ),
1673 ),
1674 array(
1675 'text' => __( 'Get a %link% with SiteOrigin Premium.', 'siteorigin-panels' ),
1676 'url' => SiteOrigin_Panels::premium_url( 'plugin/tooltip' ),
1677 'anchor' => __( 'Tooltip Addon', 'siteorigin-panels' ),
1678 ),
1679 array(
1680 'text' => __( 'Use Google Fonts in SiteOrigin Widgets with the %link%.', 'siteorigin-panels' ),
1681 'url' => SiteOrigin_Panels::premium_url( 'plugin/web-font-selector' ),
1682 'anchor' => __( 'Webfont Selector Addon', 'siteorigin-panels' ),
1683 ),
1684 array(
1685 'text' => __( 'Get fast email support for Page Builder with %link%.', 'siteorigin-panels' ),
1686 'url' => SiteOrigin_Panels::premium_url(),
1687 'anchor' => __( 'SiteOrigin Premium', 'siteorigin-panels' ),
1688 ),
1689 array(
1690 'text' => __( 'Organize your Page Builder rows with custom background colors using %link%.', 'siteorigin-panels' ),
1691 'url' => SiteOrigin_Panels::premium_url( 'plugin/custom-row-colors' ),
1692 'anchor' => __( 'SiteOrigin Premium', 'siteorigin-panels' ),
1693 ),
1694 array(
1695 'text' => __( 'Add widget, column, and row Retina background images for high-pixel-density displays with %link%.', 'siteorigin-panels' ),
1696 'url' => SiteOrigin_Panels::premium_url( 'plugin/retina-background-images' ),
1697 'anchor' => __( 'SiteOrigin Premium', 'siteorigin-panels' ),
1698 ),
1699 array(
1700 'text' => __( 'Upgrade to %link% and copy-paste rows and widgets between domains to build pages faster.', 'siteorigin-panels' ),
1701 'url' => SiteOrigin_Panels::premium_url( 'plugin/cross-domain-copy-paste' ),
1702 'anchor' => __( 'SiteOrigin Premium', 'siteorigin-panels' ),
1703 ),
1704 array(
1705 'text' => __( 'Boost your page-building speed by upgrading to %link% – copy and paste rows and widgets across domains with ease!', 'siteorigin-panels' ),
1706 'url' => SiteOrigin_Panels::premium_url( 'plugin/cross-domain-copy-paste' ),
1707 'anchor' => __( 'SiteOrigin Premium', 'siteorigin-panels' ),
1708 ),
1709 array(
1710 'text' => __( 'Introduce dynamic video backgrounds to any Page Builder row, column, or widget with %link%.', 'siteorigin-panels' ),
1711 'url' => SiteOrigin_Panels::premium_url( 'plugin/video-background' ),
1712 'anchor' => __( 'SiteOrigin Premium', 'siteorigin-panels' ),
1713 )
1714 );
1715
1716 if ( class_exists( 'woocommerce' ) ) {
1717 $links[] = array(
1718 'text' => __( 'Get the %link%. Create custom templates for the Product, Archives, Shop, Cart, and Checkout pages.', 'siteorigin-panels' ),
1719 'url' => SiteOrigin_Panels::premium_url( 'plugin/woocommerce-templates' ),
1720 'anchor' => __( 'WooCommerce Templates Addon', 'siteorigin-panels' ),
1721 );
1722 }
1723 $link = $links[ array_rand( $links ) ];
1724
1725 // If this link has an anchor, it has a custom link location.
1726 if ( isset( $link['anchor'] ) ) {
1727 echo str_replace(
1728 '%link%',
1729 '<a href="' . esc_url( $link['url'] ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $link['anchor'] ) . '</a>',
1730 esc_html( $link['text'] )
1731 );
1732 } else {
1733 ?>
1734 <a href="<?php echo esc_url( $link['url'] ); ?>" target="_blank" rel='noopener noreferrer'>
1735 <?php echo esc_html( $link['text'] ); ?>.
1736 </a>
1737 <?php
1738 }
1739 }
1740
1741 public function admin_notices() {
1742 global $typenow, $pagenow;
1743 $is_new = $pagenow == 'post-new.php';
1744 $post_types = siteorigin_panels_setting( 'post-types' );
1745 $is_panels_type = in_array( $typenow, $post_types );
1746 $use_classic = siteorigin_panels_setting( 'use-classic' );
1747 $show_classic_admin_notice = $is_new && $is_panels_type && $use_classic;
1748 $show_classic_admin_notice = apply_filters( 'so_panels_show_classic_admin_notice', $show_classic_admin_notice );
1749
1750 if ( $show_classic_admin_notice ) {
1751 $settings_url = esc_url( self_admin_url( 'options-general.php?page=siteorigin_panels' ) );
1752 $notice = sprintf(
1753 __( "This post type is set to use the Classic Editor by default for new posts. If you'd like to change this to the Block Editor, please go to <a href='%s' class='components-notice__action is-link'>Page Builder Settings</a> and disable <strong>Use Classic Editor for New Posts</strong>.", 'siteorigin-panels' ),
1754 $settings_url
1755 );
1756
1757 $dismiss_url = wp_nonce_url(
1758 add_query_arg( array(
1759 'action' => 'so_panels_dismiss_post_notice',
1760 ), admin_url( 'admin-ajax.php' ) ),
1761 'so_panels_dismiss_post_notice'
1762 );
1763 ?>
1764 <div id="siteorigin-panels-use-classic-notice" class="notice notice-info">
1765 <p id="use-classic-notice">
1766 <?php echo wp_kses_post( $notice ); ?>
1767
1768 <button
1769 type="button"
1770 class="siteorigin-notice-dismiss"
1771 data-url="<?php echo esc_url( $dismiss_url ); ?>"
1772 >
1773 <span class="dashicons dashicons-dismiss" aria-hidden="true"></span>
1774 <span class="screen-reader-text">
1775 <?php esc_html_e( 'Dismiss Notice', 'siteorigin-panels' ); ?>
1776 </span>
1777 </button>
1778 </p>
1779 </div>
1780 <?php
1781 wp_enqueue_script(
1782 'so-panels-admin-notice',
1783 esc_url( siteorigin_panels_url( 'js/admin-notice' . SITEORIGIN_PANELS_JS_SUFFIX . '.js' ) ),
1784 array( 'jquery' ),
1785 SITEORIGIN_PANELS_VERSION,
1786 true
1787 );
1788 }
1789 }
1790
1791 public function maybe_hide_admin_notice( $status ) {
1792 $user_status = get_user_meta( get_current_user_id(), 'so_panels_hide_post_notice', true );
1793 return $user_status ? false : $status;
1794 }
1795
1796 public function dismiss_admin_post_notice() {
1797 check_ajax_referer( 'so_panels_dismiss_post_notice' );
1798 add_user_meta( get_current_user_id(), 'so_panels_hide_post_notice', true, true );
1799 die();
1800 }
1801
1802 /**
1803 * Show Classic Editor for existing PB posts.
1804 *
1805 * @return bool
1806 */
1807 public function show_classic_editor_for_panels( $use_block_editor, $post_type ) {
1808 // For new pages.
1809 if ( isset( $_GET['block-editor'] ) ) {
1810 return $use_block_editor;
1811 } elseif ( isset( $_GET['siteorigin-page-builder'] ) ) {
1812 return false;
1813 }
1814
1815 $post_types = siteorigin_panels_setting( 'post-types' );
1816 global $pagenow;
1817 // If the `$post_type` is set to be used by Page Builder for new posts.
1818 $is_new_panels_type = $pagenow == 'post-new.php' && in_array( $post_type, $post_types );
1819 $use_classic = siteorigin_panels_setting( 'use-classic' );
1820 // For existing posts.
1821 global $post;
1822
1823 if ( function_exists( 'has_blocks' ) && ! empty( $post ) ) {
1824 // If the post has blocks just allow `$use_block_editor` to decide.
1825 if ( ! has_blocks( $post ) ) {
1826 $panels_data = get_post_meta( $post->ID, 'panels_data', true );
1827
1828 if ( ! empty( $panels_data ) || ( $use_classic && $is_new_panels_type ) ) {
1829 $use_block_editor = false;
1830 }
1831 }
1832 } elseif ( $is_new_panels_type ) {
1833 $use_block_editor = false;
1834 }
1835
1836 return $use_block_editor;
1837 }
1838
1839 /**
1840 * This was copied from Gutenberg and slightly modified as a quick way to allow users to create new Page Builder pages
1841 * in the classic editor without requiring the classic editor plugin be installed.
1842 */
1843 public function add_panels_add_new_button() {
1844 global $typenow;
1845
1846 if ( 'wp_block' === $typenow ) {
1847 ?>
1848 <style type="text/css">
1849 .page-title-action {
1850 display: none;
1851 }
1852 </style>
1853 <?php
1854 }
1855
1856 if ( ! $this->show_add_new_dropdown_for_type( $typenow ) ) {
1857 return;
1858 }
1859
1860 ?>
1861 <style type="text/css">
1862 .split-page-title-action {
1863 display: inline-block;
1864 }
1865
1866 .split-page-title-action a,
1867 .split-page-title-action a:active,
1868 .split-page-title-action .expander:after {
1869 padding: 6px 10px;
1870 position: relative;
1871 top: -3px;
1872 text-decoration: none;
1873 border: 1px solid #ccc;
1874 border-radius: 2px 0px 0px 2px;
1875 background: #f7f7f7;
1876 text-shadow: none;
1877 font-weight: 600;
1878 font-size: 13px;
1879 line-height: normal; /* IE8-IE11 need this for buttons */
1880 color: #0073aa; /* some of these controls are button elements and don't inherit from links */
1881 cursor: pointer;
1882 outline: 0;
1883 }
1884
1885 .split-page-title-action a:hover,
1886 .split-page-title-action .expander:hover:after {
1887 border-color: #008EC2;
1888 background: #00a0d2;
1889 color: #fff;
1890 }
1891
1892 .split-page-title-action a:focus,
1893 .split-page-title-action .expander:focus:after {
1894 border-color: #5b9dd9;
1895 box-shadow: 0 0 2px rgba( 30, 140, 190, 0.8 );
1896 }
1897
1898 .split-page-title-action .expander:after {
1899 content: "\f140";
1900 font: 400 20px/.5 dashicons;
1901 speak: none;
1902 top: 0px;
1903 position: relative;
1904 vertical-align: top;
1905 text-decoration: none !important;
1906 padding: 4px 5px 4px 4px;
1907 border-radius: 0px 2px 2px 0px;
1908 <?php echo is_rtl() ? 'right: -1px;' : 'left: -1px;'; ?>
1909 }
1910
1911 .split-page-title-action .dropdown {
1912 display: none;
1913 }
1914
1915 .split-page-title-action .dropdown.visible {
1916 display: block;
1917 position: absolute;
1918 margin-top: 3px;
1919 z-index: 1;
1920 }
1921
1922 .split-page-title-action .dropdown.visible a {
1923 display: block;
1924 top: 0;
1925 margin: -1px 0;
1926 <?php echo is_rtl() ? 'padding-left: 9px;' : 'padding-right: 9px;'; ?>
1927 }
1928
1929 .split-page-title-action .expander {
1930 outline: none;
1931 float: right;
1932 margin-top: 1px;
1933 }
1934
1935 /* Easy Digital Downloads Compatibility */
1936 <?php if ( class_exists( 'EDD_Requirements_Check' ) ) { ?>
1937 .post-type-download .split-page-title-action .expander {
1938 margin-top: 4.5px;
1939 }
1940 <?php } ?>
1941 </style>
1942 <script type="text/javascript">
1943 document.addEventListener( 'DOMContentLoaded', function() {
1944 /* Easy Digital Downloads Compatibility */
1945 <?php if ( class_exists( 'EDD_Requirements_Check' ) ) { ?>
1946 var timeoutSetup = document.getElementsByClassName( 'post-type-download' ).length ? 100 : 0;
1947 <?php } else { ?>
1948 var timeoutSetup = 0;
1949 <?php } ?>
1950
1951 setupAddNewBTN = function() {
1952 var buttons = document.getElementsByClassName( 'page-title-action' ),
1953 button = buttons.item( 0 ),
1954 btnText;
1955
1956 if ( ! button ) {
1957 return;
1958 }
1959
1960 var url = button.href;
1961 var urlHasParams = ( -1 !== url.indexOf( '?' ) );
1962 var panelsUrl = url + ( urlHasParams ? '&' : '?' ) + 'siteorigin-page-builder';
1963 var blockEditorUrl = url + ( urlHasParams ? '&' : '?' ) + 'block-editor';
1964
1965 var newbutton = '<span id="split-page-title-action" class="split-page-title-action">';
1966 newbutton += '<a href="' + url + '">' + button.innerText + '</a>';
1967 newbutton += '<span class="expander" tabindex="0" role="button" aria-haspopup="true" aria-label="<?php echo esc_attr( __( 'Toggle editor selection menu', 'siteorigin-panels' ) ); ?>"></span>';
1968 newbutton += '<span class="dropdown"><a href="' + panelsUrl + '"><?php echo esc_html( __( 'SiteOrigin Page Builder', 'siteorigin-panels' ) ); ?></a>';
1969 newbutton += '<a href="' + blockEditorUrl + '"><?php echo esc_html( __( 'Block Editor', 'siteorigin-panels' ) ); ?></a></span></span><span class="page-title-action" style="display:none;"></span>';
1970
1971 button.insertAdjacentHTML( 'afterend', newbutton );
1972 button.parentNode.removeChild( button );
1973
1974 var expander = document.getElementById( 'split-page-title-action' ).getElementsByClassName( 'expander' ).item( 0 );
1975 var dropdown = expander.parentNode.querySelector( '.dropdown' );
1976 function toggleDropdown() {
1977 dropdown.classList.toggle( 'visible' );
1978 }
1979 expander.addEventListener( 'click', function( e ) {
1980 e.preventDefault();
1981 toggleDropdown();
1982 } );
1983 expander.addEventListener( 'keydown', function( e ) {
1984 if ( 13 === e.which || 32 === e.which ) {
1985 e.preventDefault();
1986 toggleDropdown();
1987 }
1988 } );
1989 }
1990 setTimeout( setupAddNewBTN, timeoutSetup );
1991 } );
1992 </script>
1993 <?php
1994 }
1995
1996 public function inline_saving_heartbeat_received( $response, $data ) {
1997
1998 if ( ! empty( $data['panels'] ) ) {
1999 $panels_data = json_decode( $data['panels'], true );
2000 if ( ! wp_verify_nonce( $panels_data['nonce'], 'save' ) ) {
2001 $response['error'] = __( 'Invalid nonce.', 'siteorigin-panels' );
2002 } elseif ( is_numeric( $panels_data['id'] ) && ! empty( $panels_data['data'] ) ) {
2003 $_POST['_sopanels_nonce'] = $panels_data['nonce'];
2004 $_POST['panels_data'] = wp_slash( json_encode( $panels_data['data'] ) );
2005 $this->save_post( $panels_data['id'] );
2006 }
2007 }
2008
2009 return $response;
2010 }
2011
2012 private function show_add_new_dropdown_for_type( $post_type ) {
2013 $show = in_array( $post_type, siteorigin_panels_setting( 'post-types' ) );
2014
2015 // WooCommerce product type doesn't support Block Editor.
2016 $show = $show && ! ( class_exists( 'WooCommerce' ) && $post_type == 'product' );
2017
2018 if ( class_exists( 'SiteOrigin_Premium_Plugin_Cpt_Builder' ) ) {
2019 $show = $show && $post_type != SiteOrigin_Premium_Plugin_Cpt_Builder::POST_TYPE;
2020 $cpt_builder = SiteOrigin_Premium_Plugin_Cpt_Builder::single();
2021 $so_custom_types = $cpt_builder->get_post_types();
2022 $show = $show && ! isset( $so_custom_types[ $post_type ] );
2023 }
2024
2025 return apply_filters( 'so_panels_show_add_new_dropdown_for_type', $show, $post_type );
2026 }
2027
2028 public function add_panels_post_state( $post_states, $post ) {
2029 $panels_data = get_post_meta( $post->ID, 'panels_data', true );
2030
2031 if ( ! empty( $panels_data ) ) {
2032 $post_states[] = __( 'SiteOrigin Page Builder', 'siteorigin-panels' );
2033 }
2034
2035 return $post_states;
2036 }
2037
2038 /**
2039 * Recursively escapes text to prevent HTML entities from being rendered.
2040 *
2041 * @param mixed $text The text to be escaped.
2042 * @return mixed The escaped text or array.
2043 */
2044 private function escape_text_recursive( $text ) {
2045 if ( is_array( $text ) ) {
2046 return array_map( array( $this, 'escape_text_recursive' ), $text );
2047 } else {
2048 return esc_html( $text );
2049 }
2050 }
2051 }
2052