1<?php
2
3global $config;
4
5$acp_voc_options = array();
6foreach (($config['vocations'] ?? array()) as $vid => $voc) {
7 if ((int)$vid <= 0) continue;
8 $acp_voc_options[(string)$vid] = ($voc['name'] ?? ('Vocation ' . $vid)) . ' (#' . $vid . ')';
9}
10
11$acp_town_options = array();
12foreach (($config['towns'] ?? array()) as $tid => $tname) {
13 $acp_town_options[(string)$tid] = $tname . ' (#' . $tid . ')';
14}
15
16return array(
17
18 t_default('acp.sec.Site', 'Site') => array(
19 'site_title' => array(
20 'label' => t_default('acp.set.site_title.label', 'Site title'),
21 'type' => 'text',
22 'help' => t_default('acp.set.site_title.help', 'Shown in the browser tab, the footer and the social banners.'),
23 ),
24 'site_title_context' => array(
25 'label' => t_default('acp.set.site_title_context.label', 'Tagline'),
26 'type' => 'text',
27 ),
28 'site_url' => array(
29 'label' => t_default('acp.set.site_url.label', 'Site URL'),
30 'type' => 'text',
31 'help' => t_default('acp.set.site_url.help', 'Used in e-mails and absolute links. No trailing slash.'),
32 ),
33 ),
34
35 t_default('acp.sec.Maintenance', 'Maintenance') => array(
36 'maintenance' => array(
37 'label' => t_default('acp.set.maintenance.label', 'Maintenance mode'),
38 'type' => 'bool',
39 'help' => t_default('acp.set.maintenance.help', 'Visitors see the message below. Admins keep full access.'),
40 ),
41 'maintenance_message' => array(
42 'label' => t_default('acp.set.maintenance_message.label', 'Maintenance message'),
43 'type' => 'textarea',
44 ),
45 ),
46
47 t_default('acp.sec.Language', 'Language') => array(
48 'language' => array(
49 'label' => t_default('acp.set.language.label', 'Default language'),
50 'type' => 'select',
51 'options' => array(
52 'en' => 'English',
53 'pt_br' => 'Português (Brasil)',
54 'es' => 'Español',
55 'pl' => 'Polski',
56 'de' => 'Deutsch',
57 ),
58 'help' => t_default('acp.set.language.help', 'Used when the visitor has no saved choice and their browser asks for nothing you offer.'),
59 ),
60 'languages_enabled' => array(
61 'label' => t_default('acp.set.languages_enabled.label', 'Offered languages'),
62 'type' => 'checklist',
63 'options' => array(
64 'en' => 'English',
65 'pt_br' => 'Português (Brasil)',
66 'es' => 'Español',
67 'pl' => 'Polski',
68 'de' => 'Deutsch',
69 ),
70 'help' => t_default('acp.set.languages_enabled.help', 'Which flags the switcher shows. The default language is always kept.'),
71 ),
72 'language_selector' => array(
73 'label' => t_default('acp.set.language_selector.label', 'Show the language switcher'),
74 'type' => 'bool',
75 'help' => t_default('acp.set.language_selector.help', 'Adds it to every theme, top right. Turn off if your theme places it itself.'),
76 ),
77 ),
78
79 t_default('acp.sec.Game server', 'Game server') => array(
80 'server_path' => array(
81 'label' => t_default('acp.set.server_path.label', 'Server data folder path'),
82 'type' => 'text',
83 'help' => t_default('acp.set.server_path.help', 'Absolute path to the server folder, no trailing slash (e.g. /home/ots or C:/tfs). Used by Server-Info and plugins that read data/monster, items.xml, etc.'),
84 ),
85 'client' => array(
86 'label' => t_default('acp.set.client.label', 'Client version'),
87 'type' => 'int',
88 'help' => t_default('acp.set.client.help', 'For example 1098 for client 10.98.'),
89 ),
90 'port' => array(
91 'label' => t_default('acp.set.port.label', 'Game server port'),
92 'type' => 'int',
93 ),
94 'freePremium' => array(
95 'label' => t_default('acp.set.freePremium.label', 'Free premium'),
96 'type' => 'bool',
97 ),
98 'account_create_premdays' => array(
99 'label' => t_default('acp.set.account_create_premdays.label', 'Premium days on account creation'),
100 'type' => 'int',
101 'min' => 0,
102 'max' => 999,
103 'help' => t_default('acp.set.account_create_premdays.help', '0 disables it. New accounts only; existing accounts are not changed.'),
104 ),
105 'website_char' => array(
106 'label' => t_default('acp.set.website_char.label', 'Website ban character'),
107 'type' => 'text',
108 'help' => t_default('acp.set.website_char.help', 'An existing character name that represents website bans. Bans from the panel need this.'),
109 ),
110 'htwrite' => array(
111 'label' => t_default('acp.set.htwrite.label', 'Friendly URLs'),
112 'type' => 'bool',
113 'help' => t_default('acp.set.htwrite.help', 'Needs mod_rewrite and the shipped .htaccess.'),
114 ),
115 'ping' => array(
116 'label' => t_default('acp.set.ping.label', 'In-game ping measurement'),
117 'type' => 'bool',
118 ),
119 ),
120
121 t_default('acp.sec.Characters', 'Characters') => array(
122 'max_characters' => array(
123 'label' => t_default('acp.set.max_characters.label', 'Characters per account'),
124 'type' => 'int',
125 ),
126 'minL' => array(
127 'label' => t_default('acp.set.minL.label', 'Minimum name length'),
128 'type' => 'int',
129 ),
130 'maxL' => array(
131 'label' => t_default('acp.set.maxL.label', 'Maximum name length'),
132 'type' => 'int',
133 ),
134 'maxW' => array(
135 'label' => t_default('acp.set.maxW.label', 'Maximum words in a name'),
136 'type' => 'int',
137 ),
138 'create_guild_level' => array(
139 'label' => t_default('acp.set.create_guild_level.label', 'Level required to create a guild'),
140 'type' => 'int',
141 ),
142 'free_sex_change' => array(
143 'label' => t_default('acp.set.free_sex_change.label', 'Free sex change'),
144 'type' => 'bool',
145 'help' => t_default('acp.set.free_sex_change.help', 'Let players switch their character sex for free from My Account.'),
146 ),
147 'delete_character_interval' => array(
148 'label' => t_default('acp.set.delete_character_interval.label', 'Character deletion delay'),
149 'type' => 'text',
150 'help' => t_default('acp.set.delete_character_interval.help', 'MySQL interval before a deletion request runs, e.g. "3 DAY", "1 HOUR", "2 MONTH".'),
151 ),
152 ),
153
154 t_default('acp.sec.Character creation', 'Character creation') => array(
155 'available_vocations' => array(
156 'label' => t_default('acp.set.available_vocations.label', 'Vocations players can pick'),
157 'type' => 'checklist',
158 'options' => $acp_voc_options,
159 'int_values' => true,
160 'help' => t_default('acp.set.available_vocations.help', 'Shown on the create-character page. Options come from the vocations list below.'),
161 ),
162 'available_towns' => array(
163 'label' => t_default('acp.set.available_towns.label', 'Starting towns players can pick'),
164 'type' => 'checklist',
165 'options' => $acp_town_options,
166 'int_values' => true,
167 'help' => t_default('acp.set.available_towns.help', 'Options come from the towns list below.'),
168 ),
169 'towns' => array(
170 'label' => t_default('acp.set.towns.label', 'Towns'),
171 'type' => 'table',
172 'row_key' => 'id',
173 'value_column' => 'name',
174 'columns' => array(
175 'id' => array('label' => t_default('acp.set.towns.col_id', 'ID'), 'type' => 'int'),
176 'name' => array('label' => t_default('acp.set.towns.col_name', 'Name'), 'type' => 'text'),
177 ),
178 'help' => t_default('acp.set.towns.help', 'One row per town. In RME press CTRL+T to see ids.'),
179 ),
180 'vocations' => array(
181 'label' => t_default('acp.set.vocations.label', 'Vocations'),
182 'type' => 'table',
183 'row_key' => 'id',
184 'columns' => array(
185 'id' => array('label' => t_default('acp.set.vocations.col_id', 'ID'), 'type' => 'int'),
186 'name' => array('label' => t_default('acp.set.vocations.col_name', 'Name'), 'type' => 'text'),
187 'fromVoc' => array('label' => t_default('acp.set.vocations.col_fromvoc', 'Base vocation ID (blank = base)'), 'type' => 'nullable_int'),
188 ),
189 'help' => t_default('acp.set.vocations.help', 'One row per vocation. fromVoc is the base vocation id this one promotes from, leave blank for a base vocation.'),
190 ),
191 'vocations_gain' => array(
192 'label' => t_default('acp.set.vocations_gain.label', 'Per-level stat gains'),
193 'type' => 'table',
194 'row_key' => 'id',
195 'columns' => array(
196 'id' => array('label' => t_default('acp.set.vocations_gain.col_id', 'Vocation ID'), 'type' => 'int'),
197 'hp' => array('label' => t_default('acp.set.vocations_gain.col_hp', 'HP per level'), 'type' => 'int'),
198 'mp' => array('label' => t_default('acp.set.vocations_gain.col_mp', 'MP per level'), 'type' => 'int'),
199 'cap' => array('label' => t_default('acp.set.vocations_gain.col_cap', 'Capacity per level'), 'type' => 'int'),
200 ),
201 'help' => t_default('acp.set.vocations_gain.help', 'One row per vocation id. Used to compute starting health/mana/cap.'),
202 ),
203 'player.base.level' => array(
204 'label' => t_default('acp.set.player.base.level.label', 'Base level'),
205 'type' => 'int',
206 'help' => t_default('acp.set.player.base.level.help', 'Used with vocation stat gains to calculate health/mana/cap at any level.'),
207 ),
208 'player.base.health' => array(
209 'label' => t_default('acp.set.player.base.health.label', 'Base health'),
210 'type' => 'int',
211 ),
212 'player.base.mana' => array(
213 'label' => t_default('acp.set.player.base.mana.label', 'Base mana'),
214 'type' => 'int',
215 ),
216 'player.base.cap' => array(
217 'label' => t_default('acp.set.player.base.cap.label', 'Base capacity'),
218 'type' => 'int',
219 ),
220 'player.base.soul' => array(
221 'label' => t_default('acp.set.player.base.soul.label', 'Base soul'),
222 'type' => 'int',
223 ),
224 'player.create.level' => array(
225 'label' => t_default('acp.set.player.create.level.label', 'Starting level (new character)'),
226 'type' => 'int',
227 ),
228 'player.create.novocation.level' => array(
229 'label' => t_default('acp.set.player.create.novocation.level.label', 'No-vocation starting level'),
230 'type' => 'int',
231 'help' => t_default('acp.set.player.create.novocation.help', 'Special-case settings for vocation id 0 (No vocation).'),
232 ),
233 'player.create.novocation.forceTown' => array(
234 'label' => t_default('acp.set.player.create.novocation.forcetown.label', 'Force no-vocation town'),
235 'type' => 'bool',
236 ),
237 'player.create.novocation.townId' => array(
238 'label' => t_default('acp.set.player.create.novocation.townid.label', 'No-vocation town ID'),
239 'type' => 'int',
240 ),
241 'player.create.skills' => array(
242 'label' => t_default('acp.set.player.create.skills.label', 'Starting skills per vocation'),
243 'type' => 'table',
244 'row_key' => 'id',
245 'columns' => array(
246 'id' => array('label' => t_default('acp.set.player.create.skills.col_id', 'Vocation ID'), 'type' => 'int'),
247 'magic' => array('label' => t_default('acp.set.player.create.skills.col_magic', 'Magic'), 'type' => 'int'),
248 'fist' => array('label' => t_default('acp.set.player.create.skills.col_fist', 'Fist'), 'type' => 'int'),
249 'club' => array('label' => t_default('acp.set.player.create.skills.col_club', 'Club'), 'type' => 'int'),
250 'sword' => array('label' => t_default('acp.set.player.create.skills.col_sword', 'Sword'), 'type' => 'int'),
251 'axe' => array('label' => t_default('acp.set.player.create.skills.col_axe', 'Axe'), 'type' => 'int'),
252 'dist' => array('label' => t_default('acp.set.player.create.skills.col_dist', 'Distance'), 'type' => 'int'),
253 'shield' => array('label' => t_default('acp.set.player.create.skills.col_shield', 'Shielding'), 'type' => 'int'),
254 'fishing' => array('label' => t_default('acp.set.player.create.skills.col_fishing', 'Fishing'), 'type' => 'int'),
255 ),
256 'help' => t_default('acp.set.player.create.skills.help', 'One row per vocation id: 0 = No vocation, 1 = Sorcerer, 2 = Druid, 3 = Paladin, 4 = Knight. Extra ids such as 9 are custom/other vocations from the Vocations table above.'),
257 ),
258 'player.create.male_outfit.id' => array(
259 'label' => t_default('acp.set.player.create.male_outfit.id.label', 'Male outfit - looktype ID'),
260 'type' => 'int',
261 ),
262 'player.create.male_outfit.head' => array(
263 'label' => t_default('acp.set.player.create.male_outfit.head.label', 'Male outfit - head color'),
264 'type' => 'int',
265 ),
266 'player.create.male_outfit.body' => array(
267 'label' => t_default('acp.set.player.create.male_outfit.body.label', 'Male outfit - body color'),
268 'type' => 'int',
269 ),
270 'player.create.male_outfit.legs' => array(
271 'label' => t_default('acp.set.player.create.male_outfit.legs.label', 'Male outfit - legs color'),
272 'type' => 'int',
273 ),
274 'player.create.male_outfit.feet' => array(
275 'label' => t_default('acp.set.player.create.male_outfit.feet.label', 'Male outfit - feet color'),
276 'type' => 'int',
277 ),
278 'player.create.female_outfit.id' => array(
279 'label' => t_default('acp.set.player.create.female_outfit.id.label', 'Female outfit - looktype ID'),
280 'type' => 'int',
281 ),
282 'player.create.female_outfit.head' => array(
283 'label' => t_default('acp.set.player.create.female_outfit.head.label', 'Female outfit - head color'),
284 'type' => 'int',
285 ),
286 'player.create.female_outfit.body' => array(
287 'label' => t_default('acp.set.player.create.female_outfit.body.label', 'Female outfit - body color'),
288 'type' => 'int',
289 ),
290 'player.create.female_outfit.legs' => array(
291 'label' => t_default('acp.set.player.create.female_outfit.legs.label', 'Female outfit - legs color'),
292 'type' => 'int',
293 ),
294 'player.create.female_outfit.feet' => array(
295 'label' => t_default('acp.set.player.create.female_outfit.feet.label', 'Female outfit - feet color'),
296 'type' => 'int',
297 ),
298 ),
299
300 t_default('acp.sec.Content', 'Content') => array(
301 'news_per_page' => array(
302 'label' => t_default('acp.set.news_per_page.label', 'News articles per page'),
303 'type' => 'int',
304 ),
305 'UseChangelogTicker' => array(
306 'label' => t_default('acp.set.UseChangelogTicker.label', 'Changelog ticker on the front page'),
307 'type' => 'bool',
308 ),
309 'credits_enabled' => array(
310 'label' => t_default('acp.set.credits_enabled.label', 'Credits page'),
311 'type' => 'bool',
312 'help' => t_default('acp.set.credits_enabled.help', 'Off hides credits.php and its menu link.'),
313 ),
314 'Ach' => array(
315 'label' => t_default('acp.set.Ach.label', 'Achievements page'),
316 'type' => 'bool',
317 'help' => t_default('acp.set.Ach.help', 'Shows achievements.php, its menu link and the achievement block on character profiles. Needs the achievements configured in config.php.'),
318 ),
319 'items' => array(
320 'label' => t_default('acp.set.items.label', 'Items page'),
321 'type' => 'bool',
322 'help' => t_default('acp.set.items.help', 'Shows items.php and its menu link.'),
323 ),
324 'queststatus_enabled' => array(
325 'label' => t_default('acp.set.queststatus_enabled.label', 'Quest Status page'),
326 'type' => 'bool',
327 'help' => t_default('acp.set.queststatus_enabled.help', 'Shows queststatus.php and its menu link. Off by default; edit the quest list in queststatus.php.'),
328 ),
329 'EnableQuests' => array(
330 'label' => t_default('acp.set.EnableQuests.label', 'Quest points block on profiles'),
331 'type' => 'bool',
332 'help' => t_default('acp.set.EnableQuests.help', 'Shows the quest progress block on character profiles. Needs the quest storages configured in config.php.'),
333 ),
334 'allowSubPages' => array(
335 'label' => t_default('acp.set.allowSubPages.label', 'Allow theme sub pages'),
336 'type' => 'bool',
337 ),
338 'contact_info' => array(
339 'label' => t_default('acp.set.contact_info.label', 'Contact page text'),
340 'type' => 'textarea',
341 'help' => t_default('acp.set.contact_info.help', 'Shown on contact.php. Plain text, line breaks are kept. Leave empty to use the translation default.'),
342 ),
343 'faq.entries' => array(
344 'label' => t_default('acp.set.faq.entries.label', 'FAQ questions'),
345 'type' => 'table',
346 'json_shape' => 'list',
347 'columns' => array(
348 'question' => array('label' => t_default('acp.set.faq.entries.col_question', 'Question'), 'type' => 'text'),
349 'answer' => array('label' => t_default('acp.set.faq.entries.col_answer', 'Answer'), 'type' => 'textarea'),
350 ),
351 'help' => t_default('acp.set.faq.entries.help', 'Shown on faq.php as an accordion, in this order. Add, remove or reorder rows freely. Add faq.php as a menu link (Admin Panel > Menus) to point visitors at it, e.g. from Support.'),
352 ),
353 ),
354
355 t_default('acp.sec.Privacy', 'Privacy') => array(
356 'log_ip' => array(
357 'label' => t_default('acp.set.log_ip.label', 'Log visitor IPs'),
358 'type' => 'bool',
359 'help' => t_default('acp.set.log_ip.help', 'Feeds the Visitors page. Turning it off stops the collection entirely.'),
360 ),
361 'flush_ip_logs' => array(
362 'label' => t_default('acp.set.flush_ip_logs.label', 'IP log retention (rows)'),
363 'type' => 'int',
364 'help' => t_default('acp.set.flush_ip_logs.help', 'Old visitor-IP rows past this count are trimmed.'),
365 ),
366 ),
367
368 t_default('acp.sec.Shop', 'Shop') => array(
369 'shop.enabled' => array(
370 'label' => t_default('acp.set.shop.enabled.label', 'Shop enabled'),
371 'type' => 'bool',
372 'help' => t_default('acp.set.shop.enabled.help', 'Turns off the shop page and its menu entry.'),
373 ),
374 'shop.loginToView' => array(
375 'label' => t_default('acp.set.shop.loginToView.label', 'Require login to view the shop'),
376 'type' => 'bool',
377 ),
378 'shop.enableShopConfirmation' => array(
379 'label' => t_default('acp.set.shop.enableShopConfirmation.label', 'Ask for confirmation before buying'),
380 'type' => 'bool',
381 ),
382 'shop.showImage' => array(
383 'label' => t_default('acp.set.shop.showImage.label', 'Show item images'),
384 'type' => 'bool',
385 ),
386 'shop.imageServer' => array(
387 'label' => t_default('acp.set.shop.imageServer.label', 'Item image server'),
388 'type' => 'text',
389 'help' => t_default('acp.set.shop.imageServer.help', 'An http(s):// URL, "/items" for a folder in this website, or a disk folder path (e.g. F:/server/items) served straight from PHP. Used by the shop, market, character equipment and plugins.'),
390 ),
391 'shop.imageType' => array(
392 'label' => t_default('acp.set.shop.imageType.label', 'Item image extension'),
393 'type' => 'text',
394 'help' => t_default('acp.set.shop.imageType.help', 'gif or png, depending on the image server.'),
395 ),
396 'buypoints_enabled' => array(
397 'label' => t_default('acp.set.buypoints_enabled.label', 'Buy points page'),
398 'type' => 'bool',
399 'help' => t_default('acp.set.buypoints_enabled.help', 'Off hides buypoints.php whichever gateways are configured. Gateways themselves are on the Payments page.'),
400 ),
401 ),
402
403 t_default('acp.sec.Outfit images', 'Outfit images') => array(
404 'show_outfits.imageServer' => array(
405 'label' => t_default('acp.set.show_outfits.imageServer.label', 'Outfit image server'),
406 'type' => 'text',
407 'help' => t_default('acp.set.show_outfits.imageServer.help', 'The script that renders outfits, e.g. https://outfit-images.ots.me/1285/animoutfit.php'),
408 ),
409 'show_outfits.shop' => array(
410 'label' => t_default('acp.set.show_outfits.shop.label', 'Outfits in the shop'),
411 'type' => 'bool',
412 ),
413 'show_outfits.highscores' => array(
414 'label' => t_default('acp.set.show_outfits.highscores.label', 'Outfits in the highscores'),
415 'type' => 'bool',
416 ),
417 'show_outfits.characterprofile' => array(
418 'label' => t_default('acp.set.show_outfits.characterprofile.label', 'Outfits on character profiles'),
419 'type' => 'bool',
420 ),
421 'show_outfits.onlinelist' => array(
422 'label' => t_default('acp.set.show_outfits.onlinelist.label', 'Outfits in the online list'),
423 'type' => 'bool',
424 ),
425 ),
426
427 t_default('acp.sec.Character auction', 'Character auction') => array(
428 'shop_auction.characterAuction' => array(
429 'label' => t_default('acp.set.shop_auction.characterAuction.label', 'Character auction enabled'),
430 'type' => 'bool',
431 'help' => t_default('acp.set.shop_auction.characterAuction.help', 'Lets players sell and bid on characters for shop points.'),
432 ),
433 'shop_auction.storage_account_id' => array(
434 'label' => t_default('acp.set.shop_auction.storage_account_id.label', 'Storage account ID'),
435 'type' => 'int',
436 'help' => t_default('acp.set.shop_auction.storage_account_id.help', 'Holds characters while they are listed. Use a separate, secure account - not your god account.'),
437 ),
438 'shop_auction.lowestLevel' => array(
439 'label' => t_default('acp.set.shop_auction.lowestLevel.label', 'Minimum level to sell'),
440 'type' => 'int',
441 ),
442 'shop_auction.lowestPrice' => array(
443 'label' => t_default('acp.set.shop_auction.lowestPrice.label', 'Minimum price in points'),
444 'type' => 'int',
445 ),
446 'shop_auction.step' => array(
447 'label' => t_default('acp.set.shop_auction.step.label', 'Minimum bid increase'),
448 'type' => 'int',
449 ),
450 'shop_auction.deposit' => array(
451 'label' => t_default('acp.set.shop_auction.deposit.label', 'Seller deposit (%)'),
452 'type' => 'int',
453 ),
454 ),
455
456 t_default('acp.sec.Forum', 'Forum') => array(
457 'forum.enabled' => array(
458 'label' => t_default('acp.set.forum.enabled.label', 'Forum enabled'),
459 'type' => 'bool',
460 ),
461 'forum.level' => array(
462 'label' => t_default('acp.set.forum.level.label', 'Level required to post'),
463 'type' => 'int',
464 ),
465 'forum.maxImagesPerPost' => array(
466 'label' => t_default('acp.set.forum.maxImagesPerPost.label', 'Images allowed per post'),
467 'type' => 'int',
468 'help' => t_default('acp.set.forum.maxImagesPerPost.help', '0 blocks images entirely.'),
469 ),
470 'forum.guildboard' => array(
471 'label' => t_default('acp.set.forum.guildboard.label', 'Guild boards'),
472 'type' => 'bool',
473 ),
474 'forum.outfit_avatars' => array(
475 'label' => t_default('acp.set.forum.outfit_avatars.label', 'Show outfits as avatars'),
476 'type' => 'bool',
477 ),
478 'forum.newPostsBumpThreads' => array(
479 'label' => t_default('acp.set.forum.newPostsBumpThreads.label', 'New posts bump threads'),
480 'type' => 'bool',
481 ),
482 ),
483
484 t_default('acp.sec.Guilds', 'Guilds') => array(
485 'use_guild_logos' => array(
486 'label' => t_default('acp.set.use_guild_logos.label', 'Guild logo uploads'),
487 'type' => 'bool',
488 ),
489 'guild_require_premium' => array(
490 'label' => t_default('acp.set.guild_require_premium.label', 'Premium required to create a guild'),
491 'type' => 'bool',
492 ),
493 'guild_allow_nicknames' => array(
494 'label' => t_default('acp.set.guild_allow_nicknames.label', 'Allow guild nicknames'),
495 'type' => 'bool',
496 ),
497 'guildwar_enabled' => array(
498 'label' => t_default('acp.set.guildwar_enabled.label', 'Guild wars'),
499 'type' => 'bool',
500 ),
501 'require_login.guilds' => array(
502 'label' => t_default('acp.set.require_login.guilds.label', 'Require login to view guilds'),
503 'type' => 'bool',
504 'help' => t_default('acp.set.require_login.guilds.help', 'Eases database load on large servers.'),
505 ),
506 'require_login.guildwars' => array(
507 'label' => t_default('acp.set.require_login.guildwars.label', 'Require login to view guild wars'),
508 'type' => 'bool',
509 ),
510 ),
511
512 t_default('acp.sec.Highscores & lists', 'Highscores & lists') => array(
513 'highscore.rows' => array(
514 'label' => t_default('acp.set.highscore.rows.label', 'Highscore entries'),
515 'type' => 'int',
516 ),
517 'highscore.rowsPerPage' => array(
518 'label' => t_default('acp.set.highscore.rowsPerPage.label', 'Highscore rows per page'),
519 'type' => 'int',
520 ),
521 'highscore.ignoreGroupId' => array(
522 'label' => t_default('acp.set.highscore.ignoreGroupId.label', 'Hide group ID and above'),
523 'type' => 'int',
524 'help' => t_default('acp.set.highscore.ignoreGroupId.help', 'Keeps staff out of the highscores. 2 hides gamemasters upward.'),
525 ),
526 'powergamers.enabled' => array(
527 'label' => t_default('acp.set.powergamers.enabled.label', 'Powergamers page'),
528 'type' => 'bool',
529 'help' => t_default('acp.set.powergamers.enabled.help', 'Needs the Lua script and the extra SQL columns.'),
530 ),
531 'powergamers.limit' => array(
532 'label' => t_default('acp.set.powergamers.limit.label', 'Powergamers shown'),
533 'type' => 'int',
534 ),
535 'toponline.enabled' => array(
536 'label' => t_default('acp.set.toponline.enabled.label', 'Top online page'),
537 'type' => 'bool',
538 ),
539 'toponline.limit' => array(
540 'label' => t_default('acp.set.toponline.limit.label', 'Top online shown'),
541 'type' => 'int',
542 ),
543 ),
544
545 t_default('acp.sec.Houses', 'Houses') => array(
546 'houseConfig.HouseListDefaultTown' => array(
547 'label' => t_default('acp.set.houseConfig.HouseListDefaultTown.label', 'Default town on the house list'),
548 'type' => 'int',
549 ),
550 'houseConfig.minimumBidSQM' => array(
551 'label' => t_default('acp.set.houseConfig.minimumBidSQM.label', 'Minimum bid per SQM'),
552 'type' => 'int',
553 ),
554 'houseConfig.housesPerPlayer' => array(
555 'label' => t_default('acp.set.houseConfig.housesPerPlayer.label', 'Houses per player'),
556 'type' => 'int',
557 ),
558 'houseConfig.levelToBuyHouse' => array(
559 'label' => t_default('acp.set.houseConfig.levelToBuyHouse.label', 'Level required to buy a house'),
560 'type' => 'int',
561 ),
562 'houseConfig.requirePremium' => array(
563 'label' => t_default('acp.set.houseConfig.requirePremium.label', 'Premium required'),
564 'type' => 'bool',
565 ),
566 'houseConfig.shopPoints.enabled' => array(
567 'label' => t_default('acp.set.houseConfig.shopPoints.enabled.label', 'Instant buy with shop points'),
568 'type' => 'bool',
569 ),
570 ),
571
572 t_default('acp.sec.Server status', 'Server status') => array(
573 'status.status_check' => array(
574 'label' => t_default('acp.set.status.status_check.label', 'Query the game server for status'),
575 'type' => 'bool',
576 ),
577 'status.status_ip' => array(
578 'label' => t_default('acp.set.status.status_ip.label', 'Status IP'),
579 'type' => 'text',
580 ),
581 'status.status_port' => array(
582 'label' => t_default('acp.set.status.status_port.label', 'Status port'),
583 'type' => 'text',
584 ),
585 'login_web_service' => array(
586 'label' => t_default('acp.set.login_web_service.label', 'Client login web service'),
587 'type' => 'bool',
588 'help' => t_default('acp.set.login_web_service.help', 'Required by client 11 and newer.'),
589 ),
590 'login_protocol' => array(
591 'label' => t_default('acp.set.login_protocol.label', 'Login protocol'),
592 'type' => 'select',
593 'options' => array(
594 'auto' => 'Auto (follow client version)',
595 '11' => 'Client 11',
596 '12' => 'Client 12 / Canary 12.x',
597 '13' => 'Client 13 / Canary 13.x',
598 '15' => 'Client 15 / Canary 15.x',
599 ),
600 'help' => t_default('acp.set.login_protocol.help', 'Shape of the character list the client is sent. Auto follows the client version above.'),
601 ),
602 'login_auth_type' => array(
603 'label' => t_default('acp.set.login_auth_type.label', 'Canary auth type'),
604 'type' => 'select',
605 'options' => array(
606 'password' => 'Password (Canary default)',
607 'session' => 'Session (writes account_sessions)',
608 ),
609 'help' => t_default('acp.set.login_auth_type.help', 'Must match authType in your Canary config.lua, or nobody can enter the world.'),
610 ),
611 'login_session_ttl' => array(
612 'label' => t_default('acp.set.login_session_ttl.label', 'Session lifetime'),
613 'type' => 'int',
614 'help' => t_default('acp.set.login_session_ttl.help', 'Seconds a session auth row stays valid. Ignored on password auth.'),
615 ),
616 'gameserver.ip' => array(
617 'label' => t_default('acp.set.gameserver.ip.label', 'Game server IP'),
618 'type' => 'text',
619 ),
620 'gameserver.port' => array(
621 'label' => t_default('acp.set.gameserver.port.label', 'Login web service port'),
622 'type' => 'int',
623 ),
624 'gameserver.name' => array(
625 'label' => t_default('acp.set.gameserver.name.label', 'World name'),
626 'type' => 'text',
627 ),
628 ),
629
630 t_default('acp.sec.Downloads', 'Downloads') => array(
631 'client_download' => array(
632 'label' => t_default('acp.set.client_download.label', 'Windows client URL'),
633 'type' => 'text',
634 ),
635 'client_download_linux' => array(
636 'label' => t_default('acp.set.client_download_linux.label', 'Linux client URL'),
637 'type' => 'text',
638 ),
639 'downloads.entries' => array(
640 'label' => t_default('acp.set.downloads.entries.label', 'Download entries'),
641 'type' => 'table',
642 'json_shape' => 'list',
643 'columns' => array(
644 'key' => array('label' => t_default('acp.set.downloads.entries.col_key', 'Key'), 'type' => 'text'),
645 'label' => array('label' => t_default('acp.set.downloads.entries.col_label', 'Label'), 'type' => 'text'),
646 'section' => array('label' => t_default('acp.set.downloads.entries.col_section', 'Section'), 'type' => 'select', 'options' => array(
647 'official' => t_default('acp.set.downloads.entries.sec_official', 'Official'),
648 'unsupported' => t_default('acp.set.downloads.entries.sec_unsupported', 'Unsupported'),
649 'tools' => t_default('acp.set.downloads.entries.sec_tools', 'Tools'),
650 'custom' => t_default('acp.set.downloads.entries.sec_custom', 'Custom'),
651 )),
652 'enabled' => array('label' => t_default('acp.set.downloads.entries.col_enabled', 'Enabled'), 'type' => 'bool'),
653 'url' => array('label' => t_default('acp.set.downloads.entries.col_url', 'URL'), 'type' => 'text'),
654 'image' => array('label' => t_default('acp.set.downloads.entries.col_image', 'Image'), 'type' => 'text'),
655 'description' => array('label' => t_default('acp.set.downloads.entries.col_description', 'Description'), 'type' => 'text'),
656 ),
657 'help' => t_default('acp.set.downloads.entries.help', 'Enable, disable, reorder or add download rows. Windows and Linux keep using the URL fields above.'),
658 ),
659 ),
660
661 t_default('acp.sec.Permissions', 'Permissions') => array(
662 'admin_access' => array(
663 'label' => t_default('acp.set.admin_access.label', 'Admin panel access'),
664 'type' => 'permissions',
665 'help' => t_default('acp.set.admin_access.help', 'Owners see and change everything. A scoped account only sees the categories its roles allow - everything else is hidden from the sidebar and refused if opened directly.'),
666 ),
667 ),
668
669 t_default('acp.sec.Login Protection', 'Login Protection') => array(
670 'login_guard.enabled' => array(
671 'label' => t_default('acp.set.login_guard.enabled.label', 'Lock out an IP after too many failed logins'),
672 'type' => 'bool',
673 ),
674 'login_guard.threshold' => array(
675 'label' => t_default('acp.set.login_guard.threshold.label', 'Failed attempts allowed'),
676 'type' => 'int',
677 'min' => 1,
678 'max' => 100,
679 ),
680 'login_guard.window_minutes' => array(
681 'label' => t_default('acp.set.login_guard.window_minutes.label', 'Counting window (minutes)'),
682 'type' => 'int',
683 'min' => 1,
684 'max' => 1440,
685 'help' => t_default('acp.set.login_guard.window_minutes.help', 'Failed attempts older than this many minutes no longer count toward the lockout.'),
686 ),
687 'login_guard.lockout_minutes' => array(
688 'label' => t_default('acp.set.login_guard.lockout_minutes.label', 'Lockout duration (minutes)'),
689 'type' => 'int',
690 'min' => 1,
691 'max' => 1440,
692 ),
693 ),
694
695 t_default('acp.sec.Security', 'Security') => array(
696 'use_token' => array(
697 'label' => t_default('acp.set.use_token.label', 'CSRF tokens on forms'),
698 'type' => 'bool',
699 ),
700 'use_captcha' => array(
701 'label' => t_default('acp.set.use_captcha.label', 'reCaptcha'),
702 'type' => 'bool',
703 'help' => t_default('acp.set.use_captcha.help', 'Needs the site and secret keys below, and the cURL extension.'),
704 ),
705 'captcha_site_key' => array(
706 'label' => t_default('acp.set.captcha_site_key.label', 'reCaptcha site key'),
707 'type' => 'text',
708 ),
709 'captcha_secret_key' => array(
710 'label' => t_default('acp.set.captcha_secret_key.label', 'reCaptcha secret key'),
711 'type' => 'text',
712 ),
713 'captcha_use_curl' => array(
714 'label' => t_default('acp.set.captcha_use_curl.label', 'Verify reCaptcha with cURL'),
715 'type' => 'bool',
716 'help' => t_default('acp.set.captcha_use_curl.help', 'On if the cURL extension is available, off to use file_get_contents.'),
717 ),
718 'twoFactorAuthenticator' => array(
719 'label' => t_default('acp.set.twoFactorAuthenticator.label', 'Two-factor authentication'),
720 'type' => 'bool',
721 'help' => t_default('acp.set.twoFactorAuthenticator.help', 'TFS 1.2+ only. Unavailable on Canary.'),
722 ),
723 'validate_IP' => array(
724 'label' => t_default('acp.set.validate_IP.label', 'Tie sessions to the visitor IP'),
725 'type' => 'bool',
726 ),
727 ),
728
729 t_default('acp.sec.Two-factor authentication (v2)', 'Two-factor authentication (v2)') => array(
730 'twoFactorV2.enabled' => array(
731 'label' => t_default('acp.set.twoFactorV2.enabled.label', 'Enable website 2FA v2'),
732 'type' => 'bool',
733 'help' => t_default('acp.set.twoFactorV2.enabled.help', 'Independent of the game engine - works on TFS, Canary, otHire or BlackTek alike. Players manage it from twofa.php.'),
734 ),
735 'twoFactorV2.email_otp_enabled' => array(
736 'label' => t_default('acp.set.twoFactorV2.email_otp_enabled.label', 'Allow e-mail codes'),
737 'type' => 'bool',
738 'help' => t_default('acp.set.twoFactorV2.email_otp_enabled.help', 'Lets a player receive a one-time code by e-mail instead of using an authenticator app. Requires the mail server to be configured.'),
739 ),
740 'twoFactorV2.force_admins' => array(
741 'label' => t_default('acp.set.twoFactorV2.force_admins.label', 'Require it for admin panel access'),
742 'type' => 'bool',
743 'help' => t_default('acp.set.twoFactorV2.force_admins.help', 'Any account with an admin panel role is sent to set up 2FA v2 before it can use the panel.'),
744 ),
745 'twoFactorV2.recovery_codes_count' => array(
746 'label' => t_default('acp.set.twoFactorV2.recovery_codes_count.label', 'Recovery codes generated at a time'),
747 'type' => 'int',
748 'min' => 1,
749 'max' => 20,
750 ),
751 'twoFactorV2.trusted_device_days' => array(
752 'label' => t_default('acp.set.twoFactorV2.trusted_device_days.label', '"Remember this device" duration (days)'),
753 'type' => 'int',
754 'min' => 0,
755 'max' => 365,
756 'help' => t_default('acp.set.twoFactorV2.trusted_device_days.help', '0 removes the option from the login form.'),
757 ),
758 ),
759
760 t_default('acp.sec.Security headers', 'Security headers') => array(
761 'security.headers_enabled' => array(
762 'label' => t_default('acp.set.security.headers_enabled.label', 'Send HTTP security headers'),
763 'type' => 'bool',
764 'default' => true,
765 'help' => t_default('acp.set.security.headers_enabled.help', 'Adds browser hardening headers on public and admin pages.'),
766 ),
767 'security.content_type_options' => array(
768 'label' => t_default('acp.set.security.content_type_options.label', 'Block MIME sniffing'),
769 'type' => 'bool',
770 'default' => true,
771 'help' => t_default('acp.set.security.content_type_options.help', 'Sends X-Content-Type-Options: nosniff.'),
772 ),
773 'security.frame_options' => array(
774 'label' => t_default('acp.set.security.frame_options.label', 'Frame policy'),
775 'type' => 'select',
776 'default' => 'SAMEORIGIN',
777 'options' => array(
778 'SAMEORIGIN' => 'SAMEORIGIN',
779 'DENY' => 'DENY',
780 '' => t_default('acp.opt.disabled', 'Disabled'),
781 ),
782 'help' => t_default('acp.set.security.frame_options.help', 'Controls whether the site can be embedded in an iframe. SAMEORIGIN is the safest default for admin pages.'),
783 ),
784 'security.referrer_policy' => array(
785 'label' => t_default('acp.set.security.referrer_policy.label', 'Referrer policy'),
786 'type' => 'select',
787 'default' => 'strict-origin-when-cross-origin',
788 'options' => array(
789 'strict-origin-when-cross-origin' => 'strict-origin-when-cross-origin',
790 'no-referrer' => 'no-referrer',
791 'same-origin' => 'same-origin',
792 'origin-when-cross-origin' => 'origin-when-cross-origin',
793 'strict-origin' => 'strict-origin',
794 'unsafe-url' => 'unsafe-url',
795 '' => t_default('acp.opt.disabled', 'Disabled'),
796 ),
797 'help' => t_default('acp.set.security.referrer_policy.help', 'Limits how much URL information is sent to external sites.'),
798 ),
799 'security.permissions_policy' => array(
800 'label' => t_default('acp.set.security.permissions_policy.label', 'Permissions policy'),
801 'type' => 'textarea',
802 'default' => 'camera=(), microphone=(), geolocation=(), browsing-topics=()',
803 'help' => t_default('acp.set.security.permissions_policy.help', 'Example: camera=(), microphone=(), geolocation=(). Empty disables this header.'),
804 ),
805 'security.content_security_policy' => array(
806 'label' => t_default('acp.set.security.content_security_policy.label', 'Content Security Policy'),
807 'type' => 'textarea',
808 'default' => "frame-ancestors 'self'; object-src 'none'; base-uri 'self'",
809 'help' => t_default('acp.set.security.content_security_policy.help', 'Default protects framing and plugins without blocking existing theme scripts. Empty disables this header.'),
810 ),
811 'security.cross_domain_policy' => array(
812 'label' => t_default('acp.set.security.cross_domain_policy.label', 'Block Adobe cross-domain policies'),
813 'type' => 'bool',
814 'default' => true,
815 'help' => t_default('acp.set.security.cross_domain_policy.help', 'Sends X-Permitted-Cross-Domain-Policies: none.'),
816 ),
817 'security.hsts' => array(
818 'label' => t_default('acp.set.security.hsts.label', 'Force HTTPS with HSTS'),
819 'type' => 'bool',
820 'default' => false,
821 'help' => t_default('acp.set.security.hsts.help', 'Enable only when the public domain is permanently available over HTTPS. Browsers remember this choice.'),
822 ),
823 'security.hsts_max_age' => array(
824 'label' => t_default('acp.set.security.hsts_max_age.label', 'HSTS max age'),
825 'type' => 'int',
826 'default' => 31536000,
827 'help' => t_default('acp.set.security.hsts_max_age.help', 'Seconds. 31536000 = one year.'),
828 ),
829 'security.hsts_include_subdomains' => array(
830 'label' => t_default('acp.set.security.hsts_include_subdomains.label', 'HSTS include subdomains'),
831 'type' => 'bool',
832 'default' => false,
833 'help' => t_default('acp.set.security.hsts_include_subdomains.help', 'Only enable if every subdomain also supports HTTPS.'),
834 ),
835 ),
836
837 t_default('acp.sec.Cache', 'Cache') => array(
838 'cache.prefix' => array(
839 'label' => t_default('acp.set.cache.prefix.label', 'Cache namespace'),
840 'type' => 'text',
841 'help' => t_default('acp.set.cache.prefix.help', 'Use a unique value for each ZnoteX installation sharing the same APCu server.'),
842 ),
843 'cache.lifespan' => array(
844 'label' => t_default('acp.set.cache.lifespan.label', 'Cache lifetime (seconds)'),
845 'type' => 'int',
846 'min' => 0,
847 'max' => 2147483647,
848 'help' => t_default('acp.set.cache.lifespan.help', '0 keeps entries until they are explicitly cleared.'),
849 ),
850 'cache.memory' => array(
851 'label' => t_default('acp.set.cache.memory.label', 'Keep cache in memory (APCu)'),
852 'type' => 'bool',
853 'help' => t_default('acp.set.cache.memory.help', 'Uses APCu when available and automatically falls back to files in engine/cache/.'),
854 ),
855 ),
856
857 t_default('acp.sec.E-mail', 'E-mail') => array(
858 'mailserver.register' => array(
859 'label' => t_default('acp.set.mailserver.register.label', 'Send activation mail on register'),
860 'type' => 'bool',
861 ),
862 'mailserver.accountRecovery' => array(
863 'label' => t_default('acp.set.mailserver.accountRecovery.label', 'Allow account recovery by mail'),
864 'type' => 'bool',
865 'help' => t_default('acp.set.mailserver.accountRecovery.help', 'Enables the Lost Account page for recovering usernames, passwords and legacy 2FA by e-mail.'),
866 ),
867 'mailserver.myaccount_verify_email' => array(
868 'label' => t_default('acp.set.mailserver.myaccount_verify_email.label', 'Let players verify their e-mail'),
869 'type' => 'bool',
870 ),
871 'mailserver.verify_email_points' => array(
872 'label' => t_default('acp.set.mailserver.verify_email_points.label', 'Points for verifying e-mail'),
873 'type' => 'int',
874 'help' => t_default('acp.set.mailserver.verify_email_points.help', '0 disables the reward.'),
875 ),
876 'mailserver.host' => array(
877 'label' => t_default('acp.set.mailserver.host.label', 'SMTP host'),
878 'type' => 'text',
879 ),
880 'mailserver.port' => array(
881 'label' => t_default('acp.set.mailserver.port.label', 'SMTP port'),
882 'type' => 'int',
883 'help' => t_default('acp.set.mailserver.port.help', '465 for SSL, 587 for TLS.'),
884 ),
885 'mailserver.securityType' => array(
886 'label' => t_default('acp.set.mailserver.securityType.label', 'SMTP security'),
887 'type' => 'text',
888 'help' => t_default('acp.set.mailserver.securityType.help', 'ssl or tls.'),
889 ),
890 'mailserver.email' => array(
891 'label' => t_default('acp.set.mailserver.email.label', 'From address'),
892 'type' => 'text',
893 ),
894 'mailserver.username' => array(
895 'label' => t_default('acp.set.mailserver.username.label', 'SMTP username'),
896 'type' => 'text',
897 ),
898 'mailserver.password' => array(
899 'label' => t_default('acp.set.mailserver.password.label', 'SMTP password'),
900 'type' => 'text',
901 ),
902 ),
903 );
904