File "class-editor.php"

Full Path: /home/rfaztorb/public_html/wordpress/search/file/pomo/updraft/plugins-old/updraftcentral/classes/class-editor.php
File size: 48.72 KB
MIME-type: text/x-php
Charset: utf-8

<?php
if (!defined('ABSPATH')) die('Access denied.');

if (!class_exists('UpdraftCentral_Editor')) :

/**
 * UpdraftCentral_Editor class.
 *
 * Primarily used for loading the classic and block editor resources and handles UpdraftCentral's REST
 * requests using the block editor to edit remote pages or posts.
 */
class UpdraftCentral_Editor {

	public $type = '';

	protected static $_instance = null;

	/**
	 * Initialize prechecks and hooks for this editor class
	 *
	 * @return void
	 */
	public function load() {
		if (!class_exists('UpdraftCentral_REST_Posts_Controller')) include_once UD_CENTRAL_DIR.'/classes/class-rest-posts-controller.php';
		if (!class_exists('UpdraftCentral_REST_Users_Controller')) include_once UD_CENTRAL_DIR.'/classes/class-rest-users-controller.php';
		if (!class_exists('UpdraftCentral_REST_Taxonomies_Controller')) include_once UD_CENTRAL_DIR.'/classes/class-rest-taxonomies-controller.php';
		if (!class_exists('UpdraftCentral_REST_Terms_Controller')) include_once UD_CENTRAL_DIR.'/classes/class-rest-terms-controller.php';

		add_filter('rest_pre_dispatch', array($this, 'intercept_request_data'), 0, 3);
		add_action('updraftcentral_load_dashboard_js', array($this, 'enqueue_editor_scripts'));
	}

	/**
	 * Creates an instance of this class. Singleton Pattern
	 *
	 * @return object Instance of this class
	 */
	public static function instance() {
		if (empty(self::$_instance)) {
			self::$_instance = new self();
		}

		return self::$_instance;
	}

	/**
	 * Saves placeholder details for later use and enqueus needed resources
	 *
	 * @return void
	 */
	public function enqueue_editor_scripts() {
		global $post, $post_type, $post_type_object;
		
		// We'll create a dummy post or page with status draft (if not yet created) and return its ID.
		// We will used this post or page object to preload any scripts needed by the editor later on, since
		// ajax-based calls will no longer have that opporturnity to load resources (which is only done during page load).
		$post_id = $this->get_placeholder_id($this->type);
		if (!empty($post_id)) {
			$post = get_post($post_id);

			$post_type = get_post_type($post);
			$post_type_object = get_post_type_object($post_type);

			if (!function_exists('get_current_screen')) {
				include_once(ABSPATH . 'wp-admin/includes/screen.php');
				include_once(ABSPATH . 'wp-admin/includes/class-wp-screen.php');
				include_once ABSPATH . 'wp-admin/includes/template.php';
			}

			if (!function_exists('get_page_templates')) {
				include_once ABSPATH . 'wp-admin/includes/theme.php';
			}

			$block_patterns_file = ABSPATH.WPINC.'/block-patterns.php';
			if (file_exists($block_patterns_file)) {
				if (!function_exists('_register_core_block_patterns_and_categories')) {
					include_once($block_patterns_file);
				}
				_register_core_block_patterns_and_categories();
			}

			$this->load_block_editor_resources();
		}

		$min_or_not = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min';
		wp_enqueue_script('postbox', admin_url("js/postbox".$min_or_not.".js"), array('jquery-ui-sortable'), UpdraftCentral()->version, 1);
	}

