menuEditor = $menuEditor; $this->get = $queryParameters; add_filter('admin_menu_editor-script_data', array($this, 'addEditorScriptData')); add_action('wp_ajax_ws_ame_set_test_configuration', array($this, 'ajax_set_test_configuration')); add_action('set_current_user', array($this, 'init_access_test')); } public function addEditorScriptData($scriptData) { $scriptData = array_merge( $scriptData, array( 'setTestConfigurationNonce' => wp_create_nonce('ws_ame_set_test_configuration'), 'testAccessNonce' => wp_create_nonce('ws_ame_test_access'), ) ); return $scriptData; } public function ajax_set_test_configuration() { check_ajax_referer('ws_ame_set_test_configuration'); if ( !$this->menuEditor->current_user_can_edit_menu() ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputs JSON, not HTML. exit($this->menuEditor->json_encode(array( 'error' => 'You don\'t have permission to test menu settings.', ))); } $post = $this->menuEditor->get_post_params(); $menuData = strval($post['data']); $metaId = add_user_meta(get_current_user_id(), self::TEST_DATA_META_KEY, wp_slash($menuData), false); if ( $metaId === false ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputs JSON, not HTML. exit($this->menuEditor->json_encode(array( 'error' => 'Failed to store test data. add_user_meta() returned FALSE.', ))); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputs JSON, not HTML. exit($this->menuEditor->json_encode(array('success' => true, 'meta_id' => $metaId))); } public function init_access_test() { //We want to do this only once per page load: specifically, when WP authenticates //the user at the start of the request. static $is_user_already_set = false; if ( $is_user_already_set || $this->menuEditor->is_access_test || did_action('init') ) { return; } $is_user_already_set = true; if ( !isset( $this->get['ame-test-menu-access-as'], $this->get['ame-test-target-item'] ) || !check_admin_referer('ws_ame_test_access') ) { return; } $configurations = get_user_meta(get_current_user_id(), self::TEST_DATA_META_KEY, false); if ( empty($configurations) ) { exit('Error: Test data not found.'); } //Use the most recent config. It's usually the last one. $json = array_pop($configurations); //Clean up the database. delete_user_meta(get_current_user_id(), self::TEST_DATA_META_KEY, wp_slash($json)); try { $test_menu = ameMenu::load_json($json); } catch (InvalidMenuException $e) { exit(esc_html($e->getMessage())); } $this->test_menu = $test_menu; $user = get_user_by('login', strval($this->get['ame-test-menu-access-as'])); if ( !$user ) { exit('Error: User not found.'); } //Everything looks good, proceed with the test. $this->menuEditor->is_access_test = true; $this->access_test_results = array(); $this->test_target_item = strval($this->get['ame-test-target-item']); $this->test_target_parent = ameUtils::get($this->get, 'ame-test-target-parent', null); $this->test_relevant_role = ameUtils::get($this->get, 'ame-test-relevant-role', null); if ( $this->test_target_parent === '' ) { $this->test_target_parent = null; } if ( $this->test_relevant_role === null ) { $this->test_relevant_role = null; } wp_set_current_user($user->ID, $user->user_login); $this->menuEditor->set_plugin_option('security_logging_enabled', true); add_action('admin_print_scripts', array($this, 'output_access_test_results')); add_filter('wp_die_handler', array($this, 'replace_die_handler_for_access_test'), 25, 1); } public function output_access_test_results() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Intentionally outputs generated JS. echo $this->get_access_test_result_script(); } private function get_access_test_result_script() { $response = array_merge( $this->access_test_results, array( 'securityLog' => $this->menuEditor->get_security_log(), ) ); return ''; } public function replace_die_handler_for_access_test($callback = null) { $this->original_wp_die_handler = $callback; return array($this, 'die_during_an_access_test'); } public function die_during_an_access_test($message, $title = '', $args = array()) { if ( $this->original_wp_die_handler ) { $script = $this->get_access_test_result_script(); if ( $message instanceof WP_Error ) { $message->add('ame-access-test-response', '[Access test]' . $script); } else if ( is_string($message) ) { $message .= $script; } call_user_func($this->original_wp_die_handler, $message, $title, $args); } else { exit('Unexpected error: wp_die() was called but there is no default handler.'); } } private function find_target_menu_item($items, $item_file, $parent_file = null, $current_parent = null) { foreach ($items as $item) { $this_file = ameMenuItem::get($item, 'file', null); if ( ($this_file === $item_file) && ($parent_file === $current_parent) ) { return $item; } if ( !empty($item['items']) ) { $result = $this->find_target_menu_item($item['items'], $item_file, $parent_file, $this_file); if ( $result !== null ) { return $result; } } } return null; } public function setCurrentMenuItem($menuItem) { $this->access_test_results['currentMenuItem'] = $menuItem; $this->access_test_results['currentMenuItemIsTarget'] = isset($this->access_test_results['currentMenuItem']) && (ameMenuItem::get($this->access_test_results['currentMenuItem'], 'file', null) === $this->test_target_item) && (ameMenuItem::get($this->access_test_results['currentMenuItem'], 'parent', null) === $this->test_target_parent); $this->access_test_results['isIdentity'] = ($this->access_test_results['currentMenuItem'] === $this->access_test_results['targetMenuItem']); } public function onFinalTreeReady($tree) { //Find the target item. It might not be the same as the current item. $this->access_test_results['targetMenuItem'] = $this->find_target_menu_item( $tree, $this->test_target_item, $this->test_target_parent ); } /** * Whether a offset exists * * @link http://php.net/manual/en/arrayaccess.offsetexists.php * @param mixed $offset

* An offset to check for. *

* @return boolean true on success or false on failure. *

*

* The return value will be casted to boolean if non-boolean was returned. * @since 5.0.0 */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->access_test_results); } /** * Offset to retrieve * * @link http://php.net/manual/en/arrayaccess.offsetget.php * @param mixed $offset

* The offset to retrieve. *

* @return mixed Can return all value types. * @since 5.0.0 */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->access_test_results[$offset]; } /** * Offset to set * * @link http://php.net/manual/en/arrayaccess.offsetset.php * @param mixed $offset

* The offset to assign the value to. *

* @param mixed $value

* The value to set. *

* @return void * @since 5.0.0 */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->access_test_results[$offset] = $value; } /** * Offset to unset * * @link http://php.net/manual/en/arrayaccess.offsetunset.php * @param mixed $offset

* The offset to unset. *

* @return void * @since 5.0.0 */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->access_test_results[$offset]); } }