WebOrbiton
v1.0.0.1

StocketBase

275 lines · 15.1 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. require_once __DIR__ . '/includes/config.php';
  6. require_once __DIR__ . '/includes/database.php';
  7. require_once __DIR__ . '/includes/auth.php';
  8. require_once __DIR__ . '/includes/csrf.php';
  9. require_once __DIR__ . '/includes/activity-log.php';
  10. require_once __DIR__ . '/includes/site-front.php';
  11. require_once __DIR__ . '/includes/language.php';
  12. ​
  13. Auth::boot();
  14. Auth::requireRoleAtLeast(Auth::ROLE_STORE_OWNER);
  15. ​
  16. $db = Database::site();
  17. Language::ensureKeys(Language::ADDED_DEFAULTS);
  18. $flashMessage = null;
  19. $flashType = 'success';
  20. ​
  21. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  22. if (!Csrf::verify($_POST['csrf_token'] ?? null)) {
  23. $flashMessage = 'Your session expired. Please try again.';
  24. $flashType = 'error';
  25. } else {
  26. $action = (string) ($_POST['action'] ?? '');
  27. $postedCode = strtolower(trim((string) ($_POST['code'] ?? '')));
  28. $knownCode = Languages::isValidCode($postedCode) && Languages::find($postedCode) !== null;
  29. ​
  30. if ($action === 'save_settings') {
  31. $defaultCode = strtolower(trim((string) ($_POST['multilang_default_code'] ?? 'en')));
  32. $defaultName = trim(strip_tags((string) ($_POST['multilang_default_name'] ?? '')));
  33. ​
  34. if (!Languages::isValidCode($defaultCode)) {
  35. $flashMessage = 'The language code should be 2 or 3 letters, like en, pl or pt-br.';
  36. $flashType = 'error';
  37. } elseif (Languages::find($defaultCode) !== null) {
  38. $flashMessage = 'You already added a language with that code.';
  39. $flashType = 'error';
  40. } else {
  41. Languages::setSetting('multilang_enabled', isset($_POST['multilang_enabled']) ? '1' : '0');
  42. Languages::setSetting('multilang_default_code', $defaultCode);
  43. Languages::setSetting('multilang_default_name', $defaultName !== '' ? mb_substr($defaultName, 0, 60) : 'English');
  44. Languages::setSetting('multilang_switcher', isset($_POST['multilang_switcher']) ? '1' : '0');
  45. ActivityLog::record('language.settings');
  46. $flashMessage = 'Changes saved.';
  47. }
  48. } elseif ($action === 'add_language') {
  49. $error = Languages::addLanguage((string) ($_POST['code'] ?? ''), (string) ($_POST['name'] ?? ''));
  50. if ($error !== null) {
  51. $flashMessage = $error;
  52. $flashType = 'error';
  53. } else {
  54. ActivityLog::record('language.add', null, null, $postedCode);
  55. $flashMessage = 'Language added. Translate the texts, then switch it on.';
  56. }
  57. } elseif ($action === 'toggle_language' && $knownCode) {
  58. $statement = $db->prepare('UPDATE site_languages SET is_active = 1 - is_active WHERE code = :code');
  59. $statement->execute(['code' => $postedCode]);
  60. ActivityLog::record('language.toggle', null, null, $postedCode);
  61. $flashMessage = 'Changes saved.';
  62. } elseif ($action === 'delete_language' && $knownCode) {
  63. $statement = $db->prepare('DELETE FROM site_languages WHERE code = :code');
  64. $statement->execute(['code' => $postedCode]);
  65. ActivityLog::record('language.delete', null, null, $postedCode);
  66. $flashMessage = 'Language removed. The translations are kept, so they come back if you add it again.';
  67. } elseif ($action === 'save_strings' && $knownCode) {
  68. $submitted = is_array($_POST['strings'] ?? null) ? $_POST['strings'] : [];
  69. if (Languages::saveStrings($postedCode, $submitted)) {
  70. ActivityLog::record('language.strings', null, null, $postedCode);
  71. $flashMessage = 'Changes saved.';
  72. } else {
  73. $flashMessage = 'We couldn\'t save the translation file. Ask your host to make the translations folder writable.';
  74. $flashType = 'error';
  75. }
  76. } elseif ($action === 'save_site' && $knownCode) {
  77. Languages::saveSiteValues($postedCode, is_array($_POST['site'] ?? null) ? $_POST['site'] : []);
  78. ActivityLog::record('language.site', null, null, $postedCode);
  79. $flashMessage = 'Changes saved.';
  80. } elseif ($action === 'copy_site' && $knownCode) {
  81. $added = Languages::copySiteMissing($postedCode);
  82. ActivityLog::record('language.site_copy', null, null, $postedCode);
  83. $flashMessage = $added > 0
  84. ? $added . ' missing text' . ($added === 1 ? '' : 's') . ' copied from the original. Existing translations were not changed.'
  85. : 'Everything is already translated.';
  86. } elseif ($action === 'copy_missing' && $knownCode) {
  87. $added = Languages::copyMissingStrings($postedCode);
  88. ActivityLog::record('language.copy', null, null, $postedCode);
  89. $flashMessage = $added > 0
  90. ? $added . ' missing text' . ($added === 1 ? '' : 's') . ' copied from the original language. Existing translations were not changed.'
  91. : 'Everything is already translated.';
  92. } else {
  93. $flashMessage = 'Something went wrong. Please try again.';
  94. $flashType = 'error';
  95. }
  96. ​
  97. Languages::resetCache();
  98. SiteFront::settings();
  99. }
  100. }
  101. ​
  102. Languages::resetCache();
  103. $settings = Database::site()->query('SELECT setting_key, setting_value FROM site_settings')->fetchAll(PDO::FETCH_KEY_PAIR);
  104. $multilangEnabled = ($settings['multilang_enabled'] ?? '0') === '1';
  105. $defaultCode = (string) ($settings['multilang_default_code'] ?? 'en');
  106. $defaultName = (string) ($settings['multilang_default_name'] ?? 'English');
  107. $switcherEnabled = ($settings['multilang_switcher'] ?? '1') === '1';
  108. ​
  109. $languages = Languages::all();
  110. $editCode = strtolower(trim((string) ($_GET['edit'] ?? '')));
  111. $editing = Languages::isValidCode($editCode) ? Languages::find($editCode) : null;
  112. $baseStrings = Language::base();
  113. $siteCode = strtolower(trim((string) ($_GET['site'] ?? '')));
  114. $siteEditing = $editing === null && Languages::isValidCode($siteCode) ? Languages::find($siteCode) : null;
  115. $siteValues = $siteEditing !== null ? Languages::siteValues($siteEditing['code']) : [];
  116. $siteOriginals = $siteEditing !== null ? Languages::siteOriginals() : [];
  117. $editStrings = $editing !== null ? Languages::strings($editing['code']) : [];
  118. ​
  119. $dashActivePage = 'languages';
  120. $dashPageTitle = 'Languages';
  121. ​
  122. require __DIR__ . '/includes/dash-header.php';
  123. ​
  124. ?>
  125. ​
  126. <?php if ($flashMessage !== null): ?>
  127. <div class="dash-flash dash-flash-<?= htmlspecialchars($flashType) ?>"><?= Icons::icon($flashType === 'error' ? 'x' : 'check', 'icon icon-sm') ?><?= htmlspecialchars($flashMessage) ?></div>
  128. <?php endif; ?>
  129. ​
  130. <div class="dash-header-row">
  131. <h1 class="dash-title"><?= Icons::icon('translate', 'icon icon-lg') ?>Languages</h1>
  132. </div>
  133. ​
  134. <div class="settings-tabs">
  135. <a href="languages.php" class="<?= $editing === null && $siteEditing === null ? 'active' : '' ?>"><?= Icons::icon('settings', 'icon icon-sm') ?>Overview</a>
  136. <?php if ($siteEditing !== null): ?>
  137. <a href="languages.php?site=<?= urlencode($siteEditing['code']) ?>" class="active"><?= Icons::icon('globe', 'icon icon-sm') ?>Store texts: <?= htmlspecialchars($siteEditing['name']) ?></a>
  138. <?php endif; ?>
  139. <?php if ($editing !== null): ?>
  140. <a href="languages.php?edit=<?= urlencode($editing['code']) ?>" class="active"><?= Icons::icon('translate', 'icon icon-sm') ?>Interface: <?= htmlspecialchars($editing['name']) ?></a>
  141. <?php endif; ?>
  142. </div>
  143. ​
  144. <?php if ($editing === null && $siteEditing === null): ?>
  145. ​
  146. <form method="post" class="settings-section">
  147. <?= Csrf::field() ?>
  148. <input type="hidden" name="action" value="save_settings">
  149. ​
  150. <label><input type="checkbox" name="multilang_enabled" <?= $multilangEnabled ? 'checked' : '' ?>> Offer your store in more than one language</label>
  151. ​
  152. <div class="settings-grid">
  153. <div>
  154. <label>Main language code</label>
  155. <input type="text" name="multilang_default_code" value="<?= htmlspecialchars($defaultCode) ?>" maxlength="10" spellcheck="false">
  156. </div>
  157. <div>
  158. <label>Main language name</label>
  159. <input type="text" name="multilang_default_name" value="<?= htmlspecialchars($defaultName) ?>" maxlength="60">
  160. </div>
  161. </div>
  162. <p style="font-size:12.5px;color:var(--muted);margin:6px 0 0;">This is the language you write your products and pages in. It keeps your normal web address.</p>
  163. ​
  164. <label><input type="checkbox" name="multilang_switcher" <?= $switcherEnabled ? 'checked' : '' ?>> Show a language picker in the footer</label>
  165. ​
  166. <button type="submit" class="dash-btn dash-btn-primary" style="margin-top:20px;"><?= Icons::icon('check', 'icon icon-sm') ?>Save</button>
  167. </form>
  168. ​
  169. <h2 class="dash-subtitle"><?= Icons::icon('translate', 'icon icon-sm') ?>Languages</h2>
  170. ​
  171. <table class="dash-table">
  172. <thead>
  173. <tr>
  174. <th>Language</th>
  175. <th>Web address</th>
  176. <th>Store texts</th>
  177. <th>Status</th>
  178. <th></th>
  179. </tr>
  180. </thead>
  181. <tbody>
  182. <tr>
  183. <td><?= htmlspecialchars($defaultName) ?> <span style="color:var(--muted);">(<?= htmlspecialchars($defaultCode) ?>)</span></td>
  184. <td>/</td>
  185. <td>Normal address</td>
  186. <td><span class="status-pill status-published">Main language</span></td>
  187. <td></td>
  188. </tr>
  189. <?php foreach ($languages as $language): ?>
  190. <?php $missing = Languages::missingStringCount($language['code']); ?>
  191. <tr>
  192. <td><?= htmlspecialchars($language['name']) ?> <span style="color:var(--muted);">(<?= htmlspecialchars($language['code']) ?>)</span></td>
  193. <td>/<?= htmlspecialchars($language['code']) ?>/</td>
  194. <td><?= $missing === 0 ? 'Complete' : $missing . ' texts missing' ?></td>
  195. <td><span class="status-pill status-<?= (int) $language['is_active'] === 1 ? 'published' : 'draft' ?>"><?= (int) $language['is_active'] === 1 ? 'Visible' : 'Hidden' ?></span></td>
  196. <td class="dash-table-actions">
  197. <a href="languages.php?edit=<?= urlencode($language['code']) ?>" class="dash-btn-small"><?= Icons::icon('edit', 'icon icon-sm') ?>Interface</a>
  198. <a href="languages.php?site=<?= urlencode($language['code']) ?>" class="dash-btn-small"><?= Icons::icon('globe', 'icon icon-sm') ?>Store texts</a>
  199. <form method="post" style="display:inline;">
  200. <?= Csrf::field() ?>
  201. <input type="hidden" name="action" value="toggle_language">
  202. <input type="hidden" name="code" value="<?= htmlspecialchars($language['code'], ENT_QUOTES) ?>">
  203. <button type="submit" class="dash-btn-small"><?= Icons::icon((int) $language['is_active'] === 1 ? 'x' : 'check', 'icon icon-sm') ?><?= (int) $language['is_active'] === 1 ? 'Hide' : 'Show' ?></button>
  204. </form>
  205. <form method="post" style="display:inline;" onsubmit="return confirm('Remove this language? Don\'t worry, the translations come back if you add it again.');">
  206. <?= Csrf::field() ?>
  207. <input type="hidden" name="action" value="delete_language">
  208. <input type="hidden" name="code" value="<?= htmlspecialchars($language['code'], ENT_QUOTES) ?>">
  209. <button type="submit" class="dash-btn-small dash-btn-danger"><?= Icons::icon('trash', 'icon icon-sm') ?>Remove</button>
  210. </form>
  211. </td>
  212. </tr>
  213. <?php endforeach; ?>
  214. </tbody>
  215. </table>
  216. ​
  217. <h2 class="dash-subtitle"><?= Icons::icon('plus', 'icon icon-sm') ?>Add language</h2>
  218. <form method="post" class="settings-section">
  219. <?= Csrf::field() ?>
  220. <input type="hidden" name="action" value="add_language">
  221. <div class="settings-grid">
  222. <div>
  223. <label>Code (like pl, de or pt-br)</label>
  224. <input type="text" name="code" maxlength="10" required spellcheck="false" autocomplete="off">
  225. </div>
  226. <div>
  227. <label>Name customers see (like Polski)</label>
  228. <input type="text" name="name" maxlength="60" required>
  229. </div>
  230. </div>
  231. <p style="font-size:12.5px;color:var(--muted);margin:6px 0 0;">New languages start hidden. Translate the store texts and your content, then switch it on. You translate products, pages and categories from their own edit pages.</p>
  232. <button type="submit" class="dash-btn dash-btn-primary" style="margin-top:20px;"><?= Icons::icon('plus', 'icon icon-sm') ?>Add language</button>
  233. </form>
  234. ​
  235. <?php elseif ($siteEditing !== null): ?>
  236. ​
  237. <?php require __DIR__ . '/includes/language-site-texts.php'; ?>
  238. ​
  239. <?php else: ?>
  240. ​
  241. <?php $editMissing = Languages::missingStringCount($editing['code']); ?>
  242. ​
  243. <div class="sidebar-box" style="max-width:640px;margin-bottom:20px;">
  244. <p style="margin:0 0 12px;">Store texts in <strong><?= htmlspecialchars($editing['name']) ?></strong>. Anything you leave empty shows in your main language.</p>
  245. <form method="post">
  246. <?= Csrf::field() ?>
  247. <input type="hidden" name="action" value="copy_missing">
  248. <input type="hidden" name="code" value="<?= htmlspecialchars($editing['code'], ENT_QUOTES) ?>">
  249. <button type="submit" class="dash-btn" <?= $editMissing === 0 ? 'disabled' : '' ?>><?= Icons::icon('copy', 'icon icon-sm') ?>Fill empty texts with the original (<?= $editMissing ?>)</button>
  250. </form>
  251. <p style="font-size:12.5px;color:var(--muted);margin:10px 0 0;">Only empty fields get filled, your translations stay as they are.</p>
  252. </div>
  253. ​
  254. <form method="post" class="settings-section" style="max-width:none;">
  255. <?= Csrf::field() ?>
  256. <input type="hidden" name="action" value="save_strings">
  257. <input type="hidden" name="code" value="<?= htmlspecialchars($editing['code'], ENT_QUOTES) ?>">
  258. ​
  259. <?php foreach ($baseStrings as $key => $baseValue): ?>
  260. <div class="lang-row">
  261. <div class="lang-row-key"><?= htmlspecialchars((string) $key) ?><br><span style="color:var(--muted-dim);"><?= htmlspecialchars((string) $baseValue) ?></span></div>
  262. <input type="text" name="strings[<?= htmlspecialchars((string) $key, ENT_QUOTES) ?>]" value="<?= htmlspecialchars((string) ($editStrings[$key] ?? '')) ?>" placeholder="<?= htmlspecialchars((string) $baseValue, ENT_QUOTES) ?>">
  263. </div>
  264. <?php endforeach; ?>
  265. ​
  266. <div class="publish-actions">
  267. <button type="submit" class="dash-btn dash-btn-primary"><?= Icons::icon('check', 'icon icon-sm') ?>Save</button>
  268. <a href="languages.php" class="dash-btn"><?= Icons::icon('x', 'icon icon-sm') ?>Back</a>
  269. </div>
  270. </form>
  271. ​
  272. <?php endif; ?>
  273. ​
  274. <?php require __DIR__ . '/includes/dash-footer.php'; ?>
  275. ​