This commit is contained in:
2026-05-01 15:36:42 -04:00
parent 51b888cebc
commit 9d61851a75
8 changed files with 503 additions and 108 deletions

View File

@@ -1,6 +1,5 @@
import { Link } from "react-router-dom";
import { Clock, Facebook, Mail, MapPin, Phone } from "lucide-react";
import logo from "@/assets/logo-municipality.png";
import { useLocale } from "@/i18n/LocaleContext";
import { siteSettings } from "@/content/site-settings";
import { UI } from "@/i18n/translations";
@@ -11,13 +10,14 @@ const QUICK_LINKS: PageKey[] = ["municipality", "community", "businesses", "even
export function Footer() {
const { t, locale } = useLocale();
const year = new Date().getFullYear();
const logo = (window as any).__CASCA_SITE__?.logo?.src || "";
return (
<footer className="bg-footer text-footer-foreground mt-12">
<div className="container grid gap-10 py-14 md:grid-cols-2 lg:grid-cols-4">
<div className="space-y-4">
<div className="flex items-center gap-3">
<img src={logo} alt="" width={48} height={48} className="h-12 w-12 object-contain" />
{logo && <img src={logo} alt="" width={48} height={48} className="h-12 w-12 object-contain" />}
<span className="font-display italic text-lg leading-tight">
{t(siteSettings.municipality.name)}
</span>

View File

@@ -1,7 +1,6 @@
import { Link, NavLink, useLocation } from "react-router-dom";
import { ChevronDown, Menu, X } from "lucide-react";
import { useEffect, useState } from "react";
import logo from "@/assets/logo-municipality.png";
import { useLocale } from "@/i18n/LocaleContext";
import { siteSettings } from "@/content/site-settings";
import { UI } from "@/i18n/translations";
@@ -24,6 +23,7 @@ export function Header() {
const location = useLocation();
const [open, setOpen] = useState(false);
const [openSub, setOpenSub] = useState<PageKey | null>(null);
const logo = (window as any).__CASCA_SITE__?.logo?.src || "";
useEffect(() => {
setOpen(false);
@@ -34,6 +34,7 @@ export function Header() {
<header className="bg-background border-b border-border">
<div className="container flex items-center justify-between gap-6 py-5">
<Link reloadDocument to={getPath("home", locale)} className="flex items-center gap-3 group">
{logo && (
<img
src={logo}
alt=""
@@ -41,6 +42,7 @@ export function Header() {
height={56}
className="h-12 w-12 sm:h-14 sm:w-14 object-contain"
/>
)}
<span className="font-display italic text-primary leading-tight text-xl sm:text-2xl md:text-[1.7rem]">
<span className="block">
{locale === "fr" ? "Municipalité de" : "Municipality of"}

View File

@@ -4,11 +4,7 @@ import { Calendar, MapPin } from "lucide-react";
function formatDate(iso: string, locale: "fr" | "en") {
const d = new Date(iso + "T00:00:00");
return new Intl.DateTimeFormat(locale === "fr" ? "fr-CA" : "en-CA", {
day: "numeric",
month: "long",
year: "numeric",
}).format(d);
return iso;
}
export function EventsList({ block }: { block: EventsListBlock }) {

View File

@@ -1,6 +1,7 @@
<?php
require_once __DIR__ . '/includes/core/theme-setup.php';
require_once __DIR__ . '/includes/dynamic-data.php';
require_once __DIR__ . '/includes/assets/enqueue.php';
require_once __DIR__ . '/includes/acf.php';
require_once __DIR__ . '/includes/cpt.php';

View File

@@ -12,23 +12,20 @@ function cascapedia_st_jules_register_acf(): void {
]);
}
if ( ! function_exists( 'acf_add_local_field_group' ) ) {
return;
}
if ( ! function_exists( 'acf_add_local_field_group' ) ) return;
acf_add_local_field_group([
'key' => 'group_site_settings',
'title' => 'Site Settings',
'fields' => [
cascapedia_st_jules_text_field('field_municipality_name_fr', 'municipality_name_fr', 'Nom municipal FR'),
cascapedia_st_jules_text_field('field_municipality_name_en', 'municipality_name_en', 'Nom municipal EN'),
cascapedia_st_jules_text_field('field_phone', 'phone', 'Téléphone'),
cascapedia_st_jules_text_field('field_email', 'email', 'Courriel'),
cascapedia_st_jules_textarea_field('field_address_fr', 'address_fr', 'Adresse FR'),
cascapedia_st_jules_textarea_field('field_address_en', 'address_en', 'Adresse EN'),
cascapedia_st_jules_text_field('field_hours_fr', 'hours_fr', 'Heures FR'),
cascapedia_st_jules_text_field('field_hours_en', 'hours_en', 'Heures EN'),
cascapedia_st_jules_url_field('field_facebook_url', 'facebook_url', 'Facebook'),
csj_text('field_municipality_name', 'municipality_name', 'Nom municipal'),
csj_text('field_municipality_short_name', 'municipality_short_name', 'Nom court'),
csj_image('field_site_logo', 'site_logo', 'Logo'),
csj_text('field_phone', 'phone', 'Téléphone'),
csj_text('field_email', 'email', 'Courriel'),
csj_textarea('field_address', 'address', 'Adresse'),
csj_text('field_hours', 'hours', 'Heures'),
csj_url('field_facebook_url', 'facebook_url', 'Facebook'),
],
'location' => [[['param' => 'options_page', 'operator' => '==', 'value' => 'site-settings']]],
]);
@@ -46,48 +43,62 @@ function cascapedia_st_jules_register_acf(): void {
]],
'location' => [[['param' => 'post_type', 'operator' => '==', 'value' => 'page']]],
]);
acf_add_local_field_group([
'key' => 'group_document_fields',
'title' => 'Document',
'fields' => [ csj_file('field_document_file', 'document_file', 'PDF'), csj_text('field_document_group', 'document_group', 'Groupe'), csj_text('field_document_date', 'document_date', 'Date') ],
'location' => [[['param' => 'post_type', 'operator' => '==', 'value' => 'document']]],
]);
acf_add_local_field_group([
'key' => 'group_event_fields',
'title' => 'Événement',
'fields' => [ csj_text('field_event_date', 'event_date', 'Date ISO'), csj_text('field_event_location', 'location', 'Lieu') ],
'location' => [[['param' => 'post_type', 'operator' => '==', 'value' => 'event']]],
]);
acf_add_local_field_group([
'key' => 'group_business_fields',
'title' => 'Entreprise',
'fields' => [ csj_text('field_business_phone', 'phone', 'Téléphone'), csj_text('field_business_email', 'email', 'Courriel'), csj_url('field_business_website', 'website', 'Site web'), csj_textarea('field_business_address', 'address', 'Adresse') ],
'location' => [[['param' => 'post_type', 'operator' => '==', 'value' => 'local_business']]],
]);
acf_add_local_field_group([
'key' => 'group_gallery_fields',
'title' => 'Galerie',
'fields' => [ csj_text('field_gallery_key', 'gallery_key', 'Clé'), csj_gallery('field_gallery_photos', 'gallery_photos', 'Photos') ],
'location' => [[['param' => 'post_type', 'operator' => '==', 'value' => 'gallery']]],
]);
}
add_filter( 'acf/settings/save_json', fn() => get_stylesheet_directory() . '/acf-json' );
add_filter( 'acf/settings/load_json', function( array $paths ): array {
$paths[] = get_stylesheet_directory() . '/acf-json';
return $paths;
});
add_filter( 'acf/settings/load_json', function( array $paths ): array { $paths[] = get_stylesheet_directory() . '/acf-json'; return $paths; });
function cascapedia_st_jules_acf_layouts(): array {
return [
cascapedia_st_jules_layout('hero_carousel', 'Hero carousel', [cascapedia_st_jules_repeater_field('field_hero_slides', 'slides', 'Slides')]),
cascapedia_st_jules_layout('text_intro', 'Texte intro', cascapedia_st_jules_basic_copy_fields()),
cascapedia_st_jules_layout('text_image', 'Texte + image', array_merge(cascapedia_st_jules_basic_copy_fields(), [cascapedia_st_jules_image_field('field_text_image_image', 'image', 'Image')])),
cascapedia_st_jules_layout('services_list', 'Liste de services', array_merge(cascapedia_st_jules_basic_copy_fields(), [cascapedia_st_jules_repeater_field('field_services', 'services', 'Services')])),
cascapedia_st_jules_layout('photo_gallery', 'Galerie photos', [cascapedia_st_jules_text_field('field_gallery_title_fr', 'title_fr', 'Titre FR'), cascapedia_st_jules_text_field('field_gallery_title_en', 'title_en', 'Titre EN'), cascapedia_st_jules_gallery_field('field_gallery_photos', 'photos', 'Photos')]),
cascapedia_st_jules_layout('events_list', 'Liste événements', array_merge(cascapedia_st_jules_basic_copy_fields(), [cascapedia_st_jules_repeater_field('field_events', 'events', 'Événements')])),
cascapedia_st_jules_layout('businesses_list', 'Entreprises', array_merge(cascapedia_st_jules_basic_copy_fields(), [cascapedia_st_jules_repeater_field('field_business_categories', 'categories', 'Catégories')])),
cascapedia_st_jules_layout('cta_section', 'CTA', array_merge(cascapedia_st_jules_basic_copy_fields(), [cascapedia_st_jules_link_field('field_cta_link', 'cta', 'Lien')])),
cascapedia_st_jules_layout('council_grid', 'Conseil municipal', array_merge(cascapedia_st_jules_basic_copy_fields(), [cascapedia_st_jules_repeater_field('field_council_members', 'members', 'Membres')])),
cascapedia_st_jules_layout('documents_list', 'Liste documents', array_merge(cascapedia_st_jules_basic_copy_fields(), [cascapedia_st_jules_repeater_field('field_document_groups', 'groups', 'Groupes')])),
cascapedia_st_jules_layout('pdf_embed', 'PDF intégré', [cascapedia_st_jules_text_field('field_pdf_title_fr', 'title_fr', 'Titre FR'), cascapedia_st_jules_text_field('field_pdf_title_en', 'title_en', 'Titre EN'), cascapedia_st_jules_file_field('field_pdf_file', 'file', 'PDF')]),
csj_layout('hero_carousel', 'Hero carousel', [ csj_number('field_hero_autoplay', 'autoplay_ms', 'Autoplay ms'), csj_repeater('field_hero_slides', 'slides', 'Slides', [ csj_image('field_hero_slide_image', 'image', 'Image'), csj_text('field_hero_slide_eyebrow', 'eyebrow', 'Surtitre'), csj_text('field_hero_slide_title', 'title', 'Titre'), csj_text('field_hero_slide_subtitle', 'subtitle', 'Sous-titre') ]) ]),
csj_layout('text_intro', 'Texte intro', [ csj_text('field_text_intro_eyebrow', 'eyebrow', 'Surtitre'), csj_text('field_text_intro_title', 'title', 'Titre'), csj_wysiwyg('field_text_intro_body', 'body', 'Texte'), csj_select('field_text_intro_align', 'align', 'Alignement', [ 'center' => 'Centre', 'left' => 'Gauche' ]) ]),
csj_layout('text_image', 'Texte + image', [ csj_text('field_text_image_title', 'title', 'Titre'), csj_wysiwyg('field_text_image_body', 'body', 'Texte'), csj_image('field_text_image_image', 'image', 'Image'), csj_select('field_text_image_position', 'image_position', 'Position image', [ 'right' => 'Droite', 'left' => 'Gauche' ]), ...csj_cta_fields('field_text_image') ]),
csj_layout('services_list', 'Liste de services', [ csj_text('field_services_title', 'title', 'Titre'), csj_textarea('field_services_intro', 'intro', 'Intro'), csj_repeater('field_services_items', 'services', 'Services', [ csj_text('field_service_title', 'title', 'Titre'), csj_textarea('field_service_description', 'description', 'Description'), csj_textarea('field_service_items', 'items', 'Items (un par ligne)') ]) ]),
csj_layout('photo_gallery', 'Galerie photos', [ csj_text('field_photo_gallery_title', 'title', 'Titre'), csj_textarea('field_photo_gallery_intro', 'intro', 'Intro'), csj_gallery('field_photo_gallery_photos', 'photos', 'Photos'), csj_text('field_photo_gallery_key', 'gallery_key', 'Clé galerie CPT') ]),
csj_layout('events_list', 'Liste événements', [ csj_text('field_events_title', 'title', 'Titre'), csj_textarea('field_events_intro', 'intro', 'Intro') ]),
csj_layout('businesses_list', 'Entreprises', [ csj_text('field_businesses_title', 'title', 'Titre'), csj_textarea('field_businesses_intro', 'intro', 'Intro') ]),
csj_layout('cta_section', 'CTA', [ csj_text('field_cta_title', 'title', 'Titre'), csj_textarea('field_cta_subtitle', 'subtitle', 'Sous-titre'), csj_select('field_cta_background', 'background', 'Fond', [ 'primary' => 'Primaire', 'cream' => 'Crème' ]), ...csj_cta_fields('field_cta') ]),
csj_layout('council_grid', 'Conseil municipal', [ csj_text('field_council_title', 'title', 'Titre'), csj_wysiwyg('field_council_intro', 'intro', 'Intro'), csj_repeater('field_council_members', 'members', 'Membres', [ csj_text('field_member_name', 'name', 'Nom'), csj_text('field_member_role', 'role', 'Rôle'), csj_text('field_member_email', 'email', 'Courriel'), csj_image('field_member_photo', 'photo', 'Photo') ]), csj_wysiwyg('field_council_footnote', 'footnote', 'Note') ]),
csj_layout('documents_list', 'Liste documents', [ csj_text('field_documents_title', 'title', 'Titre'), csj_wysiwyg('field_documents_intro', 'intro', 'Intro') ]),
csj_layout('pdf_embed', 'PDF intégré', [ csj_text('field_pdf_title', 'title', 'Titre'), csj_wysiwyg('field_pdf_intro', 'intro', 'Intro'), csj_file('field_pdf_file', 'file', 'PDF'), csj_number('field_pdf_height', 'height', 'Hauteur') ]),
];
}
function cascapedia_st_jules_basic_copy_fields(): array {
return [
cascapedia_st_jules_text_field('field_title_fr', 'title_fr', 'Titre FR'),
cascapedia_st_jules_text_field('field_title_en', 'title_en', 'Titre EN'),
cascapedia_st_jules_wysiwyg_field('field_body_fr', 'body_fr', 'Texte FR'),
cascapedia_st_jules_wysiwyg_field('field_body_en', 'body_en', 'Texte EN'),
];
}
function cascapedia_st_jules_layout( string $name, string $label, array $sub_fields ): array {
return ['key' => 'layout_' . $name, 'name' => $name, 'label' => $label, 'display' => 'block', 'sub_fields' => $sub_fields];
}
function cascapedia_st_jules_text_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'text']; }
function cascapedia_st_jules_url_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'url']; }
function cascapedia_st_jules_textarea_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'textarea','rows'=>3]; }
function cascapedia_st_jules_wysiwyg_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'wysiwyg','tabs'=>'all','toolbar'=>'basic','media_upload'=>0]; }
function cascapedia_st_jules_image_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'image','return_format'=>'array','preview_size'=>'medium']; }
function cascapedia_st_jules_gallery_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'gallery','return_format'=>'array','preview_size'=>'medium']; }
function cascapedia_st_jules_file_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'file','return_format'=>'array','mime_types'=>'pdf']; }
function cascapedia_st_jules_link_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'link','return_format'=>'array']; }
function cascapedia_st_jules_repeater_field($key, $name, $label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'repeater','layout'=>'row','button_label'=>'Ajouter','sub_fields'=>[cascapedia_st_jules_text_field($key.'_label_fr','label_fr','Libellé FR'), cascapedia_st_jules_text_field($key.'_label_en','label_en','Libellé EN'), cascapedia_st_jules_wysiwyg_field($key.'_content_fr','content_fr','Contenu FR'), cascapedia_st_jules_wysiwyg_field($key.'_content_en','content_en','Contenu EN')]]; }
function csj_layout( string $name, string $label, array $sub_fields ): array { return [ 'key' => 'layout_' . $name, 'name' => $name, 'label' => $label, 'display' => 'block', 'sub_fields' => $sub_fields ]; }
function csj_text($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'text']; }
function csj_url($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'url']; }
function csj_number($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'number']; }
function csj_textarea($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'textarea','rows'=>3]; }
function csj_wysiwyg($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'wysiwyg','tabs'=>'all','toolbar'=>'basic','media_upload'=>0]; }
function csj_image($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'image','return_format'=>'id','preview_size'=>'medium']; }
function csj_gallery($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'gallery','return_format'=>'id','preview_size'=>'medium']; }
function csj_file($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'file','return_format'=>'id','mime_types'=>'pdf']; }
function csj_select($key,$name,$label,array $choices): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'select','choices'=>$choices,'default_value'=>array_key_first($choices)]; }
function csj_true_false($key,$name,$label): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'true_false','ui'=>1]; }
function csj_repeater($key,$name,$label,array $sub_fields): array { return ['key'=>$key,'label'=>$label,'name'=>$name,'type'=>'repeater','layout'=>'row','button_label'=>'Ajouter','sub_fields'=>$sub_fields]; }
function csj_cta_fields(string $prefix): array { return [ csj_text($prefix.'_cta_label','cta_label','Libellé CTA'), csj_url($prefix.'_cta_url','cta_url','URL CTA'), csj_true_false($prefix.'_cta_external','cta_external','Lien externe') ]; }

