Description
Découvrez l’ELITE 10 en vidéo
<?php
/**
* ============================================================================
* EDR GALLERY — Galerie mosaïque zoomable pour WooCommerce
* ============================================================================
* Auteur : MOOVIKA / EDR Auto
* Usage : À coller dans un snippet "Code Snippets" (exécution : Partout / PHP).
*
* Fonctions :
* - Metabox sur la fiche produit pour ajouter des photos (médiathèque WP)
* - Rotation par photo (0 / 90 / 180 / 270°) modifiable dans l'admin
* - Réordonnancement (◀ ▶) et suppression par photo
* - Affichage front en mosaïque 6 colonnes (autant de lignes que nécessaire)
* - Vignettes rectangulaires 60/40 (réglable), ombre portée, survol grisé + loupe
* - Pagination auto au-delà de N lignes (défaut 10) avec compteur « 1 / 2 »
* - Responsive : sur mobile (≤768px) colonnes ÷2 et lignes/page ÷2 automatiquement
* - Zoom plein écran (lightbox maison, navigation clavier)
* - Filigrane "EDRAUTO.FR" : motif répété sur les vignettes, UN SEUL centré au zoom
*
* Affichage :
* - Shortcode : [edr_gallery] (produit courant, 6 col · 60/40)
* - Options : [edr_gallery id="123" cols="6" gap="6" ratio="60/40" rows="10" opacity="0.4" watermark="EDRAUTO.FR"]
* - Elementor : insérer un widget "Shortcode" avec [edr_gallery]
* - Auto : décommenter le hook tout en bas pour l'insérer automatiquement
* ============================================================================
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
/* ============================================================================
* 1) ADMIN — Chargement de la médiathèque sur la fiche produit
* ========================================================================== */
add_action( 'admin_enqueue_scripts', function () {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( $screen && 'product' === $screen->post_type ) {
wp_enqueue_media();
wp_enqueue_script( 'jquery-ui-sortable' ); // glisser-déposer des photos
}
} );
/* ============================================================================
* 2) ADMIN — Metabox sur la fiche produit
* ========================================================================== */
add_action( 'add_meta_boxes', function () {
add_meta_box(
'edr_gallery_metabox',
'📸 Galerie mosaïque EDR',
'edr_gallery_metabox_render',
'product',
'normal',
'default'
);
} );
function edr_gallery_metabox_render( $post ) {
// Lecture des données stockées (tableau de {id, rotation})
$stored = json_decode( get_post_meta( $post->ID, '_edr_gallery_data', true ), true );
if ( ! is_array( $stored ) ) {
$stored = array();
}
// On reconstruit un tableau avec l'URL miniature fraîche pour l'aperçu admin
$init = array();
foreach ( $stored as $it ) {
$id = isset( $it['id'] ) ? absint( $it['id'] ) : 0;
$thumb = $id ? wp_get_attachment_image_url( $id, 'thumbnail' ) : '';
if ( ! $thumb ) {
continue; // image supprimée de la médiathèque
}
$init[] = array(
'id' => $id,
'rotation' => isset( $it['rotation'] ) ? intval( $it['rotation'] ) : 0,
'thumb' => $thumb,
);
}
wp_nonce_field( 'edr_gallery_save', 'edr_gallery_nonce' );
?>
<div id="edr-gallery-wrap">
<input type="hidden" id="edr_gallery_data" name="edr_gallery_data" value="">
<div id="edr-gallery-items" class="edr-admin-grid"></div>
<p style="margin-top:12px;">
<button type="button" class="button button-primary button-large" id="edr-gallery-add">
+ Ajouter des photos
</button>
<span class="description" style="margin-left:10px;">
✋ glisser-déposer pour réordonner · ↻ pivoter · ◀ ▶ déplacer · ✕ retirer
</span>
</p>
</div>
<style>
.edr-admin-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:12px; margin-top:8px; }
.edr-admin-grid:empty::before{ content:"Aucune photo pour le moment."; color:#777; font-style:italic; }
.edr-item{ position:relative; border:1px solid #dcdcde; border-radius:6px; overflow:hidden; background:#f6f7f7; }
.edr-item .edr-thumb{ width:100%; aspect-ratio:1/1; object-fit:cover; display:block; transition:transform .2s ease; cursor:move; }
.edr-item .edr-actions{ display:flex; gap:2px; padding:4px; background:#fff; border-top:1px solid #eee; }
.edr-item .edr-actions .button{ flex:1; min-height:24px; padding:0 4px; font-size:11px; line-height:22px; }
.edr-item .edr-rot-badge{ position:absolute; top:4px; right:4px; background:rgba(0,0,0,.7); color:#fff; font-size:10px; padding:1px 5px; border-radius:3px; }
/* Glisser-déposer */
.edr-item.ui-sortable-helper{ box-shadow:0 6px 18px rgba(0,0,0,.25); opacity:.95; }
.edr-item-placeholder{ border:2px dashed #2271b1; border-radius:6px; background:#f0f6fc; visibility:visible !important; }
</style>
<script>
( function ( $ ) {
var edrInit = <?php echo wp_json_encode( $init ); ?>;
var data = Array.isArray( edrInit ) ? edrInit.slice() : [];
function sync() {
// On ne conserve que id + rotation à l'enregistrement
var out = data.map( function ( it ) {
return { id: it.id, rotation: it.rotation };
} );
$( '#edr_gallery_data' ).val( JSON.stringify( out ) );
}
function render() {
var $c = $( '#edr-gallery-items' ).empty();
data.forEach( function ( it, idx ) {
var $item = $(
'<div class="edr-item" data-idx="' + idx + '">' +
'<span class="edr-rot-badge">' + it.rotation + '°</span>' +
'<img class="edr-thumb" src="' + it.thumb + '" style="transform:rotate(' + it.rotation + 'deg);">' +
'<div class="edr-actions">' +
'<button type="button" class="button edr-rot" title="Pivoter">↻</button>' +
'<button type="button" class="button edr-left" title="Déplacer à gauche">◀</button>' +
'<button type="button" class="button edr-right" title="Déplacer à droite">▶</button>' +
'<button type="button" class="button edr-del" title="Retirer">✕</button>' +
'</div>' +
'</div>'
);
$c.append( $item );
} );
sync();
initSortable();
}
// Glisser-déposer pour réordonner les photos
function initSortable() {
if ( ! $.fn.sortable ) { return; } // sécurité si jQuery UI absent
var $c = $( '#edr-gallery-items' );
if ( $c.hasClass( 'ui-sortable' ) ) {
$c.sortable( 'destroy' );
}
$c.sortable( {
items: '> .edr-item',
cancel: '.edr-actions', // les boutons ne déclenchent pas de drag
placeholder: 'edr-item-placeholder',
forcePlaceholderSize: true,
tolerance: 'pointer',
cursor: 'move',
opacity: 0.9,
update: function () {
// Nouvel ordre lu dans le DOM (avant re-render)
var order = $c.children( '.edr-item' ).map( function () {
return $( this ).data( 'idx' );
} ).get();
var reordered = order.map( function ( i ) { return data[ i ]; } );
// On applique et on redessine au tick suivant (évite les conflits jQuery UI)
setTimeout( function () { data = reordered; render(); }, 0 );
}
} );
}
// Ajout via la médiathèque WordPress (sélection multiple)
$( '#edr-gallery-add' ).on( 'click', function ( e ) {
e.preventDefault();
var frame = wp.media( {
title: 'Sélectionner des photos pour la galerie',
button: { text: 'Ajouter à la galerie' },
library: { type: 'image' },
multiple: true
} );
frame.on( 'select', function () {
frame.state().get( 'selection' ).map( function ( att ) {
att = att.toJSON();
var thumb = ( att.sizes && att.sizes.thumbnail )
? att.sizes.thumbnail.url
: att.url;
data.push( { id: att.id, rotation: 0, thumb: thumb } );
} );
render();
} );
frame.open();
} );
// Actions par photo (délégation d'événements)
$( '#edr-gallery-items' )
.on( 'click', '.edr-rot', function () {
var i = $( this ).closest( '.edr-item' ).data( 'idx' );
data[ i ].rotation = ( data[ i ].rotation + 90 ) % 360;
render();
} )
.on( 'click', '.edr-del', function () {
var i = $( this ).closest( '.edr-item' ).data( 'idx' );
data.splice( i, 1 );
render();
} )
.on( 'click', '.edr-left', function () {
var i = $( this ).closest( '.edr-item' ).data( 'idx' );
if ( i > 0 ) { var t = data[ i - 1 ]; data[ i - 1 ] = data[ i ]; data[ i ] = t; render(); }
} )
.on( 'click', '.edr-right', function () {
var i = $( this ).closest( '.edr-item' ).data( 'idx' );
if ( i < data.length - 1 ) { var t = data[ i + 1 ]; data[ i + 1 ] = data[ i ]; data[ i ] = t; render(); }
} );
render();
} )( jQuery );
</script>
<?php
}
/* ============================================================================
* 3) ADMIN — Enregistrement
* ========================================================================== */
add_action( 'save_post_product', function ( $post_id ) {
if ( ! isset( $_POST['edr_gallery_nonce'] ) ||
! wp_verify_nonce( $_POST['edr_gallery_nonce'], 'edr_gallery_save' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$raw = isset( $_POST['edr_gallery_data'] ) ? wp_unslash( $_POST['edr_gallery_data'] ) : '[]';
$items = json_decode( $raw, true );
$clean = array();
if ( is_array( $items ) ) {
foreach ( $items as $it ) {
$id = isset( $it['id'] ) ? absint( $it['id'] ) : 0;
if ( ! $id ) {
continue;
}
$rot = isset( $it['rotation'] ) ? intval( $it['rotation'] ) : 0;
if ( ! in_array( $rot, array( 0, 90, 180, 270 ), true ) ) {
$rot = 0;
}
$clean[] = array( 'id' => $id, 'rotation' => $rot );
}
}
update_post_meta( $post_id, '_edr_gallery_data', wp_json_encode( $clean ) );
} );
/* ============================================================================
* 4) FRONT — Assets (CSS + JS lightbox), imprimés une seule fois
* ========================================================================== */
function edr_gallery_print_assets() {
static $done = false;
if ( $done ) {
return;
}
$done = true;
// Le JS de la lightbox est différé dans le footer
add_action( 'wp_footer', 'edr_gallery_footer_js', 99 );
?>
<style id="edr-gallery-css">
.edr-gallery{
display:grid;
grid-template-columns:repeat( var(--edr-cols,6), 1fr );
gap:var(--edr-gap,6px);
width:100%;
}
.edr-gallery .edr-cell{
position:relative; padding:0; margin:0; border:0;
background:#111; cursor:pointer; overflow:hidden;
aspect-ratio:var(--edr-ratio,75/25); border-radius:6px;
box-shadow:0 4px 12px rgba(0,0,0,.18);
transition:box-shadow .25s ease, transform .25s ease;
-webkit-appearance:none; appearance:none;
}
.edr-gallery .edr-cell:hover{
box-shadow:0 9px 24px rgba(0,0,0,.30);
transform:translateY(-3px);
}
.edr-gallery .edr-cell img{
width:100%; height:100%; object-fit:cover; display:block;
transform:rotate( var(--rot,0deg) );
transition:transform .3s ease;
}
.edr-gallery .edr-cell:hover img{
transform:rotate( var(--rot,0deg) ) scale(1.07);
}
/* Photo pivotée à 90/270° : on la zoome pour remplir la vignette rectangulaire */
.edr-gallery .edr-cell.edr-rot-side img{
transform:rotate( var(--rot,0deg) ) scale( var(--edr-cover,1) );
}
.edr-gallery .edr-cell.edr-rot-side:hover img{
transform:rotate( var(--rot,0deg) ) scale( calc( var(--edr-cover,1) * 1.07 ) );
}
/* Calque filigrane vignettes (motif SVG répété) */
.edr-gallery .edr-wm-layer{
position:absolute; inset:0; pointer-events:none;
background-image:var(--edr-wm);
background-repeat:repeat;
}
/* Survol : voile grisé + loupe qui apparaît */
.edr-gallery .edr-cell::after{
content:""; position:absolute; inset:0; pointer-events:none;
background-color:rgba(35,35,38,0);
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='16.5' y1='16.5' x2='21' y2='21'/%3E%3C/svg%3E");
background-repeat:no-repeat; background-position:center; background-size:0px;
transition:background-color .25s ease, background-size .25s ease;
}
.edr-gallery .edr-cell:hover::after{
background-color:rgba(35,35,38,.42);
background-size:44px;
}
/* Calque filigrane vue zoomée : un seul texte centré */
.edr-lb .edr-lb-wm{
position:absolute; inset:0; pointer-events:none;
background-image:var(--edr-wm-single);
background-repeat:no-repeat;
background-position:center;
}
/* ---- Pagination ---- */
.edr-gallery .edr-cell.edr-hidden{ display:none !important; }
.edr-pager{
display:flex; align-items:center; justify-content:center;
gap:6px; flex-wrap:wrap; margin-top:16px;
}
.edr-pager .edr-pg-nums{ display:flex; gap:6px; flex-wrap:wrap; }
.edr-pager button{
min-width:36px; height:36px; padding:0 10px;
border:1px solid #dcdcde; background:#fff; color:#333;
border-radius:8px; cursor:pointer; font-size:14px; line-height:1;
transition:all .15s ease;
}
.edr-pager button:hover:not(:disabled){ border-color:#111; }
.edr-pager button:disabled{ opacity:.4; cursor:default; }
.edr-pager .edr-pg-num.active{ background:#111; color:#fff; border-color:#111; font-weight:700; }
.edr-pager .edr-pg-count{ font-size:13px; color:#666; margin:0 8px; white-space:nowrap; }
/* ---- Responsive mobile : moitié moins de colonnes ---- */
@media (max-width:768px){
.edr-gallery{ --edr-cols:3 !important; }
}
/* ---- Lightbox plein écran ---- */
.edr-lb{
position:fixed; inset:0; z-index:99999;
display:none; align-items:center; justify-content:center;
background:rgba(0,0,0,.92);
}
.edr-lb.open{ display:flex; }
.edr-lb .edr-lb-stage{
position:relative; display:flex; align-items:center; justify-content:center;
max-width:100vw; max-height:100vh;
}
.edr-lb img{
max-width:90vw; max-height:88vh; object-fit:contain;
transition:transform .25s ease; user-select:none;
}
.edr-lb img.rot90, .edr-lb img.rot270{
max-width:86vh; max-height:90vw; /* on inverse les contraintes pour le portrait pivoté */
}
.edr-lb button{
position:absolute; z-index:2; cursor:pointer;
background:rgba(255,255,255,.12); color:#fff; border:0;
border-radius:50%; width:52px; height:52px; font-size:26px; line-height:1;
transition:background .2s ease;
}
.edr-lb button:hover{ background:rgba(255,255,255,.28); }
.edr-lb .edr-lb-close{ top:20px; right:20px; }
.edr-lb .edr-lb-prev{ left:20px; top:50%; transform:translateY(-50%); }
.edr-lb .edr-lb-next{ right:20px; top:50%; transform:translateY(-50%); }
@media(max-width:600px){
.edr-lb .edr-lb-prev{ left:8px; } .edr-lb .edr-lb-next{ right:8px; }
.edr-lb button{ width:44px; height:44px; font-size:22px; }
}
</style>
<?php
}
function edr_gallery_footer_js() {
?>
<script id="edr-gallery-js">
( function () {
var lb, lbImg;
function ensure() {
if ( lb ) { return; }
lb = document.createElement( 'div' );
lb.className = 'edr-lb';
lb.innerHTML =
'<button class="edr-lb-close" aria-label="Fermer">✕</button>' +
'<button class="edr-lb-prev" aria-label="Précédent">‹</button>' +
'<div class="edr-lb-stage"><img alt=""><span class="edr-lb-wm" aria-hidden="true"></span></div>' +
'<button class="edr-lb-next" aria-label="Suivant">›</button>';
document.body.appendChild( lb );
lbImg = lb.querySelector( 'img' );
lb.querySelector( '.edr-lb-close' ).addEventListener( 'click', close );
lb.querySelector( '.edr-lb-prev' ).addEventListener( 'click', function ( e ) { e.stopPropagation(); nav( -1 ); } );
lb.querySelector( '.edr-lb-next' ).addEventListener( 'click', function ( e ) { e.stopPropagation(); nav( 1 ); } );
lb.addEventListener( 'click', function ( e ) { if ( e.target === lb ) { close(); } } );
document.addEventListener( 'keydown', function ( e ) {
if ( ! lb.classList.contains( 'open' ) ) { return; }
if ( e.key === 'Escape' ) { close(); }
if ( e.key === 'ArrowLeft' ) { nav( -1 ); }
if ( e.key === 'ArrowRight' ) { nav( 1 ); }
} );
}
var current = [], idx = 0;
function show() {
var it = current[ idx ];
lbImg.className = '';
lbImg.style.transform = 'rotate(' + it.rot + 'deg)';
if ( it.rot === 90 ) { lbImg.classList.add( 'rot90' ); }
if ( it.rot === 270 ) { lbImg.classList.add( 'rot270' ); }
lbImg.src = it.full;
lb.style.setProperty( '--edr-wm-single', it.wm );
}
function open( cells, i ) {
ensure(); current = cells; idx = i;
lb.classList.add( 'open' ); document.body.style.overflow = 'hidden';
show();
}
function close() {
if ( ! lb ) { return; }
lb.classList.remove( 'open' ); document.body.style.overflow = '';
lbImg.src = '';
}
function nav( d ) {
idx = ( idx + d + current.length ) % current.length;
show();
}
document.addEventListener( 'click', function ( e ) {
var cell = e.target.closest( '.edr-cell' );
if ( ! cell ) { return; }
var gallery = cell.closest( '.edr-gallery' );
if ( ! gallery ) { return; }
e.preventDefault();
var wm = getComputedStyle( gallery ).getPropertyValue( '--edr-wm-single' );
var nodes = Array.prototype.slice.call( gallery.querySelectorAll( '.edr-cell' ) );
var cells = nodes.map( function ( c ) {
return {
full: c.getAttribute( 'data-full' ),
rot: parseInt( c.getAttribute( 'data-rot' ), 10 ) || 0,
wm: wm
};
} );
open( cells, nodes.indexOf( cell ) );
} );
/* -------- Pagination des vignettes (responsive) -------- */
var EDR_MOBILE = window.matchMedia( '(max-width:768px)' );
function setupGallery( g ) {
if ( g._edrPaged ) { return; }
g._edrPaged = true;
var baseRows = parseInt( g.getAttribute( 'data-rows' ), 10 ) || 10;
var cells = Array.prototype.slice.call( g.querySelectorAll( '.edr-cell' ) );
if ( cells.length === 0 ) { return; }
var pager = null, cur = 1, lastPer = 0;
// Nombre réel de colonnes rendues (le CSS peut l'avoir divisé sur mobile)
function realCols() {
var t = getComputedStyle( g ).gridTemplateColumns;
var n = t.split( ' ' ).filter( function ( s ) { return s.trim() !== ''; } ).length;
return n || 1;
}
// Lignes divisées par 2 sur mobile
function curRows() {
return EDR_MOBILE.matches ? Math.max( 1, Math.ceil( baseRows / 2 ) ) : baseRows;
}
function perPage() {
return realCols() * curRows();
}
function buildPager() {
pager = document.createElement( 'div' );
pager.className = 'edr-pager';
pager.innerHTML =
'<button type="button" class="edr-pg-prev" aria-label="Page précédente">‹</button>' +
'<span class="edr-pg-nums"></span>' +
'<span class="edr-pg-count"></span>' +
'<button type="button" class="edr-pg-next" aria-label="Page suivante">›</button>';
g.parentNode.insertBefore( pager, g.nextSibling );
pager.querySelector( '.edr-pg-prev' ).addEventListener( 'click', function () { go( cur - 1 ); } );
pager.querySelector( '.edr-pg-next' ).addEventListener( 'click', function () { go( cur + 1 ); } );
pager.querySelector( '.edr-pg-nums' ).addEventListener( 'click', function ( e ) {
var b = e.target.closest( '.edr-pg-num' );
if ( b ) { go( parseInt( b.getAttribute( 'data-p' ), 10 ) ); }
} );
}
function removePager() {
if ( pager ) { pager.parentNode.removeChild( pager ); pager = null; }
}
function render() {
var per = perPage();
var pages = Math.ceil( cells.length / per );
if ( cur > pages ) { cur = pages; }
if ( cur < 1 ) { cur = 1; }
if ( pages <= 1 ) {
cells.forEach( function ( c ) { c.classList.remove( 'edr-hidden' ); } );
removePager();
lastPer = per;
return;
}
if ( ! pager ) { buildPager(); }
var nums = pager.querySelector( '.edr-pg-nums' );
if ( nums.children.length !== pages ) {
nums.innerHTML = '';
for ( var p = 1; p <= pages; p++ ) {
var b = document.createElement( 'button' );
b.type = 'button';
b.className = 'edr-pg-num';
b.textContent = p;
b.setAttribute( 'data-p', p );
nums.appendChild( b );
}
}
cells.forEach( function ( c, i ) {
c.classList.toggle( 'edr-hidden', ( Math.floor( i / per ) + 1 ) !== cur );
} );
Array.prototype.forEach.call( nums.children, function ( b ) {
b.classList.toggle( 'active', parseInt( b.getAttribute( 'data-p' ), 10 ) === cur );
} );
pager.querySelector( '.edr-pg-count' ).textContent = cur + ' / ' + pages;
pager.querySelector( '.edr-pg-prev' ).disabled = ( cur === 1 );
pager.querySelector( '.edr-pg-next' ).disabled = ( cur === pages );
lastPer = per;
}
function go( p ) {
var pages = Math.ceil( cells.length / perPage() );
cur = Math.max( 1, Math.min( pages, p ) );
render();
g.scrollIntoView( { behavior: 'smooth', block: 'start' } );
}
g._edrRender = render;
render();
}
function initPagers() {
Array.prototype.forEach.call( document.querySelectorAll( '.edr-gallery' ), setupGallery );
}
initPagers();
// Recalcul au redimensionnement / rotation de l'écran (anti-rebond)
var edrRT;
window.addEventListener( 'resize', function () {
clearTimeout( edrRT );
edrRT = setTimeout( function () {
Array.prototype.forEach.call( document.querySelectorAll( '.edr-gallery' ), function ( g ) {
if ( g._edrRender ) { g._edrRender(); }
} );
}, 160 );
} );
} )();
</script>
<?php
}
/* ============================================================================
* 5) FRONT — Shortcode [edr_gallery]
* ========================================================================== */
add_shortcode( 'edr_gallery', function ( $atts ) {
$atts = shortcode_atts( array(
'id' => 0, // ID produit (0 = produit courant)
'cols' => 6, // nombre de colonnes
'gap' => 6, // espacement (px)
'ratio' => '60/40', // rapport largeur/hauteur des miniatures
'rows' => 10, // nb de lignes max avant pagination (desktop)
'watermark' => 'EDRAUTO.FR', // texte du filigrane
'opacity' => 0.4, // opacité du filigrane (0 à 1)
), $atts, 'edr_gallery' );
$product_id = absint( $atts['id'] );
if ( ! $product_id ) {
$product_id = get_the_ID();
}
if ( ! $product_id ) {
return '';
}
$stored = json_decode( get_post_meta( $product_id, '_edr_gallery_data', true ), true );
if ( ! is_array( $stored ) || empty( $stored ) ) {
return '';
}
$cols = max( 1, min( 12, absint( $atts['cols'] ) ) );
$gap = absint( $atts['gap'] );
$wm_text = sanitize_text_field( $atts['watermark'] );
$op = max( 0, min( 1, floatval( $atts['opacity'] ) ) );
$rows = max( 1, absint( $atts['rows'] ) ); // lignes desktop
// Rapport largeur/hauteur des miniatures (ex : "75/25")
$rp = array_map( 'floatval', explode( '/', $atts['ratio'] . '/1' ) );
$rw = ( isset( $rp[0] ) && $rp[0] > 0 ) ? $rp[0] : 75;
$rh = ( isset( $rp[1] ) && $rp[1] > 0 ) ? $rp[1] : 25;
// Facteur de zoom pour qu'une photo pivotée à 90/270° remplisse bien la vignette
$cover = max( $rw / $rh, $rh / $rw );
// Filigrane 1 : motif diagonal répété (vignettes)
$svg = "<svg xmlns='http://www.w3.org/2000/svg' width='240' height='150'>"
. "<text x='14' y='82' fill='#ffffff' fill-opacity='" . esc_attr( $op ) . "' "
. "font-family='Arial, Helvetica, sans-serif' font-size='20' font-weight='700' "
. "transform='rotate(-30 120 76)'>" . esc_html( $wm_text ) . "</text></svg>";
$wm = 'data:image/svg+xml,' . rawurlencode( $svg );
// Filigrane 2 : un seul texte centré (vue zoomée)
$svg_single = "<svg xmlns='http://www.w3.org/2000/svg' width='540' height='200'>"
. "<text x='270' y='108' text-anchor='middle' fill='#ffffff' fill-opacity='" . esc_attr( min( 1, $op + 0.05 ) ) . "' "
. "font-family='Arial, Helvetica, sans-serif' font-size='46' font-weight='700' "
. "transform='rotate(-25 270 100)'>" . esc_html( $wm_text ) . "</text></svg>";
$wm_single = 'data:image/svg+xml,' . rawurlencode( $svg_single );
edr_gallery_print_assets();
ob_start();
printf(
'<div class="edr-gallery" data-cols="%d" data-rows="%d" style="--edr-cols:%d;--edr-gap:%dpx;--edr-ratio:%s/%s;--edr-cover:%s;--edr-wm:url(\'%s\');--edr-wm-single:url(\'%s\');">',
$cols, $rows,
$cols, $gap,
esc_attr( $rw ), esc_attr( $rh ),
esc_attr( $cover ),
$wm, $wm_single
);
foreach ( $stored as $it ) {
$id = isset( $it['id'] ) ? absint( $it['id'] ) : 0;
$rot = isset( $it['rotation'] ) ? intval( $it['rotation'] ) : 0;
if ( ! in_array( $rot, array( 0, 90, 180, 270 ), true ) ) {
$rot = 0;
}
$thumb = $id ? wp_get_attachment_image_url( $id, 'large' ) : '';
$full = $id ? wp_get_attachment_image_url( $id, 'full' ) : '';
if ( ! $thumb ) {
continue;
}
$side = ( 90 === $rot || 270 === $rot ) ? ' edr-rot-side' : '';
printf(
'<button type="button" class="edr-cell%s" style="--rot:%ddeg" data-full="%s" data-rot="%d" aria-label="Agrandir la photo">'
. '<img src="%s" loading="lazy" alt="">'
. '<span class="edr-wm-layer" aria-hidden="true"></span>'
. '</button>',
$side,
$rot,
esc_url( $full ),
$rot,
esc_url( $thumb )
);
}
echo '</div>';
return ob_get_clean();
} );
/* ============================================================================
* 6) (OPTIONNEL) Insertion automatique sous le résumé produit
* Décommenter pour afficher la galerie sans shortcode.
* ========================================================================== */
/*
add_action( 'woocommerce_after_single_product_summary', function () {
echo do_shortcode( '[edr_gallery]' );
}, 15 );
*/
<?php
/**
* ============================================================================
* EDR GALLERY — Galerie mosaïque zoomable pour WooCommerce
* ============================================================================
* Auteur : MOOVIKA / EDR Auto
* Usage : À coller dans un snippet "Code Snippets" (exécution : Partout / PHP).
*
* Fonctions :
* - Metabox sur la fiche produit pour ajouter des photos (médiathèque WP)
* - Rotation par photo (0 / 90 / 180 / 270°) modifiable dans l'admin
* - Réordonnancement (◀ ▶) et suppression par photo
* - Affichage front en mosaïque 6 colonnes (autant de lignes que nécessaire)
* - Vignettes rectangulaires 60/40 (réglable), ombre portée, survol grisé + loupe
* - Pagination auto au-delà de N lignes (défaut 10) avec compteur « 1 / 2 »
* - Responsive : sur mobile (≤768px) colonnes ÷2 et lignes/page ÷2 automatiquement
* - Zoom plein écran (lightbox maison, navigation clavier)
* - Filigrane "EDRAUTO.FR" : motif répété sur les vignettes, UN SEUL centré au zoom
*
* Affichage :
* - Shortcode : [edr_gallery] (produit courant, 6 col · 60/40)
* - Options : [edr_gallery id="123" cols="6" gap="6" ratio="60/40" rows="10" opacity="0.4" watermark="EDRAUTO.FR"]
* - Elementor : insérer un widget "Shortcode" avec [edr_gallery]
* - Auto : décommenter le hook tout en bas pour l'insérer automatiquement
* ============================================================================
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
/* ============================================================================
* 1) ADMIN — Chargement de la médiathèque sur la fiche produit
* ========================================================================== */
add_action( 'admin_enqueue_scripts', function () {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( $screen && 'product' === $screen->post_type ) {
wp_enqueue_media();
wp_enqueue_script( 'jquery-ui-sortable' ); // glisser-déposer des photos
}
} );
/* ============================================================================
* 2) ADMIN — Metabox sur la fiche produit
* ========================================================================== */
add_action( 'add_meta_boxes', function () {
add_meta_box(
'edr_gallery_metabox',
'📸 Galerie mosaïque EDR',
'edr_gallery_metabox_render',
'product',
'normal',
'default'
);
} );
function edr_gallery_metabox_render( $post ) {
// Lecture des données stockées (tableau de {id, rotation})
$stored = json_decode( get_post_meta( $post->ID, '_edr_gallery_data', true ), true );
if ( ! is_array( $stored ) ) {
$stored = array();
}
// On reconstruit un tableau avec l'URL miniature fraîche pour l'aperçu admin
$init = array();
foreach ( $stored as $it ) {
$id = isset( $it['id'] ) ? absint( $it['id'] ) : 0;
$thumb = $id ? wp_get_attachment_image_url( $id, 'thumbnail' ) : '';
if ( ! $thumb ) {
continue; // image supprimée de la médiathèque
}
$init[] = array(
'id' => $id,
'rotation' => isset( $it['rotation'] ) ? intval( $it['rotation'] ) : 0,
'thumb' => $thumb,
);
}
wp_nonce_field( 'edr_gallery_save', 'edr_gallery_nonce' );
?>
<div id="edr-gallery-wrap">
<input type="hidden" id="edr_gallery_data" name="edr_gallery_data" value="">
<div id="edr-gallery-items" class="edr-admin-grid"></div>
<p style="margin-top:12px;">
<button type="button" class="button button-primary button-large" id="edr-gallery-add">
+ Ajouter des photos
</button>
<span class="description" style="margin-left:10px;">
✋ glisser-déposer pour réordonner · ↻ pivoter · ◀ ▶ déplacer · ✕ retirer
</span>
</p>
</div>
<style>
.edr-admin-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:12px; margin-top:8px; }
.edr-admin-grid:empty::before{ content:"Aucune photo pour le moment."; color:#777; font-style:italic; }
.edr-item{ position:relative; border:1px solid #dcdcde; border-radius:6px; overflow:hidden; background:#f6f7f7; }
.edr-item .edr-thumb{ width:100%; aspect-ratio:1/1; object-fit:cover; display:block; transition:transform .2s ease; cursor:move; }
.edr-item .edr-actions{ display:flex; gap:2px; padding:4px; background:#fff; border-top:1px solid #eee; }
.edr-item .edr-actions .button{ flex:1; min-height:24px; padding:0 4px; font-size:11px; line-height:22px; }
.edr-item .edr-rot-badge{ position:absolute; top:4px; right:4px; background:rgba(0,0,0,.7); color:#fff; font-size:10px; padding:1px 5px; border-radius:3px; }
/* Glisser-déposer */
.edr-item.ui-sortable-helper{ box-shadow:0 6px 18px rgba(0,0,0,.25); opacity:.95; }
.edr-item-placeholder{ border:2px dashed #2271b1; border-radius:6px; background:#f0f6fc; visibility:visible !important; }
</style>
<script>
( function ( $ ) {
var edrInit = <?php echo wp_json_encode( $init ); ?>;
var data = Array.isArray( edrInit ) ? edrInit.slice() : [];
function sync() {
// On ne conserve que id + rotation à l'enregistrement
var out = data.map( function ( it ) {
return { id: it.id, rotation: it.rotation };
} );
$( '#edr_gallery_data' ).val( JSON.stringify( out ) );
}
function render() {
var $c = $( '#edr-gallery-items' ).empty();
data.forEach( function ( it, idx ) {
var $item = $(
'<div class="edr-item" data-idx="' + idx + '">' +
'<span class="edr-rot-badge">' + it.rotation + '°</span>' +
'<img class="edr-thumb" src="' + it.thumb + '" style="transform:rotate(' + it.rotation + 'deg);">' +
'<div class="edr-actions">' +
'<button type="button" class="button edr-rot" title="Pivoter">↻</button>' +
'<button type="button" class="button edr-left" title="Déplacer à gauche">◀</button>' +
'<button type="button" class="button edr-right" title="Déplacer à droite">▶</button>' +
'<button type="button" class="button edr-del" title="Retirer">✕</button>' +
'</div>' +
'</div>'
);
$c.append( $item );
} );
sync();
initSortable();
}
// Glisser-déposer pour réordonner les photos
function initSortable() {
if ( ! $.fn.sortable ) { return; } // sécurité si jQuery UI absent
var $c = $( '#edr-gallery-items' );
if ( $c.hasClass( 'ui-sortable' ) ) {
$c.sortable( 'destroy' );
}
$c.sortable( {
items: '> .edr-item',
cancel: '.edr-actions', // les boutons ne déclenchent pas de drag
placeholder: 'edr-item-placeholder',
forcePlaceholderSize: true,
tolerance: 'pointer',
cursor: 'move',
opacity: 0.9,
update: function () {
// Nouvel ordre lu dans le DOM (avant re-render)
var order = $c.children( '.edr-item' ).map( function () {
return $( this ).data( 'idx' );
} ).get();
var reordered = order.map( function ( i ) { return data[ i ]; } );
// On applique et on redessine au tick suivant (évite les conflits jQuery UI)
setTimeout( function () { data = reordered; render(); }, 0 );
}
} );
}
// Ajout via la médiathèque WordPress (sélection multiple)
$( '#edr-gallery-add' ).on( 'click', function ( e ) {
e.preventDefault();
var frame = wp.media( {
title: 'Sélectionner des photos pour la galerie',
button: { text: 'Ajouter à la galerie' },
library: { type: 'image' },
multiple: true
} );
frame.on( 'select', function () {
frame.state().get( 'selection' ).map( function ( att ) {
att = att.toJSON();
var thumb = ( att.sizes && att.sizes.thumbnail )
? att.sizes.thumbnail.url
: att.url;
data.push( { id: att.id, rotation: 0, thumb: thumb } );
} );
render();
} );
frame.open();
} );
// Actions par photo (délégation d'événements)
$( '#edr-gallery-items' )
.on( 'click', '.edr-rot', function () {
var i = $( this ).closest( '.edr-item' ).data( 'idx' );
data[ i ].rotation = ( data[ i ].rotation + 90 ) % 360;
render();
} )
.on( 'click', '.edr-del', function () {
var i = $( this ).closest( '.edr-item' ).data( 'idx' );
data.splice( i, 1 );
render();
} )
.on( 'click', '.edr-left', function () {
var i = $( this ).closest( '.edr-item' ).data( 'idx' );
if ( i > 0 ) { var t = data[ i - 1 ]; data[ i - 1 ] = data[ i ]; data[ i ] = t; render(); }
} )
.on( 'click', '.edr-right', function () {
var i = $( this ).closest( '.edr-item' ).data( 'idx' );
if ( i < data.length - 1 ) { var t = data[ i + 1 ]; data[ i + 1 ] = data[ i ]; data[ i ] = t; render(); }
} );
render();
} )( jQuery );
</script>
<?php
}
/* ============================================================================
* 3) ADMIN — Enregistrement
* ========================================================================== */
add_action( 'save_post_product', function ( $post_id ) {
if ( ! isset( $_POST['edr_gallery_nonce'] ) ||
! wp_verify_nonce( $_POST['edr_gallery_nonce'], 'edr_gallery_save' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$raw = isset( $_POST['edr_gallery_data'] ) ? wp_unslash( $_POST['edr_gallery_data'] ) : '[]';
$items = json_decode( $raw, true );
$clean = array();
if ( is_array( $items ) ) {
foreach ( $items as $it ) {
$id = isset( $it['id'] ) ? absint( $it['id'] ) : 0;
if ( ! $id ) {
continue;
}
$rot = isset( $it['rotation'] ) ? intval( $it['rotation'] ) : 0;
if ( ! in_array( $rot, array( 0, 90, 180, 270 ), true ) ) {
$rot = 0;
}
$clean[] = array( 'id' => $id, 'rotation' => $rot );
}
}
update_post_meta( $post_id, '_edr_gallery_data', wp_json_encode( $clean ) );
} );
/* ============================================================================
* 4) FRONT — Assets (CSS + JS lightbox), imprimés une seule fois
* ========================================================================== */
function edr_gallery_print_assets() {
static $done = false;
if ( $done ) {
return;
}
$done = true;
// Le JS de la lightbox est différé dans le footer
add_action( 'wp_footer', 'edr_gallery_footer_js', 99 );
?>
<style id="edr-gallery-css">
.edr-gallery{
display:grid;
grid-template-columns:repeat( var(--edr-cols,6), 1fr );
gap:var(--edr-gap,6px);
width:100%;
}
.edr-gallery .edr-cell{
position:relative; padding:0; margin:0; border:0;
background:#111; cursor:pointer; overflow:hidden;
aspect-ratio:var(--edr-ratio,75/25); border-radius:6px;
box-shadow:0 4px 12px rgba(0,0,0,.18);
transition:box-shadow .25s ease, transform .25s ease;
-webkit-appearance:none; appearance:none;
}
.edr-gallery .edr-cell:hover{
box-shadow:0 9px 24px rgba(0,0,0,.30);
transform:translateY(-3px);
}
.edr-gallery .edr-cell img{
width:100%; height:100%; object-fit:cover; display:block;
transform:rotate( var(--rot,0deg) );
transition:transform .3s ease;
}
.edr-gallery .edr-cell:hover img{
transform:rotate( var(--rot,0deg) ) scale(1.07);
}
/* Photo pivotée à 90/270° : on la zoome pour remplir la vignette rectangulaire */
.edr-gallery .edr-cell.edr-rot-side img{
transform:rotate( var(--rot,0deg) ) scale( var(--edr-cover,1) );
}
.edr-gallery .edr-cell.edr-rot-side:hover img{
transform:rotate( var(--rot,0deg) ) scale( calc( var(--edr-cover,1) * 1.07 ) );
}
/* Calque filigrane vignettes (motif SVG répété) */
.edr-gallery .edr-wm-layer{
position:absolute; inset:0; pointer-events:none;
background-image:var(--edr-wm);
background-repeat:repeat;
}
/* Survol : voile grisé + loupe qui apparaît */
.edr-gallery .edr-cell::after{
content:""; position:absolute; inset:0; pointer-events:none;
background-color:rgba(35,35,38,0);
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='16.5' y1='16.5' x2='21' y2='21'/%3E%3C/svg%3E");
background-repeat:no-repeat; background-position:center; background-size:0px;
transition:background-color .25s ease, background-size .25s ease;
}
.edr-gallery .edr-cell:hover::after{
background-color:rgba(35,35,38,.42);
background-size:44px;
}
/* Calque filigrane vue zoomée : un seul texte centré */
.edr-lb .edr-lb-wm{
position:absolute; inset:0; pointer-events:none;
background-image:var(--edr-wm-single);
background-repeat:no-repeat;
background-position:center;
}
/* ---- Pagination ---- */
.edr-gallery .edr-cell.edr-hidden{ display:none !important; }
.edr-pager{
display:flex; align-items:center; justify-content:center;
gap:6px; flex-wrap:wrap; margin-top:16px;
}
.edr-pager .edr-pg-nums{ display:flex; gap:6px; flex-wrap:wrap; }
.edr-pager button{
min-width:36px; height:36px; padding:0 10px;
border:1px solid #dcdcde; background:#fff; color:#333;
border-radius:8px; cursor:pointer; font-size:14px; line-height:1;
transition:all .15s ease;
}
.edr-pager button:hover:not(:disabled){ border-color:#111; }
.edr-pager button:disabled{ opacity:.4; cursor:default; }
.edr-pager .edr-pg-num.active{ background:#111; color:#fff; border-color:#111; font-weight:700; }
.edr-pager .edr-pg-count{ font-size:13px; color:#666; margin:0 8px; white-space:nowrap; }
/* ---- Responsive mobile : moitié moins de colonnes ---- */
@media (max-width:768px){
.edr-gallery{ --edr-cols:3 !important; }
}
/* ---- Lightbox plein écran ---- */
.edr-lb{
position:fixed; inset:0; z-index:99999;
display:none; align-items:center; justify-content:center;
background:rgba(0,0,0,.92);
}
.edr-lb.open{ display:flex; }
.edr-lb .edr-lb-stage{
position:relative; display:flex; align-items:center; justify-content:center;
max-width:100vw; max-height:100vh;
}
.edr-lb img{
max-width:90vw; max-height:88vh; object-fit:contain;
transition:transform .25s ease; user-select:none;
}
.edr-lb img.rot90, .edr-lb img.rot270{
max-width:86vh; max-height:90vw; /* on inverse les contraintes pour le portrait pivoté */
}
.edr-lb button{
position:absolute; z-index:2; cursor:pointer;
background:rgba(255,255,255,.12); color:#fff; border:0;
border-radius:50%; width:52px; height:52px; font-size:26px; line-height:1;
transition:background .2s ease;
}
.edr-lb button:hover{ background:rgba(255,255,255,.28); }
.edr-lb .edr-lb-close{ top:20px; right:20px; }
.edr-lb .edr-lb-prev{ left:20px; top:50%; transform:translateY(-50%); }
.edr-lb .edr-lb-next{ right:20px; top:50%; transform:translateY(-50%); }
@media(max-width:600px){
.edr-lb .edr-lb-prev{ left:8px; } .edr-lb .edr-lb-next{ right:8px; }
.edr-lb button{ width:44px; height:44px; font-size:22px; }
}
</style>
<?php
}
function edr_gallery_footer_js() {
?>
<script id="edr-gallery-js">
( function () {
var lb, lbImg;
function ensure() {
if ( lb ) { return; }
lb = document.createElement( 'div' );
lb.className = 'edr-lb';
lb.innerHTML =
'<button class="edr-lb-close" aria-label="Fermer">✕</button>' +
'<button class="edr-lb-prev" aria-label="Précédent">‹</button>' +
'<div class="edr-lb-stage"><img alt=""><span class="edr-lb-wm" aria-hidden="true"></span></div>' +
'<button class="edr-lb-next" aria-label="Suivant">›</button>';
document.body.appendChild( lb );
lbImg = lb.querySelector( 'img' );
lb.querySelector( '.edr-lb-close' ).addEventListener( 'click', close );
lb.querySelector( '.edr-lb-prev' ).addEventListener( 'click', function ( e ) { e.stopPropagation(); nav( -1 ); } );
lb.querySelector( '.edr-lb-next' ).addEventListener( 'click', function ( e ) { e.stopPropagation(); nav( 1 ); } );
lb.addEventListener( 'click', function ( e ) { if ( e.target === lb ) { close(); } } );
document.addEventListener( 'keydown', function ( e ) {
if ( ! lb.classList.contains( 'open' ) ) { return; }
if ( e.key === 'Escape' ) { close(); }
if ( e.key === 'ArrowLeft' ) { nav( -1 ); }
if ( e.key === 'ArrowRight' ) { nav( 1 ); }
} );
}
var current = [], idx = 0;
function show() {
var it = current[ idx ];
lbImg.className = '';
lbImg.style.transform = 'rotate(' + it.rot + 'deg)';
if ( it.rot === 90 ) { lbImg.classList.add( 'rot90' ); }
if ( it.rot === 270 ) { lbImg.classList.add( 'rot270' ); }
lbImg.src = it.full;
lb.style.setProperty( '--edr-wm-single', it.wm );
}
function open( cells, i ) {
ensure(); current = cells; idx = i;
lb.classList.add( 'open' ); document.body.style.overflow = 'hidden';
show();
}
function close() {
if ( ! lb ) { return; }
lb.classList.remove( 'open' ); document.body.style.overflow = '';
lbImg.src = '';
}
function nav( d ) {
idx = ( idx + d + current.length ) % current.length;
show();
}
document.addEventListener( 'click', function ( e ) {
var cell = e.target.closest( '.edr-cell' );
if ( ! cell ) { return; }
var gallery = cell.closest( '.edr-gallery' );
if ( ! gallery ) { return; }
e.preventDefault();
var wm = getComputedStyle( gallery ).getPropertyValue( '--edr-wm-single' );
var nodes = Array.prototype.slice.call( gallery.querySelectorAll( '.edr-cell' ) );
var cells = nodes.map( function ( c ) {
return {
full: c.getAttribute( 'data-full' ),
rot: parseInt( c.getAttribute( 'data-rot' ), 10 ) || 0,
wm: wm
};
} );
open( cells, nodes.indexOf( cell ) );
} );
/* -------- Pagination des vignettes (responsive) -------- */
var EDR_MOBILE = window.matchMedia( '(max-width:768px)' );
function setupGallery( g ) {
if ( g._edrPaged ) { return; }
g._edrPaged = true;
var baseRows = parseInt( g.getAttribute( 'data-rows' ), 10 ) || 10;
var cells = Array.prototype.slice.call( g.querySelectorAll( '.edr-cell' ) );
if ( cells.length === 0 ) { return; }
var pager = null, cur = 1, lastPer = 0;
// Nombre réel de colonnes rendues (le CSS peut l'avoir divisé sur mobile)
function realCols() {
var t = getComputedStyle( g ).gridTemplateColumns;
var n = t.split( ' ' ).filter( function ( s ) { return s.trim() !== ''; } ).length;
return n || 1;
}
// Lignes divisées par 2 sur mobile
function curRows() {
return EDR_MOBILE.matches ? Math.max( 1, Math.ceil( baseRows / 2 ) ) : baseRows;
}
function perPage() {
return realCols() * curRows();
}
function buildPager() {
pager = document.createElement( 'div' );
pager.className = 'edr-pager';
pager.innerHTML =
'<button type="button" class="edr-pg-prev" aria-label="Page précédente">‹</button>' +
'<span class="edr-pg-nums"></span>' +
'<span class="edr-pg-count"></span>' +
'<button type="button" class="edr-pg-next" aria-label="Page suivante">›</button>';
g.parentNode.insertBefore( pager, g.nextSibling );
pager.querySelector( '.edr-pg-prev' ).addEventListener( 'click', function () { go( cur - 1 ); } );
pager.querySelector( '.edr-pg-next' ).addEventListener( 'click', function () { go( cur + 1 ); } );
pager.querySelector( '.edr-pg-nums' ).addEventListener( 'click', function ( e ) {
var b = e.target.closest( '.edr-pg-num' );
if ( b ) { go( parseInt( b.getAttribute( 'data-p' ), 10 ) ); }
} );
}
function removePager() {
if ( pager ) { pager.parentNode.removeChild( pager ); pager = null; }
}
function render() {
var per = perPage();
var pages = Math.ceil( cells.length / per );
if ( cur > pages ) { cur = pages; }
if ( cur < 1 ) { cur = 1; }
if ( pages <= 1 ) {
cells.forEach( function ( c ) { c.classList.remove( 'edr-hidden' ); } );
removePager();
lastPer = per;
return;
}
if ( ! pager ) { buildPager(); }
var nums = pager.querySelector( '.edr-pg-nums' );
if ( nums.children.length !== pages ) {
nums.innerHTML = '';
for ( var p = 1; p <= pages; p++ ) {
var b = document.createElement( 'button' );
b.type = 'button';
b.className = 'edr-pg-num';
b.textContent = p;
b.setAttribute( 'data-p', p );
nums.appendChild( b );
}
}
cells.forEach( function ( c, i ) {
c.classList.toggle( 'edr-hidden', ( Math.floor( i / per ) + 1 ) !== cur );
} );
Array.prototype.forEach.call( nums.children, function ( b ) {
b.classList.toggle( 'active', parseInt( b.getAttribute( 'data-p' ), 10 ) === cur );
} );
pager.querySelector( '.edr-pg-count' ).textContent = cur + ' / ' + pages;
pager.querySelector( '.edr-pg-prev' ).disabled = ( cur === 1 );
pager.querySelector( '.edr-pg-next' ).disabled = ( cur === pages );
lastPer = per;
}
function go( p ) {
var pages = Math.ceil( cells.length / perPage() );
cur = Math.max( 1, Math.min( pages, p ) );
render();
g.scrollIntoView( { behavior: 'smooth', block: 'start' } );
}
g._edrRender = render;
render();
}
function initPagers() {
Array.prototype.forEach.call( document.querySelectorAll( '.edr-gallery' ), setupGallery );
}
initPagers();
// Recalcul au redimensionnement / rotation de l'écran (anti-rebond)
var edrRT;
window.addEventListener( 'resize', function () {
clearTimeout( edrRT );
edrRT = setTimeout( function () {
Array.prototype.forEach.call( document.querySelectorAll( '.edr-gallery' ), function ( g ) {
if ( g._edrRender ) { g._edrRender(); }
} );
}, 160 );
} );
} )();
</script>
<?php
}
/* ============================================================================
* 5) FRONT — Shortcode [edr_gallery]
* ========================================================================== */
add_shortcode( 'edr_gallery', function ( $atts ) {
$atts = shortcode_atts( array(
'id' => 0, // ID produit (0 = produit courant)
'cols' => 6, // nombre de colonnes
'gap' => 6, // espacement (px)
'ratio' => '60/40', // rapport largeur/hauteur des miniatures
'rows' => 10, // nb de lignes max avant pagination (desktop)
'watermark' => 'EDRAUTO.FR', // texte du filigrane
'opacity' => 0.4, // opacité du filigrane (0 à 1)
), $atts, 'edr_gallery' );
$product_id = absint( $atts['id'] );
if ( ! $product_id ) {
$product_id = get_the_ID();
}
if ( ! $product_id ) {
return '';
}
$stored = json_decode( get_post_meta( $product_id, '_edr_gallery_data', true ), true );
if ( ! is_array( $stored ) || empty( $stored ) ) {
return '';
}
$cols = max( 1, min( 12, absint( $atts['cols'] ) ) );
$gap = absint( $atts['gap'] );
$wm_text = sanitize_text_field( $atts['watermark'] );
$op = max( 0, min( 1, floatval( $atts['opacity'] ) ) );
$rows = max( 1, absint( $atts['rows'] ) ); // lignes desktop
// Rapport largeur/hauteur des miniatures (ex : "75/25")
$rp = array_map( 'floatval', explode( '/', $atts['ratio'] . '/1' ) );
$rw = ( isset( $rp[0] ) && $rp[0] > 0 ) ? $rp[0] : 75;
$rh = ( isset( $rp[1] ) && $rp[1] > 0 ) ? $rp[1] : 25;
// Facteur de zoom pour qu'une photo pivotée à 90/270° remplisse bien la vignette
$cover = max( $rw / $rh, $rh / $rw );
// Filigrane 1 : motif diagonal répété (vignettes)
$svg = "<svg xmlns='http://www.w3.org/2000/svg' width='240' height='150'>"
. "<text x='14' y='82' fill='#ffffff' fill-opacity='" . esc_attr( $op ) . "' "
. "font-family='Arial, Helvetica, sans-serif' font-size='20' font-weight='700' "
. "transform='rotate(-30 120 76)'>" . esc_html( $wm_text ) . "</text></svg>";
$wm = 'data:image/svg+xml,' . rawurlencode( $svg );
// Filigrane 2 : un seul texte centré (vue zoomée)
$svg_single = "<svg xmlns='http://www.w3.org/2000/svg' width='540' height='200'>"
. "<text x='270' y='108' text-anchor='middle' fill='#ffffff' fill-opacity='" . esc_attr( min( 1, $op + 0.05 ) ) . "' "
. "font-family='Arial, Helvetica, sans-serif' font-size='46' font-weight='700' "
. "transform='rotate(-25 270 100)'>" . esc_html( $wm_text ) . "</text></svg>";
$wm_single = 'data:image/svg+xml,' . rawurlencode( $svg_single );
edr_gallery_print_assets();
ob_start();
printf(
'<div class="edr-gallery" data-cols="%d" data-rows="%d" style="--edr-cols:%d;--edr-gap:%dpx;--edr-ratio:%s/%s;--edr-cover:%s;--edr-wm:url(\'%s\');--edr-wm-single:url(\'%s\');">',
$cols, $rows,
$cols, $gap,
esc_attr( $rw ), esc_attr( $rh ),
esc_attr( $cover ),
$wm, $wm_single
);
foreach ( $stored as $it ) {
$id = isset( $it['id'] ) ? absint( $it['id'] ) : 0;
$rot = isset( $it['rotation'] ) ? intval( $it['rotation'] ) : 0;
if ( ! in_array( $rot, array( 0, 90, 180, 270 ), true ) ) {
$rot = 0;
}
$thumb = $id ? wp_get_attachment_image_url( $id, 'large' ) : '';
$full = $id ? wp_get_attachment_image_url( $id, 'full' ) : '';
if ( ! $thumb ) {
continue;
}
$side = ( 90 === $rot || 270 === $rot ) ? ' edr-rot-side' : '';
printf(
'<button type="button" class="edr-cell%s" style="--rot:%ddeg" data-full="%s" data-rot="%d" aria-label="Agrandir la photo">'
. '<img src="%s" loading="lazy" alt="">'
. '<span class="edr-wm-layer" aria-hidden="true"></span>'
. '</button>',
$side,
$rot,
esc_url( $full ),
$rot,
esc_url( $thumb )
);
}
echo '</div>';
return ob_get_clean();
} );
/* ============================================================================
* 6) (OPTIONNEL) Insertion automatique sous le résumé produit
* Décommenter pour afficher la galerie sans shortcode.
* ========================================================================== */
/*
add_action( 'woocommerce_after_single_product_summary', function () {
echo do_shortcode( '[edr_gallery]' );
}, 15 );
*/
Fiche produit
BlackVue Polarizer CPL Filter Clip Elite 8 – 9 – 10
Comparaison Avant / Après
[bafg id= »8241″]Spécifications techniques
| Car actéristiques | |
|---|---|
| Nom du modèle | ELITE 10-2CH |
| Canal | 2CH |
| Points forts | 4K UHD HDR + 4K UHD, Wi-Fi 2,4-5 GHz, Cloud (données non incluses), Mode parking à économie d’énergie |
| Dimensions et poids du produit | Avant : Longueur 129,7 mm (5,12 po) x Largeur 40 mm (1,58 po) x Hauteur 55,3 mm (2,18 po), 195 g (0,43 lb) Arrière : Longueur 67 mm (2,64 po) x Largeur 29 mm (1,14 po) x Hauteur 39,1 mm (1,54 po), 40 g (0,09 lb) |
| Connexion caméra arrière | câble coaxial |
| Support de mémoire | Carte microSD jusqu’à 1 To |
| Enregistrement intelligent d’événements | Impact en conduite, impact en stationnement, excès de vitesse, forte accélération, freinage brusque, virage serré (y compris une marge de sécurité de 10 secondes avant l’événement) |
| Mode stationnement | Économie d’énergie (< 1 mA) Détection de mouvement en accéléré (module complémentaire Mode parking requis) |
| Notifications vocales d’événements en mode stationnement | OUI |
| Systèmes de protection | Coupure automatique : 1 à 48 h Coupure basse tension : Véhicule de tourisme 11,8 à 12,5 V ou véhicule lourd 22,8 à 24 V Coupure haute température (75 °C) |
| Capteur d’imagerie | Avant et arrière : Capteur CMOS STARVIS 2 (IMX678) |
| Angle de vision | Avant et arrière : diagonale 146°, horizontale 125°, verticale 68° |
| Fréquence d’images en résolution | Avant et arrière : 4K UHD à 30 images/s (double 4K) * La fréquence d’images peut varier pendant la diffusion Wi-Fi. |
| Codec vidéo | H.265 (HEVC), H.264 (AVC) |
| Qualité d’image et débit binaire avant/arrière | Extrême (H.265) : 60 + 30 Mbit/s ; Extrême (H.264) : 40 + 30 Mbit/s ; Élevé (par défaut) : 30 + 30 Mbit/s ; Moyen : 20 + 20 Mbit/s ; Faible : 10 + 10 Mbit/s |
| Format libre | Format adaptatif sans restriction (US 10,922,270 B2) |
| Protection contre l’écrasement des fichiers d’événements | Il est possible de protéger jusqu’à 50 fichiers d’événements contre l’écrasement. |
| Alerte de défaillance de la carte SD | OUI |
| Redémarrage programmé | OUI |
| Extension de fichier vidéo | MP4 |
| Wi-Fi | Intégré (802.11ac – 2,4-5 GHz) |
| Compatible avec le cloud | OUI |
| GPS | Intégré (double bande : GPS, GLONASS) |
| Microphone | Intégré |
| Conférencier | Intégré |
| Capteur d’impact | Capteur d’accélération à 3 axes |
| Indicateurs LED | Face avant : État d’enregistrement, connectivité GPS, connectivité secteur (application/cloud) Face arrière : Voyant d’alimentation |
| Bouton | Bouton marche/arrêt : Appui court pour allumer / Appui long pour éteindre. Capteur tactile : Appuyer sur le capteur tactile déclenche l’enregistrement manuel selon les paramètres du micrologiciel. Bouton de télécommande Bluetooth (en option) : Enregistrement manuel |
| Température de fonctionnement | -20 °C − 65 °C (-4 °F − 149 °F) |
| Température de stockage | -20 °C − 80 °C (-4 °F − 176 °F) |
| Coupure haute température | Environ 75 °C (167 °F) |
| Batterie de secours | Supercondensateur intégré |
| Puissance d’entrée | Alimentation CC 12V-24V (Fiche CC (Ø3,5 x Ø1,1)) vers fils (Noir : GND / Jaune : B+ / Rouge : ACC) |
| Consommation d’énergie | Mode normal (GPS activé/2 canaux) : 510 mA ; Mode parking (GPS désactivé/2 canaux) : 380 mA ; Mode économie d’énergie : moins de 12 mW (0,012 W) (moyenne < 1 mA / 12 V) * La consommation électrique réelle peut varier en fonction des conditions d’utilisation et de l’environnement. |
| Certifications | Face avant : Telec, IC(ISED), CE, FCC, RCM, RoHS, DEEE Face arrière : FCC, IC(ISED), CE, RoHS, DEEE |
| Logiciel | Visionneuse BlackVue * Windows 7 ou supérieur, Mac OS X Sierra (10.12) ou supérieur Visionneuse Web FLEETA * Chrome 71 ou supérieur, Safari 13.0 ou supérieur |
| Application | Application BlackVue, application FLEETA * Android 10.0 ou supérieur, iOS 15.0 ou supérieur |
| Autres | Système de gestion de fichiers adaptatif sans formatage |
| Bluetooth | Intégré (V2.1+EDR/5.3) |
| LTE | Externe (avec module LTE en option) |
| Garantie | 2 ans |














Avis
Il n’y a pas encore d’avis.