register_default_listeners(); $this->load_sandbox(); add_filter('qm/collectors', [$this, 'register_collector_overview'], 1, 2); add_filter('qm/outputter/html', [$this, 'add_overview_panel'], 50, 2); } /** * Registers the default listeners. * * @since 2.0.11 * @return void */ protected function register_default_listeners() { /** * In the case of an empty listener name (?development) * adds the listener link. */ $this->listen('index', [$this, 'render_listeners_menu'], 'init'); /** * Saves the rest endpoints to the mpb data folder * on the listener save-rest-arguments. */ $this->listen('save-rest-arguments', '__return_false', 'wu_rest_get_endpoint_schema_use_cache'); $this->listen('save-rest-arguments', [$this, 'save_route_arguments'], 'wu_rest_register_routes_general'); $this->listen('save-rest-arguments', [$this, 'save_route_arguments'], 'wu_rest_register_routes_with_id'); } /** * Save route arguments to files to deal with opcache. * * @since 2.0.11 * * @param array $routes Routes passed to WordPress. * @param string $path The rest api route path. * @param string $context The context. Can be either 'create' or 'update'. * @param \WP_Ultimo\Traits\Rest_Api $manager The model manager instance. * @return void */ public function save_route_arguments($routes, $path, $context, $manager): void { $class_name = str_replace('_', '-', strtolower($path)); $args = $manager->get_arguments_schema($context === 'update'); file_put_contents(wu_path("/mpb/data/endpoint/.endpoint-$class_name-$context"), json_encode($args)); // phpcs:ignore } /** * Adds a listener for development purposes. * * @since 2.0.11 * * @param string $hook The name of the listener hook. * @param callable $callback The callback to be run. * @param string $wp_hook The WordPress hook to add this listener to. * @param integer $order The order to be run. * @return mixed */ public function listen($hook, $callback, $wp_hook = 'wp_ultimo_load', $order = 1) { $this->listeners[ $hook ] = ($this->listeners[ $hook ] ?? 0) + 1; $this->wp_hooks[ $wp_hook ] = 1; $action = $this->get_action($hook, $wp_hook); $order = $this->get_order($hook, $order); add_action( $action, function (...$arguments) use ($callback, $action, $order) { $timing_id = sprintf('%s_%s_%s', $action, $this->run + 1, $order); // phpcs:ignore do_action('qm/start', $timing_id); $result = call_user_func_array($callback, $arguments); // phpcs:ignore do_action('qm/stop', $timing_id); $this->run++; return $result; }, $order, 100 ); return $this; } /** * Sets flags for the development environment. * * @since 2.0.11 * * @param array $configs The config constants. * @return void */ public function config($configs = []): void { $allowed_configs = [ 'QM_DARK_MODE', 'QM_DB_EXPENSIVE', 'QM_DISABLED', 'QM_DISABLE_ERROR_HANDLER', 'QM_ENABLE_CAPS_PANEL', 'QM_HIDE_CORE_ACTIONS', 'QM_HIDE_SELF', 'QM_NO_JQUERY', 'QM_SHOW_ALL_HOOKS', ]; foreach ($configs as $constant_name => $constant_value) { if (in_array($constant_name, $allowed_configs, true)) { // phpcs:ignore defined($constant_name) === false && define($constant_name, $constant_value); } } } /** * Marks the development environment to finish execution after all listeners are run. * * @since 2.0.11 * * @param string|bool $should_die False to prevent it from dying, or a WordPress hook to wait before dying. * @return void */ public function die($should_die = true): void { if ($should_die === true) { $should_die = is_admin() ? 'admin_enqueue_scripts' : 'wp_enqueue_scripts'; } $this->should_die = $should_die; } /** * Run a registered listener. * * @since 2.0.11 * * @param string $wp_hook The WordPress hook to run. * @param array $arguments The arguments passed to the WordPress hook. * @return mixed */ public function run_listener($wp_hook, $arguments = []) { $listener = wu_request(self::LISTENER_PARAM, 'no-dev-param'); if ($listener === 'no-dev-param') { return current($arguments); } elseif ($listener === '') { $listener = 'index'; } $action = $this->get_action($listener, $wp_hook); return do_action_ref_array($action, $arguments); // phpcs:ignore } /** * Loads the sandbox environment. * * Checks for the existence of a development.php file * inside the root folder and loads it if it does. * * @since 2.0.11 * @return void */ protected function load_sandbox() { $dev_file = wu_path_join(dirname((string) WP_ULTIMO_SUNRISE_FILE, 2), 'development.php'); if (file_exists($dev_file)) { /** * Make the $toolkit variable available * to the development.php file. */ $toolkit = $this; include $dev_file; } $wp_hooks = array_keys($this->wp_hooks); foreach ($wp_hooks as $wp_hook) { add_action($wp_hook, fn(...$arguments) => $this->run_listener($wp_hook, $arguments), 0, 100); } add_action('shutdown', [$this, 'setup_query_monitor']); if ($this->should_die) { $this->dump_and_die(end($wp_hooks)); } } /** * Setups the query monitor integration. * * @since 2.0.11 * @return void */ public function setup_query_monitor(): void { if (class_exists('\QM_Dispatchers')) { // phpcs:ignore do_action('qm/debug', sprintf('Actions with listeners: %s', $this->get_wp_hooks_list())); // phpcs:ignore do_action('qm/debug', sprintf('Listeners: %s', $this->get_listeners_list())); // phpcs:ignore $dispatcher = \QM_Dispatchers::get('html'); if (! $dispatcher) { return; } $dispatcher->did_footer = true; if ($this->should_die && $this->run) { $this->enqueue_scripts($dispatcher); } } } /** * Registers the collector overview. * * @since 2.0.11 * * @param array $collectors The collectors array. * @param \QueryMonitor $qm The Query Monitor instance. * @return array */ public function register_collector_overview(array $collectors, \QueryMonitor $qm) { $collectors['wp-ultimo'] = new Query_Monitor\Collectors\Collector_Overview(); return $collectors; } /** * Adds the overview panel. * * @since 2.0.11 * * @param array $output Array containing all registered output-generators. * @return array */ public function add_overview_panel($output) { $collector = \QM_Collectors::get('wp-ultimo'); $output['wp-ultimo'] = new Query_Monitor\Panel\Overview($collector); return $output; } /** * Manually enqueues query monitor and WP Multisite WaaS styles. * * @since 2.0.11 * * @param \QM_Dispatcher $dispatcher The Query Monitor dispatcher object. * @return void */ protected function enqueue_scripts($dispatcher) { printf('', wu_url('inc/development/assets/development.css')); wp_print_styles( [ 'wu-admin', ] ); $dispatcher->manually_print_assets(); // phpcs:ignore } /** * Returns a comma-separated list of listeners. * * @since 2.0.11 */ protected function get_listeners_list(): string { $listener_names = array_keys($this->listeners); return implode(', ', $listener_names); } /** * Returns a comma-separated list of WordPress hooks. * * @since 2.0.11 */ protected function get_wp_hooks_list(): string { $wp_hook_names = array_keys($this->wp_hooks); return implode(', ', $wp_hook_names); } /** * Dumps the development content and kill the execution. * * @since 2.0.11 * * @param string $hook Hook to die on. * @return void */ protected function dump_and_die($hook) { add_action( $hook, function () use ($hook) { if (did_action($this->should_die) && $this->run) { $this->render_listeners_menu(); do_action('shutdown'); // phpcs:ignore $message = sprintf('Execution killed on %s.', $hook); do_action('qm/info', $message); // phpcs:ignore die(); } else { return $this->dump_and_die($this->should_die); } }, 110 ); } /** * Get the order of a newly added listener. * * @since 2.0.11 * * @param string $hook The listener action name. * @param integer $order The order of execution. * @return integer */ protected function get_order($hook, $order = 1) { return 10 + (absint($this->listeners[ $hook ]) * $order * 10) + 5; } /** * Get the action name based on the listener hook and WP action. * * @since 2.0.11 * * @param string $hook The listener action name. * @param string $wp_hook The WordPress hook. */ protected function get_action($hook, $wp_hook): string { $hook = str_replace('-', '_', $hook); return sprintf('wu_sandbox_run_%s_%s', $hook, $wp_hook); } /** * Render the list of listeners with links. * * @since 2.0.11 * @return void */ public function render_listeners_menu(): void { /** * Make sure we display it only once. */ if ($this->displayed_footer) { return; } // phpcs:disable echo '
Listeners
'; // phpcs: enable $this->displayed_footer = true; } }