PluginProbe
UpdraftCentral Dashboard / 0.8.16
UpdraftCentral Dashboard v0.8.16
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.16, at classes/class-editor.php

1,249 lines 45.0 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 'styles' => array(),
261 'imageSizes' => $available_image_sizes,
262 'imageDimensions' => $image_dimensions,
263 'richEditingEnabled' => user_can_richedit(),
264 'postLock' => $lock_details,
265 'postLockUtils' => array(
266 'nonce' => wp_create_nonce('lock-post_' . $post->ID),
267 'unlockNonce' => wp_create_nonce('update-post_' . $post->ID),
268 'ajaxUrl' => admin_url('admin-ajax.php'),
269 ),
270
271 // N.B. We will not support custom fields for now as this will complicate things even further. Perhaps
272 // in the near future we will as the need and demand arises.
273 'enableCustomFields' => false,
274 'enableCustomLineHeight' => $custom_line_height,
275 'enableCustomUnits' => $custom_units,
276 );
277
278 if (class_exists('WP_Block_Patterns_Registry')) {
279 $editor_settings['__experimentalBlockPatterns'] = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
280 }
281
282 if (class_exists('WP_Block_Pattern_Categories_Registry')) {
283 $editor_settings['__experimentalBlockPatternCategories'] = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();
284 }
285
286 $autosave = wp_get_post_autosave($post->ID);
287 if ($autosave) {
288 if (mysql2date('U', $autosave->post_modified_gmt, false) > mysql2date('U', $post->post_modified_gmt, false)) {
289 $editor_settings['autosave'] = array(
290 'editLink' => get_edit_post_link($autosave->ID),
291 );
292 } else {
293 wp_delete_post_revision($autosave->ID);
294 }
295 }
296
297 if (false !== $color_palette) {
298 $editor_settings['colors'] = $color_palette;
299 }
300
301 if (false !== $font_sizes) {
302 $editor_settings['fontSizes'] = $font_sizes;
303 }
304
305 if (false !== $gradient_presets) {
306 $editor_settings['gradients'] = $gradient_presets;
307 }
308
309 if (!empty($post_type_object->template)) {
310 $editor_settings['template'] = $post_type_object->template;
311 $editor_settings['templateLock'] = !empty($post_type_object->template_lock) ? $post_type_object->template_lock : false;
312 }
313
314 $is_new_post = false;
315 if ('auto-draft' === $post->post_status) {
316 $is_new_post = true;
317 }
318
319 // If there's no template set on a new post, use the post format, instead.
320 if ($is_new_post && !isset($editor_settings['template']) && 'post' === $post->post_type) {
321 $post_format = get_post_format($post);
322 if (in_array($post_format, array('audio', 'gallery', 'image', 'quote', 'video'), true)) {
323 $editor_settings['template'] = array(array("core/$post_format"));
324 }
325 }
326
327 /**
328 * Scripts
329 */
330 wp_enqueue_media(
331 array(
332 'post' => $post->ID,
333 )
334 );
335 wp_tinymce_inline_scripts();
336 wp_enqueue_editor();
337
338 /**
339 * Styles
340 */
341 wp_enqueue_style('wp-edit-post');
342 wp_enqueue_style('wp-format-library');
343
344 /**
345 * Fires after block assets have been enqueued for the editing interface.
346 *
347 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
348 *
349 * In the function call you supply, simply use `wp_enqueue_script` and
350 * `wp_enqueue_style` to add your functionality to the block editor.
351 *
352 * @since 5.0.0
353 */
354 do_action('enqueue_block_editor_assets');
355
356 // In order to duplicate classic meta box behaviour, we need to run the classic meta box actions.
357 require_once(ABSPATH . 'wp-admin/includes/meta-boxes.php');
358 register_and_do_post_meta_boxes($post);
359
360 // Check if the Custom Fields meta box has been removed at some point.
361 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'])) {
362 $core_meta_boxes = $wp_meta_boxes[$post->post_type]['normal']['core'];
363 if (!isset($core_meta_boxes['postcustom']) || !$core_meta_boxes['postcustom']) {
364 unset($editor_settings['enableCustomFields']);
365 }
366 }
367
368 /**
369 * Filters the settings to pass to the block editor.
370 *
371 * @since 5.0.0
372 *
373 * @param array $editor_settings Default editor settings.
374 * @param WP_Post $post Post being edited.
375 */
376 $editor_settings = apply_filters('block_editor_settings', $editor_settings, $post);
377 update_user_meta(get_current_user_id(), 'updraftcentral_editor_settings', $editor_settings);
378 }
379
380 /**
381 * Retrieves the placeholder id created for the current type (e.g. 'post' or 'page')
382 *
383 * @param string $type Determines which type of ID to return (either 'post' or 'page')
384 * @return int|boolean
385 */
386 public function get_placeholder_id($type) {
387 if (in_array($type, array('page', 'post'))) {
388 $post_id = $this->maybe_create_post_and_return_id(array(
389 'post_title' => __('UpdraftCentral Editor Placeholder', 'updraftcentral'),
390 'post_name' => 'uc-editor-placeholder-'.$type,
391 '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),
392 'post_status' => 'draft',
393 'post_type' => $type
394 ));
395 return $post_id;
396 }
397
398 return false;
399 }
400
401 /**
402 * Sends the command to the remote website
403 *
404 * @param int $user_id The current user ID
405 * @param string $command The command to process
406 * @param array $params The parameters that goes along with the current request
407 * @return array
408 */
409 private function send_remote_command($user_id, $command, $params) {
410 $user = UpdraftCentral()->get_user_object($user_id);
411 if (!empty($user) && !empty($params['site_id'])) {
412 $remote_params = array(
413 'site_id' => $params['site_id'],
414 'data' => array(
415 'command' => $command,
416 'data' => $params,
417 )
418 );
419
420 $remote_response = $user->send_remote_command($remote_params);
421 if (!empty($remote_response) && 'ok' == $remote_response['responsetype']) {
422 if ('rpcok' == $remote_response['rpc_response']['response']) {
423 $data = $remote_response['rpc_response']['data'];
424 return $data;
425 } elseif ('rpcerror' == $remote_response['rpc_response']['response']) {
426 return new WP_Error('updraftcentral_rpcerror', __('The remote website responded with an error.', 'updraftcentral'), $remote_response['rpc_response']['data']);
427 }
428 } else {
429 return new WP_Error('updraftcentral_communication_error', __('An error has occurred while communicating to the remote website.', 'updraftcentral'));
430 }
431 } else {
432 return new WP_Error('updraftcentral_missing_fields', __('The required object has not been found.', 'updraftcentral'));
433 }
434 }
435
436 /**
437 * Returns an instance of an UpdraftCentral_Site_Meta class that is used to manage
438 * our site meta entries
439 *
440 * @return UpdraftCentral_Site_Meta
441 */
442 private function get_site_meta_instance() {
443 $site_meta = UpdraftCentral()->site_meta;
444 if (empty($site_meta)) {
445 if (!class_exists('UpdraftCentral_Site_Meta')) include_once UD_CENTRAL_DIR.'/classes/site-meta.php';
446
447 return new UpdraftCentral_Site_Meta(UpdraftCentral()->table_prefix);
448 }
449
450 return $site_meta;
451 }
452
453 /**
454 * Intercepts request params/data and bypass default (local) processing
455 *
456 * @param mixed $response Current response, either response or `null` to indicate pass-through.
457 * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
458 * @param WP_REST_Request $request The request that was used to make current response.
459 * @return WP_REST_Response
460 */
461 public function intercept_request_data($response, $handler, $request) {
462
463 // Check whether we received an updraftcentral "uc_nonce", "uc_refIds" and "site_id" and "post_type" data. This also helps in preventing any overlap
464 // or overwrites when the user is using the Block editor in the WP admin area. Thus, we're only
465 // executing the processes below if the current request is made through UpdraftCentral.
466 $params = $request->get_params();
467 if (empty($params['uc_nonce']) || empty($params['uc_refIds']) || empty($params['site_id']) || empty($params['post_type'])) return $response;
468
469 $uc_nonce = is_array($params['uc_nonce']) ? $params['uc_nonce'][0] : $params['uc_nonce'];
470 $uc_refIds = is_array($params['uc_refIds']) ? $params['uc_refIds'][0] : $params['uc_refIds'];
471 $site_id = is_array($params['site_id']) ? $params['site_id'][0] : $params['site_id'];
472 $post_type = is_array($params['post_type']) ? $params['post_type'][0] : $params['post_type'];
473
474 list($user_id, $post_id) = explode('|', base64_decode($uc_refIds));
475 if (empty($user_id) || empty($post_id)) return $response;
476
477 // Verify the nonce submitted
478 if (!wp_verify_nonce($uc_nonce, 'updraftcentral-editpost-'.$post_id)) {
479 return $response;
480 }
481
482 // Pull the (stored) preloaded information from the remote website to be use for the REST request
483 // initiated by the UpdraftCentral editor.
484 $site_meta = $this->get_site_meta_instance();
485
486 $preloaded_data = $site_meta->get_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, true);
487 if (!empty($preloaded_data)) {
488 if (isset($preloaded_data['post_data'])) {
489 $post_data = $preloaded_data['post_data'];
490 $post = $this->setup_remotepost_data($post_data['post'], true);
491 }
492 }
493
494 $match_result = preg_match('#^/wp/v2/(pages|posts)/([0-9]+)#', $request->get_route(), $matches);
495 if (!empty($match_result) && $post) {
496 $params = $request->get_params();
497 $post_id = $matches[2];
498
499 switch ($request->get_method()) {
500 case 'GET':
501 $controller = new UpdraftCentral_REST_Posts_Controller($post->post_type);
502 $misc = $this->attach_media_to_post($post_data['misc'], $post);
503
504 $response = $controller->prepare_remote_item_for_response($post, $request, $misc);
505 break;
506 case 'PUT':
507 if (!empty($params['featured_media'])) {
508 $params['featured_media_url'] = wp_get_attachment_url($params['featured_media']);
509 $dir = wp_upload_dir();
510
511 $image_info = wp_get_attachment_metadata($params['featured_media'], true);
512 if (is_array($image_info) && !empty($image_info['file'])) {
513 $image_file = trailingslashit($dir['basedir']).$image_info['file'];
514 if (file_exists($image_file)) {
515 $params['featured_media_data'] = base64_encode(file_get_contents($image_file, false, null));
516 }
517 }
518 }
519
520 $command = ('pages' == $matches[1]) ? 'pages.save' : 'posts.save';
521 if (!isset($request['context'])) $request['context'] = 'edit';
522
523 // On latest WP (e.g. 5.6) 'menu_order' is passed by the block editor instead of 'order', thus, we need
524 // to make sure that it is compatible with our service handler.
525 if (isset($params['menu_order'])) $params['order'] = $params['menu_order'];
526
527 // On latest WP (e.g. 5.6) a NULL value is passed instead of zero, thus, we'll make some
528 // adjustment before sending it to our service handler for compatibility.
529 //
530 // N.B. Running the check directly with "isset" and "empty" won't work since we need to make
531 // sure that we will only process if the fields were actually edited and the block editor will
532 // only send the fields of those edited post or page properties. Thus, we used the "array_key_exists"
533 // method here before checking if the value is NULL.
534 if (array_key_exists('parent', $params) && is_null($params['parent'])) $params['parent'] = 0;
535
536 $data = $this->send_remote_command($user_id, $command, $params);
537 if (!is_wp_error($data) && !empty($data)) {
538 $post = json_decode($data['post']);
539 $misc = $this->attach_media_to_post($data['misc'], $post);
540
541 // Update/replace existing preloaded post data with the recent
542 // changes so that it gets reflected all throughout UpdraftCentral.
543 $post_data = array(
544 'post' => $post,
545 'misc' => $misc
546 );
547
548 $preloaded_data['post_data'] = $post_data;
549 $site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);
550
551 if (!empty($data['options'])) {
552 $post_data['options'] = $data['options'];
553 }
554
555 // Generate the response
556 $controller = new UpdraftCentral_REST_Posts_Controller($post->post_type);
557 $response = $controller->prepare_remote_item_for_response($post, $request, $misc);
558
559 $response_data = $response->get_data();
560 if (!empty($response_data)) {
561 // We need to add the post_data field to the response in order
562 // to update the current list item's information
563 $response_data['post_data'] = $post_data;
564 $response->set_data($response_data);
565 }
566 } else {
567 if (is_wp_error($data)) {
568 // Default message if we can't pull any relevant error messages from $data
569 $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');
570
571 $messages = $data->get_error_messages();
572 if (!empty($messages)) {
573 $error_message = $messages[0];
574 }
575
576 // More specific error information - will override any previous one
577 $error_data = $data->get_error_data();
578 if (isset($error_data['data']) && isset($error_data['data']['message'])) {
579 $error_message = $error_data['data']['message'];
580 }
581
582 return new WP_Error('rest_remote_save_failed', $error_message);
583 } else {
584 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'));
585 }
586 }
587 break;
588 }
589 }
590
591 if ('/wp/v2/users' == $request->get_route() && 'GET' == $request->get_method()) {
592 $data = $preloaded_data['authors'];
593
594 $items = array();
595 foreach ($data as $item) {
596 $remote_item = json_decode($item['user']);
597
598 $controller = new UpdraftCentral_REST_Users_Controller;
599 $response = $controller->prepare_remote_item_for_response($remote_item, $request, $item['misc']);
600
601 $merged_data = array_merge($response->get_data(), array('_links' => $response->get_links()));
602 array_push($items, $merged_data);
603 }
604
605 $response = new WP_REST_Response($items);
606 }
607
608 if ('/wp/v2/pages' == $request->get_route() && 'GET' == $request->get_method()) {
609 $data = $preloaded_data['parent_pages'];
610
611 $items = array();
612 foreach ($data as $item) {
613 $remote_item = json_decode($item['post']);
614 if ($post && $remote_item && $post->ID != $remote_item->ID) {
615 $controller = new UpdraftCentral_REST_Posts_Controller($remote_item->post_type);
616 $response = $controller->prepare_remote_item_for_response($remote_item, $request, $item['misc']);
617
618 $merged_data = array_merge($response->get_data(), array('_links' => $response->get_links()));
619 array_push($items, $merged_data);
620 }
621 }
622
623 $response = new WP_REST_Response($items);
624 }
625
626 if ('/wp/v2/taxonomies' == $request->get_route() && 'GET' == $request->get_method()) {
627 $controller = new UpdraftCentral_REST_Taxonomies_Controller;
628 if (isset($preloaded_data['taxonomies'])) {
629 $data = $preloaded_data['taxonomies'];
630
631 $taxonomies = isset($data['taxonomies']) ? $data['taxonomies'] : array();
632 if (!empty($taxonomies) && is_array($taxonomies)) {
633 foreach ($taxonomies as $key => $value) {
634 $tax = $controller->prepare_remote_item_for_response($value, $request);
635 $taxonomies[$key] = $controller->prepare_response_for_collection($tax);
636 }
637 }
638
639 $response = new WP_REST_Response($taxonomies);
640 }
641 }
642
643 $match_result = preg_match('#^/wp/v2/taxonomies/(.*)#', $request->get_route(), $matches);
644 if (!empty($match_result) && 'GET' == $request->get_method()) {
645 $taxonomy = $matches[1];
646
647 $controller = new UpdraftCentral_REST_Taxonomies_Controller;
648 if (isset($preloaded_data['taxonomies'])) {
649 $data = $preloaded_data['taxonomies'];
650
651 $result = array();
652 $taxonomies = isset($data['taxonomies']) ? $data['taxonomies'] : array();
653 if (!empty($taxonomies) && is_array($taxonomies)) {
654 foreach ($taxonomies as $key => $value) {
655 $tax = $controller->prepare_remote_item_for_response($value, $request);
656 $taxonomies[$key] = $controller->prepare_response_for_collection($tax);
657 }
658
659 if (!empty($taxonomy) && isset($taxonomies[$taxonomy])) {
660 // For individual taxonomy retrieval
661 $result = $taxonomies[$taxonomy];
662 if (!empty($params['context']) && 'edit' === $params['context']) {
663 if (isset($result['_links'])) unset($result['_links']);
664 }
665 }
666 }
667
668 $response = new WP_REST_Response($result);
669 }
670 }
671
672 if ('/wp/v2/categories' == $request->get_route() && 'GET' == $request->get_method()) {
673 $controller = new UpdraftCentral_REST_Terms_Controller('category');
674 $taxonomies = $preloaded_data['taxonomies']['taxonomies'];
675
676 $terms = array();
677 $categories = isset($preloaded_data['categories']) ? $preloaded_data['categories'] : array();
678 if (!empty($categories) && is_array($categories)) {
679 $terms = $categories['terms'];
680 foreach ($terms as $key => $value) {
681 if (isset($value['term'])) {
682 $term = json_decode($value['term']);
683
684 $misc = $value['misc'];
685 $misc['taxonomy_obj'] = $taxonomies[$misc['taxonomy']];
686
687 $item = $controller->prepare_remote_item_for_response($term, $request, $misc);
688 $terms[$key] = $controller->prepare_response_for_collection($item);
689 }
690 }
691 }
692
693 $response = new WP_REST_Response($terms);
694 }
695
696 if ('/wp/v2/tags' == $request->get_route() && 'GET' == $request->get_method()) {
697 $controller = new UpdraftCentral_REST_Terms_Controller('post_tag');
698 $taxonomies = $preloaded_data['taxonomies']['taxonomies'];
699
700 $terms = array();
701 $tags = isset($preloaded_data['tags']) ? $preloaded_data['tags'] : array();
702 if (!empty($tags) && is_array($tags)) {
703 $terms = $tags['terms'];
704
705 foreach ($terms as $key => $value) {
706 if (isset($value['term'])) {
707 $term = json_decode($value['term']);
708 $misc = $value['misc'];
709 $misc['taxonomy_obj'] = $taxonomies[$misc['taxonomy']];
710
711 $item = $controller->prepare_remote_item_for_response($term, $request, $misc);
712 $terms[$key] = $controller->prepare_response_for_collection($item);
713 }
714 }
715 }
716
717 $response = new WP_REST_Response($terms);
718 }
719
720 if ('/wp/v2/categories' == $request->get_route() && 'POST' === $request->get_method()) {
721 $data = $this->send_remote_command($user_id, 'posts.add_category', $params);
722 if (!is_wp_error($data) && !empty($data)) {
723 $categories = json_decode($data['categories'], true);
724
725 // Update preloaded data:
726 $preloaded_data['categories'] = $categories;
727 $site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);
728
729 unset($data['categories']);
730 $response = new WP_REST_Response($data);
731 }
732 }
733
734 if ('/wp/v2/tags' == $request->get_route() && 'POST' === $request->get_method()) {
735 $data = $this->send_remote_command($user_id, 'posts.add_tag', $params);
736 if (!is_wp_error($data) && !empty($data)) {
737 $tags = json_decode($data['tags'], true);
738
739 // Update preloaded data:
740 $preloaded_data['tags'] = $tags;
741 $site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);
742
743 unset($data['tags']);
744 $response = new WP_REST_Response($data);
745 }
746 }
747
748 return $response;
749 }
750
751 /**
752 * Creates a new post or return the ID of the existing post
753 *
754 * @param array $data An array of information needed to create a new post if applicable
755 * @return int|bool
756 */
757 private function maybe_create_post_and_return_id($data) {
758 global $wpdb;
759 $query = $wpdb->prepare('SELECT ID FROM '.$wpdb->posts.' WHERE post_name = %s', $data['post_name']);
760 $wpdb->query($query);
761
762 if ($wpdb->num_rows) {
763 $post_id = intval($wpdb->get_var($query));
764 } else {
765 $post_id = wp_insert_post($data);
766 }
767
768 if (is_wp_error($post_id) || empty($post_id)) return false;
769 return $post_id;
770 }
771
772 /**
773 * Loads metaboxees for the classic editor
774 *
775 * @param string $post_type Type of the post submitted
776 * @param WP_Post $post A WP_Post object
777 * @return void
778 */
779 public function load_metaboxes($post_type, $post) {
780 global $post_type_object;
781
782 add_meta_box('submitdiv', __('Publish', 'updraftcentral'), 'post_submit_meta_box', $post->post_type, 'side', 'core', null);
783
784 if ('page' == $post->post_type) {
785 if (post_type_supports($post->post_type, 'page-attributes') || count(get_page_templates($post)) > 0) {
786 add_meta_box('pageparentdiv', $post_type_object->labels->attributes, 'page_attributes_meta_box', $post->post_type, 'side', 'core');
787 }
788 } else {
789 add_meta_box('categorydiv', __('Categories', 'updraftcentral'), array($this, 'post_categories_meta_box'), $post->post_type, 'side', 'core', null);
790
791 add_meta_box('tagsdiv-post_tag', __('Tags', 'updraftcentral'), array($this, 'post_tags_meta_box'), $post->post_type, 'side', 'core', null);
792 }
793
794 if (post_type_supports($post->post_type, 'thumbnail') && current_user_can('upload_files')) {
795 add_meta_box('postimagediv', esc_html($post_type_object->labels->featured_image), 'post_thumbnail_meta_box', $post->post_type, 'side', 'low');
796 }
797 }
798
799 /**
800 * Fetch parent pages for the particular post object (applicable to 'page' post type)
801 *
802 * @return array
803 */
804 public function load_remote_pages() {
805 global $post, $site_id;
806
807 $user = UpdraftCentral()->get_user_object(get_current_user_id());
808 $remote_params = array(
809 'site_id' => $site_id,
810 'data' => array(
811 'command' => 'pages.get_parent_pages',
812 'data' => array(
813 'page' => 1,
814 'per_page' => 100,
815 'exclude' => array($post->ID),
816 'order' => 'ASC',
817 'orderby' => 'menu_order',
818 'status' => 'publish'
819 ),
820 )
821 );
822
823 $remote_response = $user->send_remote_command($remote_params);
824 if (!empty($remote_response) && 'ok' == $remote_response['responsetype']) {
825 if ('rpcok' == $remote_response['rpc_response']['response']) {
826 $data = $remote_response['rpc_response']['data'];
827 return $data['pages'];
828 }
829 }
830
831 return array();
832 }
833
834 /**
835 * Setup the global post data so that any underlying processes will
836 * get to see and acknowledge the remote post as the current object
837 *
838 * @param array $post_data The current post data to edit
839 * @param bool $disable_screen Whether to disable setting the screen or not
840 * @return WP_Post
841 */
842 private function setup_remotepost_data($post_data, $disable_screen = false) {
843 global $post;
844
845 // We need to setup and cache this edited post data so that other processes
846 // or hooks that will eventually rely on this information will succeed.
847 $post = new WP_Post((object) $post_data);
848 if ($post) {
849 setup_postdata($post);
850 if (!$disable_screen) {
851 set_current_screen($post->post_type);
852 }
853
854 // Make sure that this new object gets inserted into the cache
855 // otherwise, WP won't be able to see this information as these are
856 // coming from the remote website and not a local WP_Post object.
857 //
858 // N.B. This will bypass checking the post information from the database
859 // which in reality doesn't actually exists locally since it is coming from
860 // the remote website.
861 wp_cache_set($post->ID, $post, 'posts');
862 }
863
864 return $post;
865 }
866
867 /**
868 * Store preloaded data from remote which will be accessed later, making succeeding
869 * access to these information much more faster the next time around.
870 *
871 * @param array $params The parameters that goes along with the current request
872 * @return void
873 */
874 public function maybe_store_preloaded_data($params) {
875
876 $data = array();
877 // Preloaded data such as remote taxonomies, categories and tags are stored here if
878 // they exists as not to redo the whole request when a new REST request is executed.
879 if (!empty($params['preloaded_data'])) {
880 $data = json_decode($params['preloaded_data'], true);
881 }
882
883 if (!empty($params['post_data'])) {
884 global $post, $post_data;
885
886 // We might as well store the currently edited post here so that any succeeding REST
887 // request for this particular `post` information will no longer require us to initiate another request
888 // just for getting the same information from the remote website (which is kind of redundant
889 // since we've already got those data loaded in the first place).
890 $post_data = json_decode($params['post_data'], true);
891 $data['post_data'] = $post_data;
892
893 // Setup remote post for local access
894 $post = $this->setup_remotepost_data($post_data['post']);
895 }
896
897 // Store preloaded data as user meta, thus, making them unique for every user wanting
898 // to edit their own remote posts or pages.
899 if (!empty($data) && !empty($params['site_id'])) {
900 global $site_id;
901
902 $site_id = $params['site_id'];
903 $site_meta = $this->get_site_meta_instance();
904 $site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post->post_type, $data);
905 }
906 }
907
908 /**
909 * Loads the Gutenberg needed information to successfully load the block
910 * editor in the client.
911 *
912 * @param array $params A collection of information needed when loading the editor
913 * @return array
914 */
915 public function load_gutenberg_editor($params) {
916 global $post, $post_data;
917
918 // Store preloaded data if not yet been stored.
919 $this->maybe_store_preloaded_data($params);
920
921 ob_start();
922 the_block_editor_meta_boxes();
923 $metaboxes = ob_get_contents();
924 ob_end_clean();
925
926 $nonce = wp_create_nonce('updraftcentral-editpost-'.$post->ID);
927 $info = array(
928 'uc_nonce' => $nonce,
929 'uc_refIds' => base64_encode(get_current_user_id().'|'.$post->ID)
930 );
931
932 $settings = get_user_meta(get_current_user_id(), 'updraftcentral_editor_settings', true);
933 $settings['availableTemplates'] = array();
934 if (isset($settings['availableTemplates']) && !empty($params['template_options'])) {
935 $templates = array();
936 $options = $params['template_options'];
937
938 if (!empty($options)) {
939 foreach ($options as $key => $value) {
940 $templates[$value['filename']] = $value['template'];
941 }
942 }
943
944 $settings['availableTemplates'] = array_merge(array('' => __('Default template', 'updraftcentral')), $templates);
945 }
946
947 $misc = $this->attach_media_to_post($post_data['misc'], $post);
948 return array(
949 'post' => $post,
950 'misc' => $misc,
951 'logo' => trailingslashit(UD_CENTRAL_URL).'images/updraftcentral-logo-landscape.png',
952 'metaboxes' => $metaboxes,
953 'settings' => $settings,
954 'info' => $info,
955 '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.
956 'block_categories' => get_block_categories($post),
957 'block_definitions' => get_block_editor_server_block_settings()
958 );
959 }
960
961 /**
962 * Loads the Classic Editor
963 *
964 * @param array $params A collection of information needed when loading the editor
965 * @return array
966 */
967 public function load_classic_editor($params) {
968 global $post_type_object, $post, $site_id, $uc_categories_meta_box, $uc_tags_meta_box, $post_data;
969
970 // Store preloaded data if not yet been stored.
971 $this->maybe_store_preloaded_data($params);
972
973 $item = $post_data['misc'];
974 $site_id = $params['site_id'];
975 $data = array();
976
977 if (!empty($item)) {
978 if ($post) {
979 include_once ABSPATH . 'wp-admin/includes/meta-boxes.php';
980 include_once ABSPATH . 'wp-admin/includes/template.php';
981
982 $item = $this->attach_media_to_post($item, $post);
983 $post_type_object = get_post_type_object($post->post_type);
984 set_current_screen($post->post_type);
985
986 // Make sure that we add the necessary metaboxes for our current post_type (e.g. page or post)
987 // before actually rendering them.
988 add_action('add_meta_boxes', array($this, 'load_metaboxes'), 10, 2);
989 do_action('add_meta_boxes', $post->post_type, $post);
990
991 // Grab editor content to put inside a variable
992 ob_start();
993 wp_editor($post->post_content, 'uc_classic_editor');
994 $editor = ob_get_contents();
995 ob_end_clean();
996
997 // Now grab the metaboxes content
998 ob_start();
999 do_meta_boxes($post->post_type, 'side', $post);
1000 $metaboxes = ob_get_contents();
1001 ob_end_clean();
1002
1003 $data = array(
1004 'post' => $post,
1005 'misc' => $item,
1006 'logo' => trailingslashit(UD_CENTRAL_URL).'images/updraftcentral-logo-landscape.png',
1007 'editor' => $editor,
1008 'metaboxes' => $metaboxes,
1009 'tags_metabox_content' => $uc_tags_meta_box,
1010 'categories_metabox_content' => $uc_categories_meta_box
1011 );
1012 }
1013 }
1014
1015 return $data;
1016 }
1017
1018 /**
1019 * Searches for the media ID of a given attachment/image
1020 *
1021 * @param string $filename The filename of the image/media
1022 * @return int|bool
1023 */
1024 private function get_media_id_by_name($filename) {
1025 global $wpdb;
1026 $media_id = $wpdb->get_var($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid LIKE '%s'", '%'.$filename));
1027
1028 return $media_id ?: false;
1029 }
1030
1031 /**
1032 * Prepares the image for attachment and attaches it to the post object
1033 *
1034 * @param array $item A data array containing the featured image information
1035 * @param WP_Post $post The WP_Post object to where the featured image is to be attached
1036 *
1037 * @return array
1038 */
1039 private function attach_media_to_post($item, $post) {
1040
1041 // Nothing to do if "featured_media" and "featured_media_url" are both empty, thus, we bail.
1042 $featured_media = (int) $item['featured_media'];
1043 if (empty($featured_media) && empty($item['featured_media_url'])) return $item;
1044
1045 // Attach remote media if non-existing. If 'featured_media' is empty or zero
1046 // and the 'feature_media_url' is non-empty meaning we haven't gotten any local media reference just yet.
1047 // Thus, we're going to attach the remote media to this current post so that the editors can consume
1048 // and display the image to the users.
1049 if (empty($featured_media) && !empty($item['featured_media_url'])) {
1050 $media_id = $this->maybe_download_remote_image($item['featured_media_url']);
1051 if (!empty($media_id)) {
1052 $item['featured_media'] = (int) $media_id;
1053 }
1054 } else {
1055 // Check if featured media (image) still exists, meaning, not deleted/removed.
1056 if (!empty($featured_media)) {
1057 $attachment = wp_get_attachment_image_src($featured_media);
1058 if (empty($attachment)) {
1059 $item['featured_media'] = 0;
1060
1061 // Try downloading the image, if the remote page/post currently has
1062 // a featured_media_url set.
1063 if (!empty($item['featured_media_url'])) {
1064 $media_id = $this->maybe_download_remote_image($item['featured_media_url']);
1065 if (!empty($media_id)) {
1066 $item['featured_media'] = (int) $media_id;
1067 }
1068 }
1069 }
1070 }
1071 }
1072
1073 if (!empty($item['featured_media']) && $post) {
1074 set_post_thumbnail($post, (int) $item['featured_media']);
1075 }
1076
1077 return $item;
1078 }
1079
1080 /**
1081 * Saves or downloads the media (attachment/image) from UpdraftCentral
1082 *
1083 * @param string $image_url The URL of the image to download (if needed)
1084 * @param string $image_data The image data to save. If empty, image_url will be used to download the image
1085 * @return int
1086 */
1087 private function maybe_download_remote_image($image_url, $image_data = '') {
1088 if (empty($image_url)) return false;
1089
1090 $image = pathinfo($image_url);
1091 $image_name = $image['basename'];
1092
1093 $media_id = $this->get_media_id_by_name($image_name);
1094 if (!empty($media_id)) {
1095 return $media_id;
1096 }
1097
1098 $upload_dir = wp_upload_dir();
1099 if (empty($image_data)) {
1100 $response = wp_remote_get($image_url);
1101 if (!is_wp_error($response)) {
1102 $image_data = wp_remote_retrieve_body($response);
1103 }
1104 } else {
1105 $image_data = base64_decode($image_data);
1106 }
1107
1108 $media_id = 0;
1109 if (!empty($image_data)) {
1110 $filename = $image_name;
1111
1112 if (wp_mkdir_p($upload_dir['path'])) {
1113 $file = trailingslashit($upload_dir['path']).$filename;
1114 $guid = trailingslashit($upload_dir['url']).$filename;
1115 } else {
1116 $file = trailingslashit($upload_dir['basedir']).$filename;
1117 $guid = trailingslashit($upload_dir['baseurl']).$filename;
1118 }
1119
1120 file_put_contents($file, $image_data);
1121 $wp_filetype = wp_check_filetype($filename, null);
1122
1123 $attachment = array(
1124 'guid' => $guid,
1125 'post_mime_type' => $wp_filetype['type'],
1126 'post_title' => sanitize_file_name($filename),
1127 'post_content' => '',
1128 'post_status' => 'inherit'
1129 );
1130
1131 $media_id = wp_insert_attachment($attachment, $file);
1132 include_once(ABSPATH . 'wp-admin/includes/image.php');
1133
1134 $attach_data = wp_generate_attachment_metadata($media_id, $file);
1135 wp_update_attachment_metadata($media_id, $attach_data);
1136 }
1137
1138 return $media_id;
1139 }
1140
1141 /**
1142 * Gathers post categories metabox information to be rendered
1143 * in the client later on using the Handlerbarsjs templating system
1144 *
1145 * @param WP_Post $post Post object
1146 * @param array $box Categories meta box arguments
1147 */
1148 public function post_categories_meta_box($post, $box) {
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 * @param array $box Tags meta box arguments
1197 */
1198 public function post_tags_meta_box($post, $box) {
1199 global $uc_tags_meta_box, $site_id;
1200
1201 $site_meta = $this->get_site_meta_instance();
1202 $preloaded_data = $site_meta->get_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post->post_type, true);
1203 $options = $misc = array();
1204
1205 if (!empty($preloaded_data)) {
1206 if (isset($preloaded_data['tags'])) {
1207 $options = $preloaded_data['tags']['misc'];
1208 }
1209
1210 if (isset($preloaded_data['post_data'])) {
1211 $misc = $preloaded_data['post_data']['misc'];
1212 }
1213 }
1214
1215 if (empty($options) || empty($misc)) return;
1216
1217 $tag_cloud = $options['tag_cloud'];
1218 $taxonomy = json_decode($options['tax']);
1219
1220 $terms_list = array();
1221 $terms_to_edit = '';
1222 if (!empty($misc['tags_list'])) {
1223 $terms_to_edit = str_replace(', ', ',', $misc['tags_list']);
1224 $terms_list = explode(',', $terms_to_edit);
1225 }
1226
1227 // We're pulling and rendering the template in the client, so, we're
1228 // preparing the information for the template's consumption.
1229 $uc_tags_meta_box = array(
1230 'labels' => array(
1231 'add_or_remove_items' => isset($taxonomy->labels->add_or_remove_items) ? $taxonomy->labels->add_or_remove_items : __('Add or remove tags', 'updraftcentral'),
1232 'add_new_item' => isset($taxonomy->labels->add_new_item) ? $taxonomy->labels->add_new_item : __('Add New Tag', 'updraftcentral'),
1233 'separate_items_with_commas' => isset($taxonomy->labels->separate_items_with_commas) ? $taxonomy->labels->separate_items_with_commas : __('Separate tags with commas', 'updraftcentral'),
1234 'no_terms' => isset($taxonomy->labels->no_terms) ? $taxonomy->labels->no_terms : __('No tags', 'updraftcentral'),
1235 'choose_from_most_used' => isset($taxonomy->labels->choose_from_most_used) ? $taxonomy->labels->choose_from_most_used : __('Choose from the most used tags', 'updraftcentral')
1236 ),
1237 'attributes' => array(
1238 'add' => __('Add', 'updraftcentral'),
1239 ),
1240 'can_assign_terms' => (bool) $options['capabilities']['can_assign_terms'],
1241 'terms_to_edit' => $terms_to_edit,
1242 'terms_list' => $terms_list,
1243 'tag_cloud' => $tag_cloud
1244 );
1245 }
1246 }
1247
1248 endif;
1249