PluginProbe
UpdraftCentral Dashboard / 0.8.21
UpdraftCentral Dashboard v0.8.21
0.8.33 0.7.2 0.7.3 0.7.4 0.8.0 0.8.1 0.8.10 0.8.11 0.8.12 0.8.13 0.8.14 0.8.15 0.8.16 0.8.17 0.8.18 0.8.19 0.8.2 0.8.20 0.8.21 0.8.22 0.8.23 0.8.24 0.8.25 0.8.26 0.8.27 All 51 releases
updraftcentral / classes / class-editor.php

class-editor.php in UpdraftCentral Dashboard 0.8.21, at classes/class-editor.php

1,248 lines 44.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) die('Access denied.');
3
4 if (!class_exists('UpdraftCentral_Editor')) :
5
6 /**
7 * UpdraftCentral_Editor class.
8 *
9 * Primarily used for loading the classic and block editor resources and handles UpdraftCentral's REST
10 * requests using the block editor to edit remote pages or posts.
11 */
12 class UpdraftCentral_Editor {
13
14 public $type = '';
15
16 protected static $_instance = null;
17
18 /**
19 * Initialize prechecks and hooks for this editor class
20 *
21 * @return void
22 */
23 public function load() {
24 if (!class_exists('UpdraftCentral_REST_Posts_Controller')) include_once UD_CENTRAL_DIR.'/classes/class-rest-posts-controller.php';
25 if (!class_exists('UpdraftCentral_REST_Users_Controller')) include_once UD_CENTRAL_DIR.'/classes/class-rest-users-controller.php';
26 if (!class_exists('UpdraftCentral_REST_Taxonomies_Controller')) include_once UD_CENTRAL_DIR.'/classes/class-rest-taxonomies-controller.php';
27 if (!class_exists('UpdraftCentral_REST_Terms_Controller')) include_once UD_CENTRAL_DIR.'/classes/class-rest-terms-controller.php';
28
29 add_filter('rest_pre_dispatch', array($this, 'intercept_request_data'), 0, 3);
30 add_action('updraftcentral_load_dashboard_js', array($this, 'enqueue_editor_scripts'));
31 }
32
33 /**
34 * Creates an instance of this class. Singleton Pattern
35 *
36 * @return object Instance of this class
37 */
38 public static function instance() {
39 if (empty(self::$_instance)) {
40 self::$_instance = new self();
41 }
42
43 return self::$_instance;
44 }
45
46 /**
47 * Saves placeholder details for later use and enqueus needed resources
48 *
49 * @return void
50 */
51 public function enqueue_editor_scripts() {
52 global $post, $post_type, $post_type_object;
53
54 // We'll create a dummy post or page with status draft (if not yet created) and return its ID.
55 // We will used this post or page object to preload any scripts needed by the editor later on, since
56 // ajax-based calls will no longer have that opporturnity to load resources (which is only done during page load).
57 $post_id = $this->get_placeholder_id($this->type);
58 if (!empty($post_id)) {
59 $post = get_post($post_id);
60
61 $post_type = get_post_type($post);
62 $post_type_object = get_post_type_object($post_type);
63
64 if (!function_exists('get_current_screen')) {
65 include_once(ABSPATH . 'wp-admin/includes/screen.php');
66 include_once ABSPATH . 'wp-admin/includes/template.php';
67 }
68
69 if (!function_exists('get_page_templates')) {
70 include_once ABSPATH . 'wp-admin/includes/theme.php';
71 }
72
73 $this->load_block_editor_resources();
74 }
75
76 $min_or_not = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min';
77 wp_enqueue_script('postbox', admin_url("js/postbox".$min_or_not.".js"), array('jquery-ui-sortable'), UpdraftCentral()->version, 1);
78 }
79
80 /**
81 * Loads block editor resources. Copied from wp-admin/edit-form-blocks.php (WP 5.0) and made
82 * some adjustments (remove and alter lines) to prevent loading the editor prematurely, as we need it to load
83 * on demand basing on the current page/post selected. Updated to support WP 5.4 changes.
84 *
85 * N.B. Applies to WP 5.0 and later only (that is for the core-integrated version).
86 *
87 * @return void
88 */
89 private function load_block_editor_resources() {
90 global $post_type_object, $post, $wp_meta_boxes;
91
92 /*
93 * Emoji replacement is disabled for now, until it plays nicely with React.
94 */
95 remove_action('admin_print_scripts', 'print_emoji_detection_script');
96
97 /*
98 * Block editor implements its own Options menu for toggling Document Panels.
99 */
100 add_filter('screen_options_show_screen', '__return_false');
101
102 wp_enqueue_script('heartbeat');
103 wp_enqueue_script('wp-edit-post');
104 wp_enqueue_script('wp-format-library');
105
106 // Get admin url for handling meta boxes.
107 $meta_box_nonce = wp_create_nonce('meta-box-loader');
108
109 $meta_box_url = admin_url('post.php');
110 $meta_box_url = add_query_arg(
111 array(
112 'post' => $post->ID,
113 'action' => 'edit',
114 'meta-box-loader' => true,
115 'meta-box-loader-nonce' => $meta_box_nonce,
116 '_wpnonce' => $meta_box_nonce, // Supports legacy code prior to 5.4
117 ),
118 $meta_box_url
119 );
120 wp_add_inline_script(
121 'wp-editor',
122 sprintf('var _wpMetaBoxUrl = %s;', wp_json_encode($meta_box_url)),
123 'before'
124 );
125
126 /*
127 * Initialize the editor.
128 */
129
130 $align_wide = get_theme_support('align-wide');
131 $color_palette = current((array) get_theme_support('editor-color-palette'));
132 $font_sizes = current((array) get_theme_support('editor-font-sizes'));
133 $gradient_presets = current((array) get_theme_support('editor-gradient-presets'));
134 $custom_line_height = get_theme_support('custom-line-height');
135 $custom_units = get_theme_support('custom-units');
136
137 /**
138 * Filters the allowed block types for the editor, defaulting to true (all
139 * block types supported).
140 *
141 * @since 5.0.0
142 *
143 * @param bool|array $allowed_block_types Array of block type slugs, or
144 * boolean to enable/disable all.
145 * @param WP_Post $post The post resource data.
146 */
147 $allowed_block_types = apply_filters('allowed_block_types', true, $post);
148
149 /*
150 * Get all available templates for the post/page attributes meta-box.
151 * The "Default template" array element should only be added if the array is
152 * not empty so we do not trigger the template select element without any options
153 * besides the default value.
154 */
155 $available_templates = wp_get_theme()->get_page_templates(get_post($post->ID));
156 $available_templates = !empty($available_templates) ? array_merge(array('' => apply_filters('default_page_template_title', __('Default template'), 'rest-api')), $available_templates) : $available_templates;
157
158 // Media settings.
159 $max_upload_size = wp_max_upload_size();
160 if (!$max_upload_size) {
161 $max_upload_size = 0;
162 }
163
164 // Image sizes.
165
166 /** This filter is documented in wp-admin/includes/media.php */
167 $image_size_names = apply_filters(
168 'image_size_names_choose',
169 array(
170 'thumbnail' => __('Thumbnail'),
171 'medium' => __('Medium'),
172 'large' => __('Large'),
173 'full' => __('Full Size'),
174 )
175 );
176
177 $available_image_sizes = array();
178 foreach ($image_size_names as $image_size_slug => $image_size_name) {
179 $available_image_sizes[] = array(
180 'slug' => $image_size_slug,
181 'name' => $image_size_name,
182 );
183 }
184
185 $image_dimensions = array();
186 if (function_exists('wp_get_registered_image_subsizes')) {
187 $all_sizes = wp_get_registered_image_subsizes();
188 foreach ($available_image_sizes as $size) {
189 $key = $size['slug'];
190 if (isset($all_sizes[$key])) {
191 $image_dimensions[$key] = $all_sizes[$key];
192 }
193 }
194 }
195
196 if (!function_exists('wp_check_post_lock')) {
197 include_once ABSPATH.'wp-admin/includes/post.php';
198 }
199
200 // Lock settings.
201 $user_id = wp_check_post_lock($post->ID);
202 if ($user_id) {
203 $locked = false;
204
205 /** This filter is documented in wp-admin/includes/post.php */
206 if (apply_filters('show_post_locked_dialog', true, $post, $user_id)) {
207 $locked = true;
208 }
209
210 $user_details = null;
211 if ($locked) {
212 $user = get_userdata($user_id);
213 $user_details = array(
214 'name' => $user->display_name,
215 );
216 }
217
218 $lock_details = array(
219 'isLocked' => $locked,
220 'user' => $user_details,
221 );
222 } else {
223 // Lock the post.
224 $active_post_lock = wp_set_post_lock($post->ID);
225 if ($active_post_lock) {
226 $active_post_lock = esc_attr(implode(':', $active_post_lock));
227 }
228
229 $lock_details = array(
230 'isLocked' => false,
231 'activePostLock' => $active_post_lock,
232 );
233 }
234
235 /**
236 * Filters the body placeholder text.
237 *
238 * @since 5.0.0
239 *
240 * @param string $text Placeholder text. Default 'Start writing or type / to choose a block'.
241 * @param WP_Post $post Post object.
242 */
243 $body_placeholder = apply_filters('write_your_story', __('Start writing or type / to choose a block'), $post);
244
245 $editor_settings = array(
246 'alignWide' => $align_wide,
247 'availableTemplates' => $available_templates,
248 'allowedBlockTypes' => $allowed_block_types,
249 'disableCustomColors' => get_theme_support('disable-custom-colors'),
250 'disableCustomFontSizes' => get_theme_support('disable-custom-font-sizes'),
251 'disableCustomGradients' => get_theme_support('disable-custom-gradients'),
252 'disablePostFormats' => ! current_theme_supports('post-formats'),
253 /** This filter is documented in wp-admin/edit-form-advanced.php */
254 'titlePlaceholder' => apply_filters('enter_title_here', __('Add title'), $post),
255 'bodyPlaceholder' => $body_placeholder,
256 'isRTL' => is_rtl(),
257 'autosaveInterval' => defined('AUTOSAVE_INTERVAL') ? AUTOSAVE_INTERVAL : 10,
258 'maxUploadFileSize' => $max_upload_size,
259 'allowedMimeTypes' => get_allowed_mime_types(),
260 'defaultEditorStyles' => array(),
261 'styles' => array(),
262 'imageSizes' => $available_image_sizes,
263 'imageDimensions' => $image_dimensions,
264 'richEditingEnabled' => user_can_richedit(),
265 'postLock' => $lock_details,
266 'postLockUtils' => array(
267 'nonce' => wp_create_nonce('lock-post_' . $post->ID),
268 'unlockNonce' => wp_create_nonce('update-post_' . $post->ID),
269 'ajaxUrl' => admin_url('admin-ajax.php'),
270 ),
271
272 // N.B. We will not support custom fields for now as this will complicate things even further. Perhaps
273 // in the near future we will as the need and demand arises.
274 'enableCustomFields' => false,
275 'enableCustomLineHeight' => $custom_line_height,
276 'enableCustomUnits' => $custom_units,
277 );
278
279 if (class_exists('WP_Block_Patterns_Registry')) {
280 $editor_settings['__experimentalBlockPatterns'] = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
281 }
282
283 if (class_exists('WP_Block_Pattern_Categories_Registry')) {
284 $editor_settings['__experimentalBlockPatternCategories'] = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();
285 }
286
287 $autosave = wp_get_post_autosave($post->ID);
288 if ($autosave) {
289 if (mysql2date('U', $autosave->post_modified_gmt, false) > mysql2date('U', $post->post_modified_gmt, false)) {
290 $editor_settings['autosave'] = array(
291 'editLink' => get_edit_post_link($autosave->ID),
292 );
293 } else {
294 wp_delete_post_revision($autosave->ID);
295 }
296 }
297
298 if (false !== $color_palette) {
299 $editor_settings['colors'] = $color_palette;
300 }
301
302 if (false !== $font_sizes) {
303 $editor_settings['fontSizes'] = $font_sizes;
304 }
305
306 if (false !== $gradient_presets) {
307 $editor_settings['gradients'] = $gradient_presets;
308 }
309
310 if (!empty($post_type_object->template)) {
311 $editor_settings['template'] = $post_type_object->template;
312 $editor_settings['templateLock'] = !empty($post_type_object->template_lock) ? $post_type_object->template_lock : false;
313 }
314
315 $is_new_post = false;
316 if ('auto-draft' === $post->post_status) {
317 $is_new_post = true;
318 }
319
320 // If there's no template set on a new post, use the post format, instead.
321 if ($is_new_post && !isset($editor_settings['template']) && 'post' === $post->post_type) {
322 $post_format = get_post_format($post);
323 if (in_array($post_format, array('audio', 'gallery', 'image', 'quote', 'video'), true)) {
324 $editor_settings['template'] = array(array("core/$post_format"));
325 }
326 }
327
328 /**
329 * Scripts
330 */
331 wp_enqueue_media(
332 array(
333 'post' => $post->ID,
334 )
335 );
336 wp_tinymce_inline_scripts();
337 wp_enqueue_editor();
338
339 /**
340 * Styles
341 */
342 wp_enqueue_style('wp-edit-post');
343 wp_enqueue_style('wp-format-library');
344
345 /**
346 * Fires after block assets have been enqueued for the editing interface.
347 *
348 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
349 *
350 * In the function call you supply, simply use `wp_enqueue_script` and
351 * `wp_enqueue_style` to add your functionality to the block editor.
352 *
353 * @since 5.0.0
354 */
355 do_action('enqueue_block_editor_assets');
356
357 // In order to duplicate classic meta box behaviour, we need to run the classic meta box actions.
358 require_once(ABSPATH . 'wp-admin/includes/meta-boxes.php');
359 register_and_do_post_meta_boxes($post);
360
361 // Check if the Custom Fields meta box has been removed at some point.
362 if (isset($wp_meta_boxes[$post->post_type]) && isset($wp_meta_boxes[$post->post_type]['normal']) && isset($wp_meta_boxes[$post->post_type]['normal']['core'])) {
363 $core_meta_boxes = $wp_meta_boxes[$post->post_type]['normal']['core'];
364 if (!isset($core_meta_boxes['postcustom']) || !$core_meta_boxes['postcustom']) {
365 unset($editor_settings['enableCustomFields']);
366 }
367 }
368
369 /**
370 * Filters the settings to pass to the block editor.
371 *
372 * @since 5.0.0
373 *
374 * @param array $editor_settings Default editor settings.
375 * @param WP_Post $post Post being edited.
376 */
377 $editor_settings = apply_filters('block_editor_settings', $editor_settings, $post);
378 update_user_meta(get_current_user_id(), 'updraftcentral_editor_settings', $editor_settings);
379 }
380
381 /**
382 * Retrieves the placeholder id created for the current type (e.g. 'post' or 'page')
383 *
384 * @param string $type Determines which type of ID to return (either 'post' or 'page')
385 * @return int|boolean
386 */
387 public function get_placeholder_id($type) {
388 if (in_array($type, array('page', 'post'))) {
389 $post_id = $this->maybe_create_post_and_return_id(array(
390 'post_title' => __('UpdraftCentral Editor Placeholder', 'updraftcentral'),
391 'post_name' => 'uc-editor-placeholder-'.$type,
392 'post_content' => sprintf(__('UpdraftCentral plugin uses this %s as a placeholder when loading the %s editor. You should leave it with "Draft" status.', 'updraftcentral'), $type, $type),
393 'post_status' => 'draft',
394 'post_type' => $type
395 ));
396 return $post_id;
397 }
398
399 return false;
400 }
401
402 /**
403 * Sends the command to the remote website
404 *
405 * @param int $user_id The current user ID
406 * @param string $command The command to process
407 * @param array $params The parameters that goes along with the current request
408 * @return array
409 */
410 private function send_remote_command($user_id, $command, $params) {
411 $user = UpdraftCentral()->get_user_object($user_id);
412 if (!empty($user) && !empty($params['site_id'])) {
413 $remote_params = array(
414 'site_id' => $params['site_id'],
415 'data' => array(
416 'command' => $command,
417 'data' => $params,
418 )
419 );
420
421 $remote_response = $user->send_remote_command($remote_params);
422 if (!empty($remote_response) && 'ok' == $remote_response['responsetype']) {
423 if ('rpcok' == $remote_response['rpc_response']['response']) {
424 $data = $remote_response['rpc_response']['data'];
425 return $data;
426 } elseif ('rpcerror' == $remote_response['rpc_response']['response']) {
427 return new WP_Error('updraftcentral_rpcerror', __('The remote website responded with an error.', 'updraftcentral'), $remote_response['rpc_response']['data']);
428 }
429 } else {
430 return new WP_Error('updraftcentral_communication_error', __('An error has occurred while communicating to the remote website.', 'updraftcentral'));
431 }
432 } else {
433 return new WP_Error('updraftcentral_missing_fields', __('The required object has not been found.', 'updraftcentral'));
434 }
435 }
436
437 /**
438 * Returns an instance of an UpdraftCentral_Site_Meta class that is used to manage
439 * our site meta entries
440 *
441 * @return UpdraftCentral_Site_Meta
442 */
443 private function get_site_meta_instance() {
444 $site_meta = UpdraftCentral()->site_meta;
445 if (empty($site_meta)) {
446 if (!class_exists('UpdraftCentral_Site_Meta')) include_once UD_CENTRAL_DIR.'/classes/site-meta.php';
447
448 return new UpdraftCentral_Site_Meta(UpdraftCentral()->table_prefix);
449 }
450
451 return $site_meta;
452 }
453
454 /**
455 * Intercepts request params/data and bypass default (local) processing
456 *
457 * @param mixed $response Current response, either response or `null` to indicate pass-through.
458 * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
459 * @param WP_REST_Request $request The request that was used to make current response.
460 * @return WP_REST_Response
461 */
462 public function intercept_request_data($response, $handler, $request) {
463
464 // Check whether we received an updraftcentral "uc_nonce", "uc_refIds" and "site_id" and "post_type" data. This also helps in preventing any overlap
465 // or overwrites when the user is using the Block editor in the WP admin area. Thus, we're only
466 // executing the processes below if the current request is made through UpdraftCentral.
467 $params = $request->get_params();
468 if (empty($params['uc_nonce']) || empty($params['uc_refIds']) || empty($params['site_id']) || empty($params['post_type'])) return $response;
469
470 $uc_nonce = is_array($params['uc_nonce']) ? $params['uc_nonce'][0] : $params['uc_nonce'];
471 $uc_refIds = is_array($params['uc_refIds']) ? $params['uc_refIds'][0] : $params['uc_refIds'];
472 $site_id = is_array($params['site_id']) ? $params['site_id'][0] : $params['site_id'];
473 $post_type = is_array($params['post_type']) ? $params['post_type'][0] : $params['post_type'];
474
475 list($user_id, $post_id) = explode('|', base64_decode($uc_refIds));
476 if (empty($user_id) || empty($post_id)) return $response;
477
478 // Verify the nonce submitted
479 if (!wp_verify_nonce($uc_nonce, 'updraftcentral-editpost-'.$post_id)) {
480 return $response;
481 }
482
483 // Pull the (stored) preloaded information from the remote website to be use for the REST request
484 // initiated by the UpdraftCentral editor.
485 $site_meta = $this->get_site_meta_instance();
486
487 $preloaded_data = $site_meta->get_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, true);
488 if (!empty($preloaded_data)) {
489 if (isset($preloaded_data['post_data'])) {
490 $post_data = $preloaded_data['post_data'];
491 $post = $this->setup_remotepost_data($post_data['post'], true);
492 }
493 }
494
495 $match_result = preg_match('#^/wp/v2/(pages|posts)/([0-9]+)#', $request->get_route(), $matches);
496 if (!empty($match_result) && $post) {
497 $params = $request->get_params();
498 $post_id = $matches[2];
499
500 switch ($request->get_method()) {
501 case 'GET':
502 $controller = new UpdraftCentral_REST_Posts_Controller($post->post_type);
503 $misc = $this->attach_media_to_post($post_data['misc'], $post);
504
505 $response = $controller->prepare_remote_item_for_response($post, $request, $misc);
506 break;
507 case 'PUT':
508 if (!empty($params['featured_media'])) {
509 $params['featured_media_url'] = wp_get_attachment_url($params['featured_media']);
510 $dir = wp_upload_dir();
511
512 $image_info = wp_get_attachment_metadata($params['featured_media'], true);
513 if (is_array($image_info) && !empty($image_info['file'])) {
514 $image_file = trailingslashit($dir['basedir']).$image_info['file'];
515 if (file_exists($image_file)) {
516 $params['featured_media_data'] = base64_encode(file_get_contents($image_file, false, null));
517 }
518 }
519 }
520
521 $command = ('pages' == $matches[1]) ? 'pages.save' : 'posts.save';
522 if (!isset($request['context'])) $request['context'] = 'edit';
523
524 // On latest WP (e.g. 5.6) 'menu_order' is passed by the block editor instead of 'order', thus, we need
525 // to make sure that it is compatible with our service handler.
526 if (isset($params['menu_order'])) $params['order'] = $params['menu_order'];
527
528 // On latest WP (e.g. 5.6) a NULL value is passed instead of zero, thus, we'll make some
529 // adjustment before sending it to our service handler for compatibility.
530 //
531 // N.B. Running the check directly with "isset" and "empty" won't work since we need to make
532 // sure that we will only process if the fields were actually edited and the block editor will
533 // only send the fields of those edited post or page properties. Thus, we used the "array_key_exists"
534 // method here before checking if the value is NULL.
535 if (array_key_exists('parent', $params) && is_null($params['parent'])) $params['parent'] = 0;
536
537 $data = $this->send_remote_command($user_id, $command, $params);
538 if (!is_wp_error($data) && !empty($data)) {
539 $post = json_decode($data['post']);
540 $misc = $this->attach_media_to_post($data['misc'], $post);
541
542 // Update/replace existing preloaded post data with the recent
543 // changes so that it gets reflected all throughout UpdraftCentral.
544 $post_data = array(
545 'post' => $post,
546 'misc' => $misc
547 );
548
549 $preloaded_data['post_data'] = $post_data;
550 $site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);
551
552 if (!empty($data['options'])) {
553 $post_data['options'] = $data['options'];
554 }
555
556 // Generate the response
557 $controller = new UpdraftCentral_REST_Posts_Controller($post->post_type);
558 $response = $controller->prepare_remote_item_for_response($post, $request, $misc);
559
560 $response_data = $response->get_data();
561 if (!empty($response_data)) {
562 // We need to add the post_data field to the response in order
563 // to update the current list item's information
564 $response_data['post_data'] = $post_data;
565 $response->set_data($response_data);
566 }
567 } else {
568 if (is_wp_error($data)) {
569 // Default message if we can't pull any relevant error messages from $data
570 $error_message = __('An unknown error has occurred while processing your request. Please contact our support on updraftplus.com so that we can properly escalate the issue.', 'updraftcentral');
571
572 $messages = $data->get_error_messages();
573 if (!empty($messages)) {
574 $error_message = $messages[0];
575 }
576
577 // More specific error information - will override any previous one
578 $error_data = $data->get_error_data();
579 if (isset($error_data['data']) && isset($error_data['data']['message'])) {
580 $error_message = $error_data['data']['message'];
581 }
582
583 return new WP_Error('rest_remote_save_failed', $error_message);
584 } else {
585 return new WP_Error('rest_remote_save_failed', __('An unknown error has occurred while processing your request. Please contact our support on updraftplus.com so that we can properly escalate the issue.', 'updraftcentral'));
586 }
587 }
588 break;
589 }
590 }
591
592 if ('/wp/v2/users' == $request->get_route() && 'GET' == $request->get_method()) {
593 $data = $preloaded_data['authors'];
594
595 $items = array();
596 foreach ($data as $item) {
597 $remote_item = json_decode($item['user']);
598
599 $controller = new UpdraftCentral_REST_Users_Controller;
600 $response = $controller->prepare_remote_item_for_response($remote_item, $request, $item['misc']);
601
602 $merged_data = array_merge($response->get_data(), array('_links' => $response->get_links()));
603 array_push($items, $merged_data);
604 }
605
606 $response = new WP_REST_Response($items);
607 }
608
609 if ('/wp/v2/pages' == $request->get_route() && 'GET' == $request->get_method()) {
610 $data = $preloaded_data['parent_pages'];
611
612 $items = array();
613 foreach ($data as $item) {
614 $remote_item = json_decode($item['post']);
615 if ($post && $remote_item && $post->ID != $remote_item->ID) {
616 $controller = new UpdraftCentral_REST_Posts_Controller($remote_item->post_type);
617 $response = $controller->prepare_remote_item_for_response($remote_item, $request, $item['misc']);
618
619 $merged_data = array_merge($response->get_data(), array('_links' => $response->get_links()));
620 array_push($items, $merged_data);
621 }
622 }
623
624 $response = new WP_REST_Response($items);
625 }
626
627 if ('/wp/v2/taxonomies' == $request->get_route() && 'GET' == $request->get_method()) {
628 $controller = new UpdraftCentral_REST_Taxonomies_Controller;
629 if (isset($preloaded_data['taxonomies'])) {
630 $data = $preloaded_data['taxonomies'];
631
632 $taxonomies = isset($data['taxonomies']) ? $data['taxonomies'] : array();
633 if (!empty($taxonomies) && is_array($taxonomies)) {
634 foreach ($taxonomies as $key => $value) {
635 $tax = $controller->prepare_remote_item_for_response($value, $request);
636 $taxonomies[$key] = $controller->prepare_response_for_collection($tax);
637 }
638 }
639
640 $response = new WP_REST_Response($taxonomies);
641 }
642 }
643
644 $match_result = preg_match('#^/wp/v2/taxonomies/(.*)#', $request->get_route(), $matches);
645 if (!empty($match_result) && 'GET' == $request->get_method()) {
646 $taxonomy = $matches[1];
647
648 $controller = new UpdraftCentral_REST_Taxonomies_Controller;
649 if (isset($preloaded_data['taxonomies'])) {
650 $data = $preloaded_data['taxonomies'];
651
652 $result = array();
653 $taxonomies = isset($data['taxonomies']) ? $data['taxonomies'] : array();
654 if (!empty($taxonomies) && is_array($taxonomies)) {
655 foreach ($taxonomies as $key => $value) {
656 $tax = $controller->prepare_remote_item_for_response($value, $request);
657 $taxonomies[$key] = $controller->prepare_response_for_collection($tax);
658 }
659
660 if (!empty($taxonomy) && isset($taxonomies[$taxonomy])) {
661 // For individual taxonomy retrieval
662 $result = $taxonomies[$taxonomy];
663 if (!empty($params['context']) && 'edit' === $params['context']) {
664 if (isset($result['_links'])) unset($result['_links']);
665 }
666 }
667 }
668
669 $response = new WP_REST_Response($result);
670 }
671 }
672
673 if ('/wp/v2/categories' == $request->get_route() && 'GET' == $request->get_method()) {
674 $controller = new UpdraftCentral_REST_Terms_Controller('category');
675 $taxonomies = $preloaded_data['taxonomies']['taxonomies'];
676
677 $terms = array();
678 $categories = isset($preloaded_data['categories']) ? $preloaded_data['categories'] : array();
679 if (!empty($categories) && is_array($categories)) {
680 $terms = $categories['terms'];
681 foreach ($terms as $key => $value) {
682 if (isset($value['term'])) {
683 $term = json_decode($value['term']);
684
685 $misc = $value['misc'];
686 $misc['taxonomy_obj'] = $taxonomies[$misc['taxonomy']];
687
688 $item = $controller->prepare_remote_item_for_response($term, $request, $misc);
689 $terms[$key] = $controller->prepare_response_for_collection($item);
690 }
691 }
692 }
693
694 $response = new WP_REST_Response($terms);
695 }
696
697 if ('/wp/v2/tags' == $request->get_route() && 'GET' == $request->get_method()) {
698 $controller = new UpdraftCentral_REST_Terms_Controller('post_tag');
699 $taxonomies = $preloaded_data['taxonomies']['taxonomies'];
700
701 $terms = array();
702 $tags = isset($preloaded_data['tags']) ? $preloaded_data['tags'] : array();
703 if (!empty($tags) && is_array($tags)) {
704 $terms = $tags['terms'];
705
706 foreach ($terms as $key => $value) {
707 if (isset($value['term'])) {
708 $term = json_decode($value['term']);
709 $misc = $value['misc'];
710 $misc['taxonomy_obj'] = $taxonomies[$misc['taxonomy']];
711
712 $item = $controller->prepare_remote_item_for_response($term, $request, $misc);
713 $terms[$key] = $controller->prepare_response_for_collection($item);
714 }
715 }
716 }
717
718 $response = new WP_REST_Response($terms);
719 }
720
721 if ('/wp/v2/categories' == $request->get_route() && 'POST' === $request->get_method()) {
722 $data = $this->send_remote_command($user_id, 'posts.add_category', $params);
723 if (!is_wp_error($data) && !empty($data)) {
724 $categories = json_decode($data['categories'], true);
725
726 // Update preloaded data:
727 $preloaded_data['categories'] = $categories;
728 $site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);
729
730 unset($data['categories']);
731 $response = new WP_REST_Response($data);
732 }
733 }
734
735 if ('/wp/v2/tags' == $request->get_route() && 'POST' === $request->get_method()) {
736 $data = $this->send_remote_command($user_id, 'posts.add_tag', $params);
737 if (!is_wp_error($data) && !empty($data)) {
738 $tags = json_decode($data['tags'], true);
739
740 // Update preloaded data:
741 $preloaded_data['tags'] = $tags;
742 $site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);
743
744 unset($data['tags']);
745 $response = new WP_REST_Response($data);
746 }
747 }
748
749 return $response;
750 }
751
752 /**
753 * Creates a new post or return the ID of the existing post
754 *
755 * @param array $data An array of information needed to create a new post if applicable
756 * @return int|bool
757 */
758 private function maybe_create_post_and_return_id($data) {
759 global $wpdb;
760 $query = $wpdb->prepare('SELECT ID FROM '.$wpdb->posts.' WHERE post_name = %s', $data['post_name']);
761 $wpdb->query($query);
762
763 if ($wpdb->num_rows) {
764 $post_id = intval($wpdb->get_var($query));
765 } else {
766 $post_id = wp_insert_post($data);
767 }
768
769 if (is_wp_error($post_id) || empty($post_id)) return false;
770 return $post_id;
771 }
772
773 /**
774 * Loads metaboxees for the classic editor
775 *
776 * @param string $post_type Type of the post submitted
777 * @param WP_Post $post A WP_Post object
778 * @return void
779 */
780 public function load_metaboxes($post_type, $post) {
781 global $post_type_object;
782
783 add_meta_box('submitdiv', __('Publish', 'updraftcentral'), 'post_submit_meta_box', $post->post_type, 'side', 'core', null);
784
785 if ('page' == $post->post_type) {
786 if (post_type_supports($post->post_type, 'page-attributes') || count(get_page_templates($post)) > 0) {
787 add_meta_box('pageparentdiv', $post_type_object->labels->attributes, 'page_attributes_meta_box', $post->post_type, 'side', 'core');
788 }
789 } else {
790 add_meta_box('categorydiv', __('Categories', 'updraftcentral'), array($this, 'post_categories_meta_box'), $post->post_type, 'side', 'core', null);
791
792 add_meta_box('tagsdiv-post_tag', __('Tags', 'updraftcentral'), array($this, 'post_tags_meta_box'), $post->post_type, 'side', 'core', null);
793 }
794
795 if (post_type_supports($post->post_type, 'thumbnail') && current_user_can('upload_files')) {
796 add_meta_box('postimagediv', esc_html($post_type_object->labels->featured_image), 'post_thumbnail_meta_box', $post->post_type, 'side', 'low');
797 }
798 }
799
800 /**
801 * Fetch parent pages for the particular post object (applicable to 'page' post type)
802 *
803 * @return array
804 */
805 public function load_remote_pages() {
806 global $post, $site_id;
807
808 $user = UpdraftCentral()->get_user_object(get_current_user_id());
809 $remote_params = array(
810 'site_id' => $site_id,
811 'data' => array(
812 'command' => 'pages.get_parent_pages',
813 'data' => array(
814 'page' => 1,
815 'per_page' => 100,
816 'exclude' => array($post->ID),
817 'order' => 'ASC',
818 'orderby' => 'menu_order',
819 'status' => 'publish'
820 ),
821 )
822 );
823
824 $remote_response = $user->send_remote_command($remote_params);
825 if (!empty($remote_response) && 'ok' == $remote_response['responsetype']) {
826 if ('rpcok' == $remote_response['rpc_response']['response']) {
827 $data = $remote_response['rpc_response']['data'];
828 return $data['pages'];
829 }
830 }
831
832 return array();
833 }
834
835 /**
836 * Setup the global post data so that any underlying processes will
837 * get to see and acknowledge the remote post as the current object
838 *
839 * @param array $post_data The current post data to edit
840 * @param bool $disable_screen Whether to disable setting the screen or not
841 * @return WP_Post
842 */
843 private function setup_remotepost_data($post_data, $disable_screen = false) {
844 global $post;
845
846 // We need to setup and cache this edited post data so that other processes
847 // or hooks that will eventually rely on this information will succeed.
848 $post = new WP_Post((object) $post_data);
849 if ($post) {
850 setup_postdata($post);
851 if (!$disable_screen) {
852 set_current_screen($post->post_type);
853 }
854
855 // Make sure that this new object gets inserted into the cache
856 // otherwise, WP won't be able to see this information as these are
857 // coming from the remote website and not a local WP_Post object.
858 //
859 // N.B. This will bypass checking the post information from the database
860 // which in reality doesn't actually exists locally since it is coming from
861 // the remote website.
862 wp_cache_set($post->ID, $post, 'posts');
863 }
864
865 return $post;
866 }
867
868 /**
869 * Store preloaded data from remote which will be accessed later, making succeeding
870 * access to these information much more faster the next time around.
871 *
872 * @param array $params The parameters that goes along with the current request
873 * @return void
874 */
875 public function maybe_store_preloaded_data($params) {
876
877 $data = array();
878 // Preloaded data such as remote taxonomies, categories and tags are stored here if
879 // they exists as not to redo the whole request when a new REST request is executed.
880 if (!empty($params['preloaded_data'])) {
881 $data = json_decode($params['preloaded_data'], true);
882 }
883
884 if (!empty($params['post_data'])) {
885 global $post, $post_data;
886
887 // We might as well store the currently edited post here so that any succeeding REST
888 // request for this particular `post` information will no longer require us to initiate another request
889 // just for getting the same information from the remote website (which is kind of redundant
890 // since we've already got those data loaded in the first place).
891 $post_data = json_decode($params['post_data'], true);
892 $data['post_data'] = $post_data;
893
894 // Setup remote post for local access
895 $post = $this->setup_remotepost_data($post_data['post']);
896 }
897
898 // Store preloaded data as user meta, thus, making them unique for every user wanting
899 // to edit their own remote posts or pages.
900 if (!empty($data) && !empty($params['site_id'])) {
901 global $site_id;
902
903 $site_id = $params['site_id'];
904 $site_meta = $this->get_site_meta_instance();
905 $site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post->post_type, $data);
906 }
907 }
908
909 /**
910 * Loads the Gutenberg needed information to successfully load the block
911 * editor in the client.
912 *
913 * @param array $params A collection of information needed when loading the editor
914 * @return array
915 */
916 public function load_gutenberg_editor($params) {
917 global $post, $post_data;
918
919 // Store preloaded data if not yet been stored.
920 $this->maybe_store_preloaded_data($params);
921
922 ob_start();
923 the_block_editor_meta_boxes();
924 $metaboxes = ob_get_contents();
925 ob_end_clean();
926
927 $nonce = wp_create_nonce('updraftcentral-editpost-'.$post->ID);
928 $info = array(
929 'uc_nonce' => $nonce,
930 'uc_refIds' => base64_encode(get_current_user_id().'|'.$post->ID)
931 );
932
933 $settings = get_user_meta(get_current_user_id(), 'updraftcentral_editor_settings', true);
934 $settings['availableTemplates'] = array();
935 if (isset($settings['availableTemplates']) && !empty($params['template_options'])) {
936 $templates = array();
937 $options = $params['template_options'];
938
939 if (!empty($options)) {
940 foreach ($options as $value) {
941 $templates[$value['filename']] = $value['template'];
942 }
943 }
944
945 $settings['availableTemplates'] = array_merge(array('' => __('Default template', 'updraftcentral')), $templates);
946 }
947
948 $misc = $this->attach_media_to_post($post_data['misc'], $post);
949 return array(
950 'post' => $post,
951 'misc' => $misc,
952 'logo' => trailingslashit(UD_CENTRAL_URL).'images/updraftcentral-logo-landscape.png',
953 'metaboxes' => $metaboxes,
954 'settings' => $settings,
955 'info' => $info,
956 'has_upload_permissions' => current_user_can('upload_files'), // N.B. We're using the local media library interface when uploading/editing the featured image, so we need to check whether the UpdraftCentral user have the "upload_files" permission. Otherwise, the user won't be able to edit the featured image of a post or page or upload a new one for that matter.
957 'block_categories' => get_block_categories($post),
958 'block_definitions' => get_block_editor_server_block_settings()
959 );
960 }
961
962 /**
963 * Loads the Classic Editor
964 *
965 * @param array $params A collection of information needed when loading the editor
966 * @return array
967 */
968 public function load_classic_editor($params) {
969 global $post_type_object, $post, $site_id, $uc_categories_meta_box, $uc_tags_meta_box, $post_data;
970
971 // Store preloaded data if not yet been stored.
972 $this->maybe_store_preloaded_data($params);
973
974 $item = $post_data['misc'];
975 $site_id = $params['site_id'];
976 $data = array();
977
978 if (!empty($item)) {
979 if ($post) {
980 include_once ABSPATH . 'wp-admin/includes/meta-boxes.php';
981 include_once ABSPATH . 'wp-admin/includes/template.php';
982
983 $item = $this->attach_media_to_post($item, $post);
984 $post_type_object = get_post_type_object($post->post_type);
985 set_current_screen($post->post_type);
986
987 // Make sure that we add the necessary metaboxes for our current post_type (e.g. page or post)
988 // before actually rendering them.
989 add_action('add_meta_boxes', array($this, 'load_metaboxes'), 10, 2);
990 do_action('add_meta_boxes', $post->post_type, $post);
991
992 // Grab editor content to put inside a variable
993 ob_start();
994 wp_editor($post->post_content, 'uc_classic_editor');
995 $editor = ob_get_contents();
996 ob_end_clean();
997
998 // Now grab the metaboxes content
999 ob_start();
1000 do_meta_boxes($post->post_type, 'side', $post);
1001 $metaboxes = ob_get_contents();
1002 ob_end_clean();
1003
1004 $data = array(
1005 'post' => $post,
1006 'misc' => $item,
1007 'logo' => trailingslashit(UD_CENTRAL_URL).'images/updraftcentral-logo-landscape.png',
1008 'editor' => $editor,
1009 'metaboxes' => $metaboxes,
1010 'tags_metabox_content' => $uc_tags_meta_box,
1011 'categories_metabox_content' => $uc_categories_meta_box
1012 );
1013 }
1014 }
1015
1016 return $data;
1017 }
1018
1019 /**
1020 * Searches for the media ID of a given attachment/image
1021 *
1022 * @param string $filename The filename of the image/media
1023 * @return int|bool
1024 */
1025 private function get_media_id_by_name($filename) {
1026 global $wpdb;
1027 $media_id = $wpdb->get_var($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid LIKE '%s'", '%'.$filename));
1028
1029 return $media_id ?: false;
1030 }
1031
1032 /**
1033 * Prepares the image for attachment and attaches it to the post object
1034 *
1035 * @param array $item A data array containing the featured image information
1036 * @param WP_Post $post The WP_Post object to where the featured image is to be attached
1037 *
1038 * @return array
1039 */
1040 private function attach_media_to_post($item, $post) {
1041
1042 // Nothing to do if "featured_media" and "featured_media_url" are both empty, thus, we bail.
1043 $featured_media = (int) $item['featured_media'];
1044 if (empty($featured_media) && empty($item['featured_media_url'])) return $item;
1045
1046 // Attach remote media if non-existing. If 'featured_media' is empty or zero
1047 // and the 'feature_media_url' is non-empty meaning we haven't gotten any local media reference just yet.
1048 // Thus, we're going to attach the remote media to this current post so that the editors can consume
1049 // and display the image to the users.
1050 if (empty($featured_media) && !empty($item['featured_media_url'])) {
1051 $media_id = $this->maybe_download_remote_image($item['featured_media_url']);
1052 if (!empty($media_id)) {
1053 $item['featured_media'] = (int) $media_id;
1054 }
1055 } else {
1056 // Check if featured media (image) still exists, meaning, not deleted/removed.
1057 if (!empty($featured_media)) {
1058 $attachment = wp_get_attachment_image_src($featured_media);
1059 if (empty($attachment)) {
1060 $item['featured_media'] = 0;
1061
1062 // Try downloading the image, if the remote page/post currently has
1063 // a featured_media_url set.
1064 if (!empty($item['featured_media_url'])) {
1065 $media_id = $this->maybe_download_remote_image($item['featured_media_url']);
1066 if (!empty($media_id)) {
1067 $item['featured_media'] = (int) $media_id;
1068 }
1069 }
1070 }
1071 }
1072 }
1073
1074 if (!empty($item['featured_media']) && $post) {
1075 set_post_thumbnail($post, (int) $item['featured_media']);
1076 }
1077
1078 return $item;
1079 }
1080
1081 /**
1082 * Saves or downloads the media (attachment/image) from UpdraftCentral
1083 *
1084 * @param string $image_url The URL of the image to download (if needed)
1085 * @param string $image_data The image data to save. If empty, image_url will be used to download the image
1086 * @return int
1087 */
1088 private function maybe_download_remote_image($image_url, $image_data = '') {
1089 if (empty($image_url)) return false;
1090
1091 $image = pathinfo($image_url);
1092 $image_name = $image['basename'];
1093
1094 $media_id = $this->get_media_id_by_name($image_name);
1095 if (!empty($media_id)) {
1096 return $media_id;
1097 }
1098
1099 $upload_dir = wp_upload_dir();
1100 if (empty($image_data)) {
1101 $response = wp_remote_get($image_url);
1102 if (!is_wp_error($response)) {
1103 $image_data = wp_remote_retrieve_body($response);
1104 }
1105 } else {
1106 $image_data = base64_decode($image_data);
1107 }
1108
1109 $media_id = 0;
1110 if (!empty($image_data)) {
1111 $filename = $image_name;
1112
1113 if (wp_mkdir_p($upload_dir['path'])) {
1114 $file = trailingslashit($upload_dir['path']).$filename;
1115 $guid = trailingslashit($upload_dir['url']).$filename;
1116 } else {
1117 $file = trailingslashit($upload_dir['basedir']).$filename;
1118 $guid = trailingslashit($upload_dir['baseurl']).$filename;
1119 }
1120
1121 file_put_contents($file, $image_data);
1122 $wp_filetype = wp_check_filetype($filename, null);
1123
1124 $attachment = array(
1125 'guid' => $guid,
1126 'post_mime_type' => $wp_filetype['type'],
1127 'post_title' => sanitize_file_name($filename),
1128 'post_content' => '',
1129 'post_status' => 'inherit'
1130 );
1131
1132 $media_id = wp_insert_attachment($attachment, $file);
1133 include_once(ABSPATH . 'wp-admin/includes/image.php');
1134
1135 $attach_data = wp_generate_attachment_metadata($media_id, $file);
1136 wp_update_attachment_metadata($media_id, $attach_data);
1137 }
1138
1139 return $media_id;
1140 }
1141
1142 /**
1143 * Gathers post categories metabox information to be rendered
1144 * in the client later on using the Handlerbarsjs templating system
1145 *
1146 * @param WP_Post $post Post object
1147 */
1148 public function post_categories_meta_box($post) {
1149 global $uc_categories_meta_box, $site_id;
1150
1151 $site_meta = $this->get_site_meta_instance();
1152 $preloaded_data = $site_meta->get_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post->post_type, true);
1153 $options = $misc = array();
1154
1155 if (!empty($preloaded_data)) {
1156 if (isset($preloaded_data['categories'])) {
1157 $options = $preloaded_data['categories']['misc'];
1158 }
1159
1160 if (isset($preloaded_data['post_data'])) {
1161 $misc = $preloaded_data['post_data']['misc'];
1162 }
1163 }
1164
1165 if (empty($options) || empty($misc)) return;
1166
1167 $taxonomy = json_decode($options['tax']);
1168 $popular_terms_checklist = $options['popular'];
1169 $terms_checklist = $misc['categories_checklist'];
1170 $parent_dropdown = $options['parent_dropdown'];
1171
1172 // We're pulling and rendering the template in the client, so, we're
1173 // preparing the information for the template's consumption.
1174 $uc_categories_meta_box = array(
1175 'labels' => array(
1176 'all_items' => isset($taxonomy->labels->all_items) ? $taxonomy->labels->all_items : __('All Categories', 'updraftcentral'),
1177 'most_used' => isset($taxonomy->labels->most_used) ? esc_html($taxonomy->labels->most_used) : __('Most Used', 'updraftcentral'),
1178 'add_new_item' => isset($taxonomy->labels->add_new_item) ? $taxonomy->labels->add_new_item : __('Add New Category', 'updraftcentral')
1179 ),
1180 'attributes' => array(
1181 'new_item_name' => isset($taxonomy->labels->new_item_name) ? esc_attr($taxonomy->labels->new_item_name) : __('New Category Name', 'updraftcentral'),
1182 'add_new_item' => isset($taxonomy->labels->add_new_item) ? esc_attr($taxonomy->labels->add_new_item) : __('Add New Category', 'updraftcentral')
1183 ),
1184 'can_edit_terms' => (bool) $options['capabilities']['can_edit_terms'],
1185 'popular_terms_checklist' => $popular_terms_checklist,
1186 'terms_checklist' => $terms_checklist,
1187 'parent_dropdown' => $parent_dropdown
1188 );
1189 }
1190
1191 /**
1192 * Gathers post tags metabox information to be rendered
1193 * in the client later on using the Handlerbarsjs templating system
1194 *
1195 * @param WP_Post $post Post object
1196 */
1197 public function post_tags_meta_box($post) {
1198 global $uc_tags_meta_box, $site_id;
1199
1200 $site_meta = $this->get_site_meta_instance();
1201 $preloaded_data = $site_meta->get_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post->post_type, true);
1202 $options = $misc = array();
1203
1204 if (!empty($preloaded_data)) {
1205 if (isset($preloaded_data['tags'])) {
1206 $options = $preloaded_data['tags']['misc'];
1207 }
1208
1209 if (isset($preloaded_data['post_data'])) {
1210 $misc = $preloaded_data['post_data']['misc'];
1211 }
1212 }
1213
1214 if (empty($options) || empty($misc)) return;
1215
1216 $tag_cloud = $options['tag_cloud'];
1217 $taxonomy = json_decode($options['tax']);
1218
1219 $terms_list = array();
1220 $terms_to_edit = '';
1221 if (!empty($misc['tags_list'])) {
1222 $terms_to_edit = str_replace(', ', ',', $misc['tags_list']);
1223 $terms_list = explode(',', $terms_to_edit);
1224 }
1225
1226 // We're pulling and rendering the template in the client, so, we're
1227 // preparing the information for the template's consumption.
1228 $uc_tags_meta_box = array(
1229 'labels' => array(
1230 'add_or_remove_items' => isset($taxonomy->labels->add_or_remove_items) ? $taxonomy->labels->add_or_remove_items : __('Add or remove tags', 'updraftcentral'),
1231 'add_new_item' => isset($taxonomy->labels->add_new_item) ? $taxonomy->labels->add_new_item : __('Add New Tag', 'updraftcentral'),
1232 'separate_items_with_commas' => isset($taxonomy->labels->separate_items_with_commas) ? $taxonomy->labels->separate_items_with_commas : __('Separate tags with commas', 'updraftcentral'),
1233 'no_terms' => isset($taxonomy->labels->no_terms) ? $taxonomy->labels->no_terms : __('No tags', 'updraftcentral'),
1234 'choose_from_most_used' => isset($taxonomy->labels->choose_from_most_used) ? $taxonomy->labels->choose_from_most_used : __('Choose from the most used tags', 'updraftcentral')
1235 ),
1236 'attributes' => array(
1237 'add' => __('Add', 'updraftcentral'),
1238 ),
1239 'can_assign_terms' => (bool) $options['capabilities']['can_assign_terms'],
1240 'terms_to_edit' => $terms_to_edit,
1241 'terms_list' => $terms_list,
1242 'tag_cloud' => $tag_cloud
1243 );
1244 }
1245 }
1246
1247 endif;
1248