aed764c130
CPT geep_map + shortcode [geep_maps] para la sección Architecture of Genocide de la home: acordeón exclusivo con imagen por mapa, lightbox con botón de compartir y enlaces #map-<slug>. Soporte WPML (EN/AR) con herencia de la imagen del idioma original y CSS RTL.
326 lines
11 KiB
PHP
326 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: FP Genocide Maps
|
|
* Plugin URI: https://freepress.coop
|
|
* Description: Manages the "Architecture of Genocide" maps shown on the home page. Each map is a post (title + description + image); the [geep_maps] shortcode renders the accordion + map image replicating the original Divi design, swapping the image when a toggle is opened.
|
|
* Version: 1.5.2
|
|
* Author: Freepress Coop
|
|
* Author URI: https://www.freepress.coop
|
|
* Requires PHP: 7.4
|
|
* License: GPL-2.0-or-later
|
|
* Text Domain: fp-genocide-maps
|
|
*
|
|
* @package FP_Genocide_Maps
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
const FPGM_CPT = 'geep_map';
|
|
const FPGM_VERSION = '1.5.2';
|
|
|
|
/**
|
|
* Register the Map custom post type (admin-only, not publicly queryable).
|
|
*/
|
|
function fpgm_register_cpt() {
|
|
register_post_type(
|
|
FPGM_CPT,
|
|
array(
|
|
'labels' => array(
|
|
'name' => __( 'Genocide Maps', 'fp-genocide-maps' ),
|
|
'singular_name' => __( 'Map', 'fp-genocide-maps' ),
|
|
'add_new' => __( 'Add New Map', 'fp-genocide-maps' ),
|
|
'add_new_item' => __( 'Add New Map', 'fp-genocide-maps' ),
|
|
'edit_item' => __( 'Edit Map', 'fp-genocide-maps' ),
|
|
'new_item' => __( 'New Map', 'fp-genocide-maps' ),
|
|
'view_item' => __( 'View Map', 'fp-genocide-maps' ),
|
|
'search_items' => __( 'Search Maps', 'fp-genocide-maps' ),
|
|
'not_found' => __( 'No maps found', 'fp-genocide-maps' ),
|
|
'featured_image' => __( 'Map image', 'fp-genocide-maps' ),
|
|
'set_featured_image' => __( 'Select map image', 'fp-genocide-maps' ),
|
|
),
|
|
'public' => false,
|
|
'show_ui' => true,
|
|
'show_in_menu' => true,
|
|
'show_in_rest' => false,
|
|
'exclude_from_search' => true,
|
|
'menu_icon' => 'dashicons-location-alt',
|
|
'menu_position' => 21,
|
|
'supports' => array( 'title', 'editor', 'thumbnail', 'page-attributes' ),
|
|
'hierarchical' => false,
|
|
)
|
|
);
|
|
}
|
|
add_action( 'init', 'fpgm_register_cpt' );
|
|
|
|
/**
|
|
* Front-end assets. The stylesheet replicates the exact Divi-generated design
|
|
* of the original section, so it is tiny and safe to load site-wide.
|
|
*/
|
|
function fpgm_enqueue_assets() {
|
|
wp_enqueue_style( 'fpgm-maps', plugins_url( 'assets/css/geep-maps.css', __FILE__ ), array(), FPGM_VERSION );
|
|
wp_enqueue_script( 'fpgm-maps', plugins_url( 'assets/js/geep-maps.js', __FILE__ ), array(), FPGM_VERSION, true );
|
|
}
|
|
add_action( 'wp_enqueue_scripts', 'fpgm_enqueue_assets' );
|
|
|
|
/**
|
|
* [geep_maps] — image column + accordion column, mirroring the original
|
|
* "texto mapa" Divi row (columns 14/24 and 2/5, 2% gap).
|
|
*/
|
|
function fpgm_shortcode() {
|
|
$maps = get_posts(
|
|
array(
|
|
'post_type' => FPGM_CPT,
|
|
'posts_per_page' => -1,
|
|
'orderby' => array(
|
|
'menu_order' => 'ASC',
|
|
'date' => 'ASC',
|
|
),
|
|
'order' => 'ASC',
|
|
'suppress_filters' => false, // Let WPML return the current language.
|
|
)
|
|
);
|
|
|
|
if ( empty( $maps ) ) {
|
|
return '';
|
|
}
|
|
|
|
// Map images are language-independent: when a translation has no image of
|
|
// its own, fall back to the original post's image (e.g. Arabic maps show
|
|
// the image assigned to the English one). Setting an image on the
|
|
// translation still overrides this.
|
|
$get_thumb = function ( $post_id ) {
|
|
$thumb = get_post_thumbnail_id( $post_id );
|
|
if ( ! $thumb ) {
|
|
$original = apply_filters( 'wpml_original_element_id', null, $post_id, 'post_' . FPGM_CPT );
|
|
if ( $original && (int) $original !== (int) $post_id ) {
|
|
$thumb = get_post_thumbnail_id( (int) $original );
|
|
}
|
|
}
|
|
return $thumb;
|
|
};
|
|
|
|
// The first map starts open, so its image (if it has one) is the one
|
|
// visible on load. Maps without an image simply leave the gap empty.
|
|
$first_thumb = $get_thumb( $maps[0]->ID );
|
|
$active_key = $first_thumb ? (string) $first_thumb : '';
|
|
|
|
// One image per map that has one. The visible size tops out around 660px
|
|
// (58% of the 90%-wide row), so tell the browser it never needs the
|
|
// full-size file: with `sizes` it picks the smallest sufficient variant.
|
|
$sizes = '(max-width: 767px) 100vw, 50vw';
|
|
|
|
$images_html = '';
|
|
$toggles_html = '';
|
|
$first = true;
|
|
$rendered_keys = array();
|
|
foreach ( $maps as $map ) {
|
|
$thumb_id = $get_thumb( $map->ID );
|
|
$map_key = '';
|
|
|
|
if ( $thumb_id ) {
|
|
$map_key = (string) $thumb_id;
|
|
if ( ! in_array( $map_key, $rendered_keys, true ) ) {
|
|
$rendered_keys[] = $map_key;
|
|
$images_html .= wp_get_attachment_image(
|
|
$thumb_id,
|
|
'full',
|
|
false,
|
|
array(
|
|
'class' => 'geep-map-img' . ( $map_key === $active_key ? ' is-active is-shown' : '' ),
|
|
'data-map' => $map_key,
|
|
'loading' => 'lazy',
|
|
'sizes' => $sizes,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
$toggles_html .= sprintf(
|
|
'<div class="geep-toggle %s%s" data-map="%s" data-slug="%s">' .
|
|
'<h4 class="geep-toggle-title">%s</h4>' .
|
|
'<div class="geep-toggle-content">%s</div>' .
|
|
'</div>',
|
|
$first ? 'geep-toggle--open' : 'geep-toggle--close',
|
|
$first ? ' geep-toggle--first' : '',
|
|
esc_attr( $map_key ),
|
|
esc_attr( urldecode( $map->post_name ) ), // Readable form (Arabic slugs are stored percent-encoded).
|
|
esc_html( get_the_title( $map ) ),
|
|
wp_kses_post( wpautop( $map->post_content ) )
|
|
);
|
|
$first = false;
|
|
}
|
|
|
|
// UI labels for the lightbox, in the language of the current page.
|
|
$is_ar = 'ar' === apply_filters( 'wpml_current_language', null );
|
|
$labels = array(
|
|
'share' => $is_ar ? 'مشاركة الخريطة' : 'Share map',
|
|
'copied' => $is_ar ? 'تم نسخ الرابط' : 'Link copied!',
|
|
'close' => $is_ar ? 'إغلاق' : 'Close',
|
|
'zoom' => $is_ar ? 'عرض الخريطة بحجم أكبر' : 'View map larger',
|
|
);
|
|
|
|
return '<div class="geep-maps" data-labels="' . esc_attr( wp_json_encode( $labels ) ) . '">' .
|
|
'<div class="geep-maps-col-map"><div class="geep-maps-image"><span class="geep-maps-image-wrap" role="button" tabindex="0" aria-label="' . esc_attr( $labels['zoom'] ) . '">' . $images_html . '</span></div></div>' .
|
|
'<div class="geep-maps-col-toggles">' . $toggles_html . '</div>' .
|
|
'</div>';
|
|
}
|
|
add_shortcode( 'geep_maps', 'fpgm_shortcode' );
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Admin: list-table columns and default-image setting.
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* Add image + order columns to the Maps list table.
|
|
*
|
|
* @param array $columns Existing columns.
|
|
* @return array
|
|
*/
|
|
function fpgm_admin_columns( $columns ) {
|
|
$new = array();
|
|
foreach ( $columns as $key => $label ) {
|
|
if ( 'title' === $key ) {
|
|
$new['fpgm_image'] = __( 'Map image', 'fp-genocide-maps' );
|
|
}
|
|
$new[ $key ] = $label;
|
|
}
|
|
$new['fpgm_order'] = __( 'Order', 'fp-genocide-maps' );
|
|
return $new;
|
|
}
|
|
add_filter( 'manage_' . FPGM_CPT . '_posts_columns', 'fpgm_admin_columns' );
|
|
|
|
/**
|
|
* Render the custom columns.
|
|
*
|
|
* @param string $column Column key.
|
|
* @param int $post_id Post ID.
|
|
*/
|
|
function fpgm_admin_columns_content( $column, $post_id ) {
|
|
if ( 'fpgm_image' === $column ) {
|
|
if ( has_post_thumbnail( $post_id ) ) {
|
|
echo get_the_post_thumbnail( $post_id, array( 80, 60 ), array( 'style' => 'border-radius:4px;height:auto;' ) );
|
|
} else {
|
|
echo '<span style="color:#a00;">' . esc_html__( 'No image', 'fp-genocide-maps' ) . '</span>';
|
|
}
|
|
} elseif ( 'fpgm_order' === $column ) {
|
|
echo (int) get_post_field( 'menu_order', $post_id );
|
|
}
|
|
}
|
|
add_action( 'manage_' . FPGM_CPT . '_posts_custom_column', 'fpgm_admin_columns_content', 10, 2 );
|
|
|
|
/**
|
|
* Order the admin list by menu_order, like the front end.
|
|
*
|
|
* @param WP_Query $query Current query.
|
|
*/
|
|
function fpgm_admin_order( $query ) {
|
|
if ( is_admin() && $query->is_main_query() && FPGM_CPT === $query->get( 'post_type' ) && ! $query->get( 'orderby' ) ) {
|
|
$query->set( 'orderby', 'menu_order' );
|
|
$query->set( 'order', 'ASC' );
|
|
}
|
|
}
|
|
add_action( 'pre_get_posts', 'fpgm_admin_order' );
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Activation: seed the current 8 maps (EN + AR, linked in WPML).
|
|
* ---------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* Create the initial maps from the content that used to live in the Divi
|
|
* toggles, once, on first activation.
|
|
*/
|
|
function fpgm_activate() {
|
|
fpgm_register_cpt();
|
|
|
|
// Register the CPT as translatable in WPML *before* seeding, otherwise
|
|
// wpml_set_element_language_details silently ignores the posts and the
|
|
// AR/EN pairs never get linked. Mode 2 = translatable, display the
|
|
// original when no translation exists (same as wpml-config.xml).
|
|
global $sitepress;
|
|
if ( $sitepress && method_exists( $sitepress, 'get_setting' ) ) {
|
|
$sync = (array) $sitepress->get_setting( 'custom_posts_sync_option', array() );
|
|
$sync[ FPGM_CPT ] = 2;
|
|
$sitepress->set_setting( 'custom_posts_sync_option', $sync, true );
|
|
}
|
|
|
|
$existing = get_posts(
|
|
array(
|
|
'post_type' => FPGM_CPT,
|
|
'posts_per_page' => 1,
|
|
'post_status' => 'any',
|
|
'suppress_filters' => true,
|
|
)
|
|
);
|
|
if ( ! empty( $existing ) ) {
|
|
return; // Already seeded.
|
|
}
|
|
|
|
$file = __DIR__ . '/includes/seed-data.json';
|
|
if ( ! file_exists( $file ) ) {
|
|
return;
|
|
}
|
|
$data = json_decode( file_get_contents( $file ), true );
|
|
if ( empty( $data['en'] ) ) {
|
|
return;
|
|
}
|
|
|
|
$has_wpml = has_filter( 'wpml_element_trid' );
|
|
|
|
foreach ( $data['en'] as $i => $item ) {
|
|
$en_id = wp_insert_post(
|
|
array(
|
|
'post_type' => FPGM_CPT,
|
|
'post_status' => 'publish',
|
|
'post_title' => wp_strip_all_tags( $item['title'] ),
|
|
'post_content' => $item['content'],
|
|
'menu_order' => $i + 1,
|
|
)
|
|
);
|
|
if ( ! $en_id || is_wp_error( $en_id ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( $has_wpml ) {
|
|
$trid = apply_filters( 'wpml_element_trid', null, $en_id, 'post_' . FPGM_CPT );
|
|
do_action(
|
|
'wpml_set_element_language_details',
|
|
array(
|
|
'element_id' => $en_id,
|
|
'element_type' => 'post_' . FPGM_CPT,
|
|
'trid' => $trid,
|
|
'language_code' => 'en',
|
|
'source_language_code' => null,
|
|
)
|
|
);
|
|
|
|
// Arabic counterpart (same position in the list).
|
|
if ( ! empty( $data['ar'][ $i ]['title'] ) ) {
|
|
$ar_id = wp_insert_post(
|
|
array(
|
|
'post_type' => FPGM_CPT,
|
|
'post_status' => 'publish',
|
|
'post_title' => wp_strip_all_tags( $data['ar'][ $i ]['title'] ),
|
|
'post_content' => $data['ar'][ $i ]['content'],
|
|
'menu_order' => $i + 1,
|
|
)
|
|
);
|
|
if ( $ar_id && ! is_wp_error( $ar_id ) ) {
|
|
do_action(
|
|
'wpml_set_element_language_details',
|
|
array(
|
|
'element_id' => $ar_id,
|
|
'element_type' => 'post_' . FPGM_CPT,
|
|
'trid' => $trid,
|
|
'language_code' => 'ar',
|
|
'source_language_code' => 'en',
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
register_activation_hook( __FILE__, 'fpgm_activate' );
|