2026-07-17 12:19:41 +02:00
< ? 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.
2026-07-17 18:02:33 +02:00
* Version: 1.6.0
2026-07-17 12:19:41 +02:00
* 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' ;
2026-07-17 18:02:33 +02:00
const FPGM_VERSION = '1.6.0' ;
2026-07-17 12:19:41 +02:00
/**
* 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 (
2026-07-17 18:02:33 +02:00
'copy' => $is_ar ? 'نسخ رابط الخريطة' : 'Copy map URL' ,
2026-07-17 12:19:41 +02:00
'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' );