PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-sync/src/modules/class-callables.php +684 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,684 @@
1 +<?php
2 +/**
3 + * Callables sync module.
4 + *
5 + * @package automattic/jetpack-sync
6 + */
7 +
8 +namespace Automattic\Jetpack\Sync\Modules;
9 +
10 +use Automattic\Jetpack\Constants as Jetpack_Constants;
11 +use Automattic\Jetpack\Sync\Dedicated_Sender;
12 +use Automattic\Jetpack\Sync\Defaults;
13 +use Automattic\Jetpack\Sync\Functions;
14 +use Automattic\Jetpack\Sync\Settings;
15 +
16 +if ( ! defined( 'ABSPATH' ) ) {
17 + exit( 0 );
18 +}
19 +
20 +/**
21 + * Class to handle sync for callables.
22 + */
23 +class Callables extends Module {
24 + /**
25 + * Name of the callables checksum option.
26 + *
27 + * @var string
28 + */
29 + const CALLABLES_CHECKSUM_OPTION_NAME = 'jetpack_callables_sync_checksum';
30 +
31 + /**
32 + * Name of the transient for locking callables.
33 + *
34 + * @var string
35 + */
36 + const CALLABLES_AWAIT_TRANSIENT_NAME = 'jetpack_sync_callables_await';
37 +
38 + /**
39 + * Whitelist for callables we want to sync.
40 + *
41 + * @access private
42 + *
43 + * @var array
44 + */
45 + private $callable_whitelist;
46 +
47 + /**
48 + * For some options, we should always send the change right away!
49 + *
50 + * @access public
51 + *
52 + * @var array
53 + */
54 + const ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS = array(
55 + 'jetpack_active_modules',
56 + 'home', // option is home, callable is home_url.
57 + 'siteurl',
58 + 'jetpack_sync_error_idc',
59 + 'paused_plugins',
60 + 'paused_themes',
61 + 'blog_public', // Callable is jetpack_site_visibility; privacy has to travel as fast as the option.
62 +
63 + );
64 +
65 + const ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS_NEXT_TICK = array(
66 + 'stylesheet',
67 + );
68 + /**
69 + * Setting this value to true will make it so that the callables will not be unlocked
70 + * but the lock will be removed after content is send so that callables will be
71 + * sent in the next request.
72 + *
73 + * @var bool
74 + */
75 + private $force_send_callables_on_next_tick = false;
76 +
77 + /**
78 + * For some options, the callable key differs from the option name/key
79 + *
80 + * @access public
81 + *
82 + * @var array
83 + */
84 + const OPTION_NAMES_TO_CALLABLE_NAMES = array(
85 + // @TODO: Audit the other option names for differences between the option names and callable names.
86 + 'home' => 'home_url',
87 + 'siteurl' => 'site_url',
88 + 'jetpack_active_modules' => 'active_modules',
89 + 'blog_public' => 'jetpack_site_visibility',
90 + );
91 +
92 + /**
93 + * Sync module name.
94 + *
95 + * @access public
96 + *
97 + * @return string
98 + */
99 + public function name() {
100 + return 'functions';
101 + }
102 +
103 + /**
104 + * Set module defaults.
105 + * Define the callable whitelist based on whether this is a single site or a multisite installation.
106 + *
107 + * @access public
108 + */
109 + public function set_defaults() {
110 + if ( is_multisite() ) {
111 + $this->callable_whitelist = array_merge( Defaults::get_callable_whitelist(), Defaults::get_multisite_callable_whitelist() );
112 + } else {
113 + $this->callable_whitelist = Defaults::get_callable_whitelist();
114 + }
115 + $this->force_send_callables_on_next_tick = false; // Resets here as well mostly for tests.
116 + }
117 +
118 + /**
119 + * Set module defaults at a later time.
120 + * Reset the callable whitelist if needed to account for plugins adding the 'jetpack_sync_callable_whitelist'
121 + * and 'jetpack_sync_multisite_callable_whitelist' filters late.
122 + *
123 + * @see Automattic\Jetpack\Sync\Modules::set_module_defaults
124 + * @access public
125 + */
126 + public function set_late_default() {
127 + if ( is_multisite() ) {
128 + $late_callables = array_merge(
129 + apply_filters( 'jetpack_sync_callable_whitelist', array() ),
130 + apply_filters( 'jetpack_sync_multisite_callable_whitelist', array() )
131 + );
132 + } else {
133 + $late_callables = apply_filters( 'jetpack_sync_callable_whitelist', array() );
134 + }
135 + if ( ! empty( $late_callables ) && is_array( $late_callables ) ) {
136 + $this->callable_whitelist = array_merge( $this->callable_whitelist, $late_callables );
137 + }
138 + }
139 +
140 + /**
141 + * Initialize callables action listeners.
142 + *
143 + * @access public
144 + *
145 + * @param callable $callable Action handler callable.
146 + */
147 + public function init_listeners( $callable ) {
148 + add_action( 'jetpack_sync_callable', $callable, 10, 2 );
149 + add_action( 'current_screen', array( $this, 'set_plugin_action_links' ), 9999 ); // Should happen very late.
150 +
151 + foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS as $option ) {
152 + add_action( "update_option_{$option}", array( $this, 'unlock_sync_callable' ) );
153 + add_action( "delete_option_{$option}", array( $this, 'unlock_sync_callable' ) );
154 + }
155 +
156 + foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS_NEXT_TICK as $option ) {
157 + add_action( "update_option_{$option}", array( $this, 'unlock_sync_callable_next_tick' ) );
158 + add_action( "delete_option_{$option}", array( $this, 'unlock_sync_callable_next_tick' ) );
159 + }
160 +
161 + // Provide a hook so that hosts can send changes to certain callables right away.
162 + // Especially useful when a host uses constants to change home and siteurl.
163 + add_action( 'jetpack_sync_unlock_sync_callable', array( $this, 'unlock_sync_callable' ) );
164 +
165 + // get_plugins and wp_version
166 + // gets fired when new code gets installed, updates etc.
167 + add_action( 'upgrader_process_complete', array( $this, 'unlock_plugin_action_link_and_callables' ) );
168 + add_action( 'update_option_active_plugins', array( $this, 'unlock_plugin_action_link_and_callables' ) );
169 + // Deleting an inactive plugin fires neither of the above, so without this the synced
170 + // get_plugins callable stays stale until the periodic re-check happens to run.
171 + add_action( 'deleted_plugin', array( $this, 'unlock_plugin_action_link_and_callables' ) );
172 + }
173 +
174 + /**
175 + * Initialize callables action listeners for full sync.
176 + *
177 + * @access public
178 + *
179 + * @param callable $callable Action handler callable.
180 + */
181 + public function init_full_sync_listeners( $callable ) {
182 + add_action( 'jetpack_full_sync_callables', $callable );
183 + }
184 +
185 + /**
186 + * Initialize the module in the sender.
187 + *
188 + * @access public
189 + */
190 + public function init_before_send() {
191 + add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_sync_callables' ) );
192 +
193 + // Full sync.
194 + add_filter( 'jetpack_sync_before_send_jetpack_full_sync_callables', array( $this, 'expand_callables' ) );
195 + }
196 +
197 + /**
198 + * Perform module cleanup.
199 + * Deletes any transients and options that this module uses.
200 + * Usually triggered when uninstalling the plugin.
201 + *
202 + * @access public
203 + */
204 + public function reset_data() {
205 + delete_option( self::CALLABLES_CHECKSUM_OPTION_NAME );
206 + delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
207 +
208 + $url_callables = array( 'home_url', 'site_url', 'main_network_site_url' );
209 + foreach ( $url_callables as $callable ) {
210 + delete_option( Functions::HTTPS_CHECK_OPTION_PREFIX . $callable );
211 + }
212 + }
213 +
214 + /**
215 + * Set the callable whitelist.
216 + *
217 + * @access public
218 + *
219 + * @param array $callables The new callables whitelist.
220 + */
221 + public function set_callable_whitelist( $callables ) {
222 + $this->callable_whitelist = $callables;
223 + }
224 +
225 + /**
226 + * Get the callable whitelist.
227 + *
228 + * @access public
229 + *
230 + * @return array The callables whitelist.
231 + */
232 + public function get_callable_whitelist() {
233 + return $this->callable_whitelist;
234 + }
235 +
236 + /**
237 + * Retrieve all callables as per the current callables whitelist.
238 + *
239 + * @access public
240 + *
241 + * @return array All callables.
242 + */
243 + public function get_all_callables() {
244 + // get_all_callables should run as the master user always.
245 + $current_user_id = get_current_user_id();
246 + wp_set_current_user( \Jetpack_Options::get_option( 'master_user' ) );
247 + $callables = array_combine(
248 + array_keys( $this->get_callable_whitelist() ),
249 + array_map( array( $this, 'get_callable' ), array_values( $this->get_callable_whitelist() ) )
250 + );
251 + wp_set_current_user( $current_user_id );
252 + return $callables;
253 + }
254 +
255 + /**
256 + * Invoke a particular callable.
257 + * Used as a wrapper to standartize invocation.
258 + *
259 + * @access private
260 + *
261 + * @param callable $callable Callable to invoke.
262 + * @return mixed Return value of the callable, null if not callable.
263 + */
264 + private function get_callable( $callable ) {
265 + if ( is_callable( $callable ) ) {
266 + return call_user_func( $callable );
267 + } else {
268 + return null;
269 + }
270 + }
271 +
272 + /**
273 + * Enqueue the callable actions for full sync.
274 + *
275 + * @access public
276 + *
277 + * @param array $config Full sync configuration for this sync module.
278 + * @param int $max_items_to_enqueue Maximum number of items to enqueue.
279 + * @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
280 + * @return array Number of actions enqueued, and next module state.
281 + */
282 + public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
283 + /**
284 + * Tells the client to sync all callables to the server
285 + *
286 + * @since 1.6.3
287 + * @since-jetpack 4.2.0
288 + *
289 + * @param boolean Whether to expand callables (should always be true)
290 + */
291 + do_action( 'jetpack_full_sync_callables', true );
292 +
293 + // The number of actions enqueued, and next module state (true == done).
294 + return array( 1, true );
295 + }
296 +
297 + /**
298 + * Send the callable actions for full sync.
299 + *
300 + * @access public
301 + *
302 + * @param array $config Full sync configuration for this sync module.
303 + * @param array $status This Module Full Sync Status.
304 + * @param int $send_until The timestamp until the current request can send.
305 + * @param int $started The timestamp when the full sync started.
306 + *
307 + * @return array This Module Full Sync Status.
308 + */
309 + public function send_full_sync_actions( $config, $status, $send_until, $started ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
310 + // we call this instead of do_action when sending immediately.
311 + $result = $this->send_action( 'jetpack_full_sync_callables', array( true ) );
312 +
313 + if ( is_wp_error( $result ) ) {
314 + $status['error'] = true;
315 + return $status;
316 + }
317 + $status['finished'] = true;
318 + $status['sent'] = $status['total'];
319 + return $status;
320 + }
321 +
322 + /**
323 + * Retrieve an estimated number of actions that will be enqueued.
324 + *
325 + * @access public
326 + *
327 + * @param array $config Full sync configuration for this sync module.
328 + * @return int Number of items yet to be enqueued.
329 + */
330 + public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
331 + return 1;
332 + }
333 +
334 + /**
335 + * Retrieve the actions that will be sent for this module during a full sync.
336 + *
337 + * @access public
338 + *
339 + * @return array Full sync actions of this module.
340 + */
341 + public function get_full_sync_actions() {
342 + return array( 'jetpack_full_sync_callables' );
343 + }
344 +
345 + /**
346 + * Unlock callables so they would be available for syncing again.
347 + *
348 + * @access public
349 + */
350 + public function unlock_sync_callable() {
351 + delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
352 + }
353 +
354 + /**
355 + * Unlock callables on the next tick.
356 + * Sometime the true callable values are only present on the next tick.
357 + * When switching themes for example.
358 + *
359 + * @access public
360 + */
361 + public function unlock_sync_callable_next_tick() {
362 + $this->force_send_callables_on_next_tick = true;
363 + }
364 +
365 + /**
366 + * Unlock callables and plugin action links.
367 + *
368 + * @access public
369 + */
370 + public function unlock_plugin_action_link_and_callables() {
371 + delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
372 + delete_transient( 'jetpack_plugin_api_action_links_refresh' );
373 + add_filter( 'jetpack_check_and_send_callables', '__return_true' );
374 + }
375 +
376 + /**
377 + * Parse and store the plugin action links if on the plugins page.
378 + *
379 + * @uses \DOMDocument
380 + * @uses libxml_use_internal_errors
381 + * @uses mb_convert_encoding
382 + *
383 + * @access public
384 + */
385 + public function set_plugin_action_links() {
386 + if (
387 + ! class_exists( '\DOMDocument' ) ||
388 + ! function_exists( 'mb_convert_encoding' )
389 + ) {
390 + return;
391 + }
392 +
393 + $current_screeen = get_current_screen();
394 +
395 + $plugins_action_links = array();
396 + // Is the transient lock in place?
397 + $plugins_lock = get_transient( 'jetpack_plugin_api_action_links_refresh' );
398 + if ( ! empty( $plugins_lock ) && ( isset( $current_screeen->id ) && 'plugins' !== $current_screeen->id ) ) {
399 + return;
400 + }
401 + $plugins = Functions::get_plugins();
402 + if ( ! is_array( $plugins ) ) {
403 + return;
404 + }
405 + foreach ( $plugins as $plugin_file => $plugin_data ) {
406 + /**
407 + * Plugins often like to unset things but things break if they are not able to.
408 + */
409 + $action_links = array(
410 + 'deactivate' => '',
411 + 'activate' => '',
412 + 'details' => '',
413 + 'delete' => '',
414 + 'edit' => '',
415 + );
416 + /** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
417 + $action_links = apply_filters( 'plugin_action_links', $action_links, $plugin_file, $plugin_data, 'all' );
418 + // Verify $action_links is still an array.
419 + if ( ! is_array( $action_links ) ) {
420 + $action_links = array();
421 + }
422 + /** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
423 + $action_links = apply_filters( "plugin_action_links_{$plugin_file}", $action_links, $plugin_file, $plugin_data, 'all' );
424 + // Verify $action_links is still an array to resolve warnings from filters not returning an array.
425 + if ( is_array( $action_links ) ) {
426 + $action_links = array_filter( $action_links );
427 + } else {
428 + $action_links = array();
429 + }
430 + $formatted_action_links = null;
431 + if ( $action_links ) {
432 + $dom_doc = new \DOMDocument();
433 + foreach ( $action_links as $action_link ) {
434 + // The @ is not enough to suppress errors when dealing with libxml,
435 + // we have to tell it directly how we want to handle errors.
436 + libxml_use_internal_errors( true );
437 + $dom_doc->loadHTML( '<?xml encoding="utf-8" ?>' . $action_link );
438 + libxml_use_internal_errors( false );
439 +
440 + $link_elements = $dom_doc->getElementsByTagName( 'a' );
441 + if ( 0 === $link_elements->length ) {
442 + continue;
443 + }
444 +
445 + $link_element = $link_elements->item( 0 );
446 + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
447 + if ( $link_element instanceof \DOMElement && $link_element->hasAttribute( 'href' ) && $link_element->nodeValue ) {
448 + $link_url = trim( $link_element->getAttribute( 'href' ) );
449 +
450 + // Add the full admin path to the url if the plugin did not provide it.
451 + $link_url_scheme = wp_parse_url( $link_url, PHP_URL_SCHEME );
452 + if ( empty( $link_url_scheme ) ) {
453 + $link_url = admin_url( $link_url );
454 + }
455 +
456 + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
457 + $formatted_action_links[ $link_element->nodeValue ] = $link_url;
458 + }
459 + }
460 + }
461 + if ( $formatted_action_links ) {
462 + $plugins_action_links[ $plugin_file ] = $formatted_action_links;
463 + }
464 + }
465 + // Cache things for a long time.
466 + set_transient( 'jetpack_plugin_api_action_links_refresh', time(), DAY_IN_SECONDS );
467 + update_option( 'jetpack_plugin_api_action_links', $plugins_action_links );
468 + }
469 +
470 + /**
471 + * Whether a certain callable should be sent.
472 + *
473 + * @access public
474 + *
475 + * @param array $callable_checksums Callable checksums.
476 + * @param string $name Name of the callable.
477 + * @param string $checksum A checksum of the callable.
478 + * @return boolean Whether to send the callable.
479 + */
480 + public function should_send_callable( $callable_checksums, $name, $checksum ) {
481 + $idc_override_callables = array(
482 + 'main_network_site',
483 + 'home_url',
484 + 'site_url',
485 + );
486 + if ( in_array( $name, $idc_override_callables, true ) && \Jetpack_Options::get_option( 'migrate_for_idc' ) ) {
487 + return true;
488 + }
489 +
490 + return ! $this->still_valid_checksum( $callable_checksums, $name, $checksum );
491 + }
492 +
493 + /**
494 + * Sync the callables if we're supposed to.
495 + *
496 + * @access public
497 + */
498 + public function maybe_sync_callables() {
499 + $callables = $this->get_all_callables();
500 + if ( ! apply_filters( 'jetpack_check_and_send_callables', false ) ) {
501 + /**
502 + * Treating Dedicated Sync requests a bit differently from normal. We want to send callables
503 + * normally with all Sync actions, no matter if they were with admin action origin or not,
504 + * since Dedicated Sync runs out of bound and the requests are never coming from an admin.
505 + */
506 + if ( ! is_admin() && ! Dedicated_Sender::is_dedicated_sync_request() ) {
507 + // If we're not an admin and we're not doing cron and this isn't WP_CLI, don't sync anything.
508 + if ( ! Settings::is_doing_cron() && ! Jetpack_Constants::get_constant( 'WP_CLI' ) ) {
509 + return;
510 + }
511 + // If we're not an admin and we are doing cron, sync the Callables that are always supposed to sync ( See https://github.com/Automattic/jetpack/issues/12924 ).
512 + $callables = $this->get_always_sent_callables();
513 + }
514 + if ( get_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME ) ) {
515 + if ( $this->force_send_callables_on_next_tick ) {
516 + $this->unlock_sync_callable();
517 + }
518 + return;
519 + }
520 + }
521 +
522 + if ( empty( $callables ) ) {
523 + return;
524 + }
525 + // No need to set the transiant we are trying to remove it anyways.
526 + if ( ! $this->force_send_callables_on_next_tick ) {
527 + set_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME, microtime( true ), Defaults::$default_sync_callables_wait_time );
528 + }
529 +
530 + $callable_checksums = (array) \Jetpack_Options::get_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, array() );
531 + $has_changed = false;
532 + // Only send the callables that have changed.
533 + foreach ( $callables as $name => $value ) {
534 + $checksum = $this->get_check_sum( $value );
535 +
536 + // Explicitly not using Identical comparison as get_option returns a string.
537 + if ( $value !== null && $this->should_send_callable( $callable_checksums, $name, $checksum ) ) {
538 +
539 + // Only send callable if the non sorted checksum also does not match.
540 + if ( $this->should_send_callable( $callable_checksums, $name, $this->get_check_sum( $value, false ) ) ) {
541 +
542 + /**
543 + * Tells the client to sync a callable (aka function) to the server
544 + *
545 + * @param string The name of the callable
546 + * @param mixed The value of the callable
547 + *
548 + * @since 1.6.3
549 + * @since-jetpack 4.2.0
550 + */
551 + do_action( 'jetpack_sync_callable', $name, $value );
552 + }
553 +
554 + $callable_checksums[ $name ] = $checksum;
555 + $has_changed = true;
556 + } else {
557 + $callable_checksums[ $name ] = $checksum;
558 + }
559 + }
560 + if ( $has_changed ) {
561 + \Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callable_checksums );
562 + }
563 +
564 + if ( $this->force_send_callables_on_next_tick ) {
565 + $this->unlock_sync_callable();
566 + }
567 + }
568 +
569 + /**
570 + * Get the callables that should always be sent, e.g. on cron.
571 + *
572 + * @return array Callables that should always be sent
573 + */
574 + protected function get_always_sent_callables() {
575 + $callables = $this->get_all_callables();
576 + $cron_callables = array();
577 + foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS as $option_name ) {
578 + if ( array_key_exists( $option_name, $callables ) ) {
579 + $cron_callables[ $option_name ] = $callables[ $option_name ];
580 + continue;
581 + }
582 +
583 + // Check for the Callable name/key for the option, if different from option name.
584 + if ( array_key_exists( $option_name, self::OPTION_NAMES_TO_CALLABLE_NAMES ) ) {
585 + $callable_name = self::OPTION_NAMES_TO_CALLABLE_NAMES[ $option_name ];
586 + if ( array_key_exists( $callable_name, $callables ) ) {
587 + $cron_callables[ $callable_name ] = $callables[ $callable_name ];
588 + }
589 + }
590 + }
591 + return $cron_callables;
592 + }
593 +
594 + /**
595 + * Expand the callables within a hook before they are serialized and sent to the server.
596 + *
597 + * @access public
598 + *
599 + * @param array $args The hook parameters.
600 + * @return array $args The hook parameters.
601 + */
602 + public function expand_callables( $args ) {
603 + if ( $args[0] ) {
604 + $callables = $this->get_all_callables();
605 + $callables_checksums = array();
606 + foreach ( $callables as $name => $value ) {
607 + $callables_checksums[ $name ] = $this->get_check_sum( $value );
608 + }
609 + \Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callables_checksums );
610 + return $callables;
611 + }
612 +
613 + return $args;
614 + }
615 +
616 + /**
617 + * Return Total number of objects.
618 + *
619 + * @param array $config Full Sync config.
620 + *
621 + * @return int total
622 + */
623 + public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
624 + return count( $this->get_callable_whitelist() );
625 + }
626 +
627 + /**
628 + * Retrieve a set of callables by their IDs.
629 + *
630 + * @access public
631 + *
632 + * @param string $object_type Object type.
633 + * @param array $ids Object IDs.
634 + * @return array Array of objects.
635 + */
636 + public function get_objects_by_id( $object_type, $ids ) {
637 + if ( empty( $ids ) || empty( $object_type ) || 'callable' !== $object_type ) {
638 + return array();
639 + }
640 +
641 + $objects = array();
642 + foreach ( (array) $ids as $id ) {
643 + $object = $this->get_object_by_id( $object_type, $id );
644 +
645 + if ( 'CALLABLE-DOES-NOT-EXIST' !== $object ) {
646 + if ( 'all' === $id ) {
647 + // If all was requested it contains all options and can simply be returned.
648 + return $object;
649 + }
650 + $objects[ $id ] = $object;
651 + }
652 + }
653 +
654 + return $objects;
655 + }
656 +
657 + /**
658 + * Retrieve a callable by its name.
659 + *
660 + * @access public
661 + *
662 + * @param string $object_type Type of the sync object.
663 + * @param string $id ID of the sync object.
664 + * @return mixed Value of Callable.
665 + */
666 + public function get_object_by_id( $object_type, $id ) {
667 + if ( 'callable' === $object_type ) {
668 +
669 + // Only whitelisted options can be returned.
670 + if ( array_key_exists( $id, $this->get_callable_whitelist() ) ) {
671 + // requires master user to be in context.
672 + $current_user_id = get_current_user_id();
673 + wp_set_current_user( \Jetpack_Options::get_option( 'master_user' ) );
674 + $callable = $this->get_callable( $this->callable_whitelist[ $id ] );
675 + wp_set_current_user( $current_user_id );
676 + return $callable;
677 + } elseif ( 'all' === $id ) {
678 + return $this->get_all_callables();
679 + }
680 + }
681 +
682 + return 'CALLABLE-DOES-NOT-EXIST';
683 + }
684 +}