	/**
	 * Loads block editor resources. Copied from wp-admin/edit-form-blocks.php (WP 5.0) and made
	 * some adjustments (remove and alter lines) to prevent loading the editor prematurely, as we need it to load
	 * on demand basing on the current page/post selected. Updated to support WP 5.4 changes.
	 *
	 * N.B. Applies to WP 5.0 and later only (that is for the core-integrated version).
	 *
	 * @return void
	 */
	private function load_block_editor_resources() {
		global $post_type_object, $post, $wp_meta_boxes;

		// Load block patterns from w.org.
		if (function_exists('_load_remote_block_patterns')) _load_remote_block_patterns();
		if (function_exists('_load_remote_featured_patterns')) _load_remote_featured_patterns();

		/*
		 * Emoji replacement is disabled for now, until it plays nicely with React.
		 */
		remove_action('admin_print_scripts', 'print_emoji_detection_script');

		/*
		 * Block editor implements its own Options menu for toggling Document Panels.
		 */
		add_filter('screen_options_show_screen', '__return_false');

		wp_enqueue_script('heartbeat');
		wp_enqueue_script('wp-edit-post');
		wp_enqueue_script('wp-format-library');

		// Get admin url for handling meta boxes.
		$meta_box_nonce = wp_create_nonce('meta-box-loader');

		$meta_box_url = admin_url('post.php');
		$meta_box_url = add_query_arg(
			array(
				'post'            => $post->ID,
				'action'          => 'edit',
				'meta-box-loader' => true,
				'meta-box-loader-nonce'	=> $meta_box_nonce,
				'_wpnonce'        		=> $meta_box_nonce,	// Supports legacy code prior to 5.4
			),
			$meta_box_url
		);
		wp_add_inline_script(
			'wp-editor',
			sprintf('var _wpMetaBoxUrl = %s;', wp_json_encode($meta_box_url)),
			'before'
		);

		/*
		 * Initialize the editor.
		 */

		$align_wide    = get_theme_support('align-wide');
		$color_palette = current((array) get_theme_support('editor-color-palette'));
		$font_sizes    = current((array) get_theme_support('editor-font-sizes'));
		$gradient_presets = current((array) get_theme_support('editor-gradient-presets'));
		$custom_line_height = get_theme_support('custom-line-height');
		$custom_units = get_theme_support('custom-units');

		/**
		 * Filters the allowed block types for the editor, defaulting to true (all
		 * block types supported).
		 *
		 * @since 5.0.0
		 *
		 * @param bool|array $allowed_block_types Array of block type slugs, or
		 *                                        boolean to enable/disable all.
		 * @param WP_Post $post                    The post resource data.
		 */
		$allowed_block_types = apply_filters('allowed_block_types', true, $post);

		/*
		 * Get all available templates for the post/page attributes meta-box.
		 * The "Default template" array element should only be added if the array is
		 * not empty so we do not trigger the template select element without any options
		 * besides the default value.
		 */
		$available_templates = wp_get_theme()->get_page_templates(get_post($post->ID));
		$available_templates = !empty($available_templates) ? array_merge(array('' => apply_filters('default_page_template_title', __('Default template'), 'rest-api')), $available_templates) : $available_templates;

		// Media settings.
		$max_upload_size = wp_max_upload_size();
		if (!$max_upload_size) {
			$max_upload_size = 0;
		}

		// Image sizes.

		/** This filter is documented in wp-admin/includes/media.php */
		$image_size_names = apply_filters(
			'image_size_names_choose',
			array(
				'thumbnail' => __('Thumbnail'),
				'medium'    => __('Medium'),
				'large'     => __('Large'),
				'full'      => __('Full Size'),
			)
		);

		$available_image_sizes = array();
		foreach ($image_size_names as $image_size_slug => $image_size_name) {
			$available_image_sizes[] = array(
				'slug' => $image_size_slug,
				'name' => $image_size_name,
			);
		}

		$image_dimensions = array();
		if (function_exists('wp_get_registered_image_subsizes')) {
			$all_sizes = wp_get_registered_image_subsizes();
			foreach ($available_image_sizes as $size) {
				$key = $size['slug'];
				if (isset($all_sizes[$key])) {
					$image_dimensions[$key] = $all_sizes[$key];
				}
			}
		}

		if (!function_exists('wp_check_post_lock')) {
			include_once ABSPATH.'wp-admin/includes/post.php';
		}

		$placeholder_editor_id = wp_check_post_lock($post->ID);
		if ($placeholder_editor_id) {
			// Release any lock set at the editor's placeholder. It wasn't suppose
			// to be edited anyway because it wasn't created manually by any user.
			delete_post_meta($post->ID, '_edit_lock');
		}

		// Lock settings.
		$user_id = wp_check_post_lock($post->ID);
		if ($user_id) {
			$locked = false;

			/** This filter is documented in wp-admin/includes/post.php */
			if (apply_filters('show_post_locked_dialog', true, $post, $user_id)) {
				$locked = true;
			}

			$user_details = null;
			if ($locked) {
				$user         = get_userdata($user_id);
				$user_details = array(
					'name' => $user->display_name,
				);
			}

			$lock_details = array(
				'isLocked' => $locked,
				'user'     => $user_details,
			);
		} else {
			// Lock the post.
			$active_post_lock = wp_set_post_lock($post->ID);
			if ($active_post_lock) {
				$active_post_lock = esc_attr(implode(':', $active_post_lock));
			}

			$lock_details     = array(
				'isLocked'       => false,
				'activePostLock' => $active_post_lock,
			);
		}

		// These styles are used if the "no theme styles" options is triggered or on
		// themes without their own editor styles.
		$default_editor_styles_file = ABSPATH.WPINC.'/css/dist/block-editor/default-editor-styles.css';
		if (file_exists($default_editor_styles_file)) {
			$default_editor_styles = array(
				array('css' => file_get_contents($default_editor_styles_file )),
			);
		} else {
			$default_editor_styles = array();
		}

		/**
		 * Filters the body placeholder text.
		 *
		 * @since 5.0.0
		 *
		 * @param string  $text Placeholder text. Default 'Start writing or type / to choose a block'.
		 * @param WP_Post $post Post object.
		 */
		$body_placeholder = apply_filters('write_your_story', __('Type / to choose a block'), $post);

		$editor_settings = array(
			'alignWide'              => $align_wide,
			'availableTemplates'     => $available_templates,
			'allowedBlockTypes'      => $allowed_block_types,
			'disableCustomColors'    => get_theme_support('disable-custom-colors'),
			'disableCustomFontSizes' => get_theme_support('disable-custom-font-sizes'),
			'disableCustomGradients' => get_theme_support('disable-custom-gradients'),
			'disablePostFormats'     => ! current_theme_supports('post-formats'),
			/** This filter is documented in wp-admin/edit-form-advanced.php */
			'titlePlaceholder'       => apply_filters('enter_title_here', __('Add title'), $post),
			'bodyPlaceholder'        => $body_placeholder,
			'isRTL'                  => is_rtl(),
			'autosaveInterval'       => defined('AUTOSAVE_INTERVAL') ? AUTOSAVE_INTERVAL : 10,
			'maxUploadFileSize'      => $max_upload_size,
			'allowedMimeTypes'       => get_allowed_mime_types(),
			'defaultEditorStyles'    => $default_editor_styles,
			'styles'                 => array(),
			'imageSizes'             => $available_image_sizes,
			'imageDimensions'        => $image_dimensions,
			'richEditingEnabled'     => user_can_richedit(),
			'postLock'               => $lock_details,
			'postLockUtils'          => array(
				'nonce'       => wp_create_nonce('lock-post_' . $post->ID),
				'unlockNonce' => wp_create_nonce('update-post_' . $post->ID),
				'ajaxUrl'     => admin_url('admin-ajax.php'),
			),
			'supportsTemplateMode'   => current_theme_supports('block-templates'),
			// N.B. We will not support custom fields for now as this will complicate things even further. Perhaps
			// in the near future we will as the need and demand arises.
			'enableCustomFields'     => false,
			'enableCustomLineHeight' => $custom_line_height,
			'enableCustomUnits'      => $custom_units,
		);

		if (function_exists('wp_theme_has_theme_json')) {
			$editor_settings['supportsLayout'] = wp_theme_has_theme_json();
		} else {
			if (class_exists('WP_Theme_JSON_Resolver')) {
				$editor_settings['supportsLayout'] = WP_Theme_JSON_Resolver::theme_has_support();
			}
		}

		if (class_exists('WP_Block_Patterns_Registry')) {
			$editor_settings['__experimentalBlockPatterns'] = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
		}

		if (class_exists('WP_Block_Pattern_Categories_Registry')) {
			$editor_settings['__experimentalBlockPatternCategories'] = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();
		}

		$autosave = wp_get_post_autosave($post->ID);
		if ($autosave) {
			if (mysql2date('U', $autosave->post_modified_gmt, false) > mysql2date('U', $post->post_modified_gmt, false)) {
				$editor_settings['autosave'] = array(
					'editLink' => get_edit_post_link($autosave->ID),
				);
			} else {
				wp_delete_post_revision($autosave->ID);
			}
		}

		if (false !== $color_palette) {
			$editor_settings['colors'] = $color_palette;
		}

		if (false !== $font_sizes) {
			$editor_settings['fontSizes'] = $font_sizes;
		}

		if (false !== $gradient_presets) {
			$editor_settings['gradients'] = $gradient_presets;
		}

		if (!empty($post_type_object->template)) {
			$editor_settings['template']     = $post_type_object->template;
			$editor_settings['templateLock'] = !empty($post_type_object->template_lock) ? $post_type_object->template_lock : false;
		}

		$is_new_post = false;
		if ('auto-draft' === $post->post_status) {
			$is_new_post = true;
		}

		// If there's no template set on a new post, use the post format, instead.
		if ($is_new_post && !isset($editor_settings['template']) && 'post' === $post->post_type) {
			$post_format = get_post_format($post);
			if (in_array($post_format, array('audio', 'gallery', 'image', 'quote', 'video'), true)) {
				$editor_settings['template'] = array(array("core/$post_format"));
			}
		}

		if (function_exists('wp_is_block_theme') && wp_is_block_theme() && $editor_settings['supportsTemplateMode']) {
			if (function_exists('get_allowed_block_template_part_areas')) {
				$editor_settings['defaultTemplatePartAreas'] = get_allowed_block_template_part_areas();
			}
		}

		/**
		 * Scripts
		 */
		wp_enqueue_media(
			array(
				'post' => $post->ID,
			)
		);
		wp_tinymce_inline_scripts();
		wp_enqueue_editor();

		/**
		 * Styles
		 */
		wp_enqueue_style('wp-edit-post');
		wp_enqueue_style('wp-format-library');

		do_action('enqueue_block_assets');

		/**
		 * Fires after block assets have been enqueued for the editing interface.
		 *
		 * Call `add_action` on any hook before 'admin_enqueue_scripts'.
		 *
		 * In the function call you supply, simply use `wp_enqueue_script` and
		 * `wp_enqueue_style` to add your functionality to the block editor.
		 *
		 * @since 5.0.0
		 */
		do_action('enqueue_block_editor_assets');

		// In order to duplicate classic meta box behaviour, we need to run the classic meta box actions.
		require_once(ABSPATH . 'wp-admin/includes/meta-boxes.php');
		register_and_do_post_meta_boxes($post);

		// Check if the Custom Fields meta box has been removed at some point.
		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'])) {
			$core_meta_boxes = $wp_meta_boxes[$post->post_type]['normal']['core'];
			if (!isset($core_meta_boxes['postcustom']) || !$core_meta_boxes['postcustom']) {
				unset($editor_settings['enableCustomFields']);
			}
		}

