¿Cómo hacer que el estado de metaboxes abierto / cerrado y oculto / mostrado se guarde por publicación?

9

Mi verdadero problema es un poco complejo, así que intentaré resumirlo y mantenerlo simple.

Estoy trabajando en una aplicación personalizada basada en WordPress. Registré un tipo de publicación personalizada, llamémosla "personas" donde almaceno información sobre ... personas.

El CPT solo admite campos predeterminados de título y contenido de publicación, pero hay algunos metaboxes para almacenar información de personas (piense en mi aplicación como una libreta de direcciones).

Entonces, hay un metabox para almacenar información personal, uno para almacenar información de redes sociales, otro para almacenar información relacionada con el trabajo, es decir, si esa persona es para mí un cliente, un proveedor, si tenemos créditos o débitos ...

Simplifiqué aquí, pero hay una cantidad constante de metaboxes, digamos 12.

Mi problema es que algunas personas para las que quiero almacenar información son solo contactos aleatorios, y quiero almacenar solo información personal, otras son amigos y quiero almacenar información personal y de redes sociales, otras son clientes o proveedores y yo desea almacenar información relacionada con el trabajo.

Si al editar una publicación me oculto (a través del menú de opciones de pantalla ) o cierro cualquier metabox que no necesito, cuando abro otra publicación donde la necesito, tengo que mostrarla o abrirla nuevamente. Esto se debe a que la posición / estado / orden de los metaboxes se guardan por usuario como metadatos de usuario .

Si te imaginas en algunas publicaciones que necesito 2 metaboxes, en algunas 10 y en algunas 5, entiendes que es molesto porque mantener todas ellas mostradas / abiertas hace que la pantalla de edición sea accesible (la barra de desplazamiento parece interminable), y a veces la información que busco es al final de la página después de un montón de metaboxes sin información ...

Pregunta:

¿Es posible guardar la posición / estado / orden de metaboxes por publicación para un tipo de publicación específico?


PD: Sé que algunos js / jQuery pueden resolver el problema, pero si fuera posible evitaría las soluciones de JavaScript.

gmazzap
fuente

Respuestas:

8

El problema principal:

El principal problema aquí es que en las llamadas ajax de cierre , ocultamiento y pedido , no se envía una identificación de publicación con la carga útil. Aquí hay dos ejemplos de datos de formulario:

1) action:closed-postboxes
closed:formatdiv,tagsdiv-post_tag,trackbacksdiv,authordiv
hidden:slugdiv
closedpostboxesnonce:8723ee108f
page:post

2) action:meta-box-order
_ajax_nonce:b6b48d2d16
page_columns:2
page:post
order[side]:submitdiv,formatdiv,categorydiv,tagsdiv-post_tag,postimagediv
order[normal]:postexcerpt,postcustom,trackbacksdiv,commentsdiv,authordiv
order[advanced]:

Podríamos evitar esto usando otra llamada ajax personalizada.

Por supuesto, podríamos simplemente conectarlo save_posty modificar los datos cada vez que se guarde la publicación. Pero esa no es la experiencia normal de UI, así que eso no se considera aquí

Hay otra solución no elegante disponible con PHP, que se describe a continuación:

Una solución no Javascript:

La pregunta es ¿dónde almacenar los datos? Como metadatos de usuario , publicar metadatos o tal vez en una tabla personalizada?

Aquí lo almacenamos como metadatos de usuario y tomamos el cierre de metacajas posteriores como un ejemplo.

Cuando closedpostboxes_postse actualiza el metavalor, también lo guardamos en el closedpostboxes_post_{post_id}metavalor.

Luego secuestramos la obtención de closedpostboxes_postpara anularlo con el metavalor correspondiente en función del ID de usuario y el ID de publicación.

a) Actualización durante la closed-postboxesacción ajax:

Podemos obtener la ID de la publicación, a través de wp_get_referer()y luego usar la url_to_postid()función práctica . Conocí por primera vez esta función "divertida" después de leer la respuesta de @s_ha_dum , hace unos meses ;-) Desafortunadamente, la función no reconoce las ?post=123variables GET, pero podemos hacer un pequeño truco simplemente cambiándola para evitarlap=123 .

