layouts_browse.php

main 196 lines · 7.2 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2/**
3 * Browse tab: themes offered by the remote catalogue.
4 * Included by admin/modules/layouts.php when ?tab=browse.
5 *
6 * @var array $themes installed themes, keyed by folder
7 * @var string $active the active theme's key
8 */
9
10$repoConfig = theme_repository_config();
11$catalogue = theme_repository_list(isset($_GET['refresh']));
12$remote = $catalogue['themes'];
13$repoError = (string)($catalogue['error'] ?? '');
14?>
15
16<div class="acp-toolbar">
17 <div>
18 <a class="acp-btn acp-btn--ghost acp-btn--sm" href="<?= h(acp_url('layouts')) ?>">
19 <i class="fa fa-arrow-left"></i> <?= t('acp.laybr.back') ?>
20 </a>
21 </div>
22 <div class="acp-actions is-tight">
23 <span class="is-muted"><?= t('acp.laybr.available', ['n' => count($remote)]) ?></span>
24 <a class="acp-btn acp-btn--ghost acp-btn--sm" href="<?= h(acp_url('layouts', ['tab' => 'browse', 'refresh' => 1])) ?>">
25 <i class="fa fa-refresh"></i> <?= t('acp.laybr.refresh') ?>
26 </a>
27 </div>
28</div>
29
30<?php if (!$repoConfig['enabled']): ?>
31
32 <section class="acp-card">
33 <div class="acp-card-body">
34 <?php acp_empty(t('acp.laybr.repo_off'), 'fa-plug'); ?>
35 <p class="is-muted" style="text-align:center;">
36 <?= t('acp.laybr.repo_off_hint', ['code' => '<code>$config[\'layout_repository\'][\'enabled\'] = true;</code>']) ?>
37 </p>
38 </div>
39 </section>
40
41<?php elseif ($repoError !== ''): ?>
42
43 <div class="acp-actions is-tight" style="margin-bottom:12px;">
44 <form class="acp-inline-form" method="post">
45 <?= acp_csrf_field() ?>
46 <input type="hidden" name="clear_layout_repository_cache" value="1">
47 <button class="acp-btn acp-btn--red acp-btn--sm" type="submit">
48 <i class="fa fa-trash"></i> <?= t('acp.laybr.delete_cache') ?>
49 </button>
50 </form>
51 </div>
52
53 <div class="acp-flash acp-flash--error">
54 <i class="fa fa-exclamation-triangle"></i>
55 <span>
56 <strong><?= t('acp.laybr.catalogue_error') ?></strong><br>
57 <?= h($repoError) ?><br>
58 <span class="is-muted"><?= t('acp.laybr.catalogue_url') ?> <code><?= h($repoConfig['index']) ?></code></span>
59 </span>
60 </div>
61
62<?php elseif (!$remote): ?>
63
64 <section class="acp-card">
65 <div class="acp-card-body">
66 <?php acp_empty(t('acp.laybr.catalogue_empty'), 'fa-inbox'); ?>
67 </div>
68 </section>
69
70<?php else: ?>
71
72 <div class="acp-flash acp-flash--info">
73 <i class="fa fa-info-circle"></i>
74 <span>
75 <?= t('acp.laybr.warning', [
76 'hosts' => '<code>' . h(implode('</code>, <code>', $repoConfig['allowed_hosts'])) . '</code>',
77 'configphp' => '<code>config.php</code>',
78 ]) ?>
79 </span>
80 </div>
81
82 <div class="acp-media">
83 <?php foreach ($remote as $key => $item):
84 $installed = isset($themes[$key]);
85 $isActive = ($key === $active);
86 $localVer = $installed ? (string)$themes[$key]['version'] : '';
87 $isUpdate = ($installed && $item['version'] !== '' && $localVer !== '' && version_compare($item['version'], $localVer, '>'));
88 ?>
89 <article class="acp-media-item<?= $installed && !$isUpdate ? ' is-dimmed' : '' ?>">
90
91 <?php if ($item['screenshot'] !== ''): ?>
92 <img src="<?= h($item['screenshot']) ?>" alt="<?= h($item['name']) ?>" loading="lazy"
93 referrerpolicy="no-referrer">
94 <?php else: ?>
95 <div style="display:grid;place-items:center;height:170px;background:var(--acp-panel-2);color:var(--acp-fg-muted);">
96 <span><i class="fa fa-picture-o"></i> &nbsp;<?= t('acp.laybr.no_screenshot') ?></span>
97 </div>
98 <?php endif; ?>
99
100 <div class="acp-media-body">
101 <h3>
102 <?= h($item['name']) ?>
103 <?php if ($isActive): ?>
104 <span class="acp-pill acp-pill--green"><?= t('acp.laybr.active') ?></span>
105 <?php elseif ($isUpdate): ?>
106 <span class="acp-pill acp-pill--amber"><?= t('acp.laybr.update_available') ?></span>
107 <?php elseif ($installed): ?>
108 <span class="acp-pill acp-pill--grey"><?= t('acp.laybr.installed_pill') ?></span>
109 <?php endif; ?>
110 </h3>
111
112 <p><?= h($item['description']) ?></p>
113
114 <?php if ($isUpdate && (string)($item['changelog'] ?? '') !== ''): ?>
115 <div style="margin:10px 0;padding:10px 12px;border:1px solid var(--acp-border);border-left:3px solid var(--acp-amber);border-radius:8px;background:var(--acp-panel-2);font-size:12px;">
116 <strong style="display:block;margin-bottom:5px;">
117 <i class="fa fa-list-ul"></i>
118 What&apos;s new in v<?= h($item['version']) ?>
119 </strong>
120 <div class="is-muted" style="white-space:pre-line;line-height:1.45;"><?= h($item['changelog']) ?></div>
121 </div>
122 <?php endif; ?>
123
124 <p class="is-muted" style="font-size:12px;">
125 <code>layouts/<?= h($key) ?>/</code>
126 <?php if ($item['author'] !== ''): ?>&middot; <?= t('acp.laybr.by_author', ['author' => h($item['author'])]) ?><?php endif; ?>
127 <?php if ($item['version'] !== ''): ?>
128 &middot; v<?= h($item['version']) ?>
129 <?php if ($isUpdate): ?>
130 <span class="acp-pill acp-pill--amber"><?= t('acp.laybr.you_have', ['version' => h($localVer)]) ?></span>
131 <?php endif; ?>
132 <?php endif; ?>
133 </p>
134
135 <?php if (!$item['installable']): ?>
136 <p style="font-size:12px;color:var(--acp-red);">
137 <?= t('acp.laybr.not_installable') ?>
138 </p>
139 <?php endif; ?>
140 </div>
141
142 <div class="acp-media-foot">
143 <?php if (!$item['installable']): ?>
144
145 <?php if ($item['url'] !== ''): ?>
146 <a class="acp-btn acp-btn--ghost acp-btn--sm" href="<?= h($item['url']) ?>" target="_blank" rel="noopener">
147 <i class="fa fa-external-link"></i> <?= t('acp.laybr.open_page') ?>
148 </a>
149 <?php endif; ?>
150
151 <?php elseif ($installed && !$isUpdate): ?>
152
153 <span class="acp-btn acp-btn--ghost acp-btn--sm is-disabled">
154 <i class="fa fa-check"></i> <?= t('acp.laybr.installed_badge') ?>
155 </span>
156 <form class="acp-inline-form" method="post"
157 data-confirm="<?= h(t('acp.laybr.confirm_reinstall', ['name' => $item['name'], 'key' => $key])) ?>">
158 <?= acp_csrf_field() ?>
159 <input type="hidden" name="install" value="<?= h($key) ?>">
160 <input type="hidden" name="overwrite" value="1">
161 <button class="acp-btn acp-btn--ghost acp-btn--sm" type="submit">
162 <i class="fa fa-refresh"></i> <?= t('acp.laybr.reinstall') ?>
163 </button>
164 </form>
165
166 <?php else: ?>
167
168 <form class="acp-inline-form" method="post"
169 data-confirm="<?= $installed
170 ? h(t('acp.laybr.confirm_update', ['name' => $item['name'], 'version' => $item['version'], 'key' => $key]))
171 : h(t('acp.laybr.confirm_install', ['name' => $item['name']])) ?>">
172 <?= acp_csrf_field() ?>
173 <input type="hidden" name="install" value="<?= h($key) ?>">
174 <?php if ($installed): ?>
175 <input type="hidden" name="overwrite" value="1">
176 <?php endif; ?>
177 <button class="acp-btn acp-btn--sm" type="submit">
178 <i class="fa fa-<?= $installed ? 'arrow-up' : 'download' ?>"></i>
179 <?= $installed ? t('acp.laybr.update_btn') : t('acp.laybr.install_btn') ?>
180 </button>
181 </form>
182
183 <?php endif; ?>
184
185 <?php if ($item['url'] !== '' && $item['installable']): ?>
186 <a class="acp-btn acp-btn--ghost acp-btn--sm" href="<?= h($item['url']) ?>" target="_blank" rel="noopener">
187 <i class="fa fa-external-link"></i> <?= t('acp.laybr.details') ?>
188 </a>
189 <?php endif; ?>
190 </div>
191 </article>
192 <?php endforeach; ?>
193 </div>
194
195<?php endif; ?>
196
Top