Haciendo un plugin necesitaba hacer una petición ajax al mismo plugin, fácil si no tienes el directorio wp-admin protegido por contraseña en el servidor.
Buscando información encontré esto en WordPress Org donde básicamente te dice que cualquier petición que no se dirija al wp-admin.php ni es correcta ni es segura.

Después de varios días intentando atacar a wp-admin.php sin éxito descarté la posibilidad y seguí con mi plugin sin esa funcionalidad. Investigando otras funciones se me ocurrió la solución que se explica a continuación.
Paso 1 – Crear el plugin
Hay cientos de manuales que resumidos quedan en crear un archivo .php con el nombre igual al nombre del plugin si sólo necesitas un archivo o crear una carpeta llamada igual que el plugin y meter en ella los archivos necesarios incluido el .php con el nombre igual al nombre del plugin.
Dicho archivo debe empezar al menos con
/*
Plugin Name: ajax-sin-wp-admin
*/
En este ejemplo usaremos la carpeta sin-wp-admin con los archivos sin-wp-admin.php y sin-wp-admin.js
Paso2 – Insertar el archivo .js en la web
Ampliamos el código …
<?php
/*
Plugin Name: ajax-sin-wp-admin
*/
register_activation_hook(__FILE__, 'function_instalar');
add_action('wp_head', 'carga_script');
function function_instalar(){
/* CREA BASES DE DATOS, CREA OPTIONS, CREA TAREAS PROGRAMADAS, ...
global $wpdb;
$sql = "CREATE TABLE IF NOT EXISTS ....;";
$wpdb->query($sql);
$plugin = 'ajax-sin-wp-admin'
add_option($plugin.'_optionA', true);
add_option($plugin.'_option2', 'plugin activo');
add_option($plugin.'_xxx ...
wp_schedule_event( strtotime(date('Y-m-d ').'04:22:00'), 'daily', 'function_programada' );
*/
}
function function_programada(){
/* HACE SUS COSAS */
}
function carga_script(){
wp_enqueue_script('nombre_del_script', plugins_url('ajax-sin-wp-admin.js', __FILE__), array('jquery'));
wp_localize_script('nombre_del_script', 'variable_datos', array(
'ajaxurl' => '', // con la url vacía va al index de WordPress y la gestiona
'action' => 'function_trata_ajax',
'dato_cual' => 'otros datos',
'nonce' => wp_create_nonce('clave_validacion_nonce')
));
}
?>
Con add_action hacemos que al visitar la web se ejecute la función carga_script
Paso3 – Editamos el archivo .js
var ajax_sin_wp_admin_nonce = variable_datos.nonce;
jQuery(document).ready(function(jQuery) { // Cuando la web está cargada
if(typeof ajax_sin_wp_admin_nonce !== 'undefined'){ // Existe la clave nonce ??
// if( variable_datos.dato_cual == 'otros datos') hago_algo(); // Ejemplo
jQuery.ajax ({
type:"POST",
// dataType:'json',
url: variable_datos.ajaxurl,
data:{
action: variable_datos.action,
nonce: ajax_sin_wp_admin_nonce,
mas_datos: 'Otros_datos',
},
success:function(msg){
/* HACE SUS COSAS */
alert(msg); // por ejemplo
},
error : function(XMLHttpRequest, textStatus, errorThrown) {
/* HACE SUS COSAS */
}
});
}
});
Paso 4 – Interceptamos el envío ajax
Incluimos al principio de sin-wp-admin.php
if( isset($_POST['mas_datos']) && $_POST['mas_datos']!='' ){
add_action( 'init', 'trata_ajax');
}
Ojo!!! es importante que add_action se ejecute al inicio ‘init’.
Paso 5 – Tratamiento de los datos y respuesta ajax
La función ‘trata_ajax’ hay que hacerla muy segura puesto que puede afectar a la ejecución de WordPress si alguien hace un uso irregular de la puerta que estamos abriendo.
Se pueden comprobar más cosas como el $_SERVER[‘HTTP_REFERER’] o el número de datos que vienen en la variable $_POST[‘mas_datos’] en el mismo if del paso 4. Cuanto más datos se comprueben más seguridad.
function trata_ajax(){
check_ajax_referer('clave_validacion_nonce', 'nonce');
/* Se puede añadir más seguridad
* if( isset($_POST['dataA']) && is_array($_POST['dataA']) && count($_POST['dataA']) == 5 ){..
* /
/* HACE SUS COSAS */
echo 'He recibido con la variable mas_datos el valor "'.$_POST['mas_datos'].'"';
die(); // IMPORTANTE - paramos la ejecución normal de WordPress
}
Como seguridad básica comprobar el nonce con check_ajax_referer()
Descarga
Hemos acabado y al activar el plugin

Si visitamos la web, tenemos el alert
