Está llamando a la .pointer( 'open' );función javascript en todos los objetos de punteros, por lo que no es sorprendente que todos los punteros aparezcan al mismo tiempo ...
Dicho esto, no entiendo por qué devuelve todos los punteros (incluso los no activos) custom_admin_pointers()y luego agrega una función adicional para verificar si hay algunos punteros activos y una marca dentro del bucle de punteros ( if ( $array['active'] ) {) para elegir agregar un puntero javascript o no. ¿No es más simple devolver solo punteros activos?
Además, está agregando que javascript en todas las páginas de administración, ¿no es demasiado? También considere que algunos elementos como "# save-post" están disponibles solo en la nueva página de publicación, por lo que no es mejor agregar los punteros solo en la nueva página de bote.
Finalmente, cuán desordenado es ese javascript mezclado con PHP, creo que debería considerar usarlo wp_localize_scriptpara pasar datos a javascript.
El plan:
- Mueva las definiciones de punteros en PHP a un archivo separado, de esta manera es fácil de editar y también elimina el marcado del código PHP, todo resulta más legible y sostenible
- En los punteros de configuración añadir una propiedad "donde" que se utiliza para establecer en la que la página de administración debería aparecer una ventana emergente:
post-new.php, index.php...
- Escriba una clase que manejará la carga, el análisis y el filtrado de información de punteros
- Escriba algunas bondades de js que nos ayudarán a cambiar el botón predeterminado "Eliminar" a "Siguiente"
El # 4 puede (probablemente) hacerse fácilmente conociendo bien el complemento de puntero, pero no es mi caso. Así que usaré el código general de jQuery para obtener el resultado, si alguien puede mejorar mi código, lo agradeceré.
Editar
Edité el código (principalmente js) porque hay cosas diferentes que no había considerado: algunos punteros pueden agregarse al mismo anclaje, o los mismos punteros pueden agregarse a anclajes no existentes o no visibles. En todo ese caso, el código anterior no funcionó, la nueva versión parece abordar muy bien esos problemas.
También configuré un Gist con todo el código que solía probar.
Comencemos con los puntos 1 y 2 : cree un archivo llamado pointers.phpy escriba allí:
<?php
$pointers = array();
$pointers['new-items'] = array(
'title' => sprintf( '<h3>%s</h3>', esc_html__( 'Add New Item' ) ),
'content' => sprintf( '<p>%s</p>', esc_html__( 'Easily add a new post..' ) ),
'anchor_id' => '#wp-admin-bar-new-content',
'edge' => 'top',
'align' => 'left',
'where' => array( 'index.php', 'post-new.php' ) // <-- Please note this
);
$pointers['story_cover_help'] = array(
'title' => sprintf( '<h3>%s</h3>', esc_html__( 'Another info' ) ),
'content' => sprintf( '<p>%s</p>', esc_html__( 'Lore ipsum....' ) ),
'anchor_id' => '#save-post',
'edge' => 'top',
'align' => 'right',
'where' => array( 'post-new.php' ) // <-- Please note this
);
// more pointers here...
return $pointers;
La configuración de todos los punteros está aquí. Cuando necesite cambiar algo, simplemente abra este archivo y edítelo.
Tenga en cuenta la propiedad "donde" que es una matriz de páginas donde el puntero debería estar disponible.
Si desea mostrar punteros en una página generada por un complemento, busque esta línea que se describe a continuación public function filter( $page ) {y agregue die($page);inmediatamente debajo de ella. Luego abra la página del complemento respectivo y use esa cadena en la wherepropiedad.
Ok, ahora el punto # 3 .
Antes de escribir la clase, solo quiero codificar una interfaz: allí pondré comentarios para que pueda comprender mejor qué hará la clase.
<?php
interface PointersManagerInterface {
/**
* Load pointers from file and setup id with prefix and version.
* Cast pointers to objects.
*/
public function parse();
/**
* Remove from parse pointers dismissed ones and pointers
* that should not be shown on given page
*
* @param string $page Current admin page file
*/
public function filter( $page );
}
Creo que debería estar bastante claro. Ahora escribamos la clase, contendrá los 2 métodos de la interfaz más el constructor.
<?php namespace GM;
class PointersManager implements PointersManagerInterface {
private $pfile;
private $version;
private $prefix;
private $pointers = array();
public function __construct( $file, $version, $prefix ) {
$this->pfile = file_exists( $file ) ? $file : FALSE;
$this->version = str_replace( '.', '_', $version );
$this->prefix = $prefix;
}
public function parse() {
if ( empty( $this->pfile ) ) return;
$pointers = (array) require_once $this->pfile;
if ( empty($pointers) ) return;
foreach ( $pointers as $i => $pointer ) {
$pointer['id'] = "{$this->prefix}{$this->version}_{$i}";
$this->pointers[$pointer['id']] = (object) $pointer;
}
}
public function filter( $page ) {
if ( empty( $this->pointers ) ) return array();
$uid = get_current_user_id();
$no = explode( ',', (string) get_user_meta( $uid, 'dismissed_wp_pointers', TRUE ) );
$active_ids = array_diff( array_keys( $this->pointers ), $no );
$good = array();
foreach( $this->pointers as $i => $pointer ) {
if (
in_array( $i, $active_ids, TRUE ) // is active
&& isset( $pointer->where ) // has where
&& in_array( $page, (array) $pointer->where, TRUE ) // current page is in where
) {
$good[] = $pointer;
}
}
$count = count( $good );
if ( $good === 0 ) return array();
foreach( array_values( $good ) as $i => $pointer ) {
$good[$i]->next = $i+1 < $count ? $good[$i+1]->id : '';
}
return $good;
}
}
El código es muy simple y hace exactamente lo que la interfaz espera.
Sin embargo, la clase no hace nada por sí misma, necesitamos un gancho donde instanciar la clase y lanzar los 2 métodos pasando los argumentos adecuados.
El 'admin_enqueue_scripts'es perfecto para nuestro alcance: allí tendremos acceso a la página de administración actual y también podemos poner en cola los scripts y los estilos necesarios.
add_action( 'admin_enqueue_scripts', function( $page ) {
$file = plugin_dir_path( __FILE__ ) . 'pointers.php';
// Arguments: pointers php file, version (dots will be replaced), prefix
$manager = new PointersManager( $file, '5.0', 'custom_admin_pointers' );
$manager->parse();
$pointers = $manager->filter( $page );
if ( empty( $pointers ) ) { // nothing to do if no pointers pass the filter
return;
}
wp_enqueue_style( 'wp-pointer' );
$js_url = plugins_url( 'pointers.js', __FILE__ );
wp_enqueue_script( 'custom_admin_pointers', $js_url, array('wp-pointer'), NULL, TRUE );
// data to pass to javascript
$data = array(
'next_label' => __( 'Next' ),
'close_label' => __('Close'),
'pointers' => $pointers
);
wp_localize_script( 'custom_admin_pointers', 'MyAdminPointers', $data );
} );
Nada especial: solo usar la clase para obtener datos de punteros y si algunos punteros pasan los filtros, ponen en cola estilos y scripts. Luego, pase los datos de los punteros al script a la etiqueta "Siguiente" localizada para el botón.
Ok, ahora la parte "más difícil": el js. Una vez más, quiero resaltar que no conozco el plugin de puntero que usa WordPress, por lo que lo que hago en mi código se puede hacer mejor si alguien lo sabe, sin embargo, mi código hace su trabajo y, en términos generales, no es tan malo.
( function($, MAP) {
$(document).on( 'MyAdminPointers.setup_done', function( e, data ) {
e.stopImmediatePropagation();
MAP.setPlugin( data ); // open first popup
} );
$(document).on( 'MyAdminPointers.current_ready', function( e ) {
e.stopImmediatePropagation();
MAP.openPointer(); // open a popup
} );
MAP.js_pointers = {}; // contain js-parsed pointer objects
MAP.first_pointer = false; // contain first pointer anchor jQuery object
MAP.current_pointer = false; // contain current pointer jQuery object
MAP.last_pointer = false; // contain last pointer jQuery object
MAP.visible_pointers = []; // contain ids of pointers whose anchors are visible
MAP.hasNext = function( data ) { // check if a given pointer has valid next property
return typeof data.next === 'string'
&& data.next !== ''
&& typeof MAP.js_pointers[data.next].data !== 'undefined'
&& typeof MAP.js_pointers[data.next].data.id === 'string';
};
MAP.isVisible = function( data ) { // check if anchor for given pointer is visible
return $.inArray( data.id, MAP.visible_pointers ) !== -1;
};
// given a pointer object, return its the anchor jQuery object if available
// otherwise return first available, lookin at next property of subsequent pointers
MAP.getPointerData = function( data ) {
var $target = $( data.anchor_id );
if ( $.inArray(data.id, MAP.visible_pointers) !== -1 ) {
return { target: $target, data: data };
}
$target = false;
while( MAP.hasNext( data ) && ! MAP.isVisible( data ) ) {
data = MAP.js_pointers[data.next].data;
if ( MAP.isVisible( data ) ) {
$target = $(data.anchor_id);
}
}
return MAP.isVisible( data )
? { target: $target, data: data }
: { target: false, data: false };
};
// take pointer data and setup pointer plugin for anchor element
MAP.setPlugin = function( data ) {
if ( typeof MAP.last_pointer === 'object') {
MAP.last_pointer.pointer('destroy');
MAP.last_pointer = false;
}
MAP.current_pointer = false;
var pointer_data = MAP.getPointerData( data );
if ( ! pointer_data.target || ! pointer_data.data ) {
return;
}
$target = pointer_data.target;
data = pointer_data.data;
$pointer = $target.pointer({
content: data.title + data.content,
position: { edge: data.edge, align: data.align },
close: function() {
// open next pointer if it exists
if ( MAP.hasNext( data ) ) {
MAP.setPlugin( MAP.js_pointers[data.next].data );
}
$.post( ajaxurl, { pointer: data.id, action: 'dismiss-wp-pointer' } );
}
});
MAP.current_pointer = { pointer: $pointer, data: data };
$(document).trigger( 'MyAdminPointers.current_ready' );
};
// scroll the page to current pointer then open it
MAP.openPointer = function() {
var $pointer = MAP.current_pointer.pointer;
if ( ! typeof $pointer === 'object' ) {
return;
}
$('html, body').animate({ // scroll page to pointer
scrollTop: $pointer.offset().top - 30
}, 300, function() { // when scroll complete
MAP.last_pointer = $pointer;
var $widget = $pointer.pointer('widget');
MAP.setNext( $widget, MAP.current_pointer.data );
$pointer.pointer( 'open' ); // open
});
};
// if there is a next pointer set button label to "Next", to "Close" otherwise
MAP.setNext = function( $widget, data ) {
if ( typeof $widget === 'object' ) {
var $buttons = $widget.find('.wp-pointer-buttons').eq(0);
var $close = $buttons.find('a.close').eq(0);
$button = $close.clone(true, true).removeClass('close');
$buttons.find('a.close').remove();
$button.addClass('button').addClass('button-primary');
has_next = false;
if ( MAP.hasNext( data ) ) {
has_next_data = MAP.getPointerData(MAP.js_pointers[data.next].data);
has_next = has_next_data.target && has_next_data.data;
}
var label = has_next ? MAP.next_label : MAP.close_label;
$button.html(label).appendTo($buttons);
}
};
$(MAP.pointers).each(function(index, pointer) { // loop pointers data
if( ! $().pointer ) return; // do nothing if pointer plugin isn't available
MAP.js_pointers[pointer.id] = { data: pointer };
var $target = $(pointer.anchor_id);
if ( $target.length && $target.is(':visible') ) { // anchor exists and is visible?
MAP.visible_pointers.push(pointer.id);
if ( ! MAP.first_pointer ) {
MAP.first_pointer = pointer;
}
}
if ( index === ( MAP.pointers.length - 1 ) && MAP.first_pointer ) {
$(document).trigger( 'MyAdminPointers.setup_done', MAP.first_pointer );
}
});
} )(jQuery, MyAdminPointers); // MyAdminPointers is passed by `wp_localize_script`
Con la ayuda de los comentarios, el código debería ser bastante claro, al menos, eso espero.
Ok ya hemos terminado. Nuestro PHP es más simple y está mejor organizado, nuestro javascript es más legible, los punteros son más fáciles de editar y, lo que es más importante, todo funciona.