WebOrbiton
v1.0.0.8

Publisium

1,082 lines · 45.3 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. require_once __DIR__ . '/includes/config.php';
  6. require_once __DIR__ . '/includes/asset.php';
  7. ​
  8. session_start();
  9. ​
  10. $envRoot = dirname(__DIR__) . '/publisium-env';
  11. $lockFile = $envRoot . '/install.lock';
  12. ​
  13. if (is_file($lockFile)) {
  14. http_response_code(403);
  15. echo '<!DOCTYPE html><html><head><meta charset="utf-8"><title>Publisium</title></head><body>';
  16. echo '<p>Installation already completed. Delete install.lock manually to reinstall.</p>';
  17. echo '</body></html>';
  18. exit;
  19. }
  20. ​
  21. $errors = [];
  22. $step = isset($_POST['step']) ? (int) $_POST['step'] : (isset($_GET['step']) ? (int) $_GET['step'] : 1);
  23. $step = max(1, min(5, $step));
  24. ​
  25. function pdoConnect(string $host, string $port, string $name, string $user, string $pass): PDO
  26. {
  27. $dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $host, $port, $name);
  28. return new PDO($dsn, $user, $pass, [
  29. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  30. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  31. ]);
  32. }
  33. ​
  34. function runSqlFile(PDO $pdo, string $path): void
  35. {
  36. $sql = (string) file_get_contents($path);
  37. $statements = array_filter(array_map('trim', explode(';', $sql)));
  38. foreach ($statements as $statement) {
  39. if ($statement === '') {
  40. continue;
  41. }
  42. $pdo->exec($statement);
  43. }
  44. }
  45. ​
  46. function installerLanguages(): array
  47. {
  48. return [
  49. ['en-US', 'English (US)', 'English (United States)'],
  50. ['en-GB', 'English (UK)', 'English (United Kingdom)'],
  51. ['pt-BR', 'Portuguese (Brazil)', 'Português (Brasil)'],
  52. ['pt-PT', 'Portuguese (Portugal)', 'Português (Portugal)'],
  53. ['zh-CN', 'Chinese (Simplified)', '中文(简体)'],
  54. ['zh-TW', 'Chinese (Traditional)', '中文(繁體)'],
  55. ['es-ES', 'Spanish (Spain)', 'Español (España)'],
  56. ['es-MX', 'Spanish (Mexico)', 'Español (México)'],
  57. ['fr-FR', 'French (France)', 'Français (France)'],
  58. ['fr-CA', 'French (Canada)', 'Français (Canada)'],
  59. ['fr-BE', 'French (Belgium)', 'Français (Belgique)'],
  60. ['fr-CH', 'French (Switzerland)', 'Français (Suisse)'],
  61. ['de-DE', 'German (Germany)', 'Deutsch (Deutschland)'],
  62. ['de-AT', 'German (Austria)', 'Deutsch (Österreich)'],
  63. ['de-CH', 'German (Switzerland)', 'Deutsch (Schweiz)'],
  64. ['nl-NL', 'Dutch (Netherlands)', 'Nederlands (Nederland)'],
  65. ['nl-BE', 'Dutch (Belgium)', 'Nederlands (België)'],
  66. ['it-IT', 'Italian (Italy)', 'Italiano (Italia)'],
  67. ['it-CH', 'Italian (Switzerland)', 'Italiano (Svizzera)'],
  68. ['ar-SA', 'Arabic (Saudi Arabia)', 'العربية (السعودية)'],
  69. ['ar-EG', 'Arabic (Egypt)', 'العربية (مصر)'],
  70. ['ar-AE', 'Arabic (UAE)', 'العربية (الإمارات)'],
  71. ['ru-RU', 'Russian (Russia)', 'Русский (Россия)'],
  72. ['sv-SE', 'Swedish (Sweden)', 'Svenska (Sverige)'],
  73. ['sv-FI', 'Swedish (Finland)', 'Svenska (Finland)'],
  74. ['en-CA', 'English (Canada)', 'English (Canada)'],
  75. ['en-AU', 'English (Australia)', 'English (Australia)'],
  76. ['en-NZ', 'English (New Zealand)', 'English (New Zealand)'],
  77. ['en-IE', 'English (Ireland)', 'English (Ireland)'],
  78. ['en-IN', 'English (India)', 'English (India)'],
  79. ['en-ZA', 'English (South Africa)', 'English (South Africa)'],
  80. ['es-AR', 'Spanish (Argentina)', 'Español (Argentina)'],
  81. ['es-CO', 'Spanish (Colombia)', 'Español (Colombia)'],
  82. ['es-CL', 'Spanish (Chile)', 'Español (Chile)'],
  83. ['es-US', 'Spanish (United States)', 'Español (Estados Unidos)'],
  84. ['zh-HK', 'Chinese (Hong Kong)', '中文(香港)'],
  85. ['zh-SG', 'Chinese (Singapore)', '中文(新加坡)'],
  86. ['aa', 'Afar', 'Afaraf'],
  87. ['ab', 'Abkhazian', 'аҧсуа бызшәа'],
  88. ['ae', 'Avestan', 'avesta'],
  89. ['af', 'Afrikaans', 'Afrikaans'],
  90. ['ak', 'Akan', 'Akan'],
  91. ['am', 'Amharic', 'አማርኛ'],
  92. ['an', 'Aragonese', 'aragonés'],
  93. ['ar', 'Arabic', 'العربية'],
  94. ['as', 'Assamese', 'অসমীয়া'],
  95. ['av', 'Avaric', 'авар мацӀ'],
  96. ['ay', 'Aymara', 'aymar aru'],
  97. ['az', 'Azerbaijani', 'azərbaycan dili'],
  98. ['ba', 'Bashkir', 'башҡорт теле'],
  99. ['be', 'Belarusian', 'беларуская мова'],
  100. ['bg', 'Bulgarian', 'български език'],
  101. ['bh', 'Bihari', 'भोजपुरी'],
  102. ['bi', 'Bislama', 'Bislama'],
  103. ['bm', 'Bambara', 'bamanankan'],
  104. ['bn', 'Bengali', 'বাংলা'],
  105. ['bo', 'Tibetan', 'བོད་ཡིག'],
  106. ['br', 'Breton', 'brezhoneg'],
  107. ['bs', 'Bosnian', 'bosanski jezik'],
  108. ['ca', 'Catalan', 'català'],
  109. ['ce', 'Chechen', 'нохчийн мотт'],
  110. ['ch', 'Chamorro', 'Chamoru'],
  111. ['co', 'Corsican', 'corsu'],
  112. ['cr', 'Cree', 'ᓀᐦᐃᔭᐍᐏᐣ'],
  113. ['cs', 'Czech', 'čeština'],
  114. ['cu', 'Church Slavic', 'ѩзыкъ словѣньскъ'],
  115. ['cv', 'Chuvash', 'чӑваш чӗлхи'],
  116. ['cy', 'Welsh', 'Cymraeg'],
  117. ['da', 'Danish', 'dansk'],
  118. ['de', 'German', 'Deutsch'],
  119. ['dv', 'Divehi', 'ދިވެހި'],
  120. ['dz', 'Dzongkha', 'རྫོང་ཁ'],
  121. ['ee', 'Ewe', 'Eʋegbe'],
  122. ['el', 'Greek', 'ελληνικά'],
  123. ['en', 'English', 'English'],
  124. ['eo', 'Esperanto', 'Esperanto'],
  125. ['es', 'Spanish', 'Español'],
  126. ['et', 'Estonian', 'eesti'],
  127. ['eu', 'Basque', 'euskara'],
  128. ['fa', 'Persian', 'فارسی'],
  129. ['ff', 'Fulah', 'Fulfulde'],
  130. ['fi', 'Finnish', 'suomi'],
  131. ['fj', 'Fijian', 'vosa Vakaviti'],
  132. ['fo', 'Faroese', 'føroyskt'],
  133. ['fr', 'French', 'Français'],
  134. ['fy', 'Western Frisian', 'Frysk'],
  135. ['ga', 'Irish', 'Gaeilge'],
  136. ['gd', 'Scottish Gaelic', 'Gàidhlig'],
  137. ['gl', 'Galician', 'galego'],
  138. ['gn', 'Guarani', "Avañe'ẽ"],
  139. ['gu', 'Gujarati', 'ગુજરાતી'],
  140. ['gv', 'Manx', 'Gaelg'],
  141. ['ha', 'Hausa', 'هَوُسَ'],
  142. ['he', 'Hebrew', 'עברית'],
  143. ['hi', 'Hindi', 'हिन्दी'],
  144. ['ho', 'Hiri Motu', 'Hiri Motu'],
  145. ['hr', 'Croatian', 'hrvatski jezik'],
  146. ['ht', 'Haitian Creole', 'Kreyòl ayisyen'],
  147. ['hu', 'Hungarian', 'magyar'],
  148. ['hy', 'Armenian', 'Հայերեն'],
  149. ['hz', 'Herero', 'Otjiherero'],
  150. ['ia', 'Interlingua', 'Interlingua'],
  151. ['id', 'Indonesian', 'Bahasa Indonesia'],
  152. ['ie', 'Interlingue', 'Interlingue'],
  153. ['ig', 'Igbo', 'Asụsụ Igbo'],
  154. ['ii', 'Sichuan Yi', 'ꆈꌠ꒿ Nuosuhxop'],
  155. ['ik', 'Inupiaq', 'Iñupiaq'],
  156. ['io', 'Ido', 'Ido'],
  157. ['is', 'Icelandic', 'Íslenska'],
  158. ['it', 'Italian', 'Italiano'],
  159. ['iu', 'Inuktitut', 'ᐃᓄᒃᑎᑐᑦ'],
  160. ['ja', 'Japanese', '日本語'],
  161. ['jv', 'Javanese', 'ꦧꦱꦗꦮ'],
  162. ['ka', 'Georgian', 'ქართული'],
  163. ['kg', 'Kongo', 'Kikongo'],
  164. ['ki', 'Kikuyu', 'Gĩkũyũ'],
  165. ['kj', 'Kwanyama', 'Kuanyama'],
  166. ['kk', 'Kazakh', 'қазақ тілі'],
  167. ['kl', 'Kalaallisut', 'kalaallisut'],
  168. ['km', 'Khmer', 'ខ្មែរ'],
  169. ['kn', 'Kannada', 'ಕನ್ನಡ'],
  170. ['ko', 'Korean', '한국어'],
  171. ['kr', 'Kanuri', 'Kanuri'],
  172. ['ks', 'Kashmiri', 'कॉशुर'],
  173. ['ku', 'Kurdish', 'Kurdî'],
  174. ['kv', 'Komi', 'коми кыв'],
  175. ['kw', 'Cornish', 'Kernewek'],
  176. ['ky', 'Kyrgyz', 'Кыргызча'],
  177. ['la', 'Latin', 'latine'],
  178. ['lb', 'Luxembourgish', 'Lëtzebuergesch'],
  179. ['lg', 'Ganda', 'Luganda'],
  180. ['li', 'Limburgish', 'Limburgs'],
  181. ['ln', 'Lingala', 'Lingála'],
  182. ['lo', 'Lao', 'ພາສາລາວ'],
  183. ['lt', 'Lithuanian', 'lietuvių kalba'],
  184. ['lu', 'Luba-Katanga', 'Kiluba'],
  185. ['lv', 'Latvian', 'latviešu valoda'],
  186. ['mg', 'Malagasy', 'fiteny malagasy'],
  187. ['mh', 'Marshallese', 'Kajin M̧ajeļ'],
  188. ['mi', 'Maori', 'te reo Māori'],
  189. ['mk', 'Macedonian', 'македонски јазик'],
  190. ['ml', 'Malayalam', 'മലയാളം'],
  191. ['mn', 'Mongolian', 'Монгол хэл'],
  192. ['mr', 'Marathi', 'मराठी'],
  193. ['ms', 'Malay', 'Bahasa Melayu'],
  194. ['mt', 'Maltese', 'Malti'],
  195. ['my', 'Burmese', 'ဗမာစာ'],
  196. ['na', 'Nauru', 'Dorerin Naoero'],
  197. ['nb', 'Norwegian Bokmål', 'Norsk Bokmål'],
  198. ['nd', 'North Ndebele', 'isiNdebele'],
  199. ['ne', 'Nepali', 'नेपाली'],
  200. ['ng', 'Ndonga', 'Owambo'],
  201. ['nl', 'Dutch', 'Nederlands'],
  202. ['nn', 'Norwegian Nynorsk', 'Norsk Nynorsk'],
  203. ['no', 'Norwegian', 'Norsk'],
  204. ['nr', 'South Ndebele', 'isiNdebele'],
  205. ['nv', 'Navajo', 'Diné bizaad'],
  206. ['ny', 'Chichewa', 'chiCheŵa'],
  207. ['oc', 'Occitan', 'occitan'],
  208. ['oj', 'Ojibwe', 'ᐊᓂᔑᓈᐯᒧᐎᓐ'],
  209. ['om', 'Oromo', 'Afaan Oromoo'],
  210. ['or', 'Odia', 'ଓଡ଼ିଆ'],
  211. ['os', 'Ossetian', 'ирон æвзаг'],
  212. ['pa', 'Punjabi', 'ਪੰਜਾਬੀ'],
  213. ['pi', 'Pali', 'पालि'],
  214. ['pl', 'Polish', 'Polski'],
  215. ['ps', 'Pashto', 'پښتو'],
  216. ['pt', 'Portuguese', 'Português'],
  217. ['qu', 'Quechua', 'Runa Simi'],
  218. ['rm', 'Romansh', 'rumantsch grischun'],
  219. ['rn', 'Kirundi', 'Ikirundi'],
  220. ['ro', 'Romanian', 'Română'],
  221. ['ru', 'Russian', 'Русский'],
  222. ['rw', 'Kinyarwanda', 'Ikinyarwanda'],
  223. ['sa', 'Sanskrit', 'संस्कृतम्'],
  224. ['sc', 'Sardinian', 'sardu'],
  225. ['sd', 'Sindhi', 'सिन्धी'],
  226. ['se', 'Northern Sami', 'Davvisámegiella'],
  227. ['sg', 'Sango', 'yângâ tî sängö'],
  228. ['si', 'Sinhala', 'සිංහල'],
  229. ['sk', 'Slovak', 'slovenčina'],
  230. ['sl', 'Slovenian', 'slovenščina'],
  231. ['sm', 'Samoan', "gagana fa'a Samoa"],
  232. ['sn', 'Shona', 'chiShona'],
  233. ['so', 'Somali', 'Soomaaliga'],
  234. ['sq', 'Albanian', 'Shqip'],
  235. ['sr', 'Serbian', 'српски језик'],
  236. ['ss', 'Swati', 'SiSwati'],
  237. ['st', 'Southern Sotho', 'Sesotho'],
  238. ['su', 'Sundanese', 'Basa Sunda'],
  239. ['sv', 'Swedish', 'Svenska'],
  240. ['sw', 'Swahili', 'Kiswahili'],
  241. ['ta', 'Tamil', 'தமிழ்'],
  242. ['te', 'Telugu', 'తెలుగు'],
  243. ['tg', 'Tajik', 'тоҷикӣ'],
  244. ['th', 'Thai', 'ไทย'],
  245. ['ti', 'Tigrinya', 'ትግርኛ'],
  246. ['tk', 'Turkmen', 'Türkmençe'],
  247. ['tl', 'Tagalog', 'Wikang Tagalog'],
  248. ['tn', 'Tswana', 'Setswana'],
  249. ['to', 'Tonga', 'faka Tonga'],
  250. ['tr', 'Turkish', 'Türkçe'],
  251. ['ts', 'Tsonga', 'Xitsonga'],
  252. ['tt', 'Tatar', 'татар теле'],
  253. ['tw', 'Twi', 'Twi'],
  254. ['ty', 'Tahitian', 'Reo Tahiti'],
  255. ['ug', 'Uyghur', 'ئۇيغۇرچە'],
  256. ['uk', 'Ukrainian', 'Українська'],
  257. ['ur', 'Urdu', 'اردو'],
  258. ['uz', 'Uzbek', "Oʻzbek"],
  259. ['ve', 'Venda', 'Tshivenḓa'],
  260. ['vi', 'Vietnamese', 'Tiếng Việt'],
  261. ['vo', 'Volapük', 'Volapük'],
  262. ['wa', 'Walloon', 'walon'],
  263. ['wo', 'Wolof', 'Wollof'],
  264. ['xh', 'Xhosa', 'isiXhosa'],
  265. ['yi', 'Yiddish', 'ייִדיש'],
  266. ['yo', 'Yoruba', 'Yorùbá'],
  267. ['za', 'Zhuang', 'Saɯ cueŋƅ'],
  268. ['zh', 'Chinese', '中文'],
  269. ['zu', 'Zulu', 'isiZulu'],
  270. ];
  271. }
  272. ​
  273. function installerLanguageAliases(): array
  274. {
  275. return [
  276. 'en' => ['english', 'eng', 'angielski'],
  277. 'en-US' => ['english us', 'american english', 'angielski amerykanski', 'us', 'usa'],
  278. 'en-GB' => ['english uk', 'british english', 'angielski brytyjski', 'gb', 'uk'],
  279. 'pl' => ['polish', 'polski', 'pol'],
  280. 'de' => ['german', 'niemiecki', 'deutsch', 'ger'],
  281. 'fr' => ['french', 'francuski', 'francais'],
  282. 'fr-FR' => ['french france', 'francuski francuski'],
  283. 'fr-CA' => ['french canada', 'canadian french', 'quebecois', 'francuski kanadyjski'],
  284. 'fr-BE' => ['french belgium', 'belgian french', 'francuski belgijski'],
  285. 'fr-CH' => ['french switzerland', 'swiss french', 'francuski szwajcarski'],
  286. 'de-DE' => ['german germany', 'niemiecki niemcy'],
  287. 'de-AT' => ['german austria', 'austrian german', 'niemiecki austriacki'],
  288. 'de-CH' => ['german switzerland', 'swiss german', 'niemiecki szwajcarski'],
  289. 'nl-NL' => ['dutch netherlands', 'holenderski holandia'],
  290. 'nl-BE' => ['dutch belgium', 'flemish', 'flamandzki', 'holenderski belgijski'],
  291. 'it-IT' => ['italian italy', 'wloski wlochy'],
  292. 'it-CH' => ['italian switzerland', 'swiss italian', 'wloski szwajcarski'],
  293. 'ar-SA' => ['arabic saudi arabia', 'arabski saudyjski'],
  294. 'ar-EG' => ['arabic egypt', 'egyptian arabic', 'arabski egipski'],
  295. 'ar-AE' => ['arabic uae', 'arabski emiraty'],
  296. 'ru-RU' => ['russian russia', 'rosyjski rosja'],
  297. 'sv-SE' => ['swedish sweden', 'szwedzki szwecja'],
  298. 'sv-FI' => ['swedish finland', 'szwedzki finlandia'],
  299. 'en-CA' => ['english canada', 'canadian english', 'angielski kanadyjski'],
  300. 'en-AU' => ['english australia', 'australian english', 'angielski australijski', 'aussie'],
  301. 'en-NZ' => ['english new zealand', 'kiwi english', 'angielski nowozelandzki'],
  302. 'en-IE' => ['english ireland', 'irish english', 'angielski irlandzki'],
  303. 'en-IN' => ['english india', 'indian english', 'angielski indyjski'],
  304. 'en-ZA' => ['english south africa', 'south african english', 'angielski poludniowoafrykanski'],
  305. 'es-AR' => ['spanish argentina', 'argentinian spanish', 'hiszpanski argentynski'],
  306. 'es-CO' => ['spanish colombia', 'colombian spanish', 'hiszpanski kolumbijski'],
  307. 'es-CL' => ['spanish chile', 'chilean spanish', 'hiszpanski chilijski'],
  308. 'es-US' => ['spanish united states', 'us spanish', 'hiszpanski amerykanski'],
  309. 'zh-HK' => ['chinese hong kong', 'cantonese hong kong', 'chinski hongkong'],
  310. 'zh-SG' => ['chinese singapore', 'chinski singapur'],
  311. 'es' => ['spanish', 'hiszpanski', 'espanol', 'castellano'],
  312. 'es-ES' => ['spanish spain', 'castellano', 'hiszpanski europejski'],
  313. 'es-MX' => ['spanish mexico', 'mexican spanish', 'hiszpanski meksykanski'],
  314. 'it' => ['italian', 'wloski', 'italiano'],
  315. 'pt' => ['portuguese', 'portugalski', 'portugues'],
  316. 'pt-BR' => ['portuguese brazil', 'brazilian portuguese', 'brazylijski', 'portugalski brazylijski'],
  317. 'pt-PT' => ['portuguese portugal', 'european portuguese', 'portugalski europejski'],
  318. 'ru' => ['russian', 'rosyjski', 'russkiy'],
  319. 'uk' => ['ukrainian', 'ukrainski'],
  320. 'zh' => ['chinese', 'chinski', 'mandarin', 'mandarynski', 'zhongwen'],
  321. 'zh-CN' => ['chinese simplified', 'simplified chinese', 'chinski uproszczony', 'mainland china', 'putonghua'],
  322. 'zh-TW' => ['chinese traditional', 'traditional chinese', 'chinski tradycyjny', 'taiwan', 'taiwanski'],
  323. 'ja' => ['japanese', 'japonski', 'nihongo'],
  324. 'ko' => ['korean', 'koreanski', 'hangugeo'],
  325. 'ar' => ['arabic', 'arabski'],
  326. 'he' => ['hebrew', 'hebrajski', 'iw'],
  327. 'nl' => ['dutch', 'holenderski', 'niderlandzki', 'flemish'],
  328. 'sv' => ['swedish', 'szwedzki', 'svenska'],
  329. 'no' => ['norwegian', 'norweski'],
  330. 'nb' => ['norwegian bokmal', 'bokmal'],
  331. 'nn' => ['nynorsk'],
  332. 'da' => ['danish', 'dunski'],
  333. 'fi' => ['finnish', 'finski', 'suomi'],
  334. 'cs' => ['czech', 'czeski', 'cestina'],
  335. 'sk' => ['slovak', 'slowacki'],
  336. 'hu' => ['hungarian', 'wegierski'],
  337. 'ro' => ['romanian', 'rumunski'],
  338. 'bg' => ['bulgarian', 'bulgarski'],
  339. 'el' => ['greek', 'grecki'],
  340. 'tr' => ['turkish', 'turecki'],
  341. 'hi' => ['hindi'],
  342. 'bn' => ['bengali', 'bengalski'],
  343. 'vi' => ['vietnamese', 'wietnamski'],
  344. 'th' => ['thai', 'tajski'],
  345. 'id' => ['indonesian', 'indonezyjski', 'bahasa'],
  346. 'ms' => ['malay', 'malajski'],
  347. 'sr' => ['serbian', 'serbski'],
  348. 'hr' => ['croatian', 'chorwacki'],
  349. 'sl' => ['slovenian', 'slowenski'],
  350. 'lt' => ['lithuanian', 'litewski'],
  351. 'lv' => ['latvian', 'lotewski'],
  352. 'et' => ['estonian', 'estonski'],
  353. 'is' => ['icelandic', 'islandzki'],
  354. 'ga' => ['irish', 'irlandzki', 'gaelic'],
  355. 'cy' => ['welsh', 'walijski'],
  356. 'ca' => ['catalan', 'katalonski'],
  357. 'eu' => ['basque', 'baskijski'],
  358. 'fa' => ['persian', 'perski', 'farsi'],
  359. 'ur' => ['urdu'],
  360. 'sw' => ['swahili'],
  361. 'af' => ['afrikaans'],
  362. 'am' => ['amharic', 'amharski'],
  363. 'az' => ['azerbaijani', 'azerski'],
  364. 'ka' => ['georgian', 'gruzinski'],
  365. 'hy' => ['armenian', 'armenski'],
  366. 'kk' => ['kazakh', 'kazachski'],
  367. 'uz' => ['uzbek', 'uzbecki'],
  368. 'mn' => ['mongolian', 'mongolski'],
  369. 'ta' => ['tamil', 'tamilski'],
  370. 'te' => ['telugu'],
  371. 'ml' => ['malayalam'],
  372. 'kn' => ['kannada'],
  373. 'gu' => ['gujarati'],
  374. 'pa' => ['punjabi'],
  375. 'mr' => ['marathi'],
  376. 'ne' => ['nepali', 'nepalski'],
  377. 'si' => ['sinhala', 'sinhalski'],
  378. 'km' => ['khmer'],
  379. 'lo' => ['lao'],
  380. 'my' => ['burmese', 'birmanski'],
  381. 'ps' => ['pashto'],
  382. 'ku' => ['kurdish', 'kurdyjski'],
  383. 'yi' => ['yiddish', 'jidysz'],
  384. 'eo' => ['esperanto'],
  385. 'la' => ['latin', 'lacina'],
  386. 'sq' => ['albanian', 'albanski'],
  387. 'mk' => ['macedonian', 'macedonski'],
  388. 'be' => ['belarusian', 'bialoruski'],
  389. 'gl' => ['galician', 'galicyjski'],
  390. 'mt' => ['maltese', 'maltanski'],
  391. 'tl' => ['tagalog', 'filipino'],
  392. 'zu' => ['zulu'],
  393. 'xh' => ['xhosa'],
  394. 'so' => ['somali', 'somalijski'],
  395. 'ha' => ['hausa'],
  396. 'yo' => ['yoruba'],
  397. 'ig' => ['igbo'],
  398. ];
  399. }
  400. ​
  401. function installerLanguageCodes(): array
  402. {
  403. return array_column(installerLanguages(), 0);
  404. }
  405. ​
  406. function installerLanguagesJson(): string
  407. {
  408. $aliases = installerLanguageAliases();
  409. $rows = [];
  410. foreach (installerLanguages() as [$code, $name, $native]) {
  411. $extra = $aliases[$code] ?? [];
  412. $rows[] = [
  413. 'code' => $code,
  414. 'name' => $name,
  415. 'native' => $native,
  416. 'aliases' => $extra,
  417. ];
  418. }
  419. return json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  420. }
  421. ​
  422. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  423. if ($step === 1) {
  424. $locale = trim((string) ($_POST['locale'] ?? 'en'));
  425. if (!in_array($locale, installerLanguageCodes(), true)) {
  426. $locale = 'en';
  427. }
  428. ​
  429. $_SESSION['install']['project_name'] = trim((string) ($_POST['project_name'] ?? 'default'));
  430. $_SESSION['install']['app_name'] = trim((string) ($_POST['app_name'] ?? 'Publisium'));
  431. $_SESSION['install']['app_url'] = trim((string) ($_POST['app_url'] ?? ''));
  432. $_SESSION['install']['base_path'] = trim((string) ($_POST['base_path'] ?? ''));
  433. $_SESSION['install']['locale'] = $locale;
  434. header('Location: install.php?step=2');
  435. exit;
  436. }
  437. ​
  438. if ($step === 2) {
  439. $dbSite = [
  440. 'host' => trim((string) ($_POST['db_site_host'] ?? '127.0.0.1')),
  441. 'port' => trim((string) ($_POST['db_site_port'] ?? '3306')),
  442. 'name' => trim((string) ($_POST['db_site_name'] ?? '')),
  443. 'user' => trim((string) ($_POST['db_site_user'] ?? '')),
  444. 'pass' => (string) ($_POST['db_site_pass'] ?? ''),
  445. ];
  446. ​
  447. $dbUsers = [
  448. 'host' => trim((string) ($_POST['db_users_host'] ?? '127.0.0.1')),
  449. 'port' => trim((string) ($_POST['db_users_port'] ?? '3306')),
  450. 'name' => trim((string) ($_POST['db_users_name'] ?? '')),
  451. 'user' => trim((string) ($_POST['db_users_user'] ?? '')),
  452. 'pass' => (string) ($_POST['db_users_pass'] ?? ''),
  453. ];
  454. ​
  455. try {
  456. $siteConnection = pdoConnect($dbSite['host'], $dbSite['port'], $dbSite['name'], $dbSite['user'], $dbSite['pass']);
  457. $usersConnection = pdoConnect($dbUsers['host'], $dbUsers['port'], $dbUsers['name'], $dbUsers['user'], $dbUsers['pass']);
  458. runSqlFile($siteConnection, __DIR__ . '/sql/schema_site.sql');
  459. runSqlFile($usersConnection, __DIR__ . '/sql/schema_users.sql');
  460. ​
  461. $_SESSION['install']['db_site'] = $dbSite;
  462. $_SESSION['install']['db_users'] = $dbUsers;
  463. header('Location: install.php?step=3');
  464. exit;
  465. } catch (Throwable $exception) {
  466. $errors[] = 'Database error: ' . $exception->getMessage();
  467. }
  468. }
  469. ​
  470. if ($step === 3) {
  471. $username = trim((string) ($_POST['admin_username'] ?? ''));
  472. $email = trim((string) ($_POST['admin_email'] ?? ''));
  473. $displayName = trim((string) ($_POST['admin_display_name'] ?? ''));
  474. $password = (string) ($_POST['admin_password'] ?? '');
  475. $passwordConfirm = (string) ($_POST['admin_password_confirm'] ?? '');
  476. ​
  477. if ($username === '' || $email === '' || $password === '') {
  478. $errors[] = 'Username, email and password are required.';
  479. } elseif ($password !== $passwordConfirm) {
  480. $errors[] = 'Passwords do not match.';
  481. } elseif (strlen($password) < 10) {
  482. $errors[] = 'Password must be at least 10 characters long.';
  483. }
  484. ​
  485. if (empty($errors)) {
  486. try {
  487. $dbSite = $_SESSION['install']['db_site'];
  488. $siteConnection = pdoConnect($dbSite['host'], $dbSite['port'], $dbSite['name'], $dbSite['user'], $dbSite['pass']);
  489. ​
  490. $countStatement = $siteConnection->query("SELECT COUNT(*) AS total FROM team_accounts WHERE role = 'super_admin'");
  491. $existingSuperAdmins = (int) $countStatement->fetch()['total'];
  492. ​
  493. if ($existingSuperAdmins > 0) {
  494. $errors[] = 'A Super Admin account already exists.';
  495. } else {
  496. $hash = password_hash($password, PASSWORD_DEFAULT);
  497. $insert = $siteConnection->prepare(
  498. 'INSERT INTO team_accounts (username, email, password_hash, display_name, role, status) VALUES (:username, :email, :hash, :display_name, :role, :status)'
  499. );
  500. $insert->execute([
  501. 'username' => $username,
  502. 'email' => $email,
  503. 'hash' => $hash,
  504. 'display_name' => $displayName !== '' ? $displayName : $username,
  505. 'role' => 'super_admin',
  506. 'status' => 'active',
  507. ]);
  508. ​
  509. $_SESSION['install']['admin_created'] = true;
  510. header('Location: install.php?step=4');
  511. exit;
  512. }
  513. } catch (Throwable $exception) {
  514. $errors[] = 'Could not create Super Admin: ' . $exception->getMessage();
  515. }
  516. }
  517. }
  518. ​
  519. if ($step === 4) {
  520. $siteName = trim((string) ($_POST['site_name'] ?? ($_SESSION['install']['app_name'] ?? 'Publisium')));
  521. $siteDescription = trim((string) ($_POST['site_description'] ?? ''));
  522. ​
  523. try {
  524. $dbSite = $_SESSION['install']['db_site'];
  525. $siteConnection = pdoConnect($dbSite['host'], $dbSite['port'], $dbSite['name'], $dbSite['user'], $dbSite['pass']);
  526. ​
  527. $defaults = [
  528. 'site_name' => $siteName,
  529. 'site_description' => $siteDescription,
  530. 'clean_article_urls' => '0',
  531. 'menu_position' => 'top',
  532. 'banner_enabled' => '1',
  533. 'featured_banner_enabled' => '0',
  534. 'featured_banner_mode' => 'static',
  535. 'featured_banner_hide_duplicates' => '0',
  536. 'featured_banner_auto_window_value' => '24',
  537. 'featured_banner_auto_window_unit' => 'hours',
  538. 'featured_banner_auto_category_mode' => 'mixed',
  539. 'related_articles_enabled' => '1',
  540. 'article_reading_time_enabled' => '1',
  541. 'sitemap_rss_enabled' => '1',
  542. 'article_loading_mode' => 'scroll',
  543. 'comments_enabled' => '0',
  544. 'comments_provider' => 'disqus',
  545. ];
  546. ​
  547. $insert = $siteConnection->prepare(
  548. 'INSERT INTO site_settings (setting_key, setting_value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)'
  549. );
  550. foreach ($defaults as $key => $value) {
  551. $insert->execute(['key' => $key, 'value' => $value]);
  552. }
  553. ​
  554. $_SESSION['install']['site_name'] = $siteName;
  555. $_SESSION['install']['site_description'] = $siteDescription;
  556. header('Location: install.php?step=5');
  557. exit;
  558. } catch (Throwable $exception) {
  559. $errors[] = 'Could not save site settings: ' . $exception->getMessage();
  560. }
  561. }
  562. ​
  563. if ($step === 5) {
  564. $projectName = (string) ($_SESSION['install']['project_name'] ?? 'default');
  565. $dbSite = $_SESSION['install']['db_site'];
  566. $dbUsers = $_SESSION['install']['db_users'];
  567. ​
  568. $envValues = [
  569. 'APP_NAME' => (string) ($_SESSION['install']['app_name'] ?? 'Publisium'),
  570. 'APP_URL' => (string) ($_SESSION['install']['app_url'] ?? ''),
  571. 'APP_BASE_PATH' => (string) ($_SESSION['install']['base_path'] ?? ''),
  572. 'APP_ENV' => 'production',
  573. 'APP_KEY' => bin2hex(random_bytes(32)),
  574. 'APP_LOCALE' => (string) ($_SESSION['install']['locale'] ?? 'en'),
  575. 'DB_SITE_HOST' => $dbSite['host'],
  576. 'DB_SITE_PORT' => $dbSite['port'],
  577. 'DB_SITE_NAME' => $dbSite['name'],
  578. 'DB_SITE_USER' => $dbSite['user'],
  579. 'DB_SITE_PASS' => $dbSite['pass'],
  580. 'DB_USERS_HOST' => $dbUsers['host'],
  581. 'DB_USERS_PORT' => $dbUsers['port'],
  582. 'DB_USERS_NAME' => $dbUsers['name'],
  583. 'DB_USERS_USER' => $dbUsers['user'],
  584. 'DB_USERS_PASS' => $dbUsers['pass'],
  585. 'SESSION_COOKIE_NAME' => 'publisium_session',
  586. 'SESSION_SECURE' => '1',
  587. 'INSTALL_LOCKED' => '1',
  588. ];
  589. ​
  590. $written = Config::write($projectName, $envValues);
  591. ​
  592. if ($written) {
  593. if (!is_dir($envRoot)) {
  594. mkdir($envRoot, 0750, true);
  595. }
  596. file_put_contents($lockFile, date('c'));
  597. chmod($lockFile, 0640);
  598. unset($_SESSION['install']);
  599. header('Location: install.php?step=6');
  600. exit;
  601. }
  602. ​
  603. $errors[] = 'Could not write environment file. Check folder permissions for ' . htmlspecialchars($envRoot);
  604. }
  605. }
  606. ​
  607. if (isset($_GET['step']) && (int) $_GET['step'] === 6) {
  608. $step = 6;
  609. }
  610. ​
  611. $selectedLocale = (string) ($_SESSION['install']['locale'] ?? 'en');
  612. $selectedLocaleLabel = 'English';
  613. foreach (installerLanguages() as [$code, $name, $native]) {
  614. if ($code === $selectedLocale) {
  615. $selectedLocaleLabel = $name . ' (' . $native . ')';
  616. break;
  617. }
  618. }
  619. ​
  620. ?>
  621. <!DOCTYPE html>
  622. <html lang="en">
  623. ​
  624. <head>
  625. <meta charset="utf-8">
  626. <meta name="viewport" content="width=device-width, initial-scale=1">
  627. <title>Publisium Installer</title>
  628. <?= Asset::favicon() ?>
  629. <style>
  630. :root {
  631. --bg: #0b0b0d;
  632. --panel: #141416;
  633. --border: #232327;
  634. --text: #f2f2f3;
  635. --muted: #9a9aa0;
  636. --accent: #6c5ce7;
  637. }
  638. ​
  639. * {
  640. box-sizing: border-box;
  641. }
  642. ​
  643. body {
  644. margin: 0;
  645. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  646. background: var(--bg);
  647. color: var(--text);
  648. display: flex;
  649. min-height: 100vh;
  650. align-items: center;
  651. justify-content: center;
  652. padding: 24px;
  653. }
  654. ​
  655. .card {
  656. width: 100%;
  657. max-width: 560px;
  658. background: var(--panel);
  659. border: 1px solid var(--border);
  660. border-radius: 14px;
  661. padding: 32px;
  662. }
  663. ​
  664. h1 {
  665. font-size: 20px;
  666. margin: 0 0 4px;
  667. }
  668. ​
  669. p.sub {
  670. color: var(--muted);
  671. margin: 0 0 24px;
  672. font-size: 14px;
  673. }
  674. ​
  675. .steps {
  676. display: flex;
  677. gap: 6px;
  678. margin-bottom: 24px;
  679. }
  680. ​
  681. .steps span {
  682. flex: 1;
  683. height: 4px;
  684. border-radius: 2px;
  685. background: var(--border);
  686. }
  687. ​
  688. .steps span.done {
  689. background: var(--accent);
  690. }
  691. ​
  692. label {
  693. display: block;
  694. font-size: 13px;
  695. color: var(--muted);
  696. margin: 14px 0 6px;
  697. }
  698. ​
  699. input,
  700. select {
  701. width: 100%;
  702. padding: 10px 12px;
  703. border-radius: 8px;
  704. border: 1px solid var(--border);
  705. background: #0f0f11;
  706. color: var(--text);
  707. font-size: 14px;
  708. }
  709. ​
  710. button {
  711. margin-top: 24px;
  712. width: 100%;
  713. padding: 12px;
  714. border-radius: 8px;
  715. border: none;
  716. background: var(--accent);
  717. color: #fff;
  718. font-size: 14px;
  719. font-weight: 600;
  720. cursor: pointer;
  721. }
  722. ​
  723. .error {
  724. background: #2a1215;
  725. border: 1px solid #5c1f26;
  726. color: #ff9aa2;
  727. padding: 10px 12px;
  728. border-radius: 8px;
  729. font-size: 13px;
  730. margin-bottom: 14px;
  731. }
  732. ​
  733. .row {
  734. display: flex;
  735. gap: 12px;
  736. }
  737. ​
  738. .row>div {
  739. flex: 1;
  740. }
  741. ​
  742. .success {
  743. text-align: center;
  744. }
  745. ​
  746. .success h1 {
  747. font-size: 22px;
  748. }
  749. ​
  750. .lang-picker {
  751. position: relative;
  752. }
  753. ​
  754. .lang-picker input.lang-search {
  755. cursor: text;
  756. }
  757. ​
  758. .lang-picker .lang-panel {
  759. display: none;
  760. position: absolute;
  761. z-index: 20;
  762. top: calc(100% + 6px);
  763. left: 0;
  764. right: 0;
  765. max-height: 260px;
  766. overflow-y: auto;
  767. background: #0f0f11;
  768. border: 1px solid var(--border);
  769. border-radius: 8px;
  770. box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
  771. }
  772. ​
  773. .lang-picker.open .lang-panel {
  774. display: block;
  775. }
  776. ​
  777. .lang-option {
  778. padding: 9px 12px;
  779. font-size: 14px;
  780. cursor: pointer;
  781. display: flex;
  782. justify-content: space-between;
  783. gap: 10px;
  784. }
  785. ​
  786. .lang-option:hover,
  787. .lang-option.active {
  788. background: #1c1c20;
  789. }
  790. ​
  791. .lang-option .code {
  792. color: var(--muted);
  793. font-size: 12px;
  794. text-transform: uppercase;
  795. align-self: center;
  796. }
  797. ​
  798. .lang-empty {
  799. padding: 10px 12px;
  800. font-size: 13px;
  801. color: var(--muted);
  802. }
  803. </style>
  804. </head>
  805. ​
  806. <body>
  807. <div class="card">
  808. <div class="steps">
  809. <span class="<?= $step >= 1 ? 'done' : '' ?>"></span>
  810. <span class="<?= $step >= 2 ? 'done' : '' ?>"></span>
  811. <span class="<?= $step >= 3 ? 'done' : '' ?>"></span>
  812. <span class="<?= $step >= 4 ? 'done' : '' ?>"></span>
  813. <span class="<?= $step >= 5 ? 'done' : '' ?>"></span>
  814. </div>
  815. ​
  816. <?php foreach ($errors as $error): ?>
  817. <div class="error"><?= htmlspecialchars($error) ?></div>
  818. <?php endforeach; ?>
  819. ​
  820. <?php if ($step === 1): ?>
  821. <h1>Project setup</h1>
  822. <p class="sub">Basic identity for this Publisium instance.</p>
  823. <form method="post" autocomplete="off">
  824. <input type="hidden" name="step" value="1">
  825. <label>Project name (used for env file naming)</label>
  826. <input type="text" name="project_name" placeholder="mysite" autocomplete="off" required>
  827. <label>Site name</label>
  828. <input type="text" name="app_name" placeholder="Publisium" autocomplete="off" required>
  829. <label>Site URL</label>
  830. <input type="text" name="app_url" placeholder="https://example.com" autocomplete="off" required>
  831. <label>Base path (leave empty if installed at domain root)</label>
  832. <input type="text" name="base_path" placeholder="/blog" autocomplete="off">
  833. <label>Default language</label>
  834. <div class="lang-picker" id="lang-picker">
  835. <input type="text" class="lang-search" id="lang-search" autocomplete="off"
  836. placeholder="Type a language, native name or code (e.g. PL, Polish, Polski)"
  837. value="<?= htmlspecialchars($selectedLocaleLabel) ?>">
  838. <input type="hidden" name="locale" id="lang-code" value="<?= htmlspecialchars($selectedLocale) ?>">
  839. <div class="lang-panel" id="lang-panel"></div>
  840. </div>
  841. <button type="submit">Continue</button>
  842. </form>
  843. <script>
  844. (function() {
  845. var LANGS = <?= installerLanguagesJson() ?>;
  846. ​
  847. var picker = document.getElementById('lang-picker');
  848. var search = document.getElementById('lang-search');
  849. var hidden = document.getElementById('lang-code');
  850. var panel = document.getElementById('lang-panel');
  851. var activeIndex = -1;
  852. var currentResults = [];
  853. ​
  854. function norm(s) {
  855. return (s || '')
  856. .toString()
  857. .toLowerCase()
  858. .normalize('NFD')
  859. .replace(/[\u0300-\u036f]/g, '');
  860. }
  861. ​
  862. LANGS.forEach(function(lang) {
  863. var parts = [lang.code, lang.name, lang.native].concat(lang.aliases || []);
  864. lang._haystack = parts.map(norm);
  865. });
  866. ​
  867. function search_matches(query) {
  868. var q = norm(query);
  869. if (!q) {
  870. return LANGS.slice(0, 20);
  871. }
  872. var starts = [];
  873. var contains = [];
  874. LANGS.forEach(function(lang) {
  875. var hit = false;
  876. var startHit = false;
  877. for (var i = 0; i < lang._haystack.length; i++) {
  878. var h = lang._haystack[i];
  879. if (h === q) {
  880. startHit = true;
  881. hit = true;
  882. break;
  883. }
  884. if (h.indexOf(q) === 0) {
  885. startHit = true;
  886. hit = true;
  887. }
  888. if (h.indexOf(q) !== -1) {
  889. hit = true;
  890. }
  891. }
  892. if (startHit) {
  893. starts.push(lang);
  894. } else if (hit) {
  895. contains.push(lang);
  896. }
  897. });
  898. return starts.concat(contains).slice(0, 30);
  899. }
  900. ​
  901. function render(results) {
  902. currentResults = results;
  903. activeIndex = -1;
  904. panel.innerHTML = '';
  905. if (!results.length) {
  906. var empty = document.createElement('div');
  907. empty.className = 'lang-empty';
  908. empty.textContent = 'No language matches that search.';
  909. panel.appendChild(empty);
  910. return;
  911. }
  912. results.forEach(function(lang, idx) {
  913. var opt = document.createElement('div');
  914. opt.className = 'lang-option';
  915. opt.dataset.index = idx;
  916. ​
  917. var label = document.createElement('span');
  918. label.textContent = lang.name + (lang.native && lang.native !== lang.name ? ' — ' + lang.native : '');
  919. ​
  920. var code = document.createElement('span');
  921. code.className = 'code';
  922. code.textContent = lang.code;
  923. ​
  924. opt.appendChild(label);
  925. opt.appendChild(code);
  926. ​
  927. opt.addEventListener('mousedown', function(e) {
  928. e.preventDefault();
  929. select(lang);
  930. });
  931. ​
  932. panel.appendChild(opt);
  933. });
  934. }
  935. ​
  936. function select(lang) {
  937. hidden.value = lang.code;
  938. search.value = lang.name + ' (' + lang.native + ')';
  939. close();
  940. }
  941. ​
  942. function open() {
  943. picker.classList.add('open');
  944. render(search_matches(search.value === (hidden.value ? search.value : '') ? '' : search.value));
  945. }
  946. ​
  947. function close() {
  948. picker.classList.remove('open');
  949. }
  950. ​
  951. search.addEventListener('focus', function() {
  952. search.select();
  953. picker.classList.add('open');
  954. render(search_matches(''));
  955. });
  956. ​
  957. search.addEventListener('input', function() {
  958. picker.classList.add('open');
  959. render(search_matches(search.value));
  960. });
  961. ​
  962. search.addEventListener('keydown', function(e) {
  963. if (!picker.classList.contains('open')) {
  964. return;
  965. }
  966. var options = panel.querySelectorAll('.lang-option');
  967. if (e.key === 'ArrowDown') {
  968. e.preventDefault();
  969. activeIndex = Math.min(activeIndex + 1, options.length - 1);
  970. updateActive(options);
  971. } else if (e.key === 'ArrowUp') {
  972. e.preventDefault();
  973. activeIndex = Math.max(activeIndex - 1, 0);
  974. updateActive(options);
  975. } else if (e.key === 'Enter') {
  976. e.preventDefault();
  977. if (activeIndex >= 0 && currentResults[activeIndex]) {
  978. select(currentResults[activeIndex]);
  979. } else if (currentResults.length === 1) {
  980. select(currentResults[0]);
  981. }
  982. } else if (e.key === 'Escape') {
  983. close();
  984. }
  985. });
  986. ​
  987. function updateActive(options) {
  988. options.forEach(function(opt, idx) {
  989. opt.classList.toggle('active', idx === activeIndex);
  990. });
  991. if (options[activeIndex]) {
  992. options[activeIndex].scrollIntoView({
  993. block: 'nearest'
  994. });
  995. }
  996. }
  997. ​
  998. document.addEventListener('click', function(e) {
  999. if (!picker.contains(e.target)) {
  1000. close();
  1001. }
  1002. });
  1003. })();
  1004. </script>
  1005. ​
  1006. <?php elseif ($step === 2): ?>
  1007. <h1>Database connections</h1>
  1008. <p class="sub">Two separate databases on the same MySQL server.</p>
  1009. <form method="post" autocomplete="off" id="db-form">
  1010. <input type="text" name="fake_username" autocomplete="username" style="position:absolute;left:-9999px;width:1px;height:1px;opacity:0;" tabindex="-1" aria-hidden="true">
  1011. <input type="password" name="fake_password" autocomplete="current-password" style="position:absolute;left:-9999px;width:1px;height:1px;opacity:0;" tabindex="-1" aria-hidden="true">
  1012. <input type="hidden" name="step" value="2">
  1013. <label>MySQL 1 — Site & Team database</label>
  1014. <div class="row">
  1015. <div><input type="text" name="db_site_host" placeholder="Host" value="127.0.0.1" autocomplete="off" required></div>
  1016. <div><input type="text" name="db_site_port" placeholder="Port" value="3306" autocomplete="off" required></div>
  1017. </div>
  1018. <input type="text" name="db_site_name" placeholder="Database name" autocomplete="off" required style="margin-top:10px;">
  1019. <input type="text" name="db_site_user" placeholder="Database user" autocomplete="off" data-lpignore="true" data-1p-ignore required style="margin-top:10px;">
  1020. <input type="password" name="db_site_pass" placeholder="Database password" autocomplete="new-password" data-lpignore="true" data-1p-ignore style="margin-top:10px;">
  1021. ​
  1022. <label>MySQL 2 — Users & Webhooks database</label>
  1023. <div class="row">
  1024. <div><input type="text" name="db_users_host" placeholder="Host" value="127.0.0.1" autocomplete="off" required></div>
  1025. <div><input type="text" name="db_users_port" placeholder="Port" value="3306" autocomplete="off" required></div>
  1026. </div>
  1027. <input type="text" name="db_users_name" placeholder="Database name" autocomplete="off" required style="margin-top:10px;">
  1028. <input type="text" name="db_users_user" placeholder="Database user" autocomplete="off" data-lpignore="true" data-1p-ignore required style="margin-top:10px;">
  1029. <input type="password" name="db_users_pass" placeholder="Database password" autocomplete="new-password" data-lpignore="true" data-1p-ignore style="margin-top:10px;">
  1030. <button type="submit">Test connection & create tables</button>
  1031. </form>
  1032. ​
  1033. <?php elseif ($step === 3): ?>
  1034. <h1>Super Admin account</h1>
  1035. <p class="sub">Exactly one Super Admin account is allowed.</p>
  1036. <form method="post" autocomplete="off">
  1037. <input type="hidden" name="step" value="3">
  1038. <label>Username</label>
  1039. <input type="text" name="admin_username" autocomplete="off" data-lpignore="true" data-1p-ignore required>
  1040. <label>Email</label>
  1041. <input type="text" name="admin_email" autocomplete="off" data-lpignore="true" data-1p-ignore required>
  1042. <label>Display name</label>
  1043. <input type="text" name="admin_display_name" autocomplete="off">
  1044. <label>Password</label>
  1045. <input type="password" name="admin_password" minlength="10" autocomplete="new-password" data-lpignore="true" data-1p-ignore required>
  1046. <label>Confirm password</label>
  1047. <input type="password" name="admin_password_confirm" minlength="10" autocomplete="new-password" data-lpignore="true" data-1p-ignore required>
  1048. <button type="submit">Create account</button>
  1049. </form>
  1050. ​
  1051. <?php elseif ($step === 4): ?>
  1052. <h1>Site details</h1>
  1053. <p class="sub">Can be changed later in Settings.</p>
  1054. <form method="post" autocomplete="off">
  1055. <input type="hidden" name="step" value="4">
  1056. <label>Site name</label>
  1057. <input type="text" name="site_name" value="<?= htmlspecialchars((string) ($_SESSION['install']['app_name'] ?? '')) ?>" autocomplete="off" required>
  1058. <label>Site description</label>
  1059. <input type="text" name="site_description" placeholder="A short tagline for your site" autocomplete="off">
  1060. <button type="submit">Continue</button>
  1061. </form>
  1062. ​
  1063. <?php elseif ($step === 5): ?>
  1064. <h1>Finish installation</h1>
  1065. <p class="sub">This writes the environment file and locks the installer.</p>
  1066. <form method="post">
  1067. <input type="hidden" name="step" value="5">
  1068. <button type="submit">Install Publisium</button>
  1069. </form>
  1070. ​
  1071. <?php elseif ($step === 6): ?>
  1072. <div class="success">
  1073. <h1>Installation complete</h1>
  1074. <p class="sub">Publisium is ready. Delete install.php or rely on the automatic lock file, then log in via team-login.php.</p>
  1075. <a href="team-login.php" style="color:#fff;">Go to team login</a>
  1076. </div>
  1077. <?php endif; ?>
  1078. ​
  1079. </div>
  1080. </body>
  1081. ​
  1082. </html>