shared/platform-tools was a git submodule. On this branch it is a plain
directory of tracked files, frozen at ea3616abae30106df283e73a3e2f9e5bb0e6ae90
— the same commit fotografia.andmore.pt pins. Contents are byte-identical to
that tree, verified blob by blob.
The submodule caused every deployment failure this site has had:
- .gitmodules named no `branch`, so the update fell back to
refs/remotes/origin/HEAD — a ref a plain server-side clone never writes.
"Atualizar submódulo" failed with "Unable to find refs/remotes/origin/HEAD
revision in submodule path".
- Its URL was http://, which the origin answers with 523.
- This install is deployed by uploading files, and a submodule is invisible
to that. The tree moved forward while shared/platform-tools stayed on the
old pin, config/functions_secrets.php died on a missing compat_secrets.php,
and the whole site — public pages included — went down.
Each of those was separately fixable. Together they mean a second repository
nobody can see from the deployment path, gating whether the first one boots. A
plain directory deploys like every other file and cannot fall out of step.
The cost is that platform-tools updates no longer arrive by bumping a pin; they
are a copy-over-and-commit. shared/platform-tools/VENDORED.md records the
upstream SHA, the procedure, and which four files differ from upstream in line
endings only (social/*.js, .css, functions_publications.php — CRLF upstream,
normalised to LF here; content otherwise identical).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
475 lines
18 KiB
PHP
475 lines
18 KiB
PHP
<?php
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// A bolha de feedback — incluída pelo rodapé de cada site
|
|
//
|
|
// include __DIR__ . '/../shared/platform-tools/feedback_widget.php';
|
|
//
|
|
// Põe-se no rodapé do layout (includes/footer.php, e o rodapé do site público
|
|
// onde exista), imediatamente antes do </body>. Aparece só a quem é
|
|
// administrador — para os restantes, e para visitantes, não sai daqui uma
|
|
// única linha de HTML.
|
|
//
|
|
// Não depende de nada: nem do Bootstrap (JS ou CSS), nem de ícones, nem de
|
|
// jQuery. É um ficheiro incluído no fim de páginas que já são pesadas, e a
|
|
// alternativa — assumir que o Bootstrap está carregado — partia-se no primeiro
|
|
// site da frota que o não tivesse. O ícone é SVG inline pela mesma razão.
|
|
//
|
|
// O z-index (1035) é deliberadamente ABAIXO do backdrop dos modais do
|
|
// Bootstrap (1050): com um modal aberto a bolha desaparece por baixo dele, em
|
|
// vez de ficar a flutuar por cima de um diálogo que pediu a atenção toda.
|
|
//
|
|
// ─── Dois modos ──────────────────────────────────────────────────────────────
|
|
//
|
|
// $GLOBALS['platform_feedback_mode'] = 'footer'; // antes do include
|
|
//
|
|
// 'bubble' (por omissão) é a bolha a flutuar no canto — para o backoffice, onde
|
|
// o canto está livre e quem trabalha quer o atalho sempre à vista.
|
|
//
|
|
// 'footer' é uma ligação dentro do rodapé, sem nada a flutuar — para o site
|
|
// público, onde o canto inferior direito JÁ ESTÁ OCUPADO: o dance tem lá o
|
|
// botão do WhatsApp (e uma barra fixa em telemóvel). Duas bolhas empilhadas no
|
|
// mesmo canto são duas coisas a disputar o mesmo clique, e a que interessa ao
|
|
// visitante é a outra. O painel continua a abrir fixo no canto, por cima de
|
|
// tudo isso — está aberto por um instante e tem botão de fechar.
|
|
//
|
|
// Ver functions_feedback.php para o que é enviado, e api/feedback.php para
|
|
// quem o recebe.
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
require_once __DIR__ . '/functions_feedback.php';
|
|
require_once __DIR__ . '/functions_csrf.php';
|
|
|
|
(static function (): void {
|
|
|
|
// Uma vez por resposta. O rodapé do backoffice e o do site público podem
|
|
// acabar ambos incluídos na mesma página, e dois formulários com os mesmos
|
|
// ids não é meio widget a mais: é um widget partido.
|
|
if (!empty($GLOBALS['__platform_feedback_rendered'])) {
|
|
return;
|
|
}
|
|
|
|
$user = platform_feedback_user();
|
|
if (!$user || !platform_feedback_is_admin($user)) {
|
|
return;
|
|
}
|
|
|
|
$ligado = hub_ticket_ready();
|
|
|
|
// Sem emparelhamento não há para onde enviar. Ao administrador não se
|
|
// mostra nada — não é ele que resolve isto, e um botão que falha é pior do
|
|
// que botão nenhum. Ao sysadmin mostra-se, porque é ele que o liga: um
|
|
// canal de relatos que desaparece em silêncio quando a chave roda perde
|
|
// exactamente os relatos que mais interessavam.
|
|
if (!$ligado && !platform_feedback_is_sysadmin($user)) {
|
|
return;
|
|
}
|
|
|
|
$GLOBALS['__platform_feedback_rendered'] = true;
|
|
|
|
$h = static fn(string $s): string => htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
|
|
$cats = platform_feedback_categories();
|
|
|
|
// Caminho absoluto, e não base_url(): o site público da fotografia não
|
|
// carrega o config/functions.php, e ali base_url() não existe (ver
|
|
// ASSETS.md). O docroot é a raiz do domínio em toda a frota.
|
|
$endpoint = '/api/feedback.php';
|
|
|
|
// A página onde o sysadmin liga isto. Cada site tem a sua árvore de vistas,
|
|
// daí poder ser sobreposta pelo layout antes do include.
|
|
$hubPage = (string) ($GLOBALS['platform_feedback_hub_url'] ?? '/views/sysadmin/hub_integration.php');
|
|
|
|
$inline = ($GLOBALS['platform_feedback_mode'] ?? 'bubble') === 'footer';
|
|
?>
|
|
<div class="ptfb<?= $inline ? ' ptfb--inline' : '' ?>" id="ptfb" data-endpoint="<?= $h($endpoint) ?>">
|
|
<div class="ptfb-panel" id="ptfb-panel" role="dialog" aria-labelledby="ptfb-title" hidden>
|
|
<div class="ptfb-head">
|
|
<div>
|
|
<p class="ptfb-title" id="ptfb-title">Reportar à andmore</p>
|
|
<p class="ptfb-sub">Alguma coisa errada na plataforma? Conta aqui.</p>
|
|
</div>
|
|
<button type="button" class="ptfb-x" id="ptfb-close" aria-label="Fechar">×</button>
|
|
</div>
|
|
|
|
<?php if (!$ligado): ?>
|
|
<div class="ptfb-body">
|
|
<p class="ptfb-warn">
|
|
Esta plataforma não está ligada ao balcão de apoio — nada seria enviado.
|
|
Só tu vês esta bolha.
|
|
</p>
|
|
<p class="ptfb-warn-more">
|
|
Liga-a em <a href="<?= $h($hubPage) ?>">Integração com o hub</a>: falta o
|
|
endereço do hub, a chave de emparelhamento, ou ambos.
|
|
</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<form class="ptfb-body" id="ptfb-form" novalidate>
|
|
<input type="hidden" name="_pt_csrf" value="<?= $h(platform_csrf_token()) ?>">
|
|
<input type="hidden" name="page_url" id="ptfb-page">
|
|
|
|
<label class="ptfb-label" for="ptfb-cat">Tipo</label>
|
|
<select class="ptfb-input" name="category" id="ptfb-cat">
|
|
<?php foreach ($cats as $key => $label): ?>
|
|
<option value="<?= $h((string) $key) ?>"><?= $h((string) $label) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<label class="ptfb-label" for="ptfb-subject">Assunto</label>
|
|
<input type="text" class="ptfb-input" name="title" id="ptfb-subject"
|
|
maxlength="150" placeholder="Numa linha, o que se passa">
|
|
|
|
<label class="ptfb-label" for="ptfb-desc">O que aconteceu</label>
|
|
<textarea class="ptfb-input ptfb-area" name="description" id="ptfb-desc" rows="5"
|
|
maxlength="5000"
|
|
placeholder="O que estavas a fazer, o que esperavas, e o que aconteceu."></textarea>
|
|
|
|
<label class="ptfb-check">
|
|
<input type="checkbox" name="blocking" value="1" id="ptfb-block">
|
|
<span>Está a impedir-me de trabalhar</span>
|
|
</label>
|
|
|
|
<p class="ptfb-note">
|
|
Vai junto o contexto: a página onde estás, a versão da plataforma, a tua
|
|
conta e o browser. Não precisas de escrever nada disso.
|
|
</p>
|
|
|
|
<button type="submit" class="ptfb-send" id="ptfb-send">Enviar</button>
|
|
<p class="ptfb-msg" id="ptfb-msg" role="status" hidden></p>
|
|
</form>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<?php if ($inline): ?>
|
|
<button type="button" class="ptfb-linkbtn" id="ptfb-open"
|
|
aria-expanded="false" aria-controls="ptfb-panel">
|
|
<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true" focusable="false">
|
|
<path fill="currentColor" d="M20 2H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4v4l5-4h7a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z"/>
|
|
</svg>
|
|
Reportar um problema
|
|
</button>
|
|
<?php else: ?>
|
|
<button type="button" class="ptfb-bubble" id="ptfb-open"
|
|
aria-expanded="false" aria-controls="ptfb-panel"
|
|
title="Reportar à andmore">
|
|
<svg viewBox="0 0 24 24" width="26" height="26" aria-hidden="true" focusable="false">
|
|
<path fill="currentColor" d="M20 2H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4v4l5-4h7a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z"/>
|
|
</svg>
|
|
<span class="ptfb-sr">Reportar à andmore</span>
|
|
</button>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<style>
|
|
.ptfb {
|
|
position: fixed;
|
|
right: 18px;
|
|
bottom: 18px;
|
|
z-index: 1035; /* ver a nota no topo do ficheiro */
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
line-height: 1.45;
|
|
}
|
|
.ptfb-sr {
|
|
position: absolute; width: 1px; height: 1px;
|
|
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
|
|
}
|
|
.ptfb-bubble {
|
|
display: flex; align-items: center; justify-content: center;
|
|
width: 52px; height: 52px;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: #1f2937;
|
|
color: #fff;
|
|
box-shadow: 0 6px 20px rgba(0, 0, 0, .28);
|
|
cursor: pointer;
|
|
transition: transform .15s ease, background-color .15s ease;
|
|
}
|
|
.ptfb-bubble:hover { background: #111827; transform: translateY(-2px); }
|
|
.ptfb-bubble:focus-visible { outline: 3px solid #60a5fa; outline-offset: 2px; }
|
|
.ptfb.is-open .ptfb-bubble { background: #111827; }
|
|
|
|
.ptfb-panel {
|
|
width: 340px;
|
|
max-width: calc(100vw - 36px);
|
|
/* Num portátil com o browser a meia altura o painel passava do ecrã e o
|
|
botão de enviar ficava fora de alcance. */
|
|
max-height: calc(100vh - 36px);
|
|
overflow-y: auto;
|
|
margin-bottom: 12px;
|
|
background: #fff;
|
|
color: #1f2937;
|
|
border: 1px solid rgba(0, 0, 0, .12);
|
|
border-radius: 14px;
|
|
box-shadow: 0 18px 48px rgba(0, 0, 0, .28);
|
|
overflow: hidden;
|
|
text-align: left;
|
|
}
|
|
.ptfb-panel[hidden] { display: none; }
|
|
|
|
.ptfb-head {
|
|
display: flex; align-items: flex-start; gap: .5rem;
|
|
padding: .85rem 1rem;
|
|
background: #1f2937;
|
|
color: #fff;
|
|
}
|
|
.ptfb-title { margin: 0; font-size: .95rem; font-weight: 600; }
|
|
.ptfb-sub { margin: .1rem 0 0; font-size: .78rem; opacity: .75; }
|
|
.ptfb-x {
|
|
margin-left: auto;
|
|
background: none; border: none; color: #fff;
|
|
font-size: 1.4rem; line-height: 1;
|
|
padding: 0 .1rem; cursor: pointer; opacity: .8;
|
|
}
|
|
.ptfb-x:hover { opacity: 1; }
|
|
|
|
.ptfb-body { padding: .9rem 1rem 1rem; }
|
|
.ptfb-label {
|
|
display: block;
|
|
margin: .6rem 0 .25rem;
|
|
font-size: .72rem; font-weight: 600;
|
|
letter-spacing: .04em; text-transform: uppercase;
|
|
color: #6b7280;
|
|
}
|
|
.ptfb-label:first-of-type { margin-top: 0; }
|
|
.ptfb-input {
|
|
display: block; width: 100%;
|
|
box-sizing: border-box;
|
|
padding: .5rem .6rem;
|
|
font: inherit; font-size: .88rem;
|
|
color: #1f2937;
|
|
background: #fff;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 8px;
|
|
}
|
|
.ptfb-input:focus {
|
|
outline: none;
|
|
border-color: #2563eb;
|
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, .18);
|
|
}
|
|
.ptfb-area { resize: vertical; min-height: 92px; }
|
|
.ptfb-check {
|
|
display: flex; align-items: center; gap: .45rem;
|
|
margin: .7rem 0 0;
|
|
font-size: .83rem;
|
|
cursor: pointer;
|
|
}
|
|
.ptfb-note {
|
|
margin: .7rem 0 .85rem;
|
|
font-size: .74rem;
|
|
color: #6b7280;
|
|
}
|
|
.ptfb-send {
|
|
width: 100%;
|
|
padding: .6rem 1rem;
|
|
font: inherit; font-size: .88rem; font-weight: 600;
|
|
color: #fff; background: #2563eb;
|
|
border: none; border-radius: 8px;
|
|
cursor: pointer;
|
|
}
|
|
.ptfb-send:hover:not(:disabled) { background: #1d4ed8; }
|
|
.ptfb-send:disabled { opacity: .6; cursor: default; }
|
|
|
|
.ptfb-msg {
|
|
margin: .7rem 0 0;
|
|
padding: .55rem .7rem;
|
|
font-size: .82rem;
|
|
border-radius: 8px;
|
|
}
|
|
.ptfb-msg[hidden] { display: none; }
|
|
.ptfb-msg.is-ok { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; }
|
|
.ptfb-msg.is-bad { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }
|
|
.ptfb-msg a { color: inherit; }
|
|
|
|
.ptfb-warn { margin: 0; font-size: .85rem; }
|
|
.ptfb-warn-more { margin: .6rem 0 0; font-size: .8rem; color: #6b7280; }
|
|
|
|
@media (max-width: 480px) {
|
|
.ptfb { right: 12px; bottom: 12px; left: 12px; align-items: stretch; }
|
|
.ptfb-panel { width: 100%; max-width: none; }
|
|
.ptfb-bubble { margin-left: auto; }
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.ptfb-bubble { transition: none; }
|
|
}
|
|
|
|
/* ─── Modo rodapé ───────────────────────────────────────────────────────────
|
|
Nada a flutuar: fica a ligação na linha onde o include foi posto, a herdar
|
|
a cor e o tipo de letra do rodapé que a recebe — o da fotografia é escuro, o
|
|
do dance é claro, e um botão com cores próprias destoava num dos dois. O
|
|
painel continua fixo no canto, e vem depois destas regras no ficheiro
|
|
precisamente para ganhar às de cima. */
|
|
.ptfb--inline {
|
|
position: static;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
z-index: auto;
|
|
font-family: inherit;
|
|
}
|
|
.ptfb-linkbtn {
|
|
display: inline-flex; align-items: center; gap: .4rem;
|
|
margin: 0; padding: 0;
|
|
border: none; background: none;
|
|
color: inherit; font: inherit; font-size: .85em;
|
|
line-height: inherit;
|
|
opacity: .75;
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
text-underline-offset: 3px;
|
|
}
|
|
.ptfb-linkbtn:hover { opacity: 1; }
|
|
.ptfb-linkbtn:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }
|
|
.ptfb-linkbtn svg { flex: 0 0 auto; }
|
|
|
|
/* Cor medida, não herdada — ver a nota no JS. Só entram quando o script
|
|
conseguiu medir o fundo; se não conseguiu, fica o `color: inherit` de cima. */
|
|
.ptfb-on-dark .ptfb-linkbtn { color: rgba(255, 255, 255, .62); opacity: 1; }
|
|
.ptfb-on-dark .ptfb-linkbtn:hover { color: #fff; }
|
|
.ptfb-on-light .ptfb-linkbtn { color: rgba(0, 0, 0, .66); opacity: 1; }
|
|
.ptfb-on-light .ptfb-linkbtn:hover { color: #000; }
|
|
|
|
.ptfb--inline .ptfb-panel {
|
|
position: fixed;
|
|
right: 18px;
|
|
bottom: 18px;
|
|
margin-bottom: 0;
|
|
z-index: 1035;
|
|
/* O `font-family: inherit` acima é para a LIGAÇÃO se fundir no rodapé; o
|
|
painel é um formulário e volta ao tipo de letra do sistema. Sem isto, o
|
|
rodapé da fotografia (Georgia) punha um formulário inteiro em serifada. */
|
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
line-height: 1.45;
|
|
}
|
|
@media (max-width: 480px) {
|
|
.ptfb--inline { left: auto; right: auto; bottom: auto; }
|
|
.ptfb--inline .ptfb-panel { left: 12px; right: 12px; bottom: 12px; width: auto; }
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
(function () {
|
|
var root = document.getElementById('ptfb');
|
|
if (!root) return;
|
|
|
|
var openBtn = document.getElementById('ptfb-open');
|
|
var panel = document.getElementById('ptfb-panel');
|
|
var closeBtn = document.getElementById('ptfb-close');
|
|
var form = document.getElementById('ptfb-form');
|
|
|
|
/* ─── A cor da ligação do rodapé é medida, não herdada ──────────────────
|
|
`color: inherit` assume que o rodapé define uma cor legível para si
|
|
próprio. Há rodapés que não definem nenhuma e colorem os filhos um a um:
|
|
o do dance tem `background: var(--text)` (#1a1a1a) e cor herdada #1a1a1a,
|
|
o que dava uma ligação invisível — contraste 1:1.
|
|
Em vez de adivinhar a paleta de cada site da frota, procura-se o primeiro
|
|
antepassado com fundo opaco e escolhe-se claro ou escuro pela luminância.
|
|
Não se encontrando nenhum, não se toca em nada e fica o herdado. */
|
|
function tuneContrast() {
|
|
if (!root.classList.contains('ptfb--inline')) return;
|
|
var el = root, rgb = null;
|
|
while (el && el !== document.documentElement && !rgb) {
|
|
var m = getComputedStyle(el).backgroundColor.match(/rgba?\(([\d.,\s]+)\)/);
|
|
if (m) {
|
|
var p = m[1].split(',').map(parseFloat);
|
|
if (p.length < 4 || p[3] > 0.5) rgb = p;
|
|
}
|
|
el = el.parentElement;
|
|
}
|
|
if (!rgb) return;
|
|
// Luminância aproximada (sem correcção gama): chega de sobra para decidir
|
|
// entre texto claro e texto escuro.
|
|
var lum = (0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]) / 255;
|
|
root.classList.remove('ptfb-on-dark', 'ptfb-on-light');
|
|
root.classList.add(lum < 0.5 ? 'ptfb-on-dark' : 'ptfb-on-light');
|
|
}
|
|
tuneContrast();
|
|
// Outra vez com tudo carregado: um site que sirva a folha de estilos no fim
|
|
// do body ainda não a tinha aplicada na primeira medição.
|
|
window.addEventListener('load', tuneContrast);
|
|
|
|
function open() {
|
|
panel.hidden = false;
|
|
root.classList.add('is-open');
|
|
openBtn.setAttribute('aria-expanded', 'true');
|
|
var first = panel.querySelector('select, input[type=text], a');
|
|
if (first) first.focus();
|
|
}
|
|
function close() {
|
|
panel.hidden = true;
|
|
root.classList.remove('is-open');
|
|
openBtn.setAttribute('aria-expanded', 'false');
|
|
openBtn.focus();
|
|
}
|
|
|
|
openBtn.addEventListener('click', function () { panel.hidden ? open() : close(); });
|
|
closeBtn.addEventListener('click', close);
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape' && !panel.hidden) close();
|
|
});
|
|
|
|
if (!form) return; // painel de aviso: não há formulário para submeter
|
|
|
|
var msg = document.getElementById('ptfb-msg');
|
|
var send = document.getElementById('ptfb-send');
|
|
|
|
// O URL da página é lido aqui e não no servidor: o rodapé pode ser incluído
|
|
// por uma página que não é a que o browser mostra na barra de endereço.
|
|
document.getElementById('ptfb-page').value = window.location.href;
|
|
|
|
// Texto por textContent e a ligação como elemento: a referência e o URL vêm
|
|
// de outro serviço, e nada do que vem de fora entra por innerHTML.
|
|
function say(text, ok, link) {
|
|
msg.className = 'ptfb-msg ' + (ok ? 'is-ok' : 'is-bad');
|
|
msg.textContent = text;
|
|
if (link && /^https?:\/\//i.test(link)) {
|
|
var a = document.createElement('a');
|
|
a.href = link;
|
|
a.target = '_blank';
|
|
a.rel = 'noopener';
|
|
a.textContent = 'Acompanhar';
|
|
msg.appendChild(document.createTextNode(' '));
|
|
msg.appendChild(a);
|
|
}
|
|
msg.hidden = false;
|
|
}
|
|
|
|
form.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
send.disabled = true;
|
|
send.textContent = 'A enviar…';
|
|
msg.hidden = true;
|
|
|
|
fetch(root.dataset.endpoint, {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: { 'Accept': 'application/json' },
|
|
body: new FormData(form)
|
|
})
|
|
.then(function (r) {
|
|
return r.json().catch(function () {
|
|
return { ok: false, error: 'O servidor respondeu de forma inesperada (HTTP ' + r.status + ').' };
|
|
});
|
|
})
|
|
.then(function (res) {
|
|
if (!res.ok) {
|
|
say(res.error || 'Não foi possível enviar.', false);
|
|
return;
|
|
}
|
|
say('Enviado. Ficou como ' + (res.ref || '—') + '.', true, res.track_url);
|
|
document.getElementById('ptfb-subject').value = '';
|
|
document.getElementById('ptfb-desc').value = '';
|
|
document.getElementById('ptfb-block').checked = false;
|
|
})
|
|
.catch(function () {
|
|
say('Sem ligação ao servidor. Tenta outra vez daqui a bocado.', false);
|
|
})
|
|
.finally(function () {
|
|
send.disabled = false;
|
|
send.textContent = 'Enviar';
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
<?php
|
|
})();
|