Make WordPress Core

Opened 8 years ago

Last modified 8 weeks ago

#39003 new defect (bug)

menu_page_url() not working on Ajax call

Reported by: vinoth06's profile vinoth06 Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 4.6.1
Component: Administration Keywords: needs-testing reporter-feedback
Focuses: Cc:

Description

menu_page_url( 'menu_slug', false ) is always returning empty string on AJAX response. The global $_parent_pages; is return NULL on AJAX call. But the same menu slug is working on normal page load.

Change History (7)

#1 @mostafa.s1990
5 years ago

  • Keywords needs-testing added

#2 @SergeyBiryukov
5 years ago

  • Component changed from General to Administration

#3 @donmhico
5 years ago

  • Keywords reporter-feedback added

Hello @vinoth06,

Welcome to WordPress trac! Thank you for submitting this ticket.

I looked more into the issue and I found out that I'm getting the correct value from menu_page_url() via AJAX if the current user has the capability to see the created admin page. If the user isn't logged-in or lacking capability then I also receive nothing.

Here's the code I used for testing.

<?php
// Create admin menu page.
function test_admin_menu_page() {
        add_menu_page( 'Test Menu', 'Test Menu', 'manage_options', 'test_menu', 'test_menu_page' );
}
add_action( 'admin_init', 'test_admin_menu_page' );

// AJAX handler.
function test_ajax_menu_page_url() {
        // Outputs - http://wp.test/wp-admin/admin.php?page=test_menu if logged in as admin.
        // Outputs nothing if logged-out.
        echo menu_page_url( 'test_menu', false );
        wp_die();
}
add_action( 'wp_ajax_ampu', 'test_ajax_menu_page_url' );
add_action( 'wp_ajax_nopriv_ampu', 'test_ajax_menu_page_url' );

#4 @vinoth06
5 years ago

Hi donmhico

Thanks for you code, I have modified your code which will get the result from AJAX request. Kindly check and share feedback.

I have created as an plugin, so you can add it in your plugin directory to activate.

I have added the comments too for your clarifications.

<?php
/**
 * Plugin Name: Menu Page URL on AJAX
 * Plugin URI: https://buffercode.com/plugin/frontend-dashboard
 * Description: Menu Page URL on AJAX
 * Version: 1
 * Author: vinoth06
 * Author URI: https://buffercode.com/
 * License: GPLv2
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 *
 */

if ( ! defined( 'ABSPATH' ) ) {
        exit;
}

add_action( 'admin_init', 'enqueue_jquery' );
function enqueue_jquery() {
        wp_enqueue_script( 'jquery' );
}

// Create admin menu page.
function test_admin_menu_page() {
        add_menu_page( 'Test Menu', 'Test Menu', 'manage_options', 'test_menu_slug', 'test_menu_page' );
}

// Hope its not admin_init. it should be admin_menu like in the line 21, Correct me if am wrong
//add_action( 'admin_init', 'test_admin_menu_page' );
add_action( 'admin_menu', 'test_admin_menu_page' );

// AJAX handler.
function test_ajax_menu_page_url() {
        // Outputs - http://wp.test/wp-admin/admin.php?page=test_menu if logged in as admin.
        // Outputs nothing if logged-out.
        echo menu_page_url( 'test_menu', false );
        // Try the below it will give the exact URL of the page.
        //echo admin_url( '/admin.php?page=test_menu' );

        // Conclusion: If you use menu_page_url in ajax return it will send empty string.
        wp_die();
}

add_action( 'wp_ajax_ampu', 'test_ajax_menu_page_url' );
add_action( 'wp_ajax_nopriv_ampu', 'test_ajax_menu_page_url' );

function test_menu_page() {
        ?>
    <form method="post" class="submitOnAjax" action="<?php echo admin_url( 'admin-ajax.php?action=ampu' ) ?>">
        <button type="submit">Submit</button>
    </form>
    <script>
        jQuery(document).ready(function ($) {
            $('body').on('submit', '.submitOnAjax', function (e) {
                var form = $(this);
                $.ajax({
                    type: 'POST',
                    url: form.attr('action'),
                    data: form.serialize(),
                    success: function (results) {
                        //Check the Value here
                        console.log(results);
                        // It will echo empty string
                    }
                });
                e.preventDefault();
            });
        });
    </script>
        <?php
}

#5 @donmhico
4 years ago

Hello @vinoth06,

Sorry for the delay in response. I re-checked and you are right menu_page_url() returns nothing in AJAX. I believe it's because global $_parent_pages; is NULL in AJAX request vs in normal admin request.

Upon diving a little deeper, I don't see menu.php being loaded in admin-ajax.php. So what you can do is edit your function to be

<?php
// AJAX handler.
function test_ajax_menu_page_url() {
    // Include the menu.
    if ( WP_NETWORK_ADMIN ) {
        require ABSPATH . 'wp-admin/network/menu.php';
    } elseif ( WP_USER_ADMIN ) {
        require ABSPATH . 'wp-admin/user/menu.php';
    } else {
        require ABSPATH . 'wp-admin/menu.php';
    }

    // Outputs - http://wp.test/wp-admin/admin.php?page=test_menu if logged in as admin.
    // Outputs nothing if logged-out.
    echo menu_page_url( 'test_menu_slug', false );
    // Try the below it will give the exact URL of the page.
    //echo admin_url( '/admin.php?page=test_menu' );

    // Conclusion: If you use menu_page_url in ajax return it will send empty string.
    wp_die();
}
add_action( 'wp_ajax_ampu', 'test_ajax_menu_page_url' );
add_action( 'wp_ajax_nopriv_ampu', 'test_ajax_menu_page_url' );

As for including this in the core, i'm not sure if we are suppose to have the menu available in AJAX. Let's hear more feedback.

#6 follow-up: @zamandevs
22 months ago

Use cases needed.

It doesn't make sense to get the admin menu in the ajax call as all of the menus load on admin init. if requires ajax can explicitly include necessary menu files as we do in the other areas.

#7 in reply to: ↑ 6 @davidhbrown
8 weeks ago

Replying to zamandevs:

Use cases needed.

My use case is that I have one admin subpage for editing (or creating new) records and another for displaying the listing of these records. To reduce clutter and help avoid accidental clicks on the listing page, records can be deleted only from their individual edit page. After deletion, further edits are invalid, so it would be preferable to return to the listing subpage. The delete is handled by an ajax call (as are edits) and the response to a successful deletion would include a location URL -- the listing page -- which the receiving JavaScript would assign to window.location.

(Just creating a custom post type wouldn't be appropriate to this application. These records are, as it happens, linked to a custom post type and these pages are subpages of that post type's admin page, but the records need to be used in a non-WordPress context in a different database with different permissions.)

It doesn't make sense to get the admin menu in the ajax call as all of the menus load on admin init. if requires ajax can explicitly include necessary menu files as we do in the other areas.

I do agree with the general principle of not loading more of WordPress than you have to for ajax calls.

Workarounds for this case require calling menu_page_url() while generating the admin page and storing the URL(s) somewhere client-side in the delivered HTML or JavaScript. E.g., set a property in the localized JavaScript (wp_localize_script) that contains the appropriate URL and return the name of that property instead of the direct URL. Could also create a hidden input or data-location property or something in the HTML, but that seems messier.

(I implemented the first option; it's working fine for me.)

Note: See TracTickets for help on using tickets.