View File

@@ -21,7 +21,7 @@ function cascapedia_st_jules_enqueue_assets(): void {
wp_enqueue_style(
'cascapedia-st-jules-' . $index,
get_theme_file_uri( 'assets/dist/' . $css_file ),
array(),
[],
file_exists( $css_path ) ? (string) filemtime( $css_path ) : wp_get_theme()->get( 'Version' )
);
}
@@ -32,57 +32,18 @@ function cascapedia_st_jules_enqueue_assets(): void {
wp_enqueue_script(
'cascapedia-st-jules-app',
get_theme_file_uri( 'assets/dist/' . $entry['file'] ),
array(),
[],
file_exists( $js_path ) ? (string) filemtime( $js_path ) : wp_get_theme()->get( 'Version' ),
true
);
$page_data = cascapedia_st_jules_current_page_data();
$site_data = cascapedia_st_jules_site_data();
wp_add_inline_script(
'cascapedia-st-jules-app',
'window.__CASCA_PAGE__ = ' . wp_json_encode( $page_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) . ';',
'window.__CASCA_PAGE__ = ' . wp_json_encode( $page_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) . ';' . "\n" .
'window.__CASCA_SITE__ = ' . wp_json_encode( $site_data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) . ';',
'before'
);
}
}
function cascapedia_st_jules_current_page_data(): array {
$post_id = get_queried_object_id();
if ( ! $post_id && ( is_home() || is_front_page() ) ) {
$post_id = (int) get_option( 'page_on_front' );
}
$sections = $post_id ? json_decode( (string) get_post_meta( $post_id, '_react_sections_json', true ), true ) : array();
$data = array(
'id' => $post_id,
'seo' => array(
'title' => array(
'fr' => $post_id ? (string) get_post_meta( $post_id, '_seo_title_fr', true ) : '',
'en' => $post_id ? (string) get_post_meta( $post_id, '_seo_title_en', true ) : '',
),
'description' => array(
'fr' => $post_id ? (string) get_post_meta( $post_id, '_seo_description_fr', true ) : '',
'en' => $post_id ? (string) get_post_meta( $post_id, '_seo_description_en', true ) : '',
),
),
'sections' => is_array( $sections ) ? $sections : array(),
);
return cascapedia_st_jules_rewrite_asset_urls( $data );
}
function cascapedia_st_jules_rewrite_asset_urls( mixed $value ): mixed {
if ( is_array( $value ) ) {
foreach ( $value as $key => $item ) {
$value[ $key ] = cascapedia_st_jules_rewrite_asset_urls( $item );
}
return $value;
}
if ( is_string( $value ) && str_starts_with( $value, '/assets/' ) ) {
return get_theme_file_uri( 'assets/dist' . $value );
}
return $value;
}

