feat: vários ginásios (/3070/ e /<código postal>/), CRUD de ginásios, modalidades após a galeria
Estrutura multi-ginásio: as mesmas secções do site, conteúdo próprio por ginásio, e cada um numa pasta da URL com o código postal da zona. - migration_20260926_gyms: tabela gyms (pasta, nome, localidade, publicado, principal, secções visíveis, título/imagem do header, SEO, galeria, Place ID, página CrossFit) com o FitMe 3070 como ginásio 1 em /3070/; gym_id em team, modalidades (o horário segue a modalidade), work_hours, memberships, membership_leads e website_offer (tudo o existente → ginásio 1); events com gym_id NULL = todos; fitme3070_info passa a uma linha por ginásio (id = gym). - Site: /<pasta>/, /<pasta>/crossfit, /<pasta>/agendar|inscricao. Ginásio por publicar → 404 para o público, pré-visualização (com faixa) para a equipa. / e /home → 301 para o ginásio principal (a manter a query); com mais de um ginásio publicado, a raiz passa a uma página para escolher. Secções desligadas não aparecem nem no menu; menu, canonical, JSON-LD, sitemap, formulários, e-mails e reviews (Place ID e cache) são do ginásio. - Backoffice: Gestão → Ginásios (criar/editar/apagar, secções, e «começar com o conteúdo de» outro ginásio — cópias independentes) e o seletor «A editar» no topo; equipa, modalidades, horários, horário de funcionamento, mensalidades, pedidos, contactos e reviews trabalham sobre o ginásio escolhido, e um registo de outro ginásio não abre. - Modalidades passam para logo a seguir à galeria das instalações. - Galeria sem cantos arredondados (raio da marca). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2f8f68c61a
commit
5e300fc341
33 files changed
+1146
-197
No files matched your search
@@ -24,6 +24,7 @@ require_once __DIR__ . '/functions_queue.php'; // message_queue + enque
|
||||
require_once __DIR__ . '/functions_csrf.php';
|
||||
require_once __DIR__ . '/functions_html.php'; // allowlist HTML sanitiser (clean_richtext/clean_map_embed)
|
||||
require_once __DIR__ . '/ci_compat.php'; // CI4 view-helper shim (script_tag/link_tag/view/esc/session)
|
||||
require_once __DIR__ . '/functions_gyms.php'; // multi-gym: gyms, URL folders, admin gym switcher
|
||||
require_once __DIR__ . '/functions_site.php'; // public-site data (site_info/offer/team/modalidades/horario)
|
||||
require_once __DIR__ . '/functions_images.php'; // upload resize/compress (gallery/team/modalidades caps)
|
||||
require_once __DIR__ . '/functions_team.php'; // team CRUD
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
// ─── Ginásios (multi-gym) ─────────────────────────────────────────────────────
|
||||
// One site, several gyms: the same homepage sections, each gym with its own
|
||||
// content. A gym lives in a URL folder named after its postal code
|
||||
// (/3070/, /XXXX/) and can be unpublished — then only logged-in staff can open
|
||||
// it (preview), everyone else gets a 404.
|
||||
//
|
||||
// Content tables carry gym_id (team, modalidades → horario, work_hours,
|
||||
// memberships, membership_leads, website_offer; events: NULL = every gym) and
|
||||
// fitme3070_info has one row per gym with id = gyms.id.
|
||||
//
|
||||
// Public site — site_gym($pdo): the gym of the current request (?gym=<slug>,
|
||||
// set by .htaccess from the URL folder), default gym otherwise.
|
||||
// Backoffice — admin_gym_id(): the gym picked in the header switcher
|
||||
// (session), which every content CRUD page reads and writes.
|
||||
|
||||
// Homepage sections a gym can switch off. Order = page order.
|
||||
function gym_sections(): array {
|
||||
return [
|
||||
'oferta' => 'A Nossa Oferta',
|
||||
'galeria' => 'Galeria (instalações)',
|
||||
'modalidades' => 'Modalidades',
|
||||
'equipa' => 'Equipa',
|
||||
'horario' => 'Horário de aulas',
|
||||
'mensalidades' => 'Mensalidades',
|
||||
'reviews' => 'Reviews Google',
|
||||
'contactos' => 'Contactos + formulário',
|
||||
'workhours' => 'Horário de funcionamento',
|
||||
'personal_trainer' => 'Personal Trainer (chamada)',
|
||||
];
|
||||
}
|
||||
|
||||
// URL folders that would shadow a real path or route.
|
||||
function gym_reserved_slugs(): array {
|
||||
return ['home', 'site', 'views', 'api', 'assets', 'uploads', 'shared', 'config', 'database',
|
||||
'includes', 'templates', 'vendor', 'logs', 'backups', 'evento', 'robots.txt', 'sitemap.xml'];
|
||||
}
|
||||
|
||||
function gym_defaults(): array {
|
||||
return [
|
||||
'slug' => '', 'name' => '', 'locality' => '', 'postal_code' => '', 'is_published' => 0,
|
||||
'is_default' => 0, 'sections' => null, 'hero_title' => '', 'hero_subtitle' => '',
|
||||
'hero_image' => '', 'seo_title' => '', 'seo_description' => '', 'gallery_id' => null,
|
||||
'google_place_id' => '', 'has_crossfit' => 0, 'item_order' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
// ─── Reads ────────────────────────────────────────────────────────────────────
|
||||
|
||||
function db_gyms_list(PDO $pdo, bool $include_deleted = false): array {
|
||||
try {
|
||||
$sql = "SELECT * FROM gyms" . ($include_deleted ? "" : " WHERE deleted_at IS NULL")
|
||||
. " ORDER BY deleted_at IS NOT NULL, item_order, id";
|
||||
return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Throwable $e) { return []; }
|
||||
}
|
||||
|
||||
function db_gyms_get(PDO $pdo, int $id): ?array {
|
||||
try {
|
||||
$st = $pdo->prepare("SELECT * FROM gyms WHERE id = ? LIMIT 1");
|
||||
$st->execute([$id]);
|
||||
return $st->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
} catch (Throwable $e) { return null; }
|
||||
}
|
||||
|
||||
function db_gyms_by_slug(PDO $pdo, string $slug): ?array {
|
||||
try {
|
||||
$st = $pdo->prepare("SELECT * FROM gyms WHERE slug = ? AND deleted_at IS NULL LIMIT 1");
|
||||
$st->execute([$slug]);
|
||||
return $st->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
} catch (Throwable $e) { return null; }
|
||||
}
|
||||
|
||||
function db_gyms_default(PDO $pdo): ?array {
|
||||
try {
|
||||
$row = $pdo->query("SELECT * FROM gyms WHERE deleted_at IS NULL
|
||||
ORDER BY is_default DESC, is_published DESC, item_order, id LIMIT 1")
|
||||
->fetch(PDO::FETCH_ASSOC);
|
||||
return $row ?: null;
|
||||
} catch (Throwable $e) { return null; }
|
||||
}
|
||||
|
||||
function db_gyms_published(PDO $pdo): array {
|
||||
return array_values(array_filter(db_gyms_list($pdo), fn($g) => (int) $g['is_published'] === 1));
|
||||
}
|
||||
|
||||
// A gym row, or a stand-in for installs that haven't run the migration yet so
|
||||
// the public site keeps working exactly as before.
|
||||
function gym_fallback(): array {
|
||||
return array_merge(gym_defaults(), [
|
||||
'id' => 1, 'slug' => '3070', 'name' => 'FitMe 3070', 'locality' => 'Mira', 'postal_code' => '3070',
|
||||
'is_published' => 1, 'is_default' => 1, 'hero_title' => 'Ginásio, Box, Aulas',
|
||||
'hero_subtitle' => '& Acompanhamento', 'hero_image' => 'site/assets/img/home.jpg',
|
||||
'gallery_id' => 1, 'has_crossfit' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
// Is a homepage section on for this gym? NULL/empty config = everything on.
|
||||
function gym_section_on(array $gym, string $key): bool {
|
||||
$s = $gym['sections'] ?? null;
|
||||
if ($s === null || $s === '') return true;
|
||||
$list = json_decode((string) $s, true);
|
||||
return !is_array($list) || in_array($key, $list, true);
|
||||
}
|
||||
|
||||
function gym_url(array $gym, string $path = ''): string {
|
||||
return base_url(rawurlencode((string) $gym['slug']) . '/' . ltrim($path, '/'));
|
||||
}
|
||||
|
||||
// ─── Public request → gym ─────────────────────────────────────────────────────
|
||||
// Set once per request by site/_open.php (and the POST handlers). Unknown or
|
||||
// unpublished-for-this-visitor gyms return null; the caller 404s.
|
||||
function site_gym(PDO $pdo, ?string $slug = null): ?array {
|
||||
static $cache = [];
|
||||
$slug = $slug ?? (string) ($_GET['gym'] ?? '');
|
||||
if (isset($cache[$slug])) return $cache[$slug];
|
||||
$gym = $slug !== '' ? db_gyms_by_slug($pdo, $slug) : db_gyms_default($pdo);
|
||||
if ($gym === null && $slug === '' ) $gym = gym_fallback(); // pre-migration install
|
||||
if ($gym && !(int) $gym['is_published'] && !(function_exists('is_staff') && is_staff())) $gym = null;
|
||||
return $cache[$slug] = $gym;
|
||||
}
|
||||
|
||||
// The gym the current page is rendering (set by _open.php); default gym for
|
||||
// code that runs outside a gym page (privacy page, events, mail handlers).
|
||||
function current_gym(): array {
|
||||
return $GLOBALS['_gym'] ?? gym_fallback();
|
||||
}
|
||||
|
||||
// ─── Backoffice: which gym am I editing? ─────────────────────────────────────
|
||||
|
||||
function admin_gym_id(): int {
|
||||
global $pdo;
|
||||
$id = (int) ($_SESSION['admin_gym_id'] ?? 0);
|
||||
if ($id && $pdo instanceof PDO && db_gyms_get($pdo, $id) && !db_gyms_get($pdo, $id)['deleted_at']) return $id;
|
||||
$def = ($pdo instanceof PDO) ? db_gyms_default($pdo) : null;
|
||||
return $def ? (int) $def['id'] : 1;
|
||||
}
|
||||
|
||||
function admin_gym(PDO $pdo): array {
|
||||
return db_gyms_get($pdo, admin_gym_id()) ?? gym_fallback();
|
||||
}
|
||||
|
||||
// ─── Writes ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function gyms_from_post(PDO $pdo, array $post, array &$errors, ?int $id = null): array {
|
||||
$slug = strtolower(trim((string) ($post['slug'] ?? '')));
|
||||
$data = [
|
||||
'slug' => $slug,
|
||||
'name' => mb_substr(trim((string) ($post['name'] ?? '')), 0, 100),
|
||||
'locality' => mb_substr(trim((string) ($post['locality'] ?? '')), 0, 100),
|
||||
'postal_code' => mb_substr(trim((string) ($post['postal_code'] ?? '')), 0, 20),
|
||||
'is_published' => !empty($post['is_published']) ? 1 : 0,
|
||||
'is_default' => !empty($post['is_default']) ? 1 : 0,
|
||||
'hero_title' => mb_substr(trim((string) ($post['hero_title'] ?? '')), 0, 150),
|
||||
'hero_subtitle' => mb_substr(trim((string) ($post['hero_subtitle'] ?? '')), 0, 150),
|
||||
'hero_image' => mb_substr(trim((string) ($post['hero_image'] ?? '')), 0, 255),
|
||||
'seo_title' => mb_substr(trim((string) ($post['seo_title'] ?? '')), 0, 160),
|
||||
'seo_description' => mb_substr(trim((string) ($post['seo_description'] ?? '')), 0, 300),
|
||||
'gallery_id' => (int) ($post['gallery_id'] ?? 0) ?: null,
|
||||
'google_place_id' => mb_substr(trim((string) ($post['google_place_id'] ?? '')), 0, 120),
|
||||
'has_crossfit' => !empty($post['has_crossfit']) ? 1 : 0,
|
||||
'item_order' => (int) ($post['item_order'] ?? 0),
|
||||
];
|
||||
$on = array_values(array_intersect(array_keys(gym_sections()), (array) ($post['sections'] ?? [])));
|
||||
$data['sections'] = count($on) === count(gym_sections()) ? null : json_encode($on);
|
||||
|
||||
if ($data['name'] === '') $errors[] = 'O nome é obrigatório.';
|
||||
if (!preg_match('/^[a-z0-9][a-z0-9-]{1,19}$/', $slug)) {
|
||||
$errors[] = 'A pasta da URL deve ter 2–20 letras minúsculas, números ou hífens (ex.: 3070).';
|
||||
} elseif (in_array($slug, gym_reserved_slugs(), true)) {
|
||||
$errors[] = 'Essa pasta da URL está reservada pelo site.';
|
||||
} else {
|
||||
$other = db_gyms_by_slug($pdo, $slug);
|
||||
if ($other && (int) $other['id'] !== (int) $id) $errors[] = 'Já existe um ginásio com essa pasta da URL.';
|
||||
}
|
||||
if ($data['is_default'] && !$data['is_published']) $errors[] = 'O ginásio principal tem de estar publicado.';
|
||||
if ($data['hero_image'] !== '' && !preg_match('~^(site/assets/img|uploads)/[A-Za-z0-9_./ -]+\.(jpe?g|png|webp)$~i', $data['hero_image'])) {
|
||||
$errors[] = 'Imagem do header: caminho de uma imagem do site (ex.: site/assets/img/home.jpg).';
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
function _gyms_params(array $d): array {
|
||||
return [$d['slug'], $d['name'], $d['locality'], $d['postal_code'], $d['is_published'], $d['is_default'],
|
||||
$d['sections'], $d['hero_title'], $d['hero_subtitle'], $d['hero_image'], $d['seo_title'],
|
||||
$d['seo_description'], $d['gallery_id'], $d['google_place_id'], $d['has_crossfit'], $d['item_order']];
|
||||
}
|
||||
|
||||
function db_gyms_create(PDO $pdo, array $d): int {
|
||||
$pdo->beginTransaction();
|
||||
$pdo->prepare("INSERT INTO gyms (slug, name, locality, postal_code, is_published, is_default, sections,
|
||||
hero_title, hero_subtitle, hero_image, seo_title, seo_description, gallery_id,
|
||||
google_place_id, has_crossfit, item_order, created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, NOW(), NOW())")->execute(_gyms_params($d));
|
||||
$id = (int) $pdo->lastInsertId();
|
||||
if ($d['is_default']) $pdo->prepare("UPDATE gyms SET is_default = 0 WHERE id <> ?")->execute([$id]);
|
||||
// Its own contacts row (id = gym id), starting from the gym's name.
|
||||
$pdo->prepare("INSERT IGNORE INTO fitme3070_info (id, name, adress, phone, telephone, whatsapp, instagram,
|
||||
facebook, messenger, email, website, google_maps, google_maps_link)
|
||||
VALUES (?, ?, '', '', '', '', '', '', '', '', '', '', '')")->execute([$id, $d['name']]);
|
||||
$pdo->commit();
|
||||
return $id;
|
||||
}
|
||||
|
||||
function db_gyms_update(PDO $pdo, int $id, array $d): bool {
|
||||
$pdo->beginTransaction();
|
||||
$params = _gyms_params($d); $params[] = $id;
|
||||
$st = $pdo->prepare("UPDATE gyms SET slug = ?, name = ?, locality = ?, postal_code = ?, is_published = ?,
|
||||
is_default = ?, sections = ?, hero_title = ?, hero_subtitle = ?, hero_image = ?,
|
||||
seo_title = ?, seo_description = ?, gallery_id = ?, google_place_id = ?,
|
||||
has_crossfit = ?, item_order = ?, updated_at = NOW()
|
||||
WHERE id = ?");
|
||||
$st->execute($params);
|
||||
$changed = $st->rowCount() > 0;
|
||||
if ($d['is_default']) $pdo->prepare("UPDATE gyms SET is_default = 0 WHERE id <> ?")->execute([$id]);
|
||||
$pdo->commit();
|
||||
return $changed;
|
||||
}
|
||||
|
||||
// Soft delete. The default gym can't go (the root URL needs somewhere to land).
|
||||
function db_gyms_delete(PDO $pdo, int $id): bool {
|
||||
$st = $pdo->prepare("UPDATE gyms SET deleted_at = NOW(), is_published = 0
|
||||
WHERE id = ? AND is_default = 0 AND deleted_at IS NULL");
|
||||
$st->execute([$id]);
|
||||
return $st->rowCount() > 0;
|
||||
}
|
||||
|
||||
function db_gyms_restore(PDO $pdo, int $id): bool {
|
||||
$st = $pdo->prepare("UPDATE gyms SET deleted_at = NULL WHERE id = ? AND deleted_at IS NOT NULL");
|
||||
$st->execute([$id]);
|
||||
return $st->rowCount() > 0;
|
||||
}
|
||||
|
||||
// ─── Start a gym from another one's content ──────────────────────────────────
|
||||
// What can be copied, and a copy routine for each. Copies are independent rows:
|
||||
// editing one gym never changes the other.
|
||||
function gyms_copy_options(): array {
|
||||
return [
|
||||
'modalidades' => 'Modalidades e horário de aulas',
|
||||
'work_hours' => 'Horário de funcionamento',
|
||||
'memberships' => 'Mensalidades',
|
||||
'offer' => 'A Nossa Oferta',
|
||||
'team' => 'Equipa',
|
||||
'info' => 'Contactos (redes sociais, e-mail — ajustar depois)',
|
||||
];
|
||||
}
|
||||
|
||||
function gyms_copy_content(PDO $pdo, int $from, int $to, array $what): array {
|
||||
$done = [];
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
if (in_array('modalidades', $what, true)) {
|
||||
$map = [];
|
||||
$st = $pdo->prepare("SELECT * FROM modalidades WHERE gym_id = ? AND deleted_at IS NULL");
|
||||
$st->execute([$from]);
|
||||
$ins = $pdo->prepare("INSERT INTO modalidades (gym_id, nome, cor, icon, descricao, item_order) VALUES (?,?,?,?,?,?)");
|
||||
foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $m) {
|
||||
$ins->execute([$to, $m['nome'], $m['cor'], $m['icon'], $m['descricao'], $m['item_order']]);
|
||||
$map[(int) $m['m_id']] = (int) $pdo->lastInsertId();
|
||||
}
|
||||
if ($map) {
|
||||
$in = implode(',', array_keys($map));
|
||||
$hs = $pdo->query("SELECT * FROM horario WHERE id_modalidade IN ($in)"
|
||||
. (_gyms_has_col($pdo, 'horario', 'deleted_at') ? " AND deleted_at IS NULL" : ""))
|
||||
->fetchAll(PDO::FETCH_ASSOC);
|
||||
$hi = $pdo->prepare("INSERT INTO horario (id_modalidade, dia_semana, hora_inicio, hora_fim, local) VALUES (?,?,?,?,?)");
|
||||
foreach ($hs as $h) $hi->execute([$map[(int) $h['id_modalidade']], $h['dia_semana'], $h['hora_inicio'], $h['hora_fim'], $h['local']]);
|
||||
}
|
||||
$done[] = count($map) . ' modalidades';
|
||||
}
|
||||
if (in_array('work_hours', $what, true)) {
|
||||
$n = $pdo->prepare("INSERT INTO work_hours (gym_id, week_day, open, close)
|
||||
SELECT ?, week_day, open, close FROM work_hours WHERE gym_id = ?"
|
||||
. (_gyms_has_col($pdo, 'work_hours', 'deleted_at') ? " AND deleted_at IS NULL" : ""));
|
||||
$n->execute([$to, $from]);
|
||||
$done[] = $n->rowCount() . ' horários de funcionamento';
|
||||
}
|
||||
if (in_array('memberships', $what, true)) {
|
||||
$n = $pdo->prepare("INSERT INTO memberships (gym_id, name, subtitle, price, is_addon, price_suffix, features,
|
||||
is_featured, featured_label, cta_label, item_order, is_active)
|
||||
SELECT ?, name, subtitle, price, is_addon, price_suffix, features, is_featured,
|
||||
featured_label, cta_label, item_order, is_active
|
||||
FROM memberships WHERE gym_id = ? AND deleted_at IS NULL");
|
||||
$n->execute([$to, $from]);
|
||||
$done[] = $n->rowCount() . ' mensalidades';
|
||||
}
|
||||
if (in_array('offer', $what, true)) {
|
||||
$n = $pdo->prepare("INSERT INTO website_offer (gym_id, o_img, o_title, o_description)
|
||||
SELECT ?, o_img, o_title, o_description FROM website_offer
|
||||
WHERE gym_id = ? AND (o_deleted_at IS NULL OR o_deleted_at = '0000-00-00 00:00:00' OR o_deleted_at = '')");
|
||||
$n->execute([$to, $from]);
|
||||
$done[] = $n->rowCount() . ' itens da oferta';
|
||||
}
|
||||
if (in_array('team', $what, true)) {
|
||||
$n = $pdo->prepare("INSERT INTO team (gym_id, photo, name, title, birthday, `at`, status, description, skills, certifications, created_at, updated_at)
|
||||
SELECT ?, photo, name, title, birthday, `at`, status, description, skills, certifications, NOW(), NOW()
|
||||
FROM team WHERE gym_id = ? AND deleted_at IS NULL");
|
||||
$n->execute([$to, $from]);
|
||||
$done[] = $n->rowCount() . ' membros da equipa';
|
||||
}
|
||||
if (in_array('info', $what, true)) {
|
||||
$src = $pdo->prepare("SELECT * FROM fitme3070_info WHERE id = ?");
|
||||
$src->execute([$from]);
|
||||
if ($i = $src->fetch(PDO::FETCH_ASSOC)) {
|
||||
// Brand-wide channels only; address, phones and map are the new gym's own.
|
||||
$pdo->prepare("UPDATE fitme3070_info SET instagram = ?, facebook = ?, messenger = ?, email = ?, website = ?
|
||||
WHERE id = ?")->execute([$i['instagram'], $i['facebook'], $i['messenger'], $i['email'], $i['website'], $to]);
|
||||
$done[] = 'contactos (redes sociais e e-mail)';
|
||||
}
|
||||
}
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
return $done;
|
||||
}
|
||||
|
||||
function _gyms_has_col(PDO $pdo, string $table, string $col): bool {
|
||||
static $cache = [];
|
||||
$k = "$table.$col";
|
||||
if (!isset($cache[$k])) {
|
||||
// (SHOW COLUMNS … LIKE ? can't take a bound parameter with native prepares.)
|
||||
$st = $pdo->prepare("SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?");
|
||||
$st->execute([$table, $col]);
|
||||
$cache[$k] = (bool) $st->fetch();
|
||||
}
|
||||
return $cache[$k];
|
||||
}
|
||||
@@ -10,7 +10,7 @@ function db_horario_list(PDO $pdo): array {
|
||||
TIME_FORMAT(h.hora_fim, '%H:%i') AS hora_fim_fmt
|
||||
FROM horario h
|
||||
JOIN modalidades m ON m.m_id = h.id_modalidade
|
||||
WHERE h.deleted_at IS NULL
|
||||
WHERE h.deleted_at IS NULL AND m.gym_id = " . admin_gym_id() . "
|
||||
ORDER BY FIELD(h.dia_semana,'segunda','terça','quarta','quinta','sexta','sábado','domingo'),
|
||||
h.hora_inicio ASC
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
@@ -24,9 +24,9 @@ function db_horario_get(PDO $pdo, int $id): ?array {
|
||||
m.nome AS modalidade_nome
|
||||
FROM horario h
|
||||
JOIN modalidades m ON m.m_id = h.id_modalidade
|
||||
WHERE h.id = ? AND h.deleted_at IS NULL LIMIT 1
|
||||
WHERE h.id = ? AND h.deleted_at IS NULL AND m.gym_id = ? LIMIT 1
|
||||
");
|
||||
$stmt->execute([$id]);
|
||||
$stmt->execute([$id, admin_gym_id()]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,6 @@ function db_horario_move(PDO $pdo, int $id, string $dia_semana, string $hora_ini
|
||||
// Modalidades dropdown (id => nome + cor) for horario create/edit forms.
|
||||
function db_modalidades_options(PDO $pdo): array {
|
||||
return $pdo->query("
|
||||
SELECT m_id, nome, cor FROM modalidades WHERE deleted_at IS NULL ORDER BY nome
|
||||
SELECT m_id, nome, cor FROM modalidades WHERE deleted_at IS NULL AND gym_id = " . admin_gym_id() . " ORDER BY nome
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
@@ -43,15 +43,17 @@ function info_from_post(array $post, array &$errors): array {
|
||||
|
||||
function db_info_save(PDO $pdo, array $data): bool {
|
||||
$cols = array_keys(info_fields());
|
||||
$exists = (bool) $pdo->query("SELECT COUNT(*) FROM fitme3070_info WHERE id = 1")->fetchColumn();
|
||||
// One row per gym (id = gyms.id): the gym being edited in the backoffice.
|
||||
$gid = admin_gym_id();
|
||||
$exists = (bool) $pdo->query("SELECT COUNT(*) FROM fitme3070_info WHERE id = " . $gid)->fetchColumn();
|
||||
$vals = array_map(fn($c) => $data[$c] ?? '', $cols);
|
||||
if ($exists) {
|
||||
$sets = implode(', ', array_map(fn($c) => "`$c` = ?", $cols));
|
||||
$stmt = $pdo->prepare("UPDATE fitme3070_info SET $sets, updated_at = NOW() WHERE id = 1");
|
||||
$stmt = $pdo->prepare("UPDATE fitme3070_info SET $sets, updated_at = NOW() WHERE id = " . $gid);
|
||||
} else {
|
||||
$names = implode(', ', array_map(fn($c) => "`$c`", $cols));
|
||||
$marks = implode(', ', array_fill(0, count($cols), '?'));
|
||||
$stmt = $pdo->prepare("INSERT INTO fitme3070_info (id, $names, updated_at) VALUES (1, $marks, NOW())");
|
||||
$stmt = $pdo->prepare("INSERT INTO fitme3070_info (id, $names, updated_at) VALUES (" . $gid . ", $marks, NOW())");
|
||||
}
|
||||
$stmt->execute($vals);
|
||||
return $stmt->rowCount() > 0;
|
||||
|
||||
@@ -14,23 +14,27 @@ function memberships_defaults(): array {
|
||||
}
|
||||
|
||||
function db_memberships_list(PDO $pdo, bool $include_deleted = false): array {
|
||||
$sql = "SELECT * FROM memberships";
|
||||
if (!$include_deleted) $sql .= " WHERE deleted_at IS NULL";
|
||||
// Scoped to the gym being edited in the backoffice (functions_gyms.php).
|
||||
$sql = "SELECT * FROM memberships WHERE gym_id = " . admin_gym_id();
|
||||
if (!$include_deleted) $sql .= " AND deleted_at IS NULL";
|
||||
$sql .= " ORDER BY deleted_at IS NOT NULL, item_order, name";
|
||||
return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// Public list: active, not deleted. Defensive — the table may not exist yet on
|
||||
// an install that hasn't run the migration.
|
||||
function site_memberships(PDO $pdo): array {
|
||||
function site_memberships(PDO $pdo, ?int $gym_id = null): array {
|
||||
try {
|
||||
return $pdo->query(
|
||||
"SELECT * FROM memberships WHERE deleted_at IS NULL AND is_active = 1 ORDER BY item_order, name"
|
||||
)->fetchAll(PDO::FETCH_ASSOC);
|
||||
$st = $pdo->prepare(
|
||||
"SELECT * FROM memberships WHERE deleted_at IS NULL AND is_active = 1 AND gym_id = ? ORDER BY item_order, name"
|
||||
);
|
||||
$st->execute([$gym_id ?? (int) current_gym()['id']]);
|
||||
return $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Throwable $e) { return []; }
|
||||
}
|
||||
|
||||
function db_memberships_get(PDO $pdo, int $id): ?array {
|
||||
// Any gym (the public sign-up handler checks the gym itself).
|
||||
$stmt = $pdo->prepare("SELECT * FROM memberships WHERE id = ? LIMIT 1");
|
||||
$stmt->execute([$id]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
@@ -70,7 +74,11 @@ function _memberships_params(array $d): array {
|
||||
}
|
||||
|
||||
function _memberships_clear_featured(PDO $pdo, int $except): void {
|
||||
$pdo->prepare("UPDATE memberships SET is_featured = 0 WHERE id <> ? AND is_featured = 1")->execute([$except]);
|
||||
// One featured card per gym.
|
||||
$pdo->prepare("UPDATE memberships SET is_featured = 0
|
||||
WHERE id <> ? AND is_featured = 1
|
||||
AND gym_id = (SELECT g FROM (SELECT gym_id AS g FROM memberships WHERE id = ?) x)")
|
||||
->execute([$except, $except]);
|
||||
}
|
||||
|
||||
function db_memberships_create(PDO $pdo, array $d): int {
|
||||
@@ -78,10 +86,10 @@ function db_memberships_create(PDO $pdo, array $d): int {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO memberships (name, subtitle, price, is_addon, price_suffix, features,
|
||||
is_featured, featured_label, cta_label, item_order, is_active,
|
||||
created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?, NOW(), NOW())
|
||||
gym_id, created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?, NOW(), NOW())
|
||||
");
|
||||
$stmt->execute(_memberships_params($d));
|
||||
$stmt->execute(array_merge(_memberships_params($d), [admin_gym_id()]));
|
||||
$id = (int) $pdo->lastInsertId();
|
||||
if ($d['is_featured']) _memberships_clear_featured($pdo, $id);
|
||||
$pdo->commit();
|
||||
@@ -142,8 +150,8 @@ function membership_lead_statuses(): array {
|
||||
}
|
||||
|
||||
function db_membership_leads_list(PDO $pdo, string $status = ''): array {
|
||||
$sql = "SELECT * FROM membership_leads WHERE deleted_at IS NULL";
|
||||
$args = [];
|
||||
$sql = "SELECT * FROM membership_leads WHERE deleted_at IS NULL AND gym_id = ?";
|
||||
$args = [admin_gym_id()];
|
||||
if ($status !== '' && isset(membership_lead_statuses()[$status])) { $sql .= " AND status = ?"; $args[] = $status; }
|
||||
$sql .= " ORDER BY created_at DESC, id DESC";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
@@ -152,17 +160,18 @@ function db_membership_leads_list(PDO $pdo, string $status = ''): array {
|
||||
}
|
||||
|
||||
function db_membership_leads_count_new(PDO $pdo): int {
|
||||
try { return (int) $pdo->query("SELECT COUNT(*) FROM membership_leads WHERE status = 'novo' AND deleted_at IS NULL")->fetchColumn(); }
|
||||
try { return (int) $pdo->query("SELECT COUNT(*) FROM membership_leads WHERE status = 'novo' AND deleted_at IS NULL AND gym_id = " . admin_gym_id())->fetchColumn(); }
|
||||
catch (Throwable $e) { return 0; }
|
||||
}
|
||||
|
||||
function db_membership_lead_create(PDO $pdo, array $d): int {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO membership_leads (membership_id, membership_name, name, email, phone, message,
|
||||
INSERT INTO membership_leads (gym_id, membership_id, membership_name, name, email, phone, message,
|
||||
consent, consent_text, ip, user_agent, created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?, NOW(), NOW())
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?, NOW(), NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
(int) ($d['gym_id'] ?? current_gym()['id']),
|
||||
$d['membership_id'] ?: null,
|
||||
mb_substr((string) ($d['membership_name'] ?? ''), 0, 100),
|
||||
mb_substr(trim((string) ($d['name'] ?? '')), 0, 150),
|
||||
|
||||
@@ -4,24 +4,26 @@
|
||||
// description, and item_order for sorting.
|
||||
|
||||
function db_modalidades_list(PDO $pdo, bool $include_deleted = false): array {
|
||||
$sql = "SELECT * FROM modalidades";
|
||||
if (!$include_deleted) $sql .= " WHERE deleted_at IS NULL";
|
||||
// Scoped to the gym being edited in the backoffice (functions_gyms.php).
|
||||
$sql = "SELECT * FROM modalidades WHERE gym_id = " . admin_gym_id();
|
||||
if (!$include_deleted) $sql .= " AND deleted_at IS NULL";
|
||||
$sql .= " ORDER BY item_order DESC, nome";
|
||||
return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function db_modalidades_get(PDO $pdo, int $id): ?array {
|
||||
$stmt = $pdo->prepare("SELECT * FROM modalidades WHERE m_id = ? LIMIT 1");
|
||||
$stmt->execute([$id]);
|
||||
$stmt = $pdo->prepare("SELECT * FROM modalidades WHERE m_id = ? AND gym_id = ? LIMIT 1");
|
||||
$stmt->execute([$id, admin_gym_id()]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
function db_modalidades_create(PDO $pdo, array $data): int {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO modalidades (nome, cor, icon, descricao, item_order)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
INSERT INTO modalidades (gym_id, nome, cor, icon, descricao, item_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([
|
||||
admin_gym_id(),
|
||||
$data['nome'],
|
||||
$data['cor'] ?? '#808080',
|
||||
$data['icon'] ?? '',
|
||||
|
||||
@@ -27,10 +27,26 @@
|
||||
|
||||
const REVIEWS_FIELD_MASK = 'displayName,rating,userRatingCount,googleMapsUri,reviews';
|
||||
|
||||
// Multi-gym: the API key and display options are shared; the Place ID (and so
|
||||
// the cache) belongs to a gym (gyms.google_place_id). Which gym is taken from
|
||||
// reviews_gym(): the gym the public page renders, or — on the backoffice
|
||||
// Reviews page — the gym being edited (set with reviews_use_gym()).
|
||||
function reviews_use_gym(array $gym): void {
|
||||
$GLOBALS['_reviews_gym'] = $gym;
|
||||
}
|
||||
|
||||
function reviews_gym(): array {
|
||||
return $GLOBALS['_reviews_gym'] ?? current_gym();
|
||||
}
|
||||
|
||||
function reviews_config(): array {
|
||||
$gym = reviews_gym();
|
||||
$place = trim((string) ($gym['google_place_id'] ?? ''));
|
||||
// Pre-multi-gym installs kept the Place ID in settings (gym 1 only).
|
||||
if ($place === '' && (int) ($gym['id'] ?? 1) === 1) $place = trim((string) setting('google_place_id', ''));
|
||||
return [
|
||||
'api_key' => setting_secret('google_places_api_key', 'GOOGLE_PLACES_API_KEY'),
|
||||
'place_id' => trim((string) setting('google_place_id', '')),
|
||||
'place_id' => $place,
|
||||
'min_rating' => max(1, min(5, (int) setting('google_reviews_min_rating', 4))),
|
||||
'max' => max(1, min(5, (int) setting('google_reviews_max', 3))),
|
||||
'cache_hours' => max(1, (int) setting('google_reviews_cache_hours', 24)),
|
||||
@@ -50,7 +66,7 @@ function reviews_configured(): bool {
|
||||
}
|
||||
|
||||
function _reviews_cache_path(): string {
|
||||
return __DIR__ . '/cache/google_reviews.cache.php';
|
||||
return __DIR__ . '/cache/google_reviews_' . (int) (reviews_gym()['id'] ?? 1) . '.cache.php';
|
||||
}
|
||||
|
||||
function reviews_cache_read(): ?array {
|
||||
@@ -170,7 +186,8 @@ function reviews_get(): array {
|
||||
|
||||
// What the homepage renders: summary + the best qualifying reviews, or null
|
||||
// when there is nothing worth showing (not configured, no qualifying review).
|
||||
function site_reviews(): ?array {
|
||||
function site_reviews(?array $gym = null): ?array {
|
||||
if ($gym !== null) reviews_use_gym($gym);
|
||||
try { $data = reviews_get(); } catch (Throwable $e) { error_log('[reviews] ' . $e->getMessage()); return null; }
|
||||
if (!$data || empty($data['reviews'])) return null;
|
||||
$c = reviews_config();
|
||||
|
||||
+70
-52
@@ -3,15 +3,23 @@
|
||||
// The ported website/* partials read the same shapes the CI4 models returned.
|
||||
// Every query is defensive — the gym-content tables can be empty on a fresh DB.
|
||||
|
||||
// fitme3070_info row (id=1). Always returns every key the views read, so empty
|
||||
// installs render without warnings.
|
||||
function site_info(PDO $pdo): array {
|
||||
// Every site_*() reader takes the gym (functions_gyms.php); by default the one
|
||||
// the current page renders (current_gym()), so single-gym code paths — the
|
||||
// privacy page, events, mail handlers — keep working unchanged.
|
||||
function _site_gym_id(?int $gym_id): int {
|
||||
return $gym_id ?? (int) current_gym()['id'];
|
||||
}
|
||||
|
||||
// fitme3070_info row of the gym (id = gyms.id). Always returns every key the
|
||||
// views read, so empty installs render without warnings.
|
||||
function site_info(PDO $pdo, ?int $gym_id = null): array {
|
||||
$cols = ['id','name','adress','phone','telephone','whatsapp','instagram',
|
||||
'facebook','messenger','email','website','google_maps','google_maps_link'];
|
||||
$row = [];
|
||||
try {
|
||||
$row = $pdo->query("SELECT * FROM fitme3070_info WHERE id = 1 LIMIT 1")
|
||||
->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
$st = $pdo->prepare("SELECT * FROM fitme3070_info WHERE id = ? LIMIT 1");
|
||||
$st->execute([_site_gym_id($gym_id)]);
|
||||
$row = $st->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
} catch (Throwable $e) { $row = []; }
|
||||
foreach ($cols as $c) {
|
||||
if (!array_key_exists($c, $row) || $row[$c] === null) $row[$c] = '';
|
||||
@@ -21,55 +29,63 @@ function site_info(PDO $pdo): array {
|
||||
return $row;
|
||||
}
|
||||
|
||||
// Top navigation map (label => CI-style path). Kept identical to the original
|
||||
// so navbar/footer markup is unchanged; .htaccess rewrites these to site/*.php.
|
||||
function site_navbar(): array {
|
||||
// One-page layout: Home, Equipa, Modalidades, Horário, Contactos are all
|
||||
// anchor links on the single homepage. CrossFit is a separate page styled
|
||||
// differently (see navbar.php for the 'crossfit_btn' flag).
|
||||
return [
|
||||
'Home' => '#home',
|
||||
'Equipa' => '#equipa',
|
||||
'Modalidades' => '#modalidades',
|
||||
'Horário' => '#horario',
|
||||
'Mensalidades' => '#mensalidades',
|
||||
'Contactos' => '#contactos',
|
||||
'CrossFit' => 'home/crossfit', // separate page, navbar styles it specially
|
||||
];
|
||||
// Top navigation (label => absolute URL): anchors on the gym's own page, only
|
||||
// for the sections that gym shows, plus its CrossFit page when it has one.
|
||||
function site_navbar(?array $gym = null): array {
|
||||
$gym = $gym ?? current_gym();
|
||||
$items = ['Home' => ['#home', null]];
|
||||
foreach (['Modalidades' => 'modalidades', 'Equipa' => 'equipa', 'Horário' => 'horario',
|
||||
'Mensalidades' => 'mensalidades', 'Contactos' => 'contactos'] as $label => $sec) {
|
||||
if (gym_section_on($gym, $sec)) $items[$label] = ['#' . $sec, $sec];
|
||||
}
|
||||
$nav = [];
|
||||
foreach ($items as $label => [$hash]) $nav[$label] = gym_url($gym, $hash);
|
||||
if (!empty($gym['has_crossfit'])) $nav['CrossFit'] = gym_url($gym, 'crossfit');
|
||||
return $nav;
|
||||
}
|
||||
|
||||
function site_offer(PDO $pdo): array {
|
||||
function site_offer(PDO $pdo, ?int $gym_id = null): array {
|
||||
try {
|
||||
return $pdo->query(
|
||||
$st = $pdo->prepare(
|
||||
"SELECT * FROM website_offer
|
||||
WHERE o_deleted_at IS NULL OR o_deleted_at = '0000-00-00 00:00:00' OR o_deleted_at = ''"
|
||||
)->fetchAll(PDO::FETCH_ASSOC);
|
||||
WHERE gym_id = ? AND (o_deleted_at IS NULL OR o_deleted_at = '0000-00-00 00:00:00' OR o_deleted_at = '')"
|
||||
);
|
||||
$st->execute([_site_gym_id($gym_id)]);
|
||||
return $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Throwable $e) { return []; }
|
||||
}
|
||||
|
||||
function site_work_hours(PDO $pdo): array {
|
||||
try { return $pdo->query("SELECT * FROM work_hours WHERE deleted_at IS NULL")->fetchAll(PDO::FETCH_ASSOC); }
|
||||
catch (Throwable $e) { return []; }
|
||||
}
|
||||
|
||||
function site_team(PDO $pdo): array {
|
||||
try { return $pdo->query("SELECT * FROM team WHERE deleted_at IS NULL")->fetchAll(PDO::FETCH_ASSOC); }
|
||||
catch (Throwable $e) { return []; }
|
||||
}
|
||||
|
||||
function site_modalidades(PDO $pdo): array {
|
||||
function site_work_hours(PDO $pdo, ?int $gym_id = null): array {
|
||||
try {
|
||||
return $pdo->query(
|
||||
"SELECT * FROM modalidades WHERE deleted_at IS NULL ORDER BY item_order DESC"
|
||||
)->fetchAll(PDO::FETCH_ASSOC);
|
||||
$st = $pdo->prepare("SELECT * FROM work_hours WHERE deleted_at IS NULL AND gym_id = ?");
|
||||
$st->execute([_site_gym_id($gym_id)]);
|
||||
return $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Throwable $e) { return []; }
|
||||
}
|
||||
|
||||
// Currently-visible time-limited events for the homepage banner. Thin wrapper
|
||||
// over db_events_active() (functions_events.php) so the site code reads in the
|
||||
// same site_*() style as everything else here.
|
||||
function site_active_events(PDO $pdo): array {
|
||||
return function_exists('db_events_active') ? db_events_active($pdo) : [];
|
||||
function site_team(PDO $pdo, ?int $gym_id = null): array {
|
||||
try {
|
||||
$st = $pdo->prepare("SELECT * FROM team WHERE deleted_at IS NULL AND gym_id = ?");
|
||||
$st->execute([_site_gym_id($gym_id)]);
|
||||
return $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Throwable $e) { return []; }
|
||||
}
|
||||
|
||||
function site_modalidades(PDO $pdo, ?int $gym_id = null): array {
|
||||
try {
|
||||
$st = $pdo->prepare("SELECT * FROM modalidades WHERE deleted_at IS NULL AND gym_id = ? ORDER BY item_order DESC");
|
||||
$st->execute([_site_gym_id($gym_id)]);
|
||||
return $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Throwable $e) { return []; }
|
||||
}
|
||||
|
||||
// Currently-visible time-limited events for the homepage banner: the gym's own
|
||||
// plus those for every gym (gym_id NULL).
|
||||
function site_active_events(PDO $pdo, ?int $gym_id = null): array {
|
||||
if (!function_exists('db_events_active')) return [];
|
||||
$gid = _site_gym_id($gym_id);
|
||||
return array_values(array_filter(db_events_active($pdo), fn($e) =>
|
||||
!isset($e['gym_id']) || $e['gym_id'] === null || (int) $e['gym_id'] === $gid));
|
||||
}
|
||||
|
||||
// ─── Image-path resolver ──────────────────────────────────────────────────────
|
||||
@@ -90,15 +106,15 @@ function site_img_url($value, $base_dir): string {
|
||||
|
||||
// Schedule joined to modalidades (name/colour/icon), optionally filtered by
|
||||
// local (e.g. 'crossfit'). Mirrors Horario_model::getAll().
|
||||
function site_horario(PDO $pdo, string $local = ''): array {
|
||||
function site_horario(PDO $pdo, string $local = '', ?int $gym_id = null): array {
|
||||
$sql = "SELECT h.*,
|
||||
TIME_FORMAT(h.hora_inicio, '%H:%i') AS hora_inicio,
|
||||
TIME_FORMAT(h.hora_fim, '%H:%i') AS hora_fim,
|
||||
m.nome, m.cor, m.icon
|
||||
FROM horario h
|
||||
JOIN modalidades m ON m.m_id = h.id_modalidade AND m.deleted_at IS NULL
|
||||
WHERE h.deleted_at IS NULL";
|
||||
$args = [];
|
||||
WHERE h.deleted_at IS NULL AND m.gym_id = ?";
|
||||
$args = [_site_gym_id($gym_id)];
|
||||
if ($local !== '') { $sql .= " AND h.local = ?"; $args[] = $local; }
|
||||
$sql .= " ORDER BY h.hora_inicio ASC, m.nome ASC";
|
||||
try { $st = $pdo->prepare($sql); $st->execute($args); return $st->fetchAll(PDO::FETCH_ASSOC); }
|
||||
@@ -117,12 +133,14 @@ function seo_noindex(): bool {
|
||||
// Public, indexable URLs for sitemap.xml: the fixed pages plus any live event
|
||||
// landing page.
|
||||
function site_sitemap_urls(PDO $pdo): array {
|
||||
$urls = [
|
||||
['loc' => base_url(), 'priority' => '1.0'],
|
||||
['loc' => base_url('home/crossfit'), 'priority' => '0.8'],
|
||||
['loc' => base_url('home/privacidade'), 'priority' => '0.2'],
|
||||
];
|
||||
foreach (site_active_events($pdo) as $ev) {
|
||||
// One entry per PUBLISHED gym (unpublished ones must not be discoverable).
|
||||
$urls = [];
|
||||
foreach (db_gyms_published($pdo) ?: [gym_fallback()] as $g) {
|
||||
$urls[] = ['loc' => gym_url($g), 'priority' => '1.0'];
|
||||
if (!empty($g['has_crossfit'])) $urls[] = ['loc' => gym_url($g, 'crossfit'), 'priority' => '0.8'];
|
||||
}
|
||||
$urls[] = ['loc' => base_url('home/privacidade'), 'priority' => '0.2'];
|
||||
foreach (function_exists('db_events_active') ? db_events_active($pdo) : [] as $ev) {
|
||||
$urls[] = ['loc' => base_url('evento/' . rawurlencode($ev['slug'])), 'priority' => '0.6'];
|
||||
}
|
||||
return $urls;
|
||||
|
||||
@@ -4,24 +4,26 @@
|
||||
// Used by views/admin/team/*.php
|
||||
|
||||
function db_team_list(PDO $pdo, bool $include_deleted = false): array {
|
||||
$sql = "SELECT * FROM team";
|
||||
if (!$include_deleted) $sql .= " WHERE deleted_at IS NULL";
|
||||
// Scoped to the gym being edited in the backoffice (functions_gyms.php).
|
||||
$sql = "SELECT * FROM team WHERE gym_id = " . admin_gym_id();
|
||||
if (!$include_deleted) $sql .= " AND deleted_at IS NULL";
|
||||
$sql .= " ORDER BY FIELD(status,'ativo','inativo'), name";
|
||||
return $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function db_team_get(PDO $pdo, int $id): ?array {
|
||||
$stmt = $pdo->prepare("SELECT * FROM team WHERE id = ? LIMIT 1");
|
||||
$stmt->execute([$id]);
|
||||
$stmt = $pdo->prepare("SELECT * FROM team WHERE id = ? AND gym_id = ? LIMIT 1");
|
||||
$stmt->execute([$id, admin_gym_id()]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
function db_team_create(PDO $pdo, array $data): int {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO team (name, title, birthday, `at`, status, description, skills, certifications, photo, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
INSERT INTO team (gym_id, name, title, birthday, `at`, status, description, skills, certifications, photo, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
");
|
||||
$stmt->execute([
|
||||
admin_gym_id(),
|
||||
$data['name'],
|
||||
$data['title'] ?? '',
|
||||
$data['birthday'] ?: null,
|
||||
|
||||
@@ -5,20 +5,20 @@
|
||||
function db_whours_list(PDO $pdo): array {
|
||||
return $pdo->query("
|
||||
SELECT * FROM work_hours
|
||||
WHERE deleted_at IS NULL
|
||||
WHERE deleted_at IS NULL AND gym_id = " . admin_gym_id() . "
|
||||
ORDER BY FIELD(week_day,'segunda','terça','quarta','quinta','sexta','sabado','domingo','feriados')
|
||||
")->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function db_whours_get(PDO $pdo, int $id): ?array {
|
||||
$stmt = $pdo->prepare("SELECT * FROM work_hours WHERE id = ? LIMIT 1");
|
||||
$stmt->execute([$id]);
|
||||
$stmt = $pdo->prepare("SELECT * FROM work_hours WHERE id = ? AND gym_id = ? LIMIT 1");
|
||||
$stmt->execute([$id, admin_gym_id()]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
}
|
||||
|
||||
function db_whours_create(PDO $pdo, array $data): int {
|
||||
$stmt = $pdo->prepare("INSERT INTO work_hours (week_day, open, close) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$data['week_day'], $data['open'], $data['close']]);
|
||||
$stmt = $pdo->prepare("INSERT INTO work_hours (gym_id, week_day, open, close) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([admin_gym_id(), $data['week_day'], $data['open'], $data['close']]);
|
||||
return (int) $pdo->lastInsertId();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user