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;