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

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

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