		if (class_exists('WP_Block_Editor_Context')) {
			$block_editor_context = new WP_Block_Editor_Context(array('post' => $post));
			$editor_settings = get_block_editor_settings( $editor_settings, $block_editor_context );
		} else {
			/**
			 * Filters the settings to pass to the block editor.
			 *
			 * @since 5.0.0
			 *
			 * @param array   $editor_settings Default editor settings.
			 * @param WP_Post $post            Post being edited.
			 */
			$editor_settings = apply_filters('block_editor_settings', $editor_settings, $post);
		}
		
		update_user_meta(get_current_user_id(), 'updraftcentral_editor_settings', $editor_settings);
	}

	/**
	 * Retrieves the placeholder id created for the current type (e.g. 'post' or 'page')
	 *
	 * @param string $type Determines which type of ID to return (either 'post' or 'page')
	 * @return int|boolean
	 */
	public function get_placeholder_id($type) {
		if (in_array($type, array('page', 'post'))) {
			$post_id = $this->maybe_create_post_and_return_id(array(
				'post_title' => __('UpdraftCentral Editor Placeholder', 'updraftcentral'),
				'post_name' => 'uc-editor-placeholder-'.$type,
				'post_content' => sprintf(__('UpdraftCentral plugin uses this %s as a placeholder when loading the %s editor.', 'updraftcentral').' '.__('You should leave it with "Draft" status.', 'updraftcentral'), $type, $type),
				'post_status' => 'draft',
				'post_type' => $type
			));
			return $post_id;
		}

		return false;
	}

	/**
	 * Sends the command to the remote website
	 *
	 * @param int    $user_id The current user ID
	 * @param string $command The command to process
	 * @param array  $params  The parameters that goes along with the current request
	 * @return array
	 */
	private function send_remote_command($user_id, $command, $params) {
		$user = UpdraftCentral()->get_user_object($user_id);
		if (!empty($user) && !empty($params['site_id'])) {
			$remote_params = array(
				'site_id' => $params['site_id'],
				'data' => array(
					'command' => $command,
					'data' => $params,
				)
			);

			$remote_response = $user->send_remote_command($remote_params);
			if (!empty($remote_response) && 'ok' == $remote_response['responsetype']) {
				if ('rpcok' == $remote_response['rpc_response']['response']) {
					$data = $remote_response['rpc_response']['data'];
					return $data;
				} elseif ('rpcerror' == $remote_response['rpc_response']['response']) {
					return new WP_Error('updraftcentral_rpcerror', __('The remote website responded with an error.', 'updraftcentral'), $remote_response['rpc_response']['data']);
				}
			} else {
				return new WP_Error('updraftcentral_communication_error', __('An error has occurred while communicating to the remote website.', 'updraftcentral'));
			}
		} else {
			return new WP_Error('updraftcentral_missing_fields', __('The required object has not been found.', 'updraftcentral'));
		}
	}

	/**
	 * Returns an instance of an UpdraftCentral_Site_Meta class that is used to manage
	 * our site meta entries
	 *
	 * @return UpdraftCentral_Site_Meta
	 */
	private function get_site_meta_instance() {
		$site_meta = UpdraftCentral()->site_meta;
		if (empty($site_meta)) {
			if (!class_exists('UpdraftCentral_Site_Meta')) include_once UD_CENTRAL_DIR.'/classes/site-meta.php';

			return new UpdraftCentral_Site_Meta(UpdraftCentral()->table_prefix);
		}

		return $site_meta;
	}

	/**
	 * Intercepts request params/data and bypass default (local) processing
	 *
	 * @param mixed           $response Current response, either response or `null` to indicate pass-through.
	 * @param WP_REST_Server  $handler  ResponseHandler instance (usually WP_REST_Server).
	 * @param WP_REST_Request $request  The request that was used to make current response.
	 * @return WP_REST_Response
	 */
	public function intercept_request_data($response, $handler, $request) {

		// Check whether we received an updraftcentral "uc_nonce", "uc_refIds" and "site_id" and "post_type" data. This also helps in preventing any overlap
		// or overwrites when the user is using the Block editor in the WP admin area. Thus, we're only
		// executing the processes below if the current request is made through UpdraftCentral.
		$params = $request->get_params();
		if (empty($params['uc_nonce']) || empty($params['uc_refIds']) || empty($params['site_id']) || empty($params['post_type'])) return $response;

		$uc_nonce = is_array($params['uc_nonce']) ? $params['uc_nonce'][0] : $params['uc_nonce'];
		$uc_refIds = is_array($params['uc_refIds']) ? $params['uc_refIds'][0] : $params['uc_refIds'];
		$site_id = is_array($params['site_id']) ? $params['site_id'][0] : $params['site_id'];
		$post_type = is_array($params['post_type']) ? $params['post_type'][0] : $params['post_type'];

		list($user_id, $post_id) = explode('|', base64_decode($uc_refIds));
		if (empty($user_id) || empty($post_id)) return $response;

		// Verify the nonce submitted
		if (!wp_verify_nonce($uc_nonce, 'updraftcentral-editpost-'.$post_id)) {
			return $response;
		}

		// Pull the (stored) preloaded information from the remote website to be use for the REST request
		// initiated by the UpdraftCentral editor.
		$site_meta = $this->get_site_meta_instance();

		$preloaded_data = $site_meta->get_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, true);
		if (!empty($preloaded_data)) {
			if (isset($preloaded_data['post_data'])) {
				$post_data = $preloaded_data['post_data'];
				$post = $this->setup_remotepost_data($post_data['post'], true);
			}
		}

		$match_result = preg_match('#^/wp/v2/(pages|posts)/([0-9]+)#', $request->get_route(), $matches);
		if (!empty($match_result) && $post) {
			$params = $request->get_params();
			$post_id = $matches[2];

			switch ($request->get_method()) {
				case 'GET':
					$controller = new UpdraftCentral_REST_Posts_Controller($post->post_type);
					$misc = $this->attach_media_to_post($post_data['misc'], $post);

					$response = $controller->prepare_remote_item_for_response($post, $request, $misc);
					break;
				case 'PUT':
					if (!empty($params['featured_media'])) {
						$params['featured_media_url'] = wp_get_attachment_url($params['featured_media']);
						$dir = wp_upload_dir();

						$image_info = wp_get_attachment_metadata($params['featured_media'], true);
						if (is_array($image_info) && !empty($image_info['file'])) {
							$image_file = trailingslashit($dir['basedir']).$image_info['file'];
							if (file_exists($image_file)) {
								$params['featured_media_data'] = base64_encode(file_get_contents($image_file, false, null));
							}
						}
					}

					$command = ('pages' == $matches[1]) ? 'pages.save' : 'posts.save';
					if (!isset($request['context'])) $request['context'] = 'edit';

					// On latest WP (e.g. 5.6) 'menu_order' is passed by the block editor instead of 'order', thus, we need
					// to make sure that it is compatible with our service handler.
					if (isset($params['menu_order'])) $params['order'] = $params['menu_order'];

					// On latest WP (e.g. 5.6) a NULL value is passed instead of zero, thus, we'll make some
					// adjustment before sending it to our service handler for compatibility.
					//
					// N.B. Running the check directly with "isset" and "empty" won't work since we need to make
					// sure that we will only process if the fields were actually edited and the block editor will
					// only send the fields of those edited post or page properties. Thus, we used the "array_key_exists"
					// method here before checking if the value is NULL.
					if (array_key_exists('parent', $params) && is_null($params['parent'])) $params['parent'] = 0;

					$data = $this->send_remote_command($user_id, $command, $params);
					if (!is_wp_error($data) && !empty($data)) {
						$post = json_decode($data['post']);
						$misc = $this->attach_media_to_post($data['misc'], $post);

						// Update/replace existing preloaded post data with the recent
						// changes so that it gets reflected all throughout UpdraftCentral.
						$post_data = array(
							'post' => $post,
							'misc' => $misc
						);

						$preloaded_data['post_data'] = $post_data;
						$site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);

						if (!empty($data['options'])) {
							$post_data['options'] = $data['options'];
						}

						// Generate the response
						$controller = new UpdraftCentral_REST_Posts_Controller($post->post_type);
						$response = $controller->prepare_remote_item_for_response($post, $request, $misc);

						$response_data = $response->get_data();
						if (!empty($response_data)) {
							// We need to add the post_data field to the response in order
							// to update the current list item's information
							$response_data['post_data'] = $post_data;
							$response->set_data($response_data);
						}
					} else {
						if (is_wp_error($data)) {
							// Default message if we can't pull any relevant error messages from $data
							$error_message = __('An unknown error has occurred while processing your request.', 'updraftcentral').' '.__('Please contact our support on updraftplus.com so that we can properly escalate the issue.', 'updraftcentral');

							$messages = $data->get_error_messages();
							if (!empty($messages)) {
								$error_message = $messages[0];
							}

							// More specific error information - will override any previous one
							$error_data = $data->get_error_data();
							if (isset($error_data['data']) && isset($error_data['data']['message'])) {
								$error_message = $error_data['data']['message'];
							}

							return new WP_Error('rest_remote_save_failed', $error_message);
						} else {
							return new WP_Error('rest_remote_save_failed', __('An unknown error has occurred while processing your request.', 'updraftcentral').' '.__('Please contact our support on updraftplus.com so that we can properly escalate the issue.', 'updraftcentral'));
						}
					}
					break;
			}
		}

		if ('/wp/v2/users' == $request->get_route() && 'GET' == $request->get_method()) {
			$data = $preloaded_data['authors'];

			$items = array();
			foreach ($data as $item) {
				$remote_item = json_decode($item['user']);

				$controller = new UpdraftCentral_REST_Users_Controller;
				$response = $controller->prepare_remote_item_for_response($remote_item, $request, $item['misc']);

				$merged_data = array_merge($response->get_data(), array('_links' => $response->get_links()));
				array_push($items, $merged_data);
			}

			$response = new WP_REST_Response($items);
		}

		if ('/wp/v2/pages' == $request->get_route() && 'GET' == $request->get_method()) {
			$data = $preloaded_data['parent_pages'];

			$items = array();
			foreach ($data as $item) {
				$remote_item = json_decode($item['post']);
				if ($post && $remote_item && $post->ID != $remote_item->ID) {
					$controller = new UpdraftCentral_REST_Posts_Controller($remote_item->post_type);
					$response = $controller->prepare_remote_item_for_response($remote_item, $request, $item['misc']);
	
					$merged_data = array_merge($response->get_data(), array('_links' => $response->get_links()));
					array_push($items, $merged_data);
				}
			}

			$response = new WP_REST_Response($items);
		}

		if ('/wp/v2/taxonomies' == $request->get_route() && 'GET' == $request->get_method()) {
			$controller = new UpdraftCentral_REST_Taxonomies_Controller;
			if (isset($preloaded_data['taxonomies'])) {
				$data = $preloaded_data['taxonomies'];
	
				$taxonomies = isset($data['taxonomies']) ? $data['taxonomies'] : array();
				if (!empty($taxonomies) && is_array($taxonomies)) {
					foreach ($taxonomies as $key => $value) {
						$tax = $controller->prepare_remote_item_for_response($value, $request);
						$taxonomies[$key] = $controller->prepare_response_for_collection($tax);
					}
				}
	
				$response = new WP_REST_Response($taxonomies);
			}
		}

		$match_result = preg_match('#^/wp/v2/taxonomies/(.*)#', $request->get_route(), $matches);
		if (!empty($match_result) && 'GET' == $request->get_method()) {
			$taxonomy = $matches[1];

			$controller = new UpdraftCentral_REST_Taxonomies_Controller;
			if (isset($preloaded_data['taxonomies'])) {
				$data = $preloaded_data['taxonomies'];
				
				$result = array();
				$taxonomies = isset($data['taxonomies']) ? $data['taxonomies'] : array();
				if (!empty($taxonomies) && is_array($taxonomies)) {
					foreach ($taxonomies as $key => $value) {
						$tax = $controller->prepare_remote_item_for_response($value, $request);
						$taxonomies[$key] = $controller->prepare_response_for_collection($tax);
					}
	
					if (!empty($taxonomy) && isset($taxonomies[$taxonomy])) {
						// For individual taxonomy retrieval
						$result = $taxonomies[$taxonomy];
						if (!empty($params['context']) && 'edit' === $params['context']) {
							if (isset($result['_links'])) unset($result['_links']);
						}
					}
				}
	
				$response = new WP_REST_Response($result);
			}
		}

		if ('/wp/v2/categories' == $request->get_route() && 'GET' == $request->get_method()) {
			$controller = new UpdraftCentral_REST_Terms_Controller('category');
			$taxonomies = $preloaded_data['taxonomies']['taxonomies'];

			$terms = array();
			$categories = isset($preloaded_data['categories']) ? $preloaded_data['categories'] : array();
			if (!empty($categories) && is_array($categories)) {
				$terms = $categories['terms'];
				foreach ($terms as $key => $value) {
					if (isset($value['term'])) {
						$term = json_decode($value['term']);

						$misc = $value['misc'];
						$misc['taxonomy_obj'] = $taxonomies[$misc['taxonomy']];

						$item = $controller->prepare_remote_item_for_response($term, $request, $misc);
						$terms[$key] = $controller->prepare_response_for_collection($item);
					}
				}
			}

			$response = new WP_REST_Response($terms);
		}

		if ('/wp/v2/tags' == $request->get_route() && 'GET' == $request->get_method()) {
			$controller = new UpdraftCentral_REST_Terms_Controller('post_tag');
			$taxonomies = $preloaded_data['taxonomies']['taxonomies'];

			$terms = array();
			$tags = isset($preloaded_data['tags']) ? $preloaded_data['tags'] : array();
			if (!empty($tags) && is_array($tags)) {
				$terms = $tags['terms'];

				foreach ($terms as $key => $value) {
					if (isset($value['term'])) {
						$term = json_decode($value['term']);
						$misc = $value['misc'];
						$misc['taxonomy_obj'] = $taxonomies[$misc['taxonomy']];

						$item = $controller->prepare_remote_item_for_response($term, $request, $misc);
						$terms[$key] = $controller->prepare_response_for_collection($item);
					}
				}
			}

			$response = new WP_REST_Response($terms);
		}

		if ('/wp/v2/categories' == $request->get_route() && 'POST' === $request->get_method()) {
			$data = $this->send_remote_command($user_id, 'posts.add_category', $params);
			if (!is_wp_error($data) && !empty($data)) {
				$categories = json_decode($data['categories'], true);

				// Update preloaded data:
				$preloaded_data['categories'] = $categories;
				$site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);

				unset($data['categories']);
				$response = new WP_REST_Response($data);
			}
		}

		if ('/wp/v2/tags' == $request->get_route() && 'POST' === $request->get_method()) {
			$data = $this->send_remote_command($user_id, 'posts.add_tag', $params);
			if (!is_wp_error($data) && !empty($data)) {
				$tags = json_decode($data['tags'], true);

				// Update preloaded data:
				$preloaded_data['tags'] = $tags;
				$site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post_type, $preloaded_data);

				unset($data['tags']);
				$response = new WP_REST_Response($data);
			}
		}

		return $response;
	}

	/**
	 * Creates a new post or return the ID of the existing post
	 *
	 * @param array $data An array of information needed to create a new post if applicable
	 * @return int|bool
	 */
	private function maybe_create_post_and_return_id($data) {
		global $wpdb;
		$query = $wpdb->prepare('SELECT ID FROM '.$wpdb->posts.' WHERE post_name = %s', $data['post_name']);
		$wpdb->query($query);

		if ($wpdb->num_rows) {
			$post_id = intval($wpdb->get_var($query));
		} else {
			$post_id = wp_insert_post($data);
		}
		
		if (is_wp_error($post_id) || empty($post_id)) return false;
		return $post_id;
	}

	/**
	 * Loads metaboxees for the classic editor
	 *
	 * @param string  $post_type Type of the post submitted
	 * @param WP_Post $post      A WP_Post object
	 * @return void
	 */
	public function load_metaboxes($post_type, $post) {
		global $post_type_object;

		add_meta_box('submitdiv', __('Publish', 'updraftcentral'), 'post_submit_meta_box', $post->post_type, 'side', 'core', null);

		if ('page' == $post->post_type) {
			if (post_type_supports($post->post_type, 'page-attributes') || count(get_page_templates($post)) > 0) {
				add_meta_box('pageparentdiv', $post_type_object->labels->attributes, 'page_attributes_meta_box', $post->post_type, 'side', 'core');
			}
		} else {
			add_meta_box('categorydiv', __('Categories', 'updraftcentral'), array($this, 'post_categories_meta_box'), $post->post_type, 'side', 'core', null);

			add_meta_box('tagsdiv-post_tag', __('Tags', 'updraftcentral'), array($this, 'post_tags_meta_box'), $post->post_type, 'side', 'core', null);
		}

		if (post_type_supports($post->post_type, 'thumbnail') && current_user_can('upload_files')) {
			add_meta_box('postimagediv', esc_html($post_type_object->labels->featured_image), 'post_thumbnail_meta_box', $post->post_type, 'side', 'low');
		}
	}

	/**
	 * Fetch parent pages for the particular post object (applicable to 'page' post type)
	 *
	 * @return array
	 */
	public function load_remote_pages() {
		global $post, $site_id;

		$user = UpdraftCentral()->get_user_object(get_current_user_id());
		$remote_params = array(
			'site_id' => $site_id,
			'data' => array(
				'command' => 'pages.get_parent_pages',
				'data' => array(
					'page' => 1,
					'per_page' => 100,
					'exclude' => array($post->ID),
					'order' => 'ASC',
					'orderby' => 'menu_order',
					'status' => 'publish'
				),
			)
		);

		$remote_response = $user->send_remote_command($remote_params);
		if (!empty($remote_response) && 'ok' == $remote_response['responsetype']) {
			if ('rpcok' == $remote_response['rpc_response']['response']) {
				$data = $remote_response['rpc_response']['data'];
				return $data['pages'];
			}
		}

		return array();
	}

	/**
	 * Setup the global post data so that any underlying processes will
	 * get to see and acknowledge the remote post as the current object
	 *
	 * @param array $post_data      The current post data to edit
	 * @param bool  $disable_screen Whether to disable setting the screen or not
	 * @return WP_Post
	 */
	private function setup_remotepost_data($post_data, $disable_screen = false) {
		global $post;

		// We need to setup and cache this edited post data so that other processes
		// or hooks that will eventually rely on this information will succeed.
		$post = new WP_Post((object) $post_data);
		if ($post) {
			setup_postdata($post);
			if (!$disable_screen) {
				set_current_screen($post->post_type);
			}

			// Make sure that this new object gets inserted into the cache
			// otherwise, WP won't be able to see this information as these are
			// coming from the remote website and not a local WP_Post object.
			//
			// N.B. This will bypass checking the post information from the database
			// which in reality doesn't actually exists locally since it is coming from
			// the remote website.
			wp_cache_set($post->ID, $post, 'posts');
		}

		return $post;
	}

	/**
	 * Extracts preloaded information (styles) by key
	 *
	 * @param array  $editor_styles The preloaded editor styles array
	 * @param string $key           The key or name of the data to extract
	 *
	 * @return mixed|false
	 */
	private function filter_preloaded_styles($editor_styles, $key) {
		$result = array_values(array_filter($editor_styles, function($item) use ($key) {
			return isset($item[$key]);
		}));

		if (!empty($result)) {
			return $result[0][$key];
		}
		return false;
	}

	/**
	 * Store preloaded data from remote which will be accessed later, making succeeding
	 * access to these information much more faster the next time around.
	 *
	 * @param array $params The parameters that goes along with the current request
	 * @return void
	 */
	public function maybe_store_preloaded_data($params) {

		$data = array();
		// Preloaded data such as remote taxonomies, categories and tags are stored here if
		// they exists as not to redo the whole request when a new REST request is executed.
		if (!empty($params['preloaded_data'])) {
			$data = json_decode($params['preloaded_data'], true);
		}

		if (!empty($params['post_data'])) {
			global $post, $post_data;

			// We might as well store the currently edited post here so that any succeeding REST
			// request for this particular `post` information will no longer require us to initiate another request
			// just for getting the same information from the remote website (which is kind of redundant
			// since we've already got those data loaded in the first place).
			$post_data = json_decode($params['post_data'], true);
			$data['post_data'] = $post_data;

			// Setup remote post for local access
			$post = $this->setup_remotepost_data($post_data['post']);
		}

		// Store preloaded data as user meta, thus, making them unique for every user wanting
		// to edit their own remote posts or pages.
		if (!empty($data) && !empty($params['site_id'])) {
			global $site_id;

			$site_id = $params['site_id'];
			$site_meta = $this->get_site_meta_instance();
			$site_meta->update_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post->post_type, $data);
		}
	}

	/**
	 * Loads the Gutenberg needed information to successfully load the block
	 * editor in the client.
	 *
	 * @param array	$params	A collection of information needed when loading the editor
	 * @return array
	 */
	public function load_gutenberg_editor($params) {
		global $post, $post_data;

		// Store preloaded data if not yet been stored.
		$this->maybe_store_preloaded_data($params);

		ob_start();
		the_block_editor_meta_boxes();
		$metaboxes = ob_get_contents();
		ob_end_clean();

		$nonce = wp_create_nonce('updraftcentral-editpost-'.$post->ID);
		$info = array(
			'uc_nonce' => $nonce,
			'uc_refIds' => base64_encode(get_current_user_id().'|'.$post->ID)
		);

		$settings = get_user_meta(get_current_user_id(), 'updraftcentral_editor_settings', true);

		if (isset($params['preloaded_data'])) {
			$preloaded_data = json_decode($params['preloaded_data'], true);
			$editor_styles = $preloaded_data['editor_styles'];

			$filter_map = array(
				'styles' => 'editor_css',
				'fonts' => 'font_css',
				'theme_fonts' => 'theme_json_fonts',
				'defaultEditorStyles' => 'default_editor_css',
				'editor_assets' => 'editor_assets',
			);

			foreach ($filter_map as $key => $value) {
				$filtered_data = $this->filter_preloaded_styles($editor_styles, $value);
				if (!empty($filtered_data)) $settings[$key] = $filtered_data;
			}
		}

		$settings['availableTemplates'] = array();
		if (isset($settings['availableTemplates']) && !empty($params['template_options'])) {
			$templates = array();
			$options = $params['template_options'];
			
			if (!empty($options)) {
				foreach ($options as $value) {
					$templates[$value['filename']] = $value['template'];
				}
			}

			$settings['availableTemplates'] = array_merge(array('' => __('Default template', 'updraftcentral')), $templates);
		}

		$misc = $this->attach_media_to_post($post_data['misc'], $post);
		
		$preloaded = json_decode($params['preloaded_data'], true);
		$block_categories = get_block_categories($post);
		$block_definitions = get_block_editor_server_block_settings();

		if (!empty($preloaded)) {
			if (isset($preloaded['block_patterns']) && isset($preloaded['block_pattern_categories'])) {
				$settings['__experimentalBlockPatterns'] = $preloaded['block_patterns'];
				$settings['__experimentalBlockPatternCategories'] = $preloaded['block_pattern_categories'];
			}

			$block_categories = $preloaded['block_categories'];
			$block_definitions = $preloaded['block_definitions'];
		}

		return array(
			'post' => $post,
			'misc' => $misc,
			'logo' => trailingslashit(UD_CENTRAL_URL).'images/updraftcentral-logo-landscape.png',
			'metaboxes' => $metaboxes,
			'settings' => $settings,
			'info' => $info,
			'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.
			'block_categories' => $block_categories,
			'block_definitions' => $block_definitions,
		);
	}

	/**
	 * Loads the Classic Editor
	 *
	 * @param array	$params	A collection of information needed when loading the editor
	 * @return array
	 */
	public function load_classic_editor($params) {
		global $post_type_object, $post, $site_id, $uc_categories_meta_box, $uc_tags_meta_box, $post_data;

		// Store preloaded data if not yet been stored.
		$this->maybe_store_preloaded_data($params);

		$item = $post_data['misc'];
		$site_id = $params['site_id'];
		$data = array();

		if (!empty($item)) {
			if ($post) {
				include_once ABSPATH . 'wp-admin/includes/meta-boxes.php';
				include_once ABSPATH . 'wp-admin/includes/template.php';

				$item = $this->attach_media_to_post($item, $post);
				$post_type_object = get_post_type_object($post->post_type);
				set_current_screen($post->post_type);

				// Make sure that we add the necessary metaboxes for our current post_type (e.g. page or post)
				// before actually rendering them.
				add_action('add_meta_boxes', array($this, 'load_metaboxes'), 10, 2);
				do_action('add_meta_boxes', $post->post_type, $post);

				// Grab editor content to put inside a variable
				ob_start();
				wp_editor($post->post_content, 'uc_classic_editor');
				$editor = ob_get_contents();
				ob_end_clean();

				// Now grab the metaboxes content
				ob_start();
				do_meta_boxes($post->post_type, 'side', $post);
				$metaboxes = ob_get_contents();
				ob_end_clean();

				$data = array(
					'post' => $post,
					'misc' => $item,
					'logo' => trailingslashit(UD_CENTRAL_URL).'images/updraftcentral-logo-landscape.png',
					'editor' => $editor,
					'metaboxes' => $metaboxes,
					'tags_metabox_content' => $uc_tags_meta_box,
					'categories_metabox_content' => $uc_categories_meta_box
				);
			}
		}

		return $data;
	}

	/**
	 * Searches for the media ID of a given attachment/image
	 *
	 * @param string $filename The filename of the image/media
	 * @return int|bool
	 */
	private function get_media_id_by_name($filename) {
		global $wpdb;
		$media_id = $wpdb->get_var($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE guid LIKE '%s'", '%'.$filename));

		return $media_id ?: false;
	}

	/**
	 * Prepares the image for attachment and attaches it to the post object
	 *
	 * @param array   $item A data array containing the featured image information
	 * @param WP_Post $post The WP_Post object to where the featured image is to be attached
	 *
	 * @return array
	 */
	private function attach_media_to_post($item, $post) {

		// Nothing to do if "featured_media" and "featured_media_url" are both empty, thus, we bail.
		$featured_media = (int) $item['featured_media'];
		if (empty($featured_media) && empty($item['featured_media_url'])) return $item;

		// Attach remote media if non-existing. If 'featured_media' is empty or zero
		// and the 'feature_media_url' is non-empty meaning we haven't gotten any local media reference just yet.
		// Thus, we're going to attach the remote media to this current post so that the editors can consume
		// and display the image to the users.
		if (empty($featured_media) && !empty($item['featured_media_url'])) {
			$media_id = $this->maybe_download_remote_image($item['featured_media_url']);
			if (!empty($media_id)) {
				$item['featured_media'] = (int) $media_id;
			}
		} else {
			// Check if featured media (image) still exists, meaning, not deleted/removed.
			if (!empty($featured_media)) {
				$attachment = wp_get_attachment_image_src($featured_media);
				if (empty($attachment)) {
					$item['featured_media'] = 0;

					// Try downloading the image, if the remote page/post currently has
					// a featured_media_url set.
					if (!empty($item['featured_media_url'])) {
						$media_id = $this->maybe_download_remote_image($item['featured_media_url']);
						if (!empty($media_id)) {
							$item['featured_media'] = (int) $media_id;
						}
					}
				}
			}
		}

		if (!empty($item['featured_media']) && $post) {
			set_post_thumbnail($post, (int) $item['featured_media']);
		}

		return $item;
	}

	/**
	 * Saves or downloads the media (attachment/image) from UpdraftCentral
	 *
	 * @param string $image_url  The URL of the image to download (if needed)
	 * @param string $image_data The image data to save. If empty, image_url will be used to download the image
	 * @return int
	 */
	private function maybe_download_remote_image($image_url, $image_data = '') {
		if (empty($image_url)) return false;

		$image = pathinfo($image_url);
		$image_name = $image['basename'];

		$media_id = $this->get_media_id_by_name($image_name);
		if (!empty($media_id)) {
			return $media_id;
		}

		$upload_dir = wp_upload_dir();
		if (empty($image_data)) {
			$response = wp_remote_get($image_url);
			if (!is_wp_error($response)) {
				$image_data = wp_remote_retrieve_body($response);
			}
		} else {
			$image_data = base64_decode($image_data);
		}

		$media_id = 0;
		if (!empty($image_data)) {
			$filename = $image_name;

			if (wp_mkdir_p($upload_dir['path'])) {
				$file = trailingslashit($upload_dir['path']).$filename;
				$guid = trailingslashit($upload_dir['url']).$filename;
			} else {
				$file = trailingslashit($upload_dir['basedir']).$filename;
				$guid = trailingslashit($upload_dir['baseurl']).$filename;
			}

			file_put_contents($file, $image_data);
			$wp_filetype = wp_check_filetype($filename, null);

			$attachment = array(
				'guid' => $guid,
				'post_mime_type' => $wp_filetype['type'],
				'post_title'     => sanitize_file_name($filename),
				'post_content'   => '',
				'post_status'    => 'inherit'
			);

			$media_id = wp_insert_attachment($attachment, $file);
			include_once(ABSPATH . 'wp-admin/includes/image.php');

			$attach_data = wp_generate_attachment_metadata($media_id, $file);
			wp_update_attachment_metadata($media_id, $attach_data);
		}

		return $media_id;
	}

	/**
	 * Gathers post categories metabox information to be rendered
	 * in the client later on using the Handlerbarsjs templating system
	 *
	 * @param WP_Post $post Post object
	 */
	public function post_categories_meta_box($post) {
		global $uc_categories_meta_box, $site_id;

		$site_meta = $this->get_site_meta_instance();
		$preloaded_data = $site_meta->get_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post->post_type, true);
		$options = $misc = array();

		if (!empty($preloaded_data)) {
			if (isset($preloaded_data['categories'])) {
				$options = $preloaded_data['categories']['misc'];
			}

			if (isset($preloaded_data['post_data'])) {
				$misc = $preloaded_data['post_data']['misc'];
			}
		}

		if (empty($options) || empty($misc)) return;

		$taxonomy = json_decode($options['tax']);
		$popular_terms_checklist = $options['popular'];
		$terms_checklist = $misc['categories_checklist'];
		$parent_dropdown = $options['parent_dropdown'];

		// We're pulling and rendering the template in the client, so, we're
		// preparing the information for the template's consumption.
		$uc_categories_meta_box = array(
			'labels' => array(
				'all_items' => isset($taxonomy->labels->all_items) ? $taxonomy->labels->all_items : __('All Categories', 'updraftcentral'),
				'most_used' => isset($taxonomy->labels->most_used) ? esc_html($taxonomy->labels->most_used) : __('Most Used', 'updraftcentral'),
				'add_new_item' => isset($taxonomy->labels->add_new_item) ? $taxonomy->labels->add_new_item : __('Add New Category', 'updraftcentral')
			),
			'attributes' => array(
				'new_item_name' => isset($taxonomy->labels->new_item_name) ? esc_attr($taxonomy->labels->new_item_name) : __('New Category Name', 'updraftcentral'),
				'add_new_item' => isset($taxonomy->labels->add_new_item) ? esc_attr($taxonomy->labels->add_new_item) : __('Add New Category', 'updraftcentral')
			),
			'can_edit_terms' => (bool) $options['capabilities']['can_edit_terms'],
			'popular_terms_checklist' => $popular_terms_checklist,
			'terms_checklist' => $terms_checklist,
			'parent_dropdown' => $parent_dropdown
		);
	}

	/**
	 * Gathers post tags metabox information to be rendered
	 * in the client later on using the Handlerbarsjs templating system
	 *
	 * @param WP_Post $post Post object
	 */
	public function post_tags_meta_box($post) {
		global $uc_tags_meta_box, $site_id;

		$site_meta = $this->get_site_meta_instance();
		$preloaded_data = $site_meta->get_site_meta($site_id, 'updraftcentral_editor_preloaded_data_'.$post->post_type, true);
		$options = $misc = array();

		if (!empty($preloaded_data)) {
			if (isset($preloaded_data['tags'])) {
				$options = $preloaded_data['tags']['misc'];
			}

			if (isset($preloaded_data['post_data'])) {
				$misc = $preloaded_data['post_data']['misc'];
			}
		}

		if (empty($options) || empty($misc)) return;

		$tag_cloud = $options['tag_cloud'];
		$taxonomy = json_decode($options['tax']);

		$terms_list = array();
		$terms_to_edit = '';
		if (!empty($misc['tags_list'])) {
			$terms_to_edit = str_replace(', ', ',', $misc['tags_list']);
			$terms_list = explode(',', $terms_to_edit);
		}

		// We're pulling and rendering the template in the client, so, we're
		// preparing the information for the template's consumption.
		$uc_tags_meta_box = array(
			'labels' => array(
				'add_or_remove_items' => isset($taxonomy->labels->add_or_remove_items) ? $taxonomy->labels->add_or_remove_items : __('Add or remove tags', 'updraftcentral'),
				'add_new_item' => isset($taxonomy->labels->add_new_item) ? $taxonomy->labels->add_new_item : __('Add New Tag', 'updraftcentral'),
				'separate_items_with_commas' => isset($taxonomy->labels->separate_items_with_commas) ? $taxonomy->labels->separate_items_with_commas : __('Separate tags with commas', 'updraftcentral'),
				'no_terms' => isset($taxonomy->labels->no_terms) ? $taxonomy->labels->no_terms : __('No tags', 'updraftcentral'),
				'choose_from_most_used' => isset($taxonomy->labels->choose_from_most_used) ? $taxonomy->labels->choose_from_most_used : __('Choose from the most used tags', 'updraftcentral')
			),
			'attributes' => array(
				'add' => __('Add', 'updraftcentral'),
			),
			'can_assign_terms' => (bool) $options['capabilities']['can_assign_terms'],
			'terms_to_edit' => $terms_to_edit,
			'terms_list' => $terms_list,
			'tag_cloud' => $tag_cloud
		);
	}
}

endif;