View File

@@ -0,0 +1,268 @@
<?php
function cascapedia_st_jules_locale(): string {
if ( function_exists( 'pll_current_language' ) ) {
$lang = pll_current_language( 'slug' );
if ( in_array( $lang, [ 'fr', 'en' ], true ) ) {
return $lang;
}
}
$path = parse_url( $_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH ) ?: '/';
return str_starts_with( $path, '/en' ) ? 'en' : 'fr';
}
function cascapedia_st_jules_l( ?string $value ): array {
$value = (string) $value;
return [ 'fr' => $value, 'en' => $value ];
}
function cascapedia_st_jules_image_data( mixed $image ): ?array {
if ( empty( $image ) ) {
return null;
}
$id = is_array( $image ) ? (int) ( $image['ID'] ?? $image['id'] ?? 0 ) : (int) $image;
if ( ! $id ) {
return null;
}
$src = wp_get_attachment_image_url( $id, 'full' );
if ( ! $src ) {
return null;
}
$meta = wp_get_attachment_metadata( $id );
$alt = get_post_meta( $id, '_wp_attachment_image_alt', true ) ?: get_the_title( $id );
return [
'id' => $id,
'src' => $src,
'alt' => cascapedia_st_jules_l( $alt ),
'width' => (int) ( $meta['width'] ?? 0 ),
'height' => (int) ( $meta['height'] ?? 0 ),
];
}
function cascapedia_st_jules_link_data( array $row, string $prefix = 'cta' ): ?array {
$url = (string) ( $row[ $prefix . '_url' ] ?? '' );
$label = (string) ( $row[ $prefix . '_label' ] ?? '' );
if ( ! $url || ! $label ) {
return null;
}
return [
'label' => cascapedia_st_jules_l( $label ),
'href' => $url,
'external' => ! empty( $row[ $prefix . '_external' ] ),
];
}
function cascapedia_st_jules_get_documents(): array {
$lang = cascapedia_st_jules_locale();
$posts = get_posts([
'post_type' => 'document',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => [ 'menu_order' => 'ASC', 'title' => 'ASC' ],
'lang' => $lang,
]);
$groups = [];
foreach ( $posts as $post ) {
$group = get_post_meta( $post->ID, 'document_group', true ) ?: __( 'Documents', 'cascapedia-st-jules' );
$file_id = (int) get_post_meta( $post->ID, 'document_file', true );
$href = $file_id ? wp_get_attachment_url( $file_id ) : '';
if ( ! $href ) {
continue;
}
$groups[ $group ][] = [
'title' => cascapedia_st_jules_l( get_the_title( $post ) ),
'href' => $href,
'date' => (string) get_post_meta( $post->ID, 'document_date', true ),
'description' => cascapedia_st_jules_l( wp_strip_all_tags( get_the_excerpt( $post ) ) ),
'external' => true,
];
}
$out = [];
foreach ( $groups as $label => $documents ) {
$out[] = [ 'label' => cascapedia_st_jules_l( $label ), 'documents' => $documents ];
}
return $out;
}
function cascapedia_st_jules_get_events(): array {
$lang = cascapedia_st_jules_locale();
$posts = get_posts([
'post_type' => 'event',
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_key' => 'event_date',
'orderby' => 'meta_value',
'order' => 'ASC',
'lang' => $lang,
]);
return array_map( static function ( WP_Post $post ): array {
return [
'date' => (string) get_post_meta( $post->ID, 'event_date', true ),
'title' => cascapedia_st_jules_l( get_the_title( $post ) ),
'location' => cascapedia_st_jules_l( (string) get_post_meta( $post->ID, 'location', true ) ),
'description' => cascapedia_st_jules_l( wp_strip_all_tags( $post->post_content ) ),
];
}, $posts );
}
function cascapedia_st_jules_get_businesses(): array {
$lang = cascapedia_st_jules_locale();
$posts = get_posts([
'post_type' => 'local_business',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'title',
'order' => 'ASC',
'lang' => $lang,
]);
$businesses = array_map( static function ( WP_Post $post ): array {
return [
'name' => get_the_title( $post ),
'description' => cascapedia_st_jules_l( wp_strip_all_tags( $post->post_content ) ),
'phone' => (string) get_post_meta( $post->ID, 'phone', true ),
'email' => (string) get_post_meta( $post->ID, 'email', true ),
'website' => (string) get_post_meta( $post->ID, 'website', true ),
'address' => (string) get_post_meta( $post->ID, 'address', true ),
];
}, $posts );
return [[ 'name' => cascapedia_st_jules_l( $lang === 'en' ? 'All our businesses' : 'Toutes nos entreprises' ), 'businesses' => $businesses ]];
}
function cascapedia_st_jules_get_gallery_photos( string $gallery_key = '' ): array {
$lang = cascapedia_st_jules_locale();
$args = [ 'post_type' => 'gallery', 'post_status' => 'publish', 'posts_per_page' => 1, 'lang' => $lang ];
if ( $gallery_key ) {
$args['meta_key'] = 'gallery_key';
$args['meta_value'] = $gallery_key;
}
$gallery = get_posts( $args )[0] ?? null;
$ids = $gallery ? (array) get_post_meta( $gallery->ID, 'gallery_photos', true ) : [];
return array_values( array_filter( array_map( 'cascapedia_st_jules_image_data', $ids ) ) );
}
function cascapedia_st_jules_section_data( array $row ): ?array {
$layout = $row['acf_fc_layout'] ?? '';
$type = str_replace( '_', '-', $layout );
$base = [ 'type' => $type ];
$title = (string) ( $row['title'] ?? '' );
if ( $title ) $base['title'] = cascapedia_st_jules_l( $title );
if ( ! empty( $row['body'] ) ) $base['body'] = cascapedia_st_jules_l( (string) $row['body'] );
if ( ! empty( $row['intro'] ) ) $base['intro'] = cascapedia_st_jules_l( (string) $row['intro'] );
if ( ! empty( $row['eyebrow'] ) ) $base['eyebrow'] = cascapedia_st_jules_l( (string) $row['eyebrow'] );
if ( ! empty( $row['align'] ) ) $base['align'] = (string) $row['align'];
if ( ! empty( $row['background'] ) ) $base['background'] = (string) $row['background'];
switch ( $layout ) {
case 'hero_carousel':
$base['type'] = 'hero-carousel';
$base['autoplayMs'] = (int) ( $row['autoplay_ms'] ?? 6000 );
$base['slides'] = [];
foreach ( (array) ( $row['slides'] ?? [] ) as $slide ) {
$image = cascapedia_st_jules_image_data( $slide['image'] ?? null );
if ( ! $image ) continue;
$base['slides'][] = [
'image' => $image,
'eyebrow' => cascapedia_st_jules_l( $slide['eyebrow'] ?? '' ),
'title' => cascapedia_st_jules_l( $slide['title'] ?? '' ),
'subtitle' => cascapedia_st_jules_l( $slide['subtitle'] ?? '' ),
];
}
return $base;
case 'text_image':
$base['type'] = 'text-image';
$base['image'] = cascapedia_st_jules_image_data( $row['image'] ?? null );
$base['imagePosition'] = $row['image_position'] ?? 'right';
$cta = cascapedia_st_jules_link_data( $row );
if ( $cta ) $base['cta'] = $cta;
return $base['image'] ? $base : null;
case 'services_list':
$base['type'] = 'services-list';
$base['services'] = [];
foreach ( (array) ( $row['services'] ?? [] ) as $service ) {
$items = array_filter( array_map( 'trim', preg_split( '/\r\n|\r|\n/', (string) ( $service['items'] ?? '' ) ) ) );
$base['services'][] = [ 'title' => cascapedia_st_jules_l( $service['title'] ?? '' ), 'description' => cascapedia_st_jules_l( $service['description'] ?? '' ), 'items' => array_map( 'cascapedia_st_jules_l', $items ) ];
}
return $base;
case 'photo_gallery':
$base['type'] = 'photo-gallery';
$photos = [];
foreach ( (array) ( $row['photos'] ?? [] ) as $photo ) {
$data = cascapedia_st_jules_image_data( $photo );
if ( $data ) $photos[] = $data;
}
if ( ! $photos && ! empty( $row['gallery_key'] ) ) $photos = cascapedia_st_jules_get_gallery_photos( (string) $row['gallery_key'] );
$base['photos'] = $photos;
return $base;
case 'events_list':
$base['type'] = 'events-list';
$base['events'] = cascapedia_st_jules_get_events();
return $base;
case 'businesses_list':
$base['type'] = 'businesses-list';
$base['categories'] = cascapedia_st_jules_get_businesses();
return $base;
case 'documents_list':
$base['type'] = 'documents-list';
$base['groups'] = cascapedia_st_jules_get_documents();
return $base;
case 'council_grid':
$base['type'] = 'council-grid';
$base['members'] = [];
foreach ( (array) ( $row['members'] ?? [] ) as $member ) {
$item = [ 'name' => (string) ( $member['name'] ?? '' ), 'role' => cascapedia_st_jules_l( $member['role'] ?? '' ), 'email' => (string) ( $member['email'] ?? '' ) ];
$photo = cascapedia_st_jules_image_data( $member['photo'] ?? null );
if ( $photo ) $item['photo'] = $photo;
$base['members'][] = $item;
}
if ( ! empty( $row['footnote'] ) ) $base['footnote'] = cascapedia_st_jules_l( (string) $row['footnote'] );
return $base;
case 'cta_section':
$base['type'] = 'cta-section';
if ( ! empty( $row['subtitle'] ) ) $base['subtitle'] = cascapedia_st_jules_l( (string) $row['subtitle'] );
$base['cta'] = cascapedia_st_jules_link_data( $row ) ?: [ 'label' => cascapedia_st_jules_l( '' ), 'href' => '#' ];
return $base;
case 'pdf_embed':
$base['type'] = 'pdf-embed';
$file = $row['file'] ?? null;
$file_id = is_array( $file ) ? (int) ( $file['ID'] ?? $file['id'] ?? 0 ) : (int) $file;
$base['href'] = $file_id ? (string) wp_get_attachment_url( $file_id ) : '';
$base['height'] = (int) ( $row['height'] ?? 800 );
return $base['href'] ? $base : null;
case 'text_intro':
default:
$base['type'] = 'text-intro';
return $base;
}
}
function cascapedia_st_jules_site_data(): array {
$logo = function_exists( 'get_field' ) ? get_field( 'site_logo', 'option' ) : null;
return [
'logo' => cascapedia_st_jules_image_data( $logo ),
'phone' => function_exists( 'get_field' ) ? (string) get_field( 'phone', 'option' ) : '',
'email' => function_exists( 'get_field' ) ? (string) get_field( 'email', 'option' ) : '',
'facebook' => function_exists( 'get_field' ) ? (string) get_field( 'facebook_url', 'option' ) : '',
];
}
function cascapedia_st_jules_current_page_data(): array {
$post_id = get_queried_object_id();
if ( ! $post_id && ( is_home() || is_front_page() ) ) {
$post_id = (int) get_option( 'page_on_front' );
}
$sections = [];
if ( $post_id && function_exists( 'get_field' ) ) {
foreach ( (array) get_field( 'sections', $post_id ) as $row ) {
$section = is_array( $row ) ? cascapedia_st_jules_section_data( $row ) : null;
if ( $section ) $sections[] = $section;
}
}
return [
'id' => $post_id,
'seo' => [
'title' => cascapedia_st_jules_l( $post_id ? ( get_post_meta( $post_id, '_seo_title', true ) ?: get_the_title( $post_id ) ) : '' ),
'description' => cascapedia_st_jules_l( $post_id ? ( get_post_meta( $post_id, '_seo_description', true ) ?: get_the_excerpt( $post_id ) ) : '' ),
],
'sections' => $sections,
];
}