Podemos conectarnos updated_user_meta, eso se dispara justo después de que los metadatos del usuario closedpostboxes_postse hayan actualizado:

add_action( 'updated_user_meta',                           
    function ( $meta_id, $object_id, $meta_key, $_meta_value )
    {
        $post_id = url_to_postid( str_replace( 'post=', 'p=', wp_get_referer() ) );
        if( 'closedpostboxes_post' === $meta_key && $post_id > 0 )
            update_user_meta( 
                $object_id, 
                'closedpostboxes_post_' . $post_id, 
                $_meta_value 
            );
    }
, 10, 4 );

b) Obteniendo datos:

Podemos conectarnos al get_user_option_closedpostboxes_postgancho para modificar los datos obtenidos del closedpostboxes_postmeta del usuario:

add_filter( 'get_user_option_closedpostboxes_post',
    function ( $result, $option, $user )
    {
        $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_NUMBER_INT );
        $newresult = get_user_option( 'closedpostboxes_post_'. $post_id , $user->ID );
        return ( $newresult ) ? $newresult : $result;
    }
, 10, 3 );

También podríamos querer pensar en el caso en el que no hay publicaciones basadas closedpostboxes_post_{post_id}disponibles. Por lo tanto, utilizará la última configuración guardada de closedpostboxes_post. Tal vez desee tenerlo todo abierto o todo cerrado, en ese caso predeterminado. Sería fácil modificar este comportamiento.

Para otros tipos de publicaciones personalizadas podemos usar el closedpostboxes_{post_type}gancho correspondiente .

Lo mismo debería ser posible para ordenar y ocultar metaboxes con el meta metaboxhidden_{post_type}y el meta-box-order_{post_data}usuario.

PD: lo siento por esta respuesta demasiado larga de fin de semana, ya que siempre deben ser cortos y alegres ;-)

Birgire
fuente
Genial +1. N / P para una respuesta larga, no esperaría respuestas cortas. Para ser honesto, no esperaba ninguno el fin de semana :) Dos cosas que me gustaron mucho: Primero, la idea de almacenar datos por usuario y por publicación: mi idea era almacenar en la meta meta, pero de esa manera todo Los usuarios tendrán el mismo estado. Segundo, la idea de usar 'get_user_option_*_post'para hacer que WP reconozca datos personalizados. Solo creo que no me gusta demasiado es el uso de wp_get_referereso realmente en $_SERVERvar que no es realmente confiable, pero creo que tengo una idea para superar el "problema principal";)
gmazzap
Gracias, supongo que depende de la cantidad de usuarios y publicaciones donde sería mejor almacenar los datos. ¿Quizás estos datos deberían tener un poco de TTL y borrarse, por ejemplo, una vez al mes? Sí, estoy de acuerdo con usted con respecto al wp_get_referer()método, por eso lo llamé una solución PHP no elegante ;-) Primero pensé en almacenar la identificación de publicación actual para cada usuario, pero eso no funciona si un usuario está editando dos o más Publicaciones en el navegador. Esperamos escuchar acerca de su idea sobre el "problema principal" disfrutar el fin de semana ;-)
birgire
Después de 43 días, un voto positivo me recuerda responder esto. Gracias de nuevo por su respuesta.
gmazzap
6

Como señaló birgire en su respuesta , WordPress usa AJAX para actualizar el estado de metaboxes y los datos pasados ​​en la solicitud de AJAX no incluyen identificación de publicación, y eso dificulta la actualización del estado de las cajas por publicación.

Una vez que encontré la acción AJAX utilizada por WordPress 'closed-postboxes', busqué esta cadena en la carpeta admin js para encontrar cómo WordPress realiza la solicitud AJAX.

Encontré que sucede postbox.jsen la línea # 118 .

Se ve así:

save_state : function(page) {
  var closed = $('.postbox').filter('.closed').map(function() {
      return this.id;
    }).get().join(',');
  var hidden = $('.postbox').filter(':hidden').map(function() {
      return this.id;
    }).get().join(',');
  $.post(ajaxurl, {
    action: 'closed-postboxes',
    closed: closed,
    hidden: hidden,
    closedpostboxesnonce: jQuery('#closedpostboxesnonce').val(),
    page: page
  });
}

