PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / team-updraft / lib-central / central / modules / posts.php

posts.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.7.0, at vendor/team-updraft/lib-central/central/modules/posts.php

2,035 lines 66.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
4
5 /**
6 * Handles Posts Commands
7 */
8 class UpdraftCentral_Posts_Commands extends UpdraftCentral_Commands {
9
10 protected $switched = false;
11
12 protected $post_type = 'post';
13
14 /**
15 * Function that gets called before every action
16 *
17 * @param string $command a string that corresponds to UDC command to call a certain method for this class.
18 * @param array $data an array of data post or get fields
19 * @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id
20 *
21 * link to udrpc_action main function in class UpdraftCentral_Listener
22 */
23 public function _pre_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This function is called from listener.php and $extra_info is being sent.
24 // Here we assign the current blog_id to a variable $blog_id
25 $blog_id = get_current_blog_id();
26 if (!empty($data['site_id'])) $blog_id = $data['site_id'];
27
28 if (function_exists('switch_to_blog') && is_multisite() && $blog_id) {
29 $this->switched = switch_to_blog($blog_id);
30 }
31 }
32
33 /**
34 * Function that gets called after every action
35 *
36 * @param string $command a string that corresponds to UDC command to call a certain method for this class.
37 * @param array $data an array of data post or get fields
38 * @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id
39 *
40 * link to udrpc_action main function in class UpdraftCentral_Listener
41 */
42 public function _post_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
43 // Here, we're restoring to the current (default) blog before we switched
44 if ($this->switched) restore_current_blog();
45 }
46
47 /**
48 * Returns the keys and fields names that are associated to a particular module type
49 *
50 * @param string $type The type of the module that the current request is processing
51 *
52 * @return array
53 */
54 private function get_state_fields_by_type($type) {
55 $state_fields = array(
56 'post' => array(
57 'validation_fields' => array('publish_posts', 'edit_posts', 'delete_posts'),
58 'items_key' => 'posts',
59 'count_key' => 'posts_count',
60 'list_key' => 'posts',
61 'result_key' => 'get',
62 'error_key' => 'post_state_change_failed'
63 ),
64 'page' => array(
65 'validation_fields' => array('publish_pages', 'edit_pages', 'delete_pages'),
66 'items_key' => 'pages',
67 'count_key' => 'pages_count',
68 'list_key' => 'pages',
69 'result_key' => 'get',
70 'error_key' => 'page_state_change_failed'
71 )
72 );
73
74 if (!isset($state_fields[$type])) return array();
75 return $state_fields[$type];
76 }
77
78 /**
79 * Fetch and retrieves posts based from the submitted parameters
80 *
81 * @param array $params Containing all the needed information to filter the results of the current request
82 * @return array
83 */
84 public function get($params) {
85
86 $state_fields = $this->get_state_fields_by_type($this->post_type);
87 if (empty($state_fields)) return $this->_generic_error_response('unsupported_type_on_get_posts');
88
89 $error = $this->_validate_capabilities($state_fields['validation_fields']);
90 if (!empty($error)) return $error;
91
92 // check paged parameter; if empty set to defaults
93 $paged = !empty($params['paged']) ? (int) $params['paged'] : 1;
94 $numberposts = !empty($params['numberposts']) ? (int) $params['numberposts'] : 10;
95 $offset = ($paged - 1) * $numberposts;
96
97 $args = array(
98 'posts_per_page' => $numberposts,
99 'paged' => $paged,
100 'offset' => $offset,
101 'post_type' => $this->post_type,
102 'post_status' => 'publish,private,draft,pending,future',
103 );
104
105 if (!empty($params['keyword'])) {
106 $args['s'] = $params['keyword'];
107 }
108
109 if ('post' == $this->post_type) {
110 if (!empty($params['category'])) {
111 $args['cat'] = (int) $params['category'];
112 }
113 }
114
115 if (!empty($params['date'])) {
116 list($monthnum, $year) = explode(':', $params['date']);
117
118 $args['monthnum'] = $monthnum;
119 $args['year'] = $year;
120 }
121
122 if (!empty($params['status']) && 'all' !== $params['status']) {
123 $args['post_status'] = $params['status'];
124 }
125
126 $query = new WP_Query($args);
127 $result = $query->posts;
128
129 $count_posts = (int) $query->found_posts;
130 $page_count = 0;
131
132 if ($count_posts > 0) {
133 $page_count = absint($count_posts / $numberposts);
134 $remainder = absint($count_posts % $numberposts);
135 $page_count = ($remainder > 0) ? ++$page_count : $page_count;
136 }
137
138 $info = array(
139 'page' => $paged,
140 'pages' => $page_count,
141 'results' => $count_posts,
142 'items_from' => (($paged * $numberposts) - $numberposts) + 1,
143 'items_to' => ($paged == $page_count) ? $count_posts : $paged * $numberposts,
144 );
145
146 $posts = array();
147 if (!empty($result)) {
148 foreach ($result as $post) {
149 // Pulling any other relevant and additional information regarding
150 // the post before returning it in the response.
151 $postdata = $this->get_postdata($post, false);
152 if (!empty($postdata)) {
153 array_push($posts, $postdata);
154 }
155 }
156 }
157
158 $response = array(
159 $state_fields['items_key'] => $posts,
160 'options' => $this->get_options($this->post_type),
161 'info' => $info,
162 $state_fields['count_key'] => $this->get_post_status_counts($this->post_type)
163 );
164
165 // Load any additional information if preload parameter is set. Will only be
166 // requested on initial load of items in UpdraftCentral.
167 if (isset($params['preload']) && $params['preload']) {
168 $timeout = !empty($params['timeout']) ? $params['timeout'] : 30;
169 $response = array_merge($response, $this->get_preload_data($timeout, $this->post_type));
170 }
171
172 return $this->_response($response);
173 }
174
175 /**
176 * Extracts public properties from complex object and return a simple
177 * object (stdClass) that contains the public properties of the original object.
178 *
179 * @param object $obj Any type of complex objects that needs converting (e.g. WP_Taxonomy, WP_Term or WP_User)
180 * @return stdClass
181 */
182 protected function trim_object($obj) {
183 // To preserve the object's accessibility through its properties we recreate
184 // the object using the stdClass and fill it with the public properties
185 // that will be extracted from the original object ($obj).
186 $newObj = new stdClass();
187
188 if (is_object($obj)) {
189 // Making sure that we only extract those publicly accessible properties excluding
190 // the private, protected, static ones and methods.
191 $props = get_object_vars($obj);
192 if (!empty($props)) {
193 foreach ($props as $key => $value) {
194 $newObj->{$key} = $value;
195 }
196 }
197 }
198
199 return $newObj;
200 }
201
202 /**
203 * Retrieves information that will be preloaded in UC for quick and easy access
204 * when editing a certain page or post
205 *
206 * @param int $timeout The user-defined timeout from UpdraftCentral
207 * @param string $type The type of the module that the current request is processing
208 *
209 * @return array
210 */
211 protected function get_preload_data($timeout, $type = 'post') {
212 global $updraftcentral_host_plugin, $updraftcentral_main;
213
214 if (!function_exists('get_page_templates')) {
215 require_once(ABSPATH.'wp-admin/includes/theme.php');
216 }
217
218 $templates = ('post' == $type) ? get_page_templates(null, 'post') : get_page_templates();
219 if (!empty($templates)) {
220 $templates = array_flip($templates);
221 if (!isset($templates['default'])) {
222 $templates['default'] = $updraftcentral_host_plugin->retrieve_show_message('default_template');
223 }
224 }
225
226 // Preloading elements saves time and avoid unnecessary round trips to fetch
227 // these information individually.
228 $authors = $this->get_authors();
229 $parent_pages = $this->get_parent_pages();
230
231 $data = array(
232 'authors' => $authors['data']['authors'],
233 'parent_pages' => $parent_pages['data']['pages'],
234 'templates' => $templates,
235 'editor_styles' => $this->get_editor_styles($timeout),
236 'wp_version' => $updraftcentral_main->get_wordpress_version()
237 );
238
239 if ('post' == $type) {
240 $categories = $this->get_categories();
241 $tags = $this->get_tags();
242
243 $data['taxonomies'] = $this->get_taxonomies();
244 $data['categories'] = $categories['data'];
245 $data['tags'] = $tags['data'];
246 }
247
248 global $post;
249 $context = class_exists('WP_Block_Editor_Context') ? new WP_Block_Editor_Context(array('post' => $post)) : $post;
250
251 // Load block patterns from w.org.
252 if (function_exists('_load_remote_block_patterns')) _load_remote_block_patterns();
253 if (function_exists('_load_remote_featured_patterns')) _load_remote_featured_patterns();
254
255 $block_types = class_exists('WP_Block_Type_Registry') ? WP_Block_Type_Registry::get_instance()->get_all_registered() : array();
256 $block_patterns = class_exists('WP_Block_Patterns_Registry') ? WP_Block_Patterns_Registry::get_instance()->get_all_registered() : array();
257 $block_pattern_categories = class_exists('WP_Block_Pattern_Categories_Registry') ? WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered() : array();
258 $block_styles = class_exists('WP_Block_Styles_Registry') ? WP_Block_Styles_Registry::get_instance()->get_all_registered() : array();
259
260 $block_data = array(
261 'block_categories' => get_block_categories($context),
262 'block_definitions' => get_block_editor_server_block_settings(),
263 'block_types' => $block_types,
264 'block_patterns' => $block_patterns,
265 'block_pattern_categories' => $block_pattern_categories,
266 'block_styles' => $block_styles
267 );
268 $data = array_merge($data, $block_data);
269
270 return array(
271 'preloaded' => json_encode($data)
272 );
273 }
274
275 /**
276 * Extract content from the given css path
277 *
278 * @param string $style CSS file path
279 * @param int $timeout The user-defined timeout from UpdraftCentral
280 * @return array
281 */
282 protected function extract_css_content($style, $timeout) {
283
284 $content = '';
285 if (1 === preg_match('~^(https?:)?//~i', $style)) {
286 $response = wp_remote_get($style, array('timeout' => $timeout));
287 if (!is_wp_error($response)) {
288 $result = trim(wp_remote_retrieve_body($response));
289 if (!empty($result)) $content = $result;
290 }
291 } else {
292 // Editor styles that resides in "css/dist"
293 if (false !== ($pos = stripos($style, 'css/dist'))) {
294 $file = ABSPATH.WPINC.substr_replace($style, '/', 0, $pos);
295 } else {
296 // Styles that resides in "wp-content/themes" (coming from $editor_styles global var)
297 $file = get_theme_file_path($style);
298 }
299
300 $is_valid = (function_exists('is_file')) ? is_file($file) : file_exists($file);
301 if ($is_valid) {
302 $result = trim(file_get_contents($file));
303 if (!empty($result)) $content = $result;
304 }
305 }
306
307 return $this->extract_custom_fonts($this->filter_url($content));
308 }
309
310 /**
311 * Extract custom fonts defined within the css content. Basically,
312 * separating custom font (@font-face) rules from the Style/css content.
313 *
314 * @param string $content Style content
315 * @return array
316 */
317 protected function extract_custom_fonts($content) {
318 $fonts = array();
319 while ($start = strpos($content, '@font-face')) {
320 $end = strpos($content, '}', $start) + 1;
321 $length = $end - $start;
322
323 $font = substr($content, $start, $length);
324 $fonts[]= $this->update_font_src($font);
325
326 $content = str_replace($font, '', $content);
327 }
328
329 return array(
330 'content' => $content,
331 'fonts' => $fonts
332 );
333 }
334
335 /**
336 * Updates the font URL to point to the UpdraftCentral "load_font" action
337 *
338 * @param string $font Font-face definition/content
339 * @return string
340 */
341 protected function update_font_src($font) {
342 $start = strpos($font, 'src:') + 4;
343 $end = strpos($font, ';', $start);
344 $length = $end - $start;
345
346 $src = trim(substr($font, $start, $length));
347 $temp = explode(' ', $src);
348 preg_match('/^url\((.*)\)$/i', $temp[0], $matches);
349
350 $url = '';
351 if (!empty($matches)) {
352 $url = trim(trim($matches[1], "'"), '"');
353 if (strlen($url)) {
354 $font_url = 'CENTRAL_URL/?udcentral_action=load_font&font='.urlencode($url);
355 $font = str_replace($url, $font_url, $font);
356 }
357 }
358 return $font;
359 }
360
361 /**
362 * Convert URL entries contained in the CSS content to absolute URLs
363 *
364 * @param string $content The content of the CSS file
365 * @return string
366 */
367 protected function filter_url($content) {
368
369 // Replace with valid URL (absolute)
370 preg_match_all('~url\((.+?)\)~i', $content, $all_matches);
371 if (!empty($all_matches) && isset($all_matches[1])) {
372 $urls = array_unique($all_matches[1]);
373 foreach ($urls as $url) {
374 $url = str_replace('"', '', $url);
375 if (false !== strpos($url, 'data:')) continue;
376
377 if (1 !== preg_match('~^(https?:)?//~i', $url)) {
378 if (1 === preg_match('~(plugins|themes)~i', $url, $matches)) {
379 if (false !== ($pos = stripos($url, $matches[1]))) {
380 if (!function_exists('content_url')) {
381 require_once ABSPATH.WPINC.'/link-template.php';
382 }
383
384 $absolute_url = rtrim(content_url(), '/').substr_replace($url, '/', 0, $pos);
385 $content = str_replace($url, $absolute_url, $content);
386 }
387 } else {
388 $path = preg_replace('~(\.+\/)~', '', $url);
389 $dirpath = trailingslashit(get_stylesheet_directory());
390 if (!file_exists($dirpath.$url)) $path = $this->resolve_path($path);
391
392 $absolute_url = (!empty($path)) ? trailingslashit(get_stylesheet_directory_uri()).ltrim($path, '/') : '';
393 $content = str_replace($url, $absolute_url, $content);
394 }
395 }
396 }
397 }
398
399 return $content;
400 }
401
402 /**
403 * Resolve URL to its actual absolute path
404 *
405 * @param string $path Some relative path to check
406 * @return string
407 */
408 protected function resolve_path($path) {
409 $dir = trailingslashit(get_stylesheet_directory());
410 // Some relative paths declared within the css file (e.g. only has '../fonts/etc/', called deep down from a subfolder) where parent
411 // subfolder is not articulated needs to be resolve further to get its actual absolute path. Using glob will pinpoint its actual location
412 // rather than iterating through a series of sublevels just to find the actual file.
413 $result = str_replace($dir, '', glob($dir.'{,*/}{'.$path.'}', GLOB_BRACE));
414
415 if (!empty($result)) return $result[0];
416 return false;
417 }
418
419 /**
420 * Retrieves block editor assets for iframe.
421 *
422 * @return string
423 */
424 protected function get_iframed_editor_assets() {
425 $script_handles = array();
426 $style_handles = array(
427 'wp-block-editor',
428 'wp-block-library',
429 'wp-block-library-theme',
430 'wp-edit-blocks',
431 );
432
433 if (class_exists('WP_Block_Type_Registry')) {
434 $block_registry = WP_Block_Type_Registry::get_instance();
435 foreach ($block_registry->get_all_registered() as $block_type) {
436 if (!empty($block_type->style)) {
437 if (is_array($block_type->style)) {
438 $style_handles = array_merge($style_handles, $block_type->style);
439 } else {
440 $style_handles[] = $block_type->style;
441 }
442 }
443
444 if (!empty($block_type->editor_style)) {
445 if (is_array($block_type->editor_style)) {
446 $style_handles = array_merge($style_handles, $block_type->editor_style);
447 } else {
448 $style_handles[] = $block_type->editor_style;
449 }
450 }
451
452 if (!empty($block_type->script)) {
453 if (is_array($block_type->script)) {
454 $script_handles = array_merge($script_handles, $block_type->script);
455 } else {
456 $script_handles[] = $block_type->script;
457 }
458 }
459 }
460 }
461
462 $style_handles = array_unique($style_handles);
463 $done = wp_styles()->done;
464
465 ob_start();
466 // We do not need reset styles for the iframed editor.
467 wp_styles()->done = array('wp-reset-editor-styles');
468 wp_styles()->do_items($style_handles);
469 wp_styles()->done = $done;
470 $styles = ob_get_clean();
471
472 $script_handles = array_unique($script_handles);
473 $done = wp_scripts()->done;
474
475 ob_start();
476 wp_scripts()->done = array();
477 wp_scripts()->do_items($script_handles);
478 wp_scripts()->done = $done;
479 $scripts = ob_get_clean();
480
481 return wp_json_encode(array(
482 'styles' => $styles,
483 'scripts' => $scripts,
484 ));
485 }
486
487 /**
488 * Retrieve the editor styles/assets to be use by UpdraftCentral when editing a post
489 *
490 * @param int $timeout The user-defined timeout from UpdraftCentral
491 * @return array()
492 */
493 protected function get_editor_styles($timeout) {
494 global $editor_styles, $wp_styles;
495 $editing_styles = $loaded = array();
496 $fonts = '';
497
498 $required = array('css/dist/editor/style.css', 'css/dist/block-library/style.css', 'css/dist/block-library/theme.css');
499 foreach ($required as $style) {
500 $result = $this->extract_css_content($style, $timeout);
501 if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
502 $editing_styles[] = array('css' => $result['content'], 'inline' => '');
503 };
504
505 do_action('enqueue_block_assets');
506 do_action('enqueue_block_editor_assets');
507 do_action('wp_enqueue_scripts');
508
509 // Checking for editor styles support since styles may vary from theme to theme
510 if ($editor_styles) {
511 foreach ($editor_styles as $style) {
512 if (false !== array_search($style, $loaded)) continue;
513
514 $result = $this->extract_css_content($style, $timeout);
515 if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
516 $editing_styles[] = array('css' => $result['content'], 'inline' => '');
517 $loaded[] = $style;
518 }
519 }
520
521 if ($wp_styles) {
522 foreach ($wp_styles->queue as $handle) {
523 $style = $wp_styles->registered[$handle]->src;
524 if (false !== array_search($style, $loaded)) continue;
525
526 $result = $this->extract_css_content($style, $timeout);
527 if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
528
529 $inline_style = $wp_styles->print_inline_style($handle, false);
530 if ($inline_style) {
531 $inline_result = $this->extract_custom_fonts($inline_style);
532 if (!empty($inline_result['fonts'])) $fonts .= implode('', $inline_result['fonts']);
533 }
534
535 $editing_styles[] = array(
536 'css' => $result['content'],
537 'inline' => (!$inline_style) ? '' : $inline_result['content']
538 );
539 $loaded[] = $style;
540 }
541 }
542
543 // Introduced in 5.9.0
544 if (function_exists('wp_get_global_stylesheet')) {
545 $result = $this->extract_custom_fonts(wp_get_global_stylesheet());
546 if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
547 $editing_styles[] = array('css' => $result['content'], 'inline' => '');
548 }
549
550 // Introduced in 5.8.0
551 if (function_exists('get_block_editor_settings')) {
552 $block_editor_context = new WP_Block_Editor_Context();
553 $settings = get_block_editor_settings(array(), $block_editor_context);
554
555 // Don't render but instead attached to the editor before load.
556 // We let the editor render these kind of styles as they need to be prefixed
557 // by the editor based on the current context.
558 //
559 // N.B. Leave the 'css' property empty. It is used for downward compatibility.
560 $editing_styles[] = array('editor_css' => $settings['styles'], 'inline' => '', 'css' => '');
561
562 // Get editor assets (e.g. styles) for iframe, mostly used for previewing blocks and patterns
563 $editing_styles[] = array('editor_assets' => $this->get_iframed_editor_assets(), 'inline' => '', 'css' => '');
564 }
565
566 $result = $this->extract_css_content('/style.css', $timeout);
567 if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
568
569 $editing_styles[] = array('css' => $result['content'], 'inline' => '');
570 if (strlen($fonts)) {
571 $editing_styles[] = array('font_css' => $fonts, 'inline' => '', 'css' => '');
572 }
573
574 // These styles are used if the "no theme styles" options is triggered or on
575 // themes without their own editor styles.
576 $default_editor_styles_file = ABSPATH.WPINC.'/css/dist/block-editor/default-editor-styles.css';
577 if (file_exists($default_editor_styles_file)) {
578 $editing_styles[] = array('default_editor_css' => file_get_contents($default_editor_styles_file), 'inline' => '', 'css' => '');
579 }
580
581 // Extract fonts from theme.json if the current theme supports it
582 $resolver = ABSPATH.WPINC.'/class-wp-theme-json-resolver.php';
583 if (!class_exists('WP_Theme_JSON_Resolver') && file_exists($resolver)) {
584 require_once($resolver);
585 }
586
587 $theme_has_support = false;
588 if (function_exists('wp_theme_has_theme_json')) {
589 $theme_has_support = wp_theme_has_theme_json();
590 } else {
591 if (class_exists('WP_Theme_JSON_Resolver')) {
592 $theme_has_support = WP_Theme_JSON_Resolver::theme_has_support();
593 }
594 }
595
596 if (class_exists('WP_Theme_JSON_Resolver') && $theme_has_support) {
597 $theme_json = ABSPATH.WPINC.'/class-wp-theme-json.php';
598 if (!class_exists('WP_Theme_JSON') && file_exists($theme_json)) require_once($theme_json);
599
600 $theme_json_instance = WP_Theme_JSON_Resolver::get_theme_data();
601 if ($theme_json_instance) {
602 $settings = $theme_json_instance->get_settings();
603 $theme_fonts = '';
604
605 if (isset($settings['typography']) && isset($settings['typography']['fontFamilies'])) {
606 $font_families = $settings['typography']['fontFamilies'];
607 if (isset($font_families['theme'])) {
608 foreach ($font_families['theme'] as $theme) {
609 if (isset($theme['fontFace'])) {
610 foreach ($theme['fontFace'] as $font) {
611 $theme_fonts .= '@font-face {';
612 $keys = array_keys($font);
613
614 foreach ($keys as $key) {
615 if (false !== stripos($key, 'font')) {
616 $prop = 'font-'.strtolower(str_replace('font', '', $key));
617 $theme_fonts .= $prop.': '.$font[$key].';';
618 } elseif (false !== stripos($key, 'src')) {
619 foreach ($font['src'] as $src_file) {
620 $url = trailingslashit(get_stylesheet_directory_uri()).str_replace('file:./', '', $src_file);
621 $theme_fonts .= 'src: url(CENTRAL_URL/?udcentral_action=load_font&font='.urlencode($url).');';
622 }
623 }
624 }
625 $theme_fonts .= '}';
626 }
627 }
628 }
629 }
630 }
631 $editing_styles[] = array('theme_json_fonts' => $theme_fonts, 'inline' => '', 'css' => '');
632 }
633 }
634
635 return $editing_styles;
636 }
637
638 /**
639 * Retrieves the total number of items found under each post statuses
640 *
641 * @param string $type The type of the module that the current request is processing
642 *
643 * @return array
644 */
645 protected function get_post_status_counts($type = 'post') {
646 $posts = wp_count_posts($type);
647
648 $publish = (int) $posts->publish;
649 $private = (int) $posts->private;
650 $draft = (int) $posts->draft;
651 $pending = (int) $posts->pending;
652 $future = (int) $posts->future;
653 $trash = (int) $posts->trash;
654
655 // We exclude "trash" from the overall total as WP doesn't actually
656 // consider or include it in the total count.
657 $all = $publish + $private + $draft + $pending + $future;
658
659 return array(
660 'all' => $all,
661 'publish' => $publish,
662 'private' => $private,
663 'draft' => $draft,
664 'pending' => $pending,
665 'future' => $future,
666 'trash' => $trash,
667 );
668 }
669
670 /**
671 * Retrieves a collection of formatted dates found for the given post statuses.
672 * It will be used as options for the date filter when managing the posts in UpdraftCentral.
673 *
674 * @param string $type The type of the module that the current request is processing
675 *
676 * @return array
677 */
678 protected function get_date_options($type = 'post') {
679 global $wpdb;
680
681 $date_options = $wpdb->get_col("SELECT DATE_FORMAT(`post_date`, '%M %Y') as `formatted_post_date` FROM {$wpdb->posts} WHERE `post_type` = '{$type}' AND `post_status` IN ('publish', 'private', 'draft', 'pending', 'future') GROUP BY `formatted_post_date` ORDER BY `post_date` DESC");
682
683 return $date_options;
684 }
685
686 /**
687 * Make sure that we have the required fields to use in UpdraftCentral for
688 * displaying the categories and tags sections. Add if missing.
689 *
690 * @param object $item Taxonomy item to check
691 * @return object
692 */
693 protected function map_tax($item) {
694 $taxs = array('category' => 'categories', 'post_tag' => 'tags');
695 if (array_key_exists($item->name, $taxs)) {
696 if (!isset($item->show_in_rest)) $item->show_in_rest = true;
697 if (!isset($item->rest_base)) $item->rest_base = $taxs[$item->name];
698 }
699
700 return $item;
701 }
702
703 /**
704 * Fetch and retrieves available taxonomies for this site and some capabilities specific
705 * to tags and categories when managing them.
706 *
707 * @return array
708 */
709 protected function get_taxonomies() {
710 $taxonomies = get_taxonomies(array(), 'objects');
711 $taxonomies = array_map(array($this, 'map_tax'), $taxonomies);
712
713 $response = array(
714 'taxonomies' => $taxonomies,
715 'current_user_cap' => array(
716 'manage_categories' => current_user_can('manage_categories'),
717 'edit_categories' => current_user_can('edit_categories'),
718 'delete_categories' => current_user_can('delete_categories'),
719 'assign_categories' => current_user_can('assign_categories'),
720 'manage_post_tags' => current_user_can('manage_post_tags'),
721 'edit_post_tags' => current_user_can('edit_post_tags'),
722 'delete_post_tags' => current_user_can('delete_post_tags'),
723 'assign_post_tags' => current_user_can('assign_post_tags'),
724 )
725 );
726
727 return $response;
728 }
729
730 /**
731 * Fetch and retrieves categories based from the submitted parameters
732 *
733 * @param array $query Containing all the needed information to filter the results of the current request
734 * @return array
735 */
736 public function get_categories($query = array()) {
737 $page = !empty($query['page']) ? (int) $query['page'] : 1;
738 $items_per_page = !empty($query['per_page']) ? (int) $query['per_page'] : 100;
739 $offset = ($page - 1) * $items_per_page;
740 $order = !empty($query['order']) ? $query['order'] : 'asc';
741 $orderby = !empty($query['orderby']) ? $query['orderby'] : 'name';
742
743 $args = array(
744 'hide_empty' => false,
745 'orderby' => $orderby,
746 'order' => $order,
747 'number' => $items_per_page,
748 'offset' => $offset
749 );
750
751 $categories = get_categories($args);
752 $category_options = array();
753
754 if (!empty($categories)) {
755 foreach ($categories as $key => $term) {
756 $parent_term = get_term((int) $term->parent, $term->taxonomy);
757 if (!is_wp_error($parent_term) && !is_null($parent_term)) {
758 $parent_term = json_encode($this->trim_object($parent_term));
759 } else {
760 $parent_term = '';
761 }
762
763 $category_options[] = array(
764 'id' => $term->term_id,
765 'name' => $term->name,
766 'parent' => $term->parent
767 );
768
769 $categories[$key] = array(
770 'term' => json_encode($this->trim_object($term)),
771 'misc' => array(
772 'link' => get_term_link($term),
773 'parent_term' => $parent_term,
774 'taxonomy' => $term->taxonomy
775 )
776 );
777 }
778 }
779
780 $categorytax = get_taxonomy('category');
781 $parent_dropdown_args = array(
782 'taxonomy' => 'category',
783 'hide_empty' => 0,
784 'name' => 'newcategory_parent',
785 'orderby' => 'name',
786 'hierarchical' => 1,
787 'show_option_none' => '&mdash; '.$categorytax->labels->parent_item.' &mdash;',
788 'echo' => false
789 );
790
791 $parent_dropdown_args = apply_filters('post_edit_category_parent_dropdown_args', $parent_dropdown_args);
792 $parent_dropdown = wp_dropdown_categories($parent_dropdown_args);
793
794 if (!function_exists('wp_popular_terms_checklist')) {
795 require_once ABSPATH . 'wp-admin/includes/template.php';
796 }
797
798 ob_start();
799 wp_popular_terms_checklist('category');
800 $popular_terms_checklist = ob_get_contents();
801 ob_end_clean();
802
803 return $this->_response(array(
804 'terms' => $categories,
805 'misc' => array(
806 'formatted' => $category_options,
807 'raw' => $categories,
808 'tax' => json_encode($this->trim_object($categorytax)),
809 'popular' => $popular_terms_checklist,
810 'parent_dropdown' => $parent_dropdown,
811 'capabilities' => array(
812 'can_edit_terms' => current_user_can($categorytax->cap->edit_terms)
813 )
814 )
815 ));
816 }
817
818 /**
819 * Fetch and retrieves tags based from the submitted parameters
820 *
821 * @param array $query Containing all the needed information to filter the results of the current request
822 * @return array
823 */
824 public function get_tags($query = array()) {
825 $page = !empty($query['page']) ? (int) $query['page'] : 1;
826 $items_per_page = !empty($query['per_page']) ? (int) $query['per_page'] : 100;
827 $offset = ($page - 1) * $items_per_page;
828 $order = !empty($query['order']) ? $query['order'] : 'desc';
829 $orderby = !empty($query['orderby']) ? $query['orderby'] : 'count';
830
831 $args = array(
832 'hide_empty' => false,
833 'orderby' => $orderby,
834 'order' => $order,
835 'number' => $items_per_page,
836 'offset' => $offset
837 );
838
839 $tags = get_tags($args);
840 $tag_options = array();
841 $tag_cloud = '';
842
843 if (!empty($tags)) {
844 $tags_for_cloud = array();
845 foreach ($tags as $key => $term) {
846 if (!isset($term->link)) $term->link = get_tag_link($term->term_id);
847 array_push($tags_for_cloud, $term);
848
849 $parent_term = get_term((int) $term->parent, $term->taxonomy);
850 if (!is_wp_error($parent_term) && !is_null($parent_term)) {
851 $parent_term = json_encode($this->trim_object($parent_term));
852 } else {
853 $parent_term = '';
854 }
855
856 $tag_options[] = array(
857 'id' => $term->term_id,
858 'name' => $term->name,
859 );
860
861 $tags[$key] = array(
862 'term' => json_encode($this->trim_object($term)),
863 'misc' => array(
864 'link' => get_term_link($term),
865 'parent_term' => $parent_term,
866 'taxonomy' => $term->taxonomy
867 )
868 );
869 }
870
871 add_filter('tag_cloud_sort', array($this, 'sort_tag_cloud'), 9, 2);
872
873 if (!function_exists('wp_generate_tag_cloud')) {
874 require_once ABSPATH.WPINC.'/category-template.php';
875 }
876
877 $tag_cloud = wp_generate_tag_cloud($tags_for_cloud, array(
878 'smallest' => 10,
879 'largest' => 22,
880 'unit' => 'pt',
881 'number' => 10,
882 'format' => 'flat',
883 'separator' => " ",
884 'orderby' => 'count',
885 'order' => 'DESC',
886 'show_count' => 1,
887 'echo' => false
888 ));
889 }
890
891 $tagtax = get_taxonomy('post_tag');
892 return $this->_response(array(
893 'terms' => $tags,
894 'misc' => array(
895 'formatted' => $tag_options,
896 'raw' => $tags,
897 'tax' => json_encode($this->trim_object($tagtax)),
898 'tag_cloud' => $tag_cloud,
899 'capabilities' => array(
900 'can_assign_terms' => current_user_can($tagtax->cap->assign_terms)
901 )
902 )
903 ));
904 }
905
906 /**
907 * Sorts the tag items that are to be shown within the tag cloud
908 *
909 * @param array $tags The array to be sorted. Contains the tag items
910 * @param array $args Additional parameters needed for the sorting process
911 * @return array
912 */
913 public function sort_tag_cloud($tags, $args) {
914 uasort($tags, array($this, '_wp_object_count_sort_cb'));
915 if ('DESC' === $args['order']) {
916 $tags = array_reverse($tags, true);
917 }
918
919 return $tags;
920 }
921
922 /**
923 * Serves as a callback for comparing objects based on count. Copied from WordPress 5.7
924 * core (wp-includes/category-template.php) and tweaked to return integer instead of boolean
925 * because returning boolean using uasort is now DEPRECATED in PHP 8.
926 *
927 * Used with `uasort()`.
928 *
929 * @since 3.1.0
930 * @access private
931 *
932 * @param object $a The first object to compare.
933 * @param object $b The second object to compare.
934 * @return bool Whether the count value for `$a` is greater than the count value for `$b`.
935 */
936 public function _wp_object_count_sort_cb($a, $b) {
937 if ($a->count == $b->count) {
938 return 0;
939 }
940 return ( $a->count > $b->count ) ? 1 : -1;
941 }
942
943 /**
944 * Fetch all available taxonomies and terms information for the given post object
945 *
946 * @param array $post The "Post" object to use when retrieving the information
947 * @return array
948 */
949 protected function get_taxonomies_terms($post) {
950 $taxonomies = get_object_taxonomies($post->post_type, 'objects');
951 $taxonomies = array_map(array($this, 'map_tax'), $taxonomies);
952
953 $taxonomy_names = array();
954 $taxonomy_terms = array();
955 $taxonomy_caps = array();
956
957 foreach ($taxonomies as $taxonomy) {
958 $terms = get_the_terms($post->ID, $taxonomy->name);
959 $terms = !is_array($terms) ? (array) $terms : $terms;
960
961 $taxonomy_terms[$taxonomy->name] = $terms;
962 $taxonomy_caps[$taxonomy->name] = array(
963 'hierarchical' => is_taxonomy_hierarchical($taxonomy->name),
964 'edit_terms' => current_user_can($taxonomy->cap->edit_terms),
965 'assign_terms' => current_user_can($taxonomy->cap->assign_terms),
966 );
967 array_push($taxonomy_names, $taxonomy->name);
968 }
969
970 return array(
971 'objects' => $taxonomies,
972 'names' => $taxonomy_names,
973 'terms' => $taxonomy_terms,
974 'caps' => $taxonomy_caps,
975 );
976 }
977
978 /**
979 * Take over the current editing of the post
980 *
981 * @param array $params An array of data that serves as parameters for the given request
982 * @return array
983 */
984 public function take_over($params) {
985
986 $error = $this->_validate_capabilities(array('edit_'.$this->post_type.'s'));
987 if (!empty($error)) return $error;
988
989 $result = array('lock_acquired' => false);
990 if (!empty($params['post_id'])) {
991 if (!function_exists('wp_set_post_lock')) {
992 require_once ABSPATH.'wp-admin/includes/post.php';
993 }
994 $lock = wp_set_post_lock($params['post_id']);
995
996 if (!empty($lock)) {
997 $result = array(
998 'lock_acquired' => true,
999 'details' => $lock
1000 );
1001 }
1002 }
1003
1004 return $this->_response($result);
1005 }
1006
1007 /**
1008 * Retrieves the underlying data for the given post. Some extra information are
1009 * passed along that will be consumed by the editor in UpdraftCentral
1010 *
1011 * @param int|object $param Post object or a post ID
1012 * @param boolean $encode True to encode the post object, false otherwise
1013 * @return array
1014 */
1015 public function get_postdata($param, $encode = true) {
1016 $response = array();
1017
1018 if (is_object($param) && isset($param->ID)) {
1019 $post = $param;
1020 } elseif (is_numeric($param)) {
1021 $post = get_post($param);
1022 }
1023
1024 if ($post) {
1025 $post_type_obj = get_post_type_object($post->post_type);
1026
1027 $is_post_type_viewable = false;
1028 if (!empty($post_type_obj)) {
1029 $is_post_type_viewable = $post_type_obj->publicly_queryable || ($post_type_obj->_builtin && $post_type_obj->public);
1030 }
1031
1032 if (!function_exists('get_sample_permalink')) {
1033 require_once ABSPATH.'wp-admin/includes/post.php';
1034 }
1035
1036 // Validate template exists on the current theme, otherwise,
1037 // reset the template to default.
1038 $template = get_page_template_slug($post->ID);
1039 if (!empty($template)) {
1040 $page_templates = wp_get_theme()->get_page_templates($post);
1041 if ('default' != $template && !isset($page_templates[$template])) {
1042 update_post_meta($post->ID, '_wp_page_template', 'default');
1043 }
1044 }
1045
1046 $published_date = array(
1047 'jj' => date('d', strtotime($post->post_date)),
1048 'mm' => date('m', strtotime($post->post_date)),
1049 'aa' => date('Y', strtotime($post->post_date)),
1050 'hh' => date('H', strtotime($post->post_date)),
1051 'mn' => date('i', strtotime($post->post_date)),
1052 'ss' => date('s', strtotime($post->post_date))
1053 );
1054
1055 $sample_permalink = get_sample_permalink($post->ID, $post->post_title, '');
1056 $permalink = get_permalink($post->ID);
1057 $slug = $post->post_name;
1058
1059 if (!empty($sample_permalink) && !empty($slug)) {
1060 if (isset($sample_permalink[0])) {
1061 if (false !== stripos($sample_permalink[0], '%pagename%/') || false !== stripos($sample_permalink[0], '%postname%/')) {
1062 $token = (false !== stripos($sample_permalink[0], '%pagename%/')) ? '%pagename%/' : '%postname%/';
1063 $permalink = str_replace($token, '', $sample_permalink[0]).$slug;
1064 }
1065 }
1066 }
1067
1068 $editor = null;
1069 $editor_id = wp_check_post_lock($post->ID);
1070 if ($editor_id) {
1071 $editor = get_userdata($editor_id);
1072 if (!$editor) {
1073 // The user with lock does not exist. This can happen if you created a backup or clone
1074 // where you excluded the users table during the process and you restore this backup to
1075 // a different site or the user was deleted or removed more recently. Thus, we will
1076 // release the lock so that other users with the right permission can edit the post.
1077 delete_post_meta($post->ID, '_edit_lock');
1078 }
1079 }
1080
1081 $response = array(
1082 'post' => $encode ? json_encode($post) : $post,
1083 'misc' => array(
1084 'guid_rendered' => apply_filters('get_the_guid', $post->guid, $post->ID),
1085 'link' => $permalink,
1086 'slug' => $slug,
1087 'site_url' => site_url('/'),
1088 'title_rendered' => get_the_title($post->ID),
1089 'content_rendered' => apply_filters('the_content', $post->post_content),
1090 'excerpt' => $post->post_excerpt,
1091 'featured_media' => 0,
1092 'sticky' => is_sticky($post->ID),
1093 'template' => get_page_template_slug($post->ID),
1094 'permalink_template' => get_permalink($post->ID, true),
1095 'author_name' => get_the_author_meta('display_name', $post->post_author),
1096 'publish_month_year' => date('F Y', strtotime($post->post_date)),
1097 'publish_month_year_date' => date('d F Y', strtotime($post->post_date)),
1098 'post_status_object' => get_post_status_object(get_post_status($post->ID)),
1099 'published_date' => $published_date,
1100 'format' => get_post_format($post->ID),
1101 'post_type_name' => $post_type_obj->name,
1102 'post_type_viewable' => $is_post_type_viewable,
1103 'post_type_public' => $post_type_obj->public,
1104 'post_type_hierarchical' => $post_type_obj->hierarchical,
1105 'sample_permalink' => get_sample_permalink($post->ID, $post->post_title, ''),
1106 'post_password_required' => post_password_required($post),
1107 'post_type_supports_authors' => post_type_supports($post->post_type, 'author'),
1108 'post_type_supports_comments' => post_type_supports($post->post_type, 'comments'),
1109 'post_type_supports_revisions' => post_type_supports($post->post_type, 'revisions'),
1110 'post_revisions' => array(), // N.B. We're not going to allow revisions editing for now
1111 'post_thumbnail_id' => get_post_thumbnail_id($post->ID),
1112 'can_publish_posts' => current_user_can($post_type_obj->cap->publish_posts),
1113 'can_edit_others_posts' => current_user_can($post_type_obj->cap->edit_others_posts),
1114 'can_unfiltered_html' => current_user_can('unfiltered_html'),
1115 'is_edited' => $editor ? 1 : 0,
1116 'editor_id' => $editor_id,
1117 'editor' => $editor,
1118 'edited_by_id' => $editor ? $editor->ID : 0,
1119 'edited_by_display_name' => $editor ? $editor->display_name : '',
1120 )
1121 );
1122
1123 if ('post' == $post->post_type) {
1124 $taxonomies = $this->get_taxonomies_terms($post);
1125 $response['misc']['taxonomy_objects'] = $taxonomies['objects'];
1126 $response['misc']['taxonomy_names'] = $taxonomies['names'];
1127 $response['misc']['taxonomy_terms'] = $taxonomies['terms'];
1128 $response['misc']['taxonomy_caps'] = $taxonomies['caps'];
1129
1130 if (!function_exists('wp_popular_terms_checklist') || !function_exists('get_terms_to_edit')) {
1131 require_once ABSPATH . 'wp-admin/includes/template.php';
1132 require_once ABSPATH . 'wp-admin/includes/taxonomy.php';
1133 }
1134
1135 if (!function_exists('wp_get_post_categories')) {
1136 require_once(ABSPATH.WPINC.'/post.php');
1137 }
1138
1139 $categories = wp_get_post_categories($post->ID, array('fields' => 'ids'));
1140 if (!is_wp_error($categories)) {
1141 $response['misc']['categories'] = empty($categories) ? array() : $categories;
1142 $terms_to_edit = get_terms_to_edit($post->ID, 'category');
1143 if (!empty($terms_to_edit)) {
1144 $response['misc']['categories_list'] = str_replace(',', ', ', $terms_to_edit);
1145 }
1146
1147 $popular_ids = wp_popular_terms_checklist('category', 0, 10, false);
1148 // On WP 3.4 the "wp_terms_checklist" doesn't have an "echo" parameter and will automatically
1149 // display the rendered checklist. Therefore, we're going to pull the terms so that all
1150 // versions starting from WP 3.4 will pull the content instead of displaying them.
1151
1152 ob_start();
1153 // In this call we'll have to set the "echo" parameter to true so that later version of WP
1154 // will be able to catch and process it.
1155 wp_terms_checklist($post->ID, array('taxonomy' => 'category', 'popular_cats' => $popular_ids, 'echo' => true));
1156 $popular_checklist = ob_get_contents();
1157 ob_end_clean();
1158
1159 $response['misc']['categories_checklist'] = $popular_checklist;
1160
1161 ob_start();
1162 wp_terms_checklist($post->ID, array('taxonomy' => 'category', 'checked_ontop' => 0, 'echo' => true));
1163 $quickedit_checklist = ob_get_contents();
1164 ob_end_clean();
1165
1166 $response['misc']['categories_quickedit_checklist'] = $quickedit_checklist;
1167 }
1168
1169 $tags = wp_get_post_tags($post->ID, array('fields' => 'ids'));
1170 if (!is_wp_error($tags)) {
1171 $response['misc']['tags'] = empty($tags) ? array() : $tags;
1172 $terms_to_edit = get_terms_to_edit($post->ID, 'post_tag');
1173 if (!empty($terms_to_edit)) {
1174 $response['misc']['tags_list'] = str_replace(',', ', ', $terms_to_edit);
1175 }
1176 }
1177 }
1178
1179 // Naturally, the "featured_media" will suffice when loading the image (media) in
1180 // UpdraftCentral since the value in this field is the actual image id of the featured
1181 // media used in UC. If we currently don't have an entry in the "featured_media_updraftcentral" meta,
1182 // then UC will need to download the featured media (image) for this current post/page
1183 // using the "featured_media_url" field (below) if not empty.
1184 $featured_media = get_post_meta($post->ID, 'featured_media_updraftcentral', true);
1185 if (!empty($featured_media)) {
1186 $response['misc']['featured_media'] = $featured_media;
1187 }
1188
1189 // Retrieve featured media if currently present for the given post/page.
1190 // If present, we pull the image (media) URL in case there's a need for
1191 // UpdraftCentral to download the image upon loading the editor (e.g. the featured_media id
1192 // above no longer exists).
1193 $media_id = (int) get_post_thumbnail_id($post->ID);
1194 if (!empty($media_id)) {
1195 $response['misc']['featured_media_url'] = wp_get_attachment_url($media_id);
1196 } else {
1197 // The post/page no longer has a "featured_media" or doesn't have one currently, therefore,
1198 // we're going to set the "featured_media" and "featured_media_url" fields to both empty to
1199 // to avoid any further actions (e.g. download media).
1200 $response['misc']['featured_media'] = 0;
1201 $response['misc']['featured_media_url'] = '';
1202 }
1203 }
1204
1205 return $response;
1206 }
1207
1208 /**
1209 * Changes the state/status of the submitted post(s)
1210 *
1211 * @param array $params An array of data that serves as parameters for the given request
1212 * @return array
1213 */
1214 public function set_state($params) {
1215
1216 $state_fields = $this->get_state_fields_by_type($this->post_type);
1217 if (empty($state_fields)) return $this->_generic_error_response('unsupported_type_on_set_state');
1218
1219 $error = $this->_validate_capabilities($state_fields['validation_fields']);
1220 if (!empty($error)) return $error;
1221
1222 $result = array();
1223 if (!empty($params['list'])) {
1224 $posts = array();
1225 foreach ($params['list'] as $id) {
1226 $post = $this->apply_state($id, $params['action'], $this->post_type);
1227 if (!empty($post)) {
1228 array_push($posts, $post);
1229 }
1230 }
1231
1232 if (!empty($posts)) {
1233 $result = array($state_fields['list_key'] => $posts);
1234 }
1235 } elseif (!empty($params['id'])) {
1236 $post = $this->apply_state($params['id'], $params['action'], $this->post_type);
1237 if (!empty($post)) $result = $post;
1238 }
1239
1240 if (!empty($result)) {
1241 $response = $this->get($params);
1242 if (!empty($response['response']) && 'rpcok' === $response['response']) {
1243 $result[$state_fields['result_key']] = $response['data'];
1244 }
1245
1246 return $this->_response($result);
1247 } else {
1248 return $this->_generic_error_response($state_fields['error_key'], array('action' => $params['action']));
1249 }
1250 }
1251
1252 /**
1253 * Creates new category
1254 *
1255 * @param array $params An array of data that serves as parameters for the given request
1256 * @param boolean $wrap_response Indicates whether to wrap the response based on local or UpdraftCentral calls. Default true.
1257 * @return array
1258 */
1259 public function add_category($params, $wrap_response = true) {
1260 $error = $this->_validate_capabilities(array('manage_categories'));
1261 if (!empty($error)) return $error;
1262
1263 $name = sanitize_text_field($params['name']);
1264 $args = array();
1265 if (!empty($params['parent'])) {
1266 $args['parent'] = $params['parent'];
1267 }
1268
1269 $result = wp_insert_term($name, 'category', $args);
1270 if (!is_wp_error($result)) {
1271 $term_id = $result['term_id'];
1272 $term = get_term($term_id, 'category');
1273
1274 $data = array();
1275 if (!is_wp_error($term)) {
1276 $data = array(
1277 'id' => $term->term_id,
1278 'count' => $term->count,
1279 'description' => $term->description,
1280 'link' => get_term_link($term->term_id, 'category'),
1281 'name' => $term->name,
1282 'slug' => $term->slug,
1283 'taxonomy' => $term->taxonomy,
1284 'parent' => $term->parent,
1285 'meta' => array()
1286 );
1287
1288 $categories = $this->get_categories();
1289 if ($wrap_response) $data['categories'] = json_encode($categories['data']);
1290 }
1291
1292 return $wrap_response ? $this->_response($data) : $data;
1293 } else {
1294 $error = array(
1295 'message' => $result->get_error_message()
1296 );
1297
1298 return $wrap_response ? $this->_generic_error_response('post_add_category_failed', $error) : $error;
1299 }
1300 }
1301
1302 /**
1303 * Assigns categories to a certain post object
1304 *
1305 * @param int $post_id The ID of the post object
1306 * @param array $category_ids A collection of category IDs to assign to the post object
1307 * @return void
1308 */
1309 protected function assign_category_to_post($post_id, $category_ids) {
1310 if (!empty($category_ids)) {
1311 // Making sure that we have the correct type to use and we
1312 // don't have any redundant IDs before saving.
1313 $category_ids = array_unique(array_map('intval', $category_ids));
1314
1315 // Attach (new) categories to post
1316 wp_set_object_terms($post_id, $category_ids, 'category');
1317 } else {
1318 wp_set_object_terms($post_id, get_option('default_category'), 'category');
1319 }
1320 }
1321
1322 /**
1323 * Creates new tag
1324 *
1325 * @param array $params An array of data that serves as parameters for the given request
1326 * @param boolean $wrap_response Indicates whether to wrap the response based on local or UpdraftCentral calls. Default true.
1327 * @return array
1328 */
1329 public function add_tag($params, $wrap_response = true) {
1330 // N.B. Since the "manage_post_tags" capability does not exist in WP 3.4. We'll use the "manage_categories" instead. Besides, the "manage_post_tags" along with the other tag-related capabilities in the latest versions are actually mapped to the "manage_categories" capability (refer to wp-includes/capabilities.php under the "map_meta_cap" function).
1331 $error = $this->_validate_capabilities(array('manage_categories'));
1332 if (!empty($error)) return $error;
1333
1334 $name = sanitize_text_field($params['name']);
1335 $result = wp_insert_term($name, 'post_tag');
1336 if (!is_wp_error($result)) {
1337 $term_id = $result['term_id'];
1338 $term = get_term($term_id, 'post_tag');
1339
1340 $data = array();
1341 if (!is_wp_error($term)) {
1342 $data = array(
1343 'id' => $term->term_id,
1344 'count' => $term->count,
1345 'description' => $term->description,
1346 'link' => get_term_link($term->term_id, 'post_tag'),
1347 'name' => $term->name,
1348 'slug' => $term->slug,
1349 'taxonomy' => $term->taxonomy,
1350 'meta' => array()
1351 );
1352
1353 $tags = $this->get_tags();
1354 if ($wrap_response) $data['tags'] = json_encode($tags['data']);
1355 }
1356
1357 return $wrap_response ? $this->_response($data) : $data;
1358 } else {
1359 $error = array(
1360 'message' => $result->get_error_message()
1361 );
1362
1363 return $wrap_response ? $this->_generic_error_response('post_add_tag_failed', $error) : $error;
1364 }
1365 }
1366
1367 /**
1368 * Assigns tags to a certain post object
1369 *
1370 * @param int $post_id The ID of the post object
1371 * @param array $tag_ids A collection of tag IDs to assign to the post object
1372 * @return void
1373 */
1374 protected function assign_tag_to_post($post_id, $tag_ids) {
1375 if (!empty($tag_ids)) {
1376 // Making sure that we have the correct type to use and we
1377 // don't have any redundant IDs before saving.
1378 $tag_ids = array_unique(array_map('intval', $tag_ids));
1379
1380 // Attach (new) tags to post
1381 wp_set_object_terms($post_id, $tag_ids, 'post_tag');
1382 } else {
1383 wp_set_object_terms($post_id, null, 'post_tag');
1384 }
1385 }
1386
1387 /**
1388 * Pre-validates data before running the save process
1389 *
1390 * @param WP_Post $post The post object to validate
1391 * @param array $params An array of data that serves as parameters for the given request
1392 *
1393 * @return array|void
1394 */
1395 private function pre_validation($post, $params) {
1396 if (empty($post) || empty($params)) return;
1397
1398 if (!empty($params['password'])) {
1399 if (!empty($params['sticky'])) {
1400 return $this->_generic_error_response('post_save_failed', array(
1401 'message' => __('A post can not be sticky and have a password.'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
1402 'args' => $params
1403 ));
1404 }
1405
1406 if (!isset($params['sticky']) && is_sticky($post->ID)) {
1407 return $this->_generic_error_response('post_save_failed', array(
1408 'message' => __('A sticky post can not be password protected.'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
1409 'args' => $params
1410 ));
1411 }
1412 }
1413
1414 if (!empty($params['sticky'])) {
1415 if (!isset($params['password']) && post_password_required($post->ID)) {
1416 return $this->_generic_error_response('post_save_failed', array(
1417 'message' => __('A password protected post can not be set to sticky.'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
1418 'args' => $params
1419 ));
1420 }
1421 }
1422 }
1423
1424 /**
1425 * Saves or updates post/page information based from the submitted data
1426 *
1427 * @param array $params An array of data that serves as parameters for the given request
1428 * @return array
1429 */
1430 public function save($params) {
1431 global $updraftcentral_host_plugin;
1432
1433 $validation_fields = array(
1434 'post' => array('publish_posts', 'edit_posts', 'delete_posts'),
1435 'page' => array('publish_pages', 'edit_pages', 'delete_pages')
1436 );
1437
1438 if (!isset($validation_fields[$this->post_type])) return $this->_generic_error_response('unsupported_type_on_save_post');
1439
1440 $error = $this->_validate_capabilities($validation_fields[$this->post_type]);
1441 if (!empty($error)) return $error;
1442
1443 if (!empty($params['id']) || !empty($params['new'])) {
1444 $args = array();
1445
1446 if (!empty($params['id'])) {
1447 $post = get_post($params['id']);
1448 if (!empty($post)) {
1449 $result = $this->pre_validation($post, $params);
1450 if (isset($result['response']) && 'rpcerror' == $result['response']) {
1451 return $result;
1452 }
1453 }
1454 }
1455
1456 // post_content
1457 if (!empty($params['content']))
1458 $args['post_content'] = $params['content'];
1459
1460 // post_excerpt
1461 if (!empty($params['excerpt']))
1462 $args['post_excerpt'] = $params['excerpt'];
1463
1464 // menu_order
1465 if (isset($params['order']))
1466 $args['menu_order'] = (int) $params['order'];
1467
1468 // post_parent
1469 if (isset($params['parent'])) {
1470 $args['post_parent'] = empty($params['parent']) ? 0 : $params['parent'];
1471 }
1472
1473 // post_name
1474 if (!empty($params['slug']))
1475 $args['post_name'] = $params['slug'];
1476
1477 // post_status
1478 if (!empty($params['status'])) {
1479 $args['post_status'] = $params['status'];
1480 }
1481
1482 // post_title
1483 if (!empty($params['title']))
1484 $args['post_title'] = $params['title'];
1485
1486 // post_author
1487 if (!empty($params['author']))
1488 $args['post_author'] = $params['author'];
1489
1490 // comment_status
1491 if (!empty($params['comment_status']))
1492 $args['comment_status'] = $params['comment_status'];
1493
1494 // ping_status
1495 if (!empty($params['ping_status']))
1496 $args['ping_status'] = $params['ping_status'];
1497
1498 // visibility
1499 if (!empty($params['visibility'])) {
1500 switch ($params['visibility']) {
1501 case 'public':
1502 $args['post_status'] = 'publish';
1503 $args['post_password'] = '';
1504 break;
1505 case 'password':
1506 $args['post_status'] = 'publish';
1507 $args['post_password'] = $params['password'];
1508 break;
1509 case 'private':
1510 $args['post_status'] = 'private';
1511 $args['post_password'] = '';
1512 break;
1513 default:
1514 break;
1515 }
1516 } else {
1517 if (!empty($params['password'])) {
1518 $args['post_status'] = 'publish';
1519 $args['post_password'] = $params['password'];
1520 } elseif (isset($params['password']) && '' == $params['password']) {
1521 $args['post_status'] = 'publish';
1522 $args['post_password'] = '';
1523 }
1524 }
1525
1526 // post/publish date
1527 if (!empty($params['date'])) {
1528 $datetime = strtotime($params['date']);
1529 $post_date = date('Y-m-d H:i:s', $datetime);
1530
1531 $args['post_date'] = $post_date;
1532 $args['post_date_gmt'] = gmdate('Y-m-d H:i:s', $datetime);
1533
1534 // We only change the status to "future" based from the submitted date if the post status
1535 // is not empty and equal to 'publish' and the date is for the coming future.
1536 if (!empty($params['status']) && 'publish' == $params['status']) {
1537 if (strtotime($post_date) > strtotime(date('Y-m-d H:i:s'))) $args['post_status'] = 'future';
1538 }
1539 }
1540
1541 // Make sure we have a slug/post_name generated before insert/update
1542 if (empty($params['slug']) && !empty($params['title'])) {
1543 $args['post_name'] = sanitize_title_with_dashes($params['title']);
1544 }
1545
1546 if (!empty($params['new'])) {
1547 $args['post_type'] = $this->post_type;
1548 $post_id = wp_insert_post($args, true);
1549 } else {
1550 $args['ID'] = $params['id'];
1551 $args['post_modified'] = date('Y-m-d H:i:s');
1552 $args['post_modified_gmt'] = gmdate('Y-m-d H:i:s');
1553
1554 $post_id = wp_update_post($args, true);
1555 }
1556
1557 // We have successfully created/updated a post at this point, thus, we'll continue
1558 // with implementing the other requested processes and return the result.
1559 if (!is_wp_error($post_id)) {
1560 // sticky post
1561 if (isset($params['sticky'])) {
1562 $sticky = (bool) $params['sticky'];
1563 if ($sticky) {
1564 stick_post($post_id);
1565 } else {
1566 if (is_sticky($post_id)) {
1567 unstick_post($post_id);
1568 }
1569 }
1570 }
1571
1572 // template
1573 if (!empty($params['template'])) {
1574 update_post_meta($post_id, '_wp_page_template', $params['template']);
1575 }
1576
1577 // featured_media
1578 if (isset($params['featured_media'])) {
1579 if (!empty($params['featured_media'])) {
1580 $featured_media = (int) $params['featured_media'];
1581 $attach_continue = true;
1582
1583 $url = wp_get_attachment_url($featured_media);
1584 if (!empty($url) && !empty($params['featured_media_url']) && $url == $params['featured_media_url']) {
1585 set_post_thumbnail($post_id, $featured_media);
1586 update_post_meta($post_id, 'featured_media_updraftcentral', $params['featured_media']);
1587 $attach_continue = false;
1588 }
1589
1590 if ($attach_continue) {
1591 $featured_media_data = !empty($params['featured_media_data']) ? $params['featured_media_data'] : null;
1592 $media_id = $this->attach_remote_image($params['featured_media_url'], $featured_media_data, $post_id);
1593 if (!empty($media_id)) {
1594 // If we have a successful attachment then add reference to UC's media id
1595 update_post_meta($post_id, 'featured_media_updraftcentral', $params['featured_media']);
1596 }
1597 }
1598 } else {
1599 // Remove featured image.
1600 delete_post_meta($post_id, '_thumbnail_id');
1601 delete_post_meta($post_id, 'featured_media_updraftcentral');
1602 }
1603 }
1604
1605 // categories
1606 $categories_updated = false;
1607 if (!empty($params['categories'])) {
1608 $term_ids = array();
1609 foreach ($params['categories'] as $value) {
1610 $category = sanitize_text_field($value);
1611 $parent = 0;
1612
1613 if (false !== strpos($category, ':')) {
1614 list($parent, $category) = explode(':', $category);
1615 $result = $this->add_category(array('name' => $category, 'parent' => $parent), false);
1616
1617 if (!empty($result)) {
1618 array_push($term_ids, $result['id']);
1619 }
1620 } else {
1621 $term = get_term_by('id', $category, 'category');
1622 if (!empty($term)) {
1623 $term_id = $term->term_id;
1624 array_push($term_ids, $term_id);
1625 }
1626 }
1627 }
1628
1629 $this->assign_category_to_post($post_id, $term_ids);
1630 $categories_updated = true;
1631 }
1632
1633 // tags
1634 $tags_updated = false;
1635 if (!empty($params['tags'])) {
1636 $term_ids = array();
1637 foreach ($params['tags'] as $value) {
1638 $tag = sanitize_text_field($value);
1639 $field = is_numeric($tag) ? 'id' : 'name';
1640
1641 $term = get_term_by($field, $tag, 'post_tag');
1642 if (!empty($term)) {
1643 $term_id = $term->term_id;
1644 array_push($term_ids, $term_id);
1645 } else {
1646 $result = $this->add_tag(array('name' => $tag), false);
1647 if (!empty($result)) {
1648 array_push($term_ids, $result['id']);
1649 }
1650 }
1651 }
1652
1653 $this->assign_tag_to_post($post_id, $term_ids);
1654 $tags_updated = true;
1655 }
1656
1657 // Pulling any other relevant and additional information regarding
1658 // the post before returning it in the response.
1659 $postdata = $this->get_postdata($post_id);
1660
1661 if (!empty($params['new'])) {
1662 $timeout = !empty($params['timeout']) ? $params['timeout'] : 30;
1663 $postdata = array_merge($postdata, $this->get_preload_data($timeout, $this->post_type));
1664 } else {
1665 if ($categories_updated || $tags_updated) {
1666 $categories = $this->get_categories();
1667 $tags = $this->get_tags();
1668
1669 $postdata['preloaded'] = json_encode(array(
1670 'categories' => $categories['data'],
1671 'tags' => $tags['data']
1672 ));
1673 }
1674 }
1675
1676 $postdata['options'] = $this->get_options($this->post_type);
1677 return $this->_response($postdata);
1678 } else {
1679 // ERROR: error creating or updating post
1680 return $this->_generic_error_response('post_save_failed', array(
1681 'message' => $post_id->get_error_message(),
1682 'args' => $args
1683 ));
1684 }
1685 } else {
1686 // ERROR: no id parameter, invalid request
1687 return $this->_generic_error_response('post_invalid_request', array('message' => $updraftcentral_host_plugin->retrieve_show_message('parameters_missing')));
1688 }
1689 }
1690
1691 /**
1692 * Fetch and retrieves authors based from the submitted parameters
1693 *
1694 * @param array $params Containing all the needed information to filter the results of the current request
1695 * @return array
1696 */
1697 public function get_authors($params = array()) {
1698 global $updraftcentral_main;
1699
1700 // If expected parameters are empty or does not exists then set them to some default values
1701 $page = !empty($params['page']) ? (int) $params['page'] : 1;
1702 $per_page = !empty($params['per_page']) ? (int) $params['per_page'] : 15;
1703 $offset = ($page - 1) * $per_page;
1704 $who = !empty($params['who']) ? $params['who'] : 'authors';
1705 $order = !empty($params['order']) ? strtoupper($params['order']) : 'ASC';
1706 $orderby = !empty($params['orderby']) ? $params['orderby'] : 'display_name';
1707
1708 $get_user_params = array(
1709 'number' => $per_page,
1710 'paged' => $page,
1711 'offset' => $offset,
1712 'order' => $order,
1713 'orderby' => $orderby,
1714 );
1715
1716 // WP 5.9 deprecated the 'who' parameter and introduces the 'capability'
1717 // parameter, thus we'll be replacing the 'who' parameter in 5.9 or higher
1718 if (version_compare($updraftcentral_main->get_wordpress_version(), '5.9', '<')) {
1719 $get_user_params['who'] = $who;
1720 } else {
1721 $get_user_params['capability'] = array('edit_posts');
1722 }
1723
1724 $users = get_users($get_user_params);
1725
1726 $authors = array();
1727 $locale = get_locale();
1728
1729 foreach ($users as $user) {
1730 $data = array(
1731 'user' => json_encode($this->trim_object($user)),
1732 'misc' => array(
1733 'link' => get_author_posts_url($user->ID, $user->user_nicename),
1734 'locale' => function_exists('get_user_locale') ? get_user_locale($user) : $locale,
1735 'registered_date' => date('c', strtotime($user->user_registered)),
1736 )
1737 );
1738
1739 array_push($authors, $data);
1740 }
1741
1742 return $this->_response(array(
1743 'authors' => $authors
1744 ));
1745 }
1746
1747 /**
1748 * Fetch and retrieves parent pages based from the submitted parameters
1749 *
1750 * @param array $params Containing all the needed information to filter the results of the current request
1751 * @return array
1752 */
1753 public function get_parent_pages($params = array()) {
1754 // If expected parameters are empty or does not exists then set them to some default values
1755 $page = !empty($params['page']) ? (int) $params['page'] : 1;
1756 $per_page = !empty($params['per_page']) ? (int) $params['per_page'] : 100;
1757 $offset = ($page - 1) * $per_page;
1758 $exclude = !empty($params['exclude']) ? $params['exclude'] : array();
1759 $order = !empty($params['order']) ? strtoupper($params['order']) : 'ASC';
1760 $orderby = !empty($params['orderby']) ? $params['orderby'] : 'menu_order';
1761 $status = !empty($params['status']) ? $params['status'] : 'publish';
1762
1763 $args = array(
1764 'posts_per_page' => $per_page,
1765 'paged' => $page,
1766 'offset' => $offset,
1767 'post__not_in' => $exclude,
1768 'order' => $order,
1769 'orderby' => $orderby,
1770 'post_type' => 'page',
1771 'post_status' => $status,
1772 );
1773
1774 $query = new WP_Query($args);
1775 $posts = $query->posts;
1776
1777 $pages = array();
1778 if (!empty($posts)) {
1779 foreach ($posts as $post) {
1780 // Get additional information and merge with the response
1781 $postdata = $this->get_postdata($post, true);
1782 if (!empty($postdata)) array_push($pages, $this->trim_parent_info($postdata));
1783 }
1784 }
1785
1786 return $this->_response(array(
1787 'pages' => $pages
1788 ));
1789 }
1790
1791 /**
1792 * Trim down return data for parent pages
1793 *
1794 * @param array $postdata The array containing the data to process
1795 * @return array
1796 */
1797 protected function trim_parent_info($postdata) {
1798
1799 if (isset($postdata['post'])) {
1800 $post = json_decode($postdata['post']);
1801
1802 $page = new stdClass();
1803 $page->ID = $post->ID;
1804 $page->post_title = $post->post_title;
1805 $page->post_parent = $post->post_parent;
1806 $page->post_type = $post->post_type;
1807 $page->post_status = $post->post_status;
1808
1809 $postdata['post'] = json_encode($page);
1810 }
1811
1812 return $postdata;
1813 }
1814
1815 /**
1816 * Retrieves pages, templates, authors, categories and tags data that will be
1817 * used as options when displayed on the editor in UpdraftCentral
1818 *
1819 * @param string $type The type of the module that the current request is processing
1820 *
1821 * @return array
1822 */
1823 protected function get_options($type = 'post') {
1824 // Primarily used for editor consumption so we don't include trash here. Besides,
1825 // trash posts/pages aren't included as parent options.
1826 $parent_pages = $this->get_parent_pages();
1827 $pages = $parent_pages['data']['pages'];
1828
1829 // Add flexibility by letting users filter the default roles and add their own
1830 // custom page/post "author" role(s) if need be.
1831 $author_roles = apply_filters('updraftcentral_author_roles', array('administrator', 'editor', 'author', 'contributor'));
1832 $authors = get_users(array('role__in' => $author_roles));
1833
1834 if (!function_exists('get_page_templates')) {
1835 require_once(ABSPATH.'wp-admin/includes/theme.php');
1836 }
1837
1838 $templates = ('post' == $type) ? get_page_templates(null, 'post') : get_page_templates();
1839 $template_options = array();
1840 foreach ($templates as $template => $filename) {
1841 $item = array(
1842 'filename' => $filename,
1843 'template' => $template,
1844 );
1845 $template_options[] = $item;
1846 }
1847
1848 $page_options = array();
1849 foreach ($pages as $page_item) {
1850 if (isset($page_item['post'])) {
1851 $page = json_decode($page_item['post']);
1852 $item = array(
1853 'id' => $page->ID,
1854 'title' => $page->post_title,
1855 'parent' => $page->post_parent
1856 );
1857 $page_options[] = $item;
1858 }
1859 }
1860
1861 $author_options = array();
1862 foreach ($authors as $user) {
1863 $item = array(
1864 'id' => $user->ID,
1865 'name' => $user->display_name,
1866 );
1867 $author_options[] = $item;
1868 }
1869
1870 $response = array(
1871 'page' => $page_options,
1872 'author' => $author_options,
1873 'template' => $template_options,
1874 'date' => $this->get_date_options($type),
1875 );
1876
1877 if ('post' == $type) {
1878 $categories = get_categories(array('hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC'));
1879 $tags = get_tags(array('hide_empty' => false));
1880
1881 $category_options = array();
1882 foreach ($categories as $category) {
1883 $item = array(
1884 'id' => $category->term_id,
1885 'name' => $category->name,
1886 'parent' => $category->parent
1887 );
1888 $category_options[] = $item;
1889 }
1890
1891 $tag_options = array();
1892 foreach ($tags as $tag) {
1893 $item = array(
1894 'id' => $tag->term_id,
1895 'name' => $tag->name,
1896 );
1897 $tag_options[] = $item;
1898 }
1899
1900 $response['category'] = $category_options;
1901 $response['tag'] = $tag_options;
1902 }
1903
1904 return $response;
1905 }
1906
1907 /**
1908 * Changes the state/status of the given post based from the submitted action/request
1909 *
1910 * @param int $id The ID of the current page to work on
1911 * @param string $action The type of change that the current request is going to apply
1912 * @param string $type The type of the module that the current request is processing
1913 *
1914 * @return array
1915 */
1916 protected function apply_state($id, $action, $type = 'post') {
1917 if (empty($id)) return false;
1918
1919 $post = get_post($id);
1920 if (!empty($post)) {
1921 $previous_status = $post->post_status;
1922 $deleted = false;
1923
1924 switch ($action) {
1925 case 'draft':
1926 $args = array('ID' => $id, 'post_status' => 'draft');
1927 wp_update_post($args);
1928 break;
1929 case 'trash':
1930 wp_trash_post($id);
1931 break;
1932 case 'publish':
1933 $args = array('ID' => $id, 'post_status' => 'publish');
1934 wp_update_post($args);
1935 break;
1936 case 'restore':
1937 $args = array('ID' => $id, 'post_status' => 'pending');
1938 wp_update_post($args);
1939 break;
1940 case 'delete':
1941 $result = wp_delete_post($id, true);
1942 if (!empty($result)) $deleted = true;
1943 break;
1944 default:
1945 break;
1946 }
1947
1948 $postdata = $this->get_postdata($post);
1949 if (!empty($postdata) || $deleted) {
1950 $data = $deleted ? $id : $postdata;
1951 $result = array(
1952 'id' => $id,
1953 'previous_status' => $previous_status
1954 );
1955
1956 $result[$type] = $data;
1957 return $result;
1958 }
1959 }
1960
1961 return false;
1962 }
1963
1964 /**
1965 * Imports image from UpdraftCentral's page/post editor
1966 *
1967 * @param string $image_url The URL of the image to import
1968 * @param string $image_data The image data to save. If empty, image_url will be used to download the image
1969 * @param int $post_id The ID of the page where this image is to be attached
1970 *
1971 * @return integer
1972 */
1973 protected function attach_remote_image($image_url, $image_data, $post_id) {
1974 if (empty($image_url) || empty($post_id)) return;
1975
1976 $image = pathinfo($image_url);
1977 $image_name = $image['basename'];
1978 $upload_dir = wp_upload_dir();
1979
1980 if (empty($image_data)) {
1981 $response = wp_remote_get($image_url);
1982 if (!is_wp_error($response)) {
1983 $image_data = wp_remote_retrieve_body($response);
1984 }
1985 } else {
1986 $image_data = base64_decode($image_data);
1987 }
1988
1989 $media_id = 0;
1990 if (!empty($image_data)) {
1991 $unique_file_name = wp_unique_filename($upload_dir['path'], $image_name);
1992 $filename = basename($unique_file_name);
1993
1994 if (wp_mkdir_p($upload_dir['path'])) {
1995 $file = $upload_dir['path'] . '/' . $filename;
1996 } else {
1997 $file = $upload_dir['basedir'] . '/' . $filename;
1998 }
1999
2000 file_put_contents($file, $image_data);
2001 $wp_filetype = wp_check_filetype($filename, null);
2002
2003 $attachment = array(
2004 'post_mime_type' => $wp_filetype['type'],
2005 'post_title' => sanitize_file_name($filename),
2006 'post_content' => '',
2007 'post_status' => 'inherit'
2008 );
2009
2010 $media_id = wp_insert_attachment($attachment, $file, $post_id);
2011 require_once(ABSPATH . 'wp-admin/includes/image.php');
2012
2013 $attach_data = wp_generate_attachment_metadata($media_id, $file);
2014 wp_update_attachment_metadata($media_id, $attach_data);
2015 set_post_thumbnail($post_id, $media_id);
2016 }
2017
2018 return $media_id;
2019 }
2020
2021 /**
2022 * Checks whether we have the required fields submitted and the user has
2023 * the capabilities to execute the requested action
2024 *
2025 * @param array $capabilities The capabilities to check and validate
2026 *
2027 * @return array|void
2028 */
2029 protected function _validate_capabilities($capabilities) {
2030 foreach ($capabilities as $capability) {
2031 if (!current_user_can($capability)) return $this->_generic_error_response('insufficient_permission');
2032 }
2033 }
2034 }
2035