View File

@@ -0,0 +1,156 @@
<?php
if ( ! defined('ABSPATH') ) require_once dirname(__DIR__, 4) . '/wp-load.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
do_action('acf/init');
$data = json_decode(file_get_contents(__DIR__ . '/migration-data.json'), true);
if (!$data) { fwrite(STDERR, "migration-data.json invalid\n"); exit(1); }
$fr_routes = ['home'=>'accueil','municipality'=>'municipalite','mayor'=>'mot-du-maire','council'=>'conseil-municipal','services'=>'services-municipaux-et-publics','documents'=>'documents-importants','community'=>'notre-communaute','communityPhotos'=>'album-photos-communaute','activitiesPhotos'=>'album-photos-activites','developmentPlan'=>'plan-de-developpement','townMap'=>'carte-de-la-ville','businesses'=>'entreprises-locales','events'=>'evenements','publicNotices'=>'avis-publics','minutes'=>'proces-verbaux','newsletters'=>'bulletins'];
$en_routes = ['home'=>'en','municipality'=>'en/municipality','mayor'=>'en/word-from-the-mayor','council'=>'en/our-council','services'=>'en/our-services','documents'=>'en/important-documents','community'=>'en/our-community','communityPhotos'=>'en/community-photo-album','activitiesPhotos'=>'en/activities-photo-album','developmentPlan'=>'en/development-plan','townMap'=>'en/town-map','businesses'=>'en/local-businesses','events'=>'en/events','publicNotices'=>'en/public-notices','minutes'=>'en/minutes','newsletters'=>'en/newsletters'];
function csj_v($value, string $lang, string $fallback=''): string { return is_array($value) ? (string)($value[$lang] ?? $value['fr'] ?? $fallback) : (string)($value ?? $fallback); }
function csj_post_by_path(string $path): ?WP_Post { return get_page_by_path($path, OBJECT, 'page') ?: null; }
function csj_upsert_page(string $path, string $title, int $parent=0): int {
$post = csj_post_by_path($path);
$slug = basename($path);
$args = ['post_type'=>'page','post_status'=>'publish','post_title'=>$title,'post_name'=>$slug,'post_parent'=>$parent];
if ($post) { $args['ID']=$post->ID; wp_update_post($args); return $post->ID; }
return (int) wp_insert_post($args);
}
function csj_ensure_langs(): void {
if (!function_exists('pll_languages_list')) return;
$existing = pll_languages_list(['fields'=>'slug']);
$langs = [
'fr' => ['name'=>'Français','slug'=>'fr','locale'=>'fr_CA','rtl'=>0,'term_group'=>0,'flag'=>'ca'],
'en' => ['name'=>'English','slug'=>'en','locale'=>'en_CA','rtl'=>0,'term_group'=>1,'flag'=>'ca'],
];
foreach ($langs as $slug=>$args) if (!in_array($slug,$existing,true)) PLL()->model->add_language($args);
$opts = get_option('polylang'); if (is_array($opts)) { $opts['default_lang']='fr'; $opts['force_lang']=1; $opts['hide_default']=1; update_option('polylang',$opts); }
}
function csj_set_lang(int $id, string $lang): void { if (function_exists('pll_set_post_language')) pll_set_post_language($id,$lang); }
function csj_link_translations(array $ids): void { if (function_exists('pll_save_post_translations')) pll_save_post_translations(array_filter($ids)); }
function csj_find_asset(string $src): ?string {
if (!$src) return null;
$base = basename(parse_url($src, PHP_URL_PATH) ?: $src);
$candidates = [
dirname(__DIR__) . '/assets/dist/assets/' . $base,
'/home/websimple/.openclaw/workspace-build/wordpress-clients/cascapedia-st-jules/site-migration/source-repo/dist/assets/' . $base,
'/home/websimple/.openclaw/workspace-build/wordpress-clients/cascapedia-st-jules/site-migration/source-repo/dist/pdfs/' . $base,
];
foreach ($candidates as $p) if (is_file($p)) return $p;
$matches = glob(dirname(__DIR__) . '/assets/dist/assets/' . preg_replace('/-[A-Za-z0-9_]+(?=\.[^.]+$)/','-*',$base));
return $matches[0] ?? null;
}
function csj_import_file(string $path, string $title='', string $alt=''): int {
if (!is_file($path)) return 0;
$hash = md5_file($path);
$found = get_posts(['post_type'=>'attachment','post_status'=>'inherit','meta_key'=>'_csj_source_hash','meta_value'=>$hash,'fields'=>'ids','posts_per_page'=>1]);
if ($found) return (int)$found[0];
$file_array = ['name'=>basename($path), 'tmp_name'=>wp_tempnam(basename($path))];
copy($path, $file_array['tmp_name']);
$id = media_handle_sideload($file_array, 0, $title ?: pathinfo($path, PATHINFO_FILENAME));
if (is_wp_error($id)) { @unlink($file_array['tmp_name']); return 0; }
update_post_meta($id, '_csj_source_hash', $hash);
if ($alt) update_post_meta($id, '_wp_attachment_image_alt', $alt);
return (int)$id;
}
function csj_import_src(string $src, string $title='', string $alt=''): int { $p=csj_find_asset($src); return $p ? csj_import_file($p,$title,$alt) : 0; }
function csj_upsert_typed(string $type, string $title, string $lang, array $args=[]): int {
$q = new WP_Query(['post_type'=>$type,'post_status'=>'any','title'=>$title,'posts_per_page'=>1,'fields'=>'ids','lang'=>$lang]);
$postarr = array_merge(['post_type'=>$type,'post_status'=>'publish','post_title'=>$title],$args);
if (!empty($q->posts[0])) { $postarr['ID']=(int)$q->posts[0]; wp_update_post($postarr); $id=(int)$q->posts[0]; }
else $id=(int)wp_insert_post($postarr);
csj_set_lang($id,$lang); return $id;
}
function csj_section_row(array $s, string $lang): array {
$layout = str_replace('-','_',$s['type'] ?? 'text_intro');
$row = ['acf_fc_layout'=>$layout];
foreach (['title','body','intro','eyebrow','subtitle'] as $k) if (isset($s[$k])) $row[$k] = csj_v($s[$k],$lang);
if (isset($s['align'])) $row['align']=$s['align'];
if (isset($s['background'])) $row['background']=$s['background'];
if (isset($s['cta'])) { $row['cta_label']=csj_v($s['cta']['label']??'',$lang); $row['cta_url']=$s['cta']['href']??''; $row['cta_external']=!empty($s['cta']['external']); }
if ($layout==='hero_carousel') { $row['autoplay_ms']=$s['autoplayMs']??6000; $row['slides']=[]; foreach (($s['slides']??[]) as $slide) { $img=csj_import_src($slide['image']['src']??'', csj_v($slide['title']??'',$lang), csj_v($slide['image']['alt']??'',$lang)); if($img)$row['slides'][]=['image'=>$img,'eyebrow'=>csj_v($slide['eyebrow']??'',$lang),'title'=>csj_v($slide['title']??'',$lang),'subtitle'=>csj_v($slide['subtitle']??'',$lang)]; } }
if ($layout==='text_image') { $row['image_position']=$s['imagePosition']??'right'; $row['image']=csj_import_src($s['image']['src']??'', csj_v($s['title']??'',$lang), csj_v($s['image']['alt']??'',$lang)); }
if ($layout==='services_list') { $row['services']=[]; foreach(($s['services']??[]) as $it) $row['services'][]=['title'=>csj_v($it['title']??'',$lang),'description'=>csj_v($it['description']??'',$lang),'items'=>implode("\n", array_map(fn($x)=>csj_v($x,$lang), $it['items']??[]))]; }
if ($layout==='photo_gallery') { $row['photos']=[]; foreach(($s['photos']??[]) as $p){ $id=csj_import_src($p['src']??'', csj_v($s['title']??'',$lang), csj_v($p['alt']??'',$lang)); if($id)$row['photos'][]=$id; } }
if ($layout==='council_grid') { $row['members']=[]; foreach(($s['members']??[]) as $m){ $photo=csj_import_src($m['photo']['src']??'', $m['name']??'', csj_v($m['photo']['alt']??'',$lang)); $row['members'][]=['name'=>$m['name']??'','role'=>csj_v($m['role']??'',$lang),'email'=>$m['email']??'','photo'=>$photo]; } if(isset($s['footnote']))$row['footnote']=csj_v($s['footnote'],$lang); }
if ($layout==='pdf_embed') { $href=$s['href']??($s['file']['href']??''); $row['file']=csj_import_src($href, csj_v($s['title']??'',$lang)); $row['height']=$s['height']??800; }
return $row;
}
csj_ensure_langs();
// Import logo into media and localized site options.
$logo = csj_import_file(dirname(__DIR__) . '/assets/dist/assets/logo-municipality-CW-yydo5.png', 'Logo Cascapédia-St-Jules', 'Logo Cascapédia-St-Jules');
foreach(['fr','en'] as $lang){
update_field('municipality_name', csj_v($data['siteSettings']['municipality']['name']??'',$lang), 'option');
update_field('municipality_short_name', csj_v($data['siteSettings']['municipality']['shortName']??'',$lang), 'option');
update_field('site_logo', $logo, 'option');
update_field('phone', $data['siteSettings']['contact']['phone']??'', 'option');
update_field('email', $data['siteSettings']['contact']['email']??'', 'option');
update_field('address', csj_v($data['siteSettings']['contact']['address']??'',$lang), 'option');
update_field('hours', csj_v($data['siteSettings']['contact']['hours']??'',$lang), 'option');
update_field('facebook_url', $data['siteSettings']['social']['facebook']??'', 'option');
}
// Pages + ACF sections.
$page_pairs=[]; $updated=0;
$en_parent = csj_upsert_page('en','English',0); csj_set_lang($en_parent,'en');
foreach ($data['pages'] as $key=>$page) {
if (!isset($fr_routes[$key],$en_routes[$key])) continue;
$fr_id = csj_upsert_page($fr_routes[$key], csj_v($page['seo']['title']??$key,'fr'), 0);
$parent = str_starts_with($en_routes[$key],'en/') ? $en_parent : 0;
$en_id = csj_upsert_page($en_routes[$key], csj_v($page['seo']['title']??$key,'en'), $parent);
foreach(['fr'=>$fr_id,'en'=>$en_id] as $lang=>$id){
csj_set_lang($id,$lang);
$rows=[]; foreach(($page['sections']??[]) as $s) $rows[]=csj_section_row($s,$lang);
update_field('sections',$rows,$id);
update_post_meta($id,'_seo_title',csj_v($page['seo']['title']??'',$lang));
update_post_meta($id,'_seo_description',csj_v($page['seo']['description']??'',$lang));
delete_post_meta($id,'_react_sections_json');
$updated++;
}
csj_link_translations(['fr'=>$fr_id,'en'=>$en_id]);
$page_pairs[$key]=['fr'=>$fr_id,'en'=>$en_id];
}
// Documents from documents/minutes/newsletters groups.
$doc_pairs=0;
foreach (['documents','minutes','newsletters'] as $page_key) foreach(($data['pages'][$page_key]['sections']??[]) as $s) if(($s['type']??'')==='documents-list') foreach(($s['groups']??[]) as $group) foreach(($group['documents']??[]) as $doc){
$ids=[]; $file=csj_import_src($doc['href']??'', csj_v($doc['title']??'','fr'));
foreach(['fr','en'] as $lang){
$id=csj_upsert_typed('document', csj_v($doc['title']??'Document',$lang), $lang, ['post_excerpt'=>csj_v($doc['description']??'',$lang)]);
update_field('document_file',$file,$id); update_field('document_group',csj_v($group['label']??'Documents',$lang),$id); update_field('document_date',$doc['date']??'',$id);
delete_post_meta($id,'source_pdf_path'); delete_post_meta($id,'_source_json'); $ids[$lang]=$id;
}
csj_link_translations($ids); $doc_pairs++;
}
// Businesses.
$biz_pairs=0; foreach(($data['pages']['businesses']['sections']??[]) as $s) if(($s['type']??'')==='businesses-list') foreach(($s['categories']??[]) as $cat) foreach(($cat['businesses']??[]) as $b){
$ids=[]; foreach(['fr','en'] as $lang){ $id=csj_upsert_typed('local_business',$b['name']??'Entreprise',$lang,['post_content'=>csj_v($b['description']??'',$lang)]); foreach(['phone','email','website','address'] as $m) update_field($m,$b[$m]??'',$id); delete_post_meta($id,'_source_json'); $ids[$lang]=$id; } csj_link_translations($ids); $biz_pairs++; }
// Events from home event list + event detail texts.
$event_items=[]; foreach(($data['pages']['home']['sections']??[]) as $s) if(($s['type']??'')==='events-list') foreach(($s['events']??[]) as $e) $event_items[]=$e; foreach(($data['pages']['events']['sections']??[]) as $s) if(($s['type']??'')==='text-intro') $event_items[]=['title'=>$s['title']??'','description'=>$s['body']??'','date'=>''];
$event_pairs=0; foreach($event_items as $e){ $ids=[]; foreach(['fr','en'] as $lang){ $id=csj_upsert_typed('event', csj_v($e['title']??'Événement',$lang),$lang,['post_content'=>csj_v($e['description']??'',$lang)]); update_field('event_date',$e['date']??'',$id); update_field('location',csj_v($e['location']??'',$lang),$id); delete_post_meta($id,'_source_json'); $ids[$lang]=$id; } csj_link_translations($ids); $event_pairs++; }
// Galleries.
$gallery_pairs=0; foreach(['communityPhotos'=>'community','activitiesPhotos'=>'activities'] as $page_key=>$gkey) foreach(($data['pages'][$page_key]['sections']??[]) as $s) if(($s['type']??'')==='photo-gallery'){ $photos=[]; foreach(($s['photos']??[]) as $p){$pid=csj_import_src($p['src']??'',csj_v($s['title']??'Galerie','fr'),csj_v($p['alt']??'','fr')); if($pid)$photos[]=$pid;} $ids=[]; foreach(['fr','en'] as $lang){$id=csj_upsert_typed('gallery',csj_v($s['title']??'Galerie',$lang),$lang,['post_content'=>csj_v($s['intro']??'',$lang)]); update_field('gallery_key',$gkey,$id); update_field('gallery_photos',$photos,$id); delete_post_meta($id,'_source_json'); delete_post_meta($id,'_source_photos_json'); $ids[$lang]=$id;} csj_link_translations($ids); $gallery_pairs++; }
// Clean old JSON-in-WYSIWYG/meta artifacts.
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_react_sections_json' OR meta_key LIKE '_source_%' OR (meta_key LIKE 'sections\\_%\\_content\\_%' AND meta_value LIKE '{%')");
flush_rewrite_rules();
echo "pages_updated=$updated\n";
echo "documents_pairs=$doc_pairs\n";
echo "business_pairs=$biz_pairs\n";
echo "event_pairs=$event_pairs\n";
echo "gallery_pairs=$gallery_pairs\n";
echo "image_attachments=" . (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type='attachment' AND post_mime_type LIKE 'image/%'") . "\n";
echo "pdf_attachments=" . (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type='attachment' AND post_mime_type='application/pdf'") . "\n";