Esencialmente, WordPress mira los elementos DOM con la clase 'postbox' y la clase 'cerrada' y crea una lista separada por comas de sus ID. Lo mismo se hace para los elementos DOM ocultos con la clase 'postbox'.

Entonces, mi pensamiento fue: puedo crear un metabox falso que tenga las clases correctas y que esté oculto, configurando su ID para que contenga ID de publicación, y de esta manera puedo recuperarlo en una solicitud AJAX.

Esto es lo que he hecho:

add_action( 'dbx_post_sidebar', function() {
    global $post;
    if ( $post->post_type === 'mycpt' ) {
        $id = $post->ID;
        $f = '<span id="fakebox_pid_%d" class="postbox closed" style="display:none;"></span>';
        printf( $f, $id );
    }
});

De esta manera, creé un metabox que siempre está cerrado y siempre oculto, por lo que WordPress enviará su ID como $_POSTvar en la solicitud de AJAX, y una vez que la identificación de la caja falsa contenga la ID de la publicación de una manera predecible, puedo reconocer la publicación.

Después de eso, miré cómo WordPress realiza la tarea AJAX.

En la admin-ajax.phplínea 72 , WordPress engancha 'wp_ajax_closed-postboxes'con prioridad 1.

Entonces, para actuar antes de WordPress, podría conectar la misma acción con prioridad 0.

add_action( 'wp_ajax_closed-postboxes', function() {

    // check if we are in right post type: WordPress passes it in 'page' post var
    $page = filter_input( INPUT_POST, 'page', FILTER_SANITIZE_STRING );
    if ( $page !== 'mycpt' ) return;

    // get post data
    $data = filter_input_array( INPUT_POST, array(
        'closed' => array( 'filter' => FILTER_SANITIZE_STRING ),
        'hidden' => array( 'filter' => FILTER_SANITIZE_STRING )
    ) );

    // search among closed boxes for the "fake" one, and return if not found
    $look_for_fake = array_filter( explode( ',', $data[ 'closed' ] ), function( $id ) {
         return strpos( $id, 'fakebox_pid_' ) === 0;
    } );
    if ( empty( $look_for_fake ) ) return;

    $post_id = str_replace( 'fakebox_pid_', '', $look_for_fake[0] );
    $user_id = get_current_user_id();

    // remove fake id from values
    $closed = implode(',', array_diff( explode(',', $data['closed'] ), $look_for_fake ) );
    $hidden = implode(',', array_diff( explode(',', $data['hidden'] ), $look_for_fake ) );

    // save metabox status on a per-post and per-user basis in a post meta
    update_post_meta( $post_id, "_mycpt_closed_boxes_{user_id}", $closed );
    update_post_meta( $post_id, "_mycpt_hidden_boxes_{user_id}", $hidden );

}, 0 );

Tener los datos guardados en una publicación meta permitió filtrar get_user_option_closedpostboxes_mycpty get_user_option_metaboxhidden_mycpt(ambas variaciones del get_user_option_{$option}filtro) forzar las opciones de carga de WordPress desde la publicación meta:

add_filter( 'get_user_option_closedpostboxes_mycpt', function ( $result, $key, $user ) {
    global $post;
    $meta = get_post_meta( $post->ID, "_mycpt_closed_boxes_{$user->ID}", TRUE );
    if ( ! empty( $meta ) ) {
        $result = $meta;
    }
    return $result;
}, 10, 3 );

y

add_filter( 'get_user_option_metaboxhidden_mycpt', function ( $result, $key, $user ) {
    global $post;
    $meta = get_post_meta( $post->ID, "_mycpt_hidden_boxes_{$user->ID}", TRUE );
    if ( ! empty( $meta ) ) {
        $result = $meta;
    }
    return $result;
}, 10, 3 );
gmazzap
fuente
Qué gran idea usar un metabox oculto con la información relevante +1
birgire
gracias @birgire y gracias nuevamente por tu A, la idea de guardar datos tanto por usuario como por publicación es toda tuya :)
gmazzap