Files
ms1inscription-v5/php/inc_fx_doc.php
stephan 0d042deaf2 MSIN-4416 Enhance refund functionality in registration management with new documentation panel
This commit introduces a new documentation panel for the refund process in the registration management interface. It adds a function to render the refund documentation aside from the main refund form, improving user guidance during the refund process. Additionally, CSS styles are added to ensure proper layout and responsiveness of the new panel. The version code is updated to 4.72.717 to reflect these changes.
2026-06-30 14:40:48 -04:00

598 lines
21 KiB
PHP

<?php
/**
* Module documentation MS1 — overlay paginé HTML (distinct de info / info_aide).
*
* Clés d'appel (doc_anchor_clef) → module (doc_mod_clef) → pages (doc_page).
* Usage :
* fxDocRenderModule('bib_v4_assign_doc'); — overlay complet
* fxDocRenderTrigger('bib_v4_assign_doc'); — bouton ? seul
* afficheDocPage('bib_doc_overview'); — HTML d'une page (comme afficheTexte)
* afficheDocTitre('bib_doc_overview'); — titre d'une page
*/
/** Échappement attributs HTML doc module. */
function fxDocEsc($str) {
return htmlspecialchars((string)$str, ENT_QUOTES, 'UTF-8');
}
/** Page PHP courante pour doc_prg (miroir fxBibInfoPage / info_prg). */
function fxDocPrg($mem_global = 0) {
global $vPage;
if ($mem_global == 1) {
return 'global';
}
return !empty($vPage) ? $vPage : 'compte.php';
}
/** Langue courante doc. */
function fxDocLangue() {
global $strLangue;
return !empty($strLangue) ? $strLangue : 'fr';
}
/** Tables doc_* présentes en BD (évite erreur si SQL module pas encore exécuté). */
function fxDocSchemaReady() {
global $objDatabase;
static $blnReady = null;
if ($blnReady !== null) {
return $blnReady;
}
if (!isset($objDatabase)) {
$blnReady = false;
return false;
}
$qry = $objDatabase->fxQuery("SHOW TABLES LIKE 'doc_page'");
$blnReady = ($qry && mysqli_num_rows($qry) > 0);
return $blnReady;
}
/**
* Charge en mémoire toutes les pages doc pour une page PHP (comme obtenirTextepage).
* Remplit $vdoc_page[doc_page_clef] = row.
*/
function obtenirDocPage($page = null, $langue = null) {
global $objDatabase, $vdoc_page;
if ($page === null || $page === '') {
$page = fxDocPrg(0);
}
if ($langue === null || $langue === '') {
$langue = fxDocLangue();
}
$vdoc_page = array();
if (!isset($objDatabase) || !fxDocSchemaReady()) {
return $vdoc_page;
}
$sql = "SELECT *
FROM doc_page
WHERE doc_actif = 1
AND doc_langue = '" . $objDatabase->fxEscape($langue) . "'
AND doc_prg IN ('" . $objDatabase->fxEscape($page) . "', 'global')
ORDER BY doc_tri, pk_doc_page";
$rows = $objDatabase->fxGetResults($sql);
if (!is_array($rows)) {
return $vdoc_page;
}
for ($i = 1; $i <= count($rows); $i++) {
if (!empty($rows[$i]['doc_page_clef'])) {
$vdoc_page[$rows[$i]['doc_page_clef']] = $rows[$i];
}
}
return $vdoc_page;
}
/** Ligne doc_anchor par clé d'appel. */
function fxDocGetAnchorRow($clef, $mem_global = 0) {
global $objDatabase;
static $anchorCache = array();
$strPrg = fxDocPrg($mem_global);
$cacheKey = $clef . '|' . $strPrg;
if (array_key_exists($cacheKey, $anchorCache)) {
return $anchorCache[$cacheKey];
}
if (!isset($objDatabase) || $clef === '' || !fxDocSchemaReady()) {
$anchorCache[$cacheKey] = array();
return $anchorCache[$cacheKey];
}
$sql = "SELECT *
FROM doc_anchor
WHERE doc_actif = 1
AND doc_anchor_clef = '" . $objDatabase->fxEscape($clef) . "'
AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global')
ORDER BY FIELD(doc_prg, '" . $objDatabase->fxEscape($strPrg) . "', 'global')
LIMIT 1";
$row = $objDatabase->fxGetRow($sql);
$anchorCache[$cacheKey] = is_array($row) ? $row : array();
return $anchorCache[$cacheKey];
}
/** Résout doc_mod_clef depuis une clé d'ancre ou un code module direct. */
function fxDocResolveModClef($clef, $mem_global = 0) {
$anchor = fxDocGetAnchorRow($clef, $mem_global);
if (!empty($anchor['doc_mod_clef'])) {
return $anchor['doc_mod_clef'];
}
return $clef;
}
/** Pages actives d'un module, triées (pour carrousel overlay). */
function fxDocGetModulePages($modClef, $langue = null, $mem_global = 0) {
global $objDatabase, $vdoc_page;
if ($langue === null || $langue === '') {
$langue = fxDocLangue();
}
$strPrg = fxDocPrg($mem_global);
$modClef = fxDocResolveModClef($modClef, $mem_global);
if (!isset($objDatabase) || $modClef === '' || !fxDocSchemaReady()) {
return array();
}
$sql = "SELECT *
FROM doc_page
WHERE doc_actif = 1
AND doc_mod_clef = '" . $objDatabase->fxEscape($modClef) . "'
AND doc_langue = '" . $objDatabase->fxEscape($langue) . "'
AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global')
ORDER BY doc_tri, pk_doc_page";
$rows = $objDatabase->fxGetResults($sql);
$pages = array();
if (!is_array($rows)) {
return $pages;
}
for ($i = 1; $i <= count($rows); $i++) {
$pages[] = $rows[$i];
if (!empty($rows[$i]['doc_page_clef'])) {
if (!isset($vdoc_page) || !is_array($vdoc_page)) {
$vdoc_page = array();
}
$vdoc_page[$rows[$i]['doc_page_clef']] = $rows[$i];
}
}
return $pages;
}
/** Une page par doc_page_clef (mémoire ou BD). */
function fxDocGetPageRow($clef, $mem_global = 0) {
global $vdoc_page, $objDatabase;
if (!empty($vdoc_page[$clef])) {
return $vdoc_page[$clef];
}
if (!isset($objDatabase) || $clef === '' || !fxDocSchemaReady()) {
return array();
}
$strPrg = fxDocPrg($mem_global);
$langue = fxDocLangue();
static $pageCache = array();
$cacheKey = $clef . '|' . $langue . '|' . $strPrg;
if (array_key_exists($cacheKey, $pageCache)) {
return $pageCache[$cacheKey];
}
$sql = "SELECT *
FROM doc_page
WHERE doc_actif = 1
AND doc_page_clef = '" . $objDatabase->fxEscape($clef) . "'
AND doc_langue = '" . $objDatabase->fxEscape($langue) . "'
AND doc_prg IN ('" . $objDatabase->fxEscape($strPrg) . "', 'global')
ORDER BY FIELD(doc_prg, '" . $objDatabase->fxEscape($strPrg) . "', 'global')
LIMIT 1";
$row = $objDatabase->fxGetRow($sql);
$pageCache[$cacheKey] = is_array($row) ? $row : array();
return $pageCache[$cacheKey];
}
/** Crée une page doc vide si absente (miroir afficheTexte). */
function fxDocEnsurePage($clef, $mem_global = 0, $modClef = '') {
global $objDatabase;
$row = fxDocGetPageRow($clef, $mem_global);
if (!empty($row)) {
return $row;
}
$strPrg = fxDocPrg($mem_global);
$langue = fxDocLangue();
if ($modClef === '') {
$modClef = $clef;
}
$tabData = array(
'doc_mod_clef' => $modClef,
'doc_page_clef' => $clef,
'doc_langue' => $langue,
'doc_titre' => $clef,
'doc_sous_titre' => '',
'doc_html' => '',
'doc_prg' => $strPrg,
'doc_tri' => 0,
'doc_actif' => 1,
);
fxSetDocPage('add', $tabData);
if ($langue === 'fr') {
$tabDataEn = $tabData;
$tabDataEn['doc_langue'] = 'en';
fxSetDocPage('add', $tabDataEn);
}
return fxDocGetPageRow($clef, $mem_global);
}
/**
* Titre doc_titre — équivalent afficheTexte pour doc_page.
*/
function afficheDocTitre($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') {
$row = fxDocGetPageRow($clef, $mem_global);
if (empty($row)) {
fxDocEnsurePage($clef, $mem_global, $modClef);
$row = fxDocGetPageRow($clef, $mem_global);
}
$str = trim($row['doc_titre'] ?? '');
if ($str === '' || $str === $clef) {
$str = '*' . $clef . '*';
}
if ($mem_echo == 1) {
echo fxDocEsc($str);
}
return $str;
}
/**
* Corps HTML doc_html — équivalent afficheAide (contenu long, HTML admin).
*/
function afficheDocHtml($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') {
$row = fxDocGetPageRow($clef, $mem_global);
if (empty($row)) {
fxDocEnsurePage($clef, $mem_global, $modClef);
$row = fxDocGetPageRow($clef, $mem_global);
}
$str = $row['doc_html'] ?? '';
if ($mem_echo == 1) {
echo $str;
}
return $str;
}
/** Sous-titre doc_sous_titre. */
function afficheDocSousTitre($clef, $mem_echo = 1, $mem_global = 0, $modClef = '') {
$row = fxDocGetPageRow($clef, $mem_global);
if (empty($row)) {
fxDocEnsurePage($clef, $mem_global, $modClef);
$row = fxDocGetPageRow($clef, $mem_global);
}
$str = trim($row['doc_sous_titre'] ?? '');
if ($mem_echo == 1) {
echo fxDocEsc($str);
}
return $str;
}
/** URL pop-up doc_edition.php — module documentation paginé. */
function fxAdminDocUrl($anchorClef, $mem_global = 0) {
global $vDomaine, $vPage;
$strPrg = fxDocPrg($mem_global);
$modClef = fxDocResolveModClef($anchorClef, $mem_global);
return $vDomaine . '/php/doc_edition.php?' . http_build_query(array(
'anchor' => $anchorClef,
'mod' => $modClef,
'prg' => $strPrg,
'bd' => 'client',
'page' => !empty($vPage) ? $vPage : 'compte.php',
));
}
/** Lien crayon doc_edition.php — superadm dev/préprod + switch Textes ON. */
function fxAdminDocButton($anchorClef, $mem_global = 0) {
if (!fxAdminTextEditModeActive() || trim($anchorClef) === '') {
return '';
}
return '<a href="' . fxDocEsc(fxAdminDocUrl($anchorClef, $mem_global)) . '"'
. ' class="' . fxAdminTradLinkClass() . ' ms1-doc-edit-link"'
. ' title="Modifier le guide documentation"'
. ' aria-label="Modifier le guide documentation">'
. '<i class="fa fa-book" aria-hidden="true"></i>'
. '</a>';
}
/** Bouton ? pour ouvrir le module doc (clé doc_anchor_clef) — même rond que btn-aide-bib. */
function fxDocRenderTrigger($anchorClef, $mem_global = 0, $strAriaLabel = 'Documentation') {
$modClef = fxDocResolveModClef($anchorClef, $mem_global);
return '<button type="button"'
. ' class="btn-aide-bib ms1-doc-trigger btn-doc-trigger"'
. ' data-doc-anchor="' . fxDocEsc($anchorClef) . '"'
. ' data-doc-mod="' . fxDocEsc($modClef) . '"'
. ' aria-label="' . fxDocEsc($strAriaLabel) . '"'
. ' aria-haspopup="dialog">?</button>';
}
/** Panneau overlay paginé pour un module (clé doc_anchor_clef ou doc_mod_clef). */
function fxDocRenderModule($anchorClef, $mem_global = 0) {
$modClef = fxDocResolveModClef($anchorClef, $mem_global);
$pages = fxDocGetModulePages($modClef, null, $mem_global);
if (empty($pages)) {
return '';
}
$overlayId = 'ms1-doc-overlay-' . preg_replace('/[^a-z0-9_-]/i', '-', $anchorClef);
$html = '<div id="' . fxDocEsc($overlayId) . '"'
. ' class="ms1-doc-overlay"'
. ' data-doc-anchor="' . fxDocEsc($anchorClef) . '"'
. ' data-doc-mod="' . fxDocEsc($modClef) . '"'
. ' hidden'
. ' role="dialog"'
. ' aria-modal="true"'
. ' aria-labelledby="' . fxDocEsc($overlayId) . '-title">';
$html .= '<div class="ms1-doc-backdrop" data-ms1-doc-close="1"></div>';
$html .= '<div class="ms1-doc-panel">';
$html .= '<header class="ms1-doc-header">';
$html .= '<div class="ms1-doc-header-text">';
$html .= '<h2 class="ms1-doc-title" id="' . fxDocEsc($overlayId) . '-title"></h2>';
$html .= '<p class="ms1-doc-subtitle"></p>';
$html .= '</div>';
$html .= '<div class="ms1-doc-header-actions">';
$html .= fxAdminDocButton($anchorClef, $mem_global);
$html .= '<button type="button" class="ms1-doc-close" data-ms1-doc-close="1" aria-label="Fermer">&times;</button>';
$html .= '</div>';
$html .= '</header>';
$html .= '<div class="ms1-doc-body">';
foreach ($pages as $idx => $page) {
$html .= '<div class="ms1-doc-page"'
. ($idx > 0 ? ' hidden' : '')
. ' data-doc-page-index="' . (int)$idx . '"'
. ' data-doc-page-clef="' . fxDocEsc($page['doc_page_clef']) . '">';
$html .= '<div class="ms1-doc-content ms1-doc-format">' . ($page['doc_html'] ?? '') . '</div>';
$html .= '</div>';
}
$html .= '</div>';
$html .= '<footer class="ms1-doc-footer">';
$html .= '<div class="ms1-doc-nav-dots">';
foreach ($pages as $idx => $page) {
$active = $idx === 0 ? ' is-active' : '';
$label = trim($page['doc_titre'] ?? '') !== '' ? $page['doc_titre'] : (string)($idx + 1);
$html .= '<button type="button"'
. ' class="ms1-doc-dot' . $active . '"'
. ' data-doc-page-index="' . (int)$idx . '"'
. ' title="' . fxDocEsc($label) . '"'
. ' aria-label="' . fxDocEsc($label) . '">'
. (int)($idx + 1)
. '</button>';
}
$html .= '</div>';
$html .= '<div class="ms1-doc-nav-arrows">';
$html .= '<button type="button" class="ms1-doc-prev" data-ms1-doc-prev="1">Précédent</button>';
$html .= '<span class="ms1-doc-counter"><span class="ms1-doc-counter-current">1</span> / <span class="ms1-doc-counter-total">' . count($pages) . '</span></span>';
$html .= '<button type="button" class="ms1-doc-next" data-ms1-doc-next="1">Suivant</button>';
$html .= '</div>';
$html .= '</footer>';
$html .= '</div></div>';
// Métadonnées titres pour JS (init header sans parser le HTML)
$html .= '<script type="application/json" class="ms1-doc-meta" data-doc-anchor="' . fxDocEsc($anchorClef) . '">';
$meta = array();
foreach ($pages as $page) {
$meta[] = array(
'clef' => $page['doc_page_clef'],
'titre' => $page['doc_titre'] ?? '',
'sous_titre' => $page['doc_sous_titre'] ?? '',
);
}
$html .= json_encode($meta, JSON_UNESCAPED_UNICODE);
$html .= '</script>';
return $html;
}
/** Trigger + overlay (appel typique à côté d'un titre de bloc). */
function fxDocRenderBlock($anchorClef, $mem_global = 0, $strAriaLabel = 'Documentation') {
return fxDocRenderTrigger($anchorClef, $mem_global, $strAriaLabel) . fxDocRenderModule($anchorClef, $mem_global);
}
/** Clés doc — panneau remboursement promoteur (fiche gestion inscriptions). */
function fxDocRefundPromoteurAnchorClef() {
return 'inscr_gestion_refund_doc';
}
function fxDocRefundPromoteurPageClef() {
return 'inscr_gestion_refund_promoteur_info';
}
function fxDocRefundPromoteurModClef() {
return 'inscr_gestion_refund_doc';
}
/** Texte par défaut si doc_html vide (avant import SQL ou édition). */
function fxDocRefundPromoteurFallback($strLangue) {
if ($strLangue === 'en') {
return array(
'titre' => 'Refunds and MS1 fees',
'html' => '<article class="ms1-doc-article">'
. '<p>As a promoter, you may refund the customer <strong>up to 100%</strong> of the transaction amount.</p>'
. '<p>However, <strong>MS1 platform fees are not refunded</strong> to the promoter. If you refund the customer, <strong>you absorb those fees</strong> — they remain your responsibility.</p>'
. '<p>The &laquo;&nbsp;Max refundable&nbsp;&raquo; amount is the PayPal balance available for this capture, not a reimbursement of MS1 fees.</p>'
. '</article>',
);
}
return array(
'titre' => 'Remboursement et frais MS1',
'html' => '<article class="ms1-doc-article">'
. '<p>En tant que promoteur, vous pouvez rembourser le client <strong>jusqu\'à 100&nbsp;%</strong> du montant de la transaction.</p>'
. '<p>En revanche, <strong>les frais MS1 ne sont pas remboursés</strong> par la plateforme au promoteur. Si vous remboursez le client, <strong>vous assumez ces frais</strong>&nbsp;: ils restent à votre charge.</p>'
. '<p>Le montant &laquo;&nbsp;Max remboursable&nbsp;&raquo; correspond au solde PayPal disponible pour cette capture, et non au remboursement des frais MS1.</p>'
. '</article>',
);
}
/**
* Colonne droite du panneau remboursement (fiche gestion promoteur) — doc_html inline.
* Éditable via doc_edition.php (mode Textes, icône livre).
*/
function fxDocRenderRefundPromoteurAside() {
if (!fxDocSchemaReady()) {
return '';
}
$strAnchor = fxDocRefundPromoteurAnchorClef();
$strPageClef = fxDocRefundPromoteurPageClef();
$strModClef = fxDocRefundPromoteurModClef();
$strLangue = fxDocLangue();
fxDocEnsurePage($strPageClef, 0, $strModClef);
$strTitre = trim((string)afficheDocTitre($strPageClef, 0, 0, $strModClef));
$strHtml = trim((string)afficheDocHtml($strPageClef, 0, 0, $strModClef));
$tabFallback = fxDocRefundPromoteurFallback($strLangue);
if ($strTitre === '' || $strTitre === '*' . $strPageClef . '*') {
$strTitre = $tabFallback['titre'];
}
if ($strHtml === '') {
$strHtml = $tabFallback['html'];
}
$strEditBtn = function_exists('fxAdminDocButton') ? fxAdminDocButton($strAnchor, 0) : '';
$html = '<aside class="ms1-refund-doc-aside" aria-labelledby="ms1-refund-doc-title">';
$html .= '<div class="ms1-refund-doc-panel">';
$html .= '<header class="ms1-refund-doc-head">';
$html .= '<h3 class="ms1-refund-doc-title" id="ms1-refund-doc-title">' . fxDocEsc($strTitre) . '</h3>';
if ($strEditBtn !== '') {
$html .= '<div class="ms1-refund-doc-head-actions">' . $strEditBtn . '</div>';
}
$html .= '</header>';
$html .= '<div class="ms1-refund-doc-body ms1-doc-format ms1-doc-content">' . $strHtml . '</div>';
$html .= '</div></aside>';
return $html;
}
function fxSetDocMod($strAction, $tabData, $id = 0) {
global $objDatabase;
switch ($strAction) {
case 'add':
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_mod', 'doc_mod', $GLOBALS['arrConDatabaseMain']['db']);
$sqlValide = "SELECT COUNT(doc_mod_clef) FROM doc_mod
WHERE doc_mod_clef = '" . $objDatabase->fxEscape($tabData['doc_mod_clef']) . "'
AND doc_mod_prg = '" . $objDatabase->fxEscape($tabData['doc_mod_prg']) . "'";
if ((int)$objDatabase->fxGetVar($sqlValide) > 0) {
break;
}
$sqlInsert = "INSERT INTO doc_mod " . $tabFieldsValues['set'] . ', doc_mod_creation = NOW()';
$objDatabase->fxQuery($sqlInsert);
break;
case 'mod':
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_mod', 'doc_mod', $GLOBALS['arrConDatabaseMain']['db']);
$sqlUpdate = "UPDATE doc_mod " . $tabFieldsValues['set'] . ", doc_mod_maj = NOW() WHERE " . $tabFieldsValues['key'];
$objDatabase->fxQuery($sqlUpdate);
break;
}
}
function fxSetDocPage($strAction, $tabData, $id = 0) {
global $objDatabase;
switch ($strAction) {
case 'add':
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_page', 'doc_page', $GLOBALS['arrConDatabaseMain']['db']);
$sqlValide = "SELECT COUNT(doc_page_clef) FROM doc_page
WHERE doc_page_clef = '" . $objDatabase->fxEscape($tabData['doc_page_clef']) . "'
AND doc_langue = '" . $objDatabase->fxEscape($tabData['doc_langue']) . "'
AND doc_prg = '" . $objDatabase->fxEscape($tabData['doc_prg']) . "'";
if ((int)$objDatabase->fxGetVar($sqlValide) > 0) {
break;
}
$sqlInsert = "INSERT INTO doc_page " . $tabFieldsValues['set'] . ', doc_creation = NOW()';
$objDatabase->fxQuery($sqlInsert);
break;
case 'mod':
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_page', 'doc_page', $GLOBALS['arrConDatabaseMain']['db']);
$sqlUpdate = "UPDATE doc_page " . $tabFieldsValues['set'] . ", doc_maj = NOW() WHERE " . $tabFieldsValues['key'];
$objDatabase->fxQuery($sqlUpdate);
break;
}
}
function fxSetDocAnchor($strAction, $tabData, $id = 0) {
global $objDatabase;
switch ($strAction) {
case 'add':
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_anchor', 'doc_anchor', $GLOBALS['arrConDatabaseMain']['db']);
$sqlValide = "SELECT COUNT(doc_anchor_clef) FROM doc_anchor
WHERE doc_anchor_clef = '" . $objDatabase->fxEscape($tabData['doc_anchor_clef']) . "'
AND doc_prg = '" . $objDatabase->fxEscape($tabData['doc_prg']) . "'";
if ((int)$objDatabase->fxGetVar($sqlValide) > 0) {
break;
}
$sqlInsert = "INSERT INTO doc_anchor " . $tabFieldsValues['set'] . ', doc_creation = NOW()';
$objDatabase->fxQuery($sqlInsert);
break;
case 'mod':
$tabFieldsValues = fxListFieldsValues($tabData, 'pk_doc_anchor', 'doc_anchor', $GLOBALS['arrConDatabaseMain']['db']);
$sqlUpdate = "UPDATE doc_anchor " . $tabFieldsValues['set'] . " WHERE " . $tabFieldsValues['key'];
$objDatabase->fxQuery($sqlUpdate);
break;
}
}