├── ueditor.jpg ├── Custombranding.php ├── LICENSE ├── README.md ├── lang_zh-cn.php ├── _gameboard.scss └── IndexController.php /ueditor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binfed/fbctf/HEAD/ueditor.jpg -------------------------------------------------------------------------------- /Custombranding.php: -------------------------------------------------------------------------------- 1 | 9 | :brandingLogo} /> 10 |
11 | {$this->:brandingText}
汉化由Sp4ce和MuTou提供
12 | 13 | ; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sp4ce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FBCTF汉化版 2 | FBCTF 简中汉化,基于Facebook官方FBCTF平台源码(Ubuntu 16.04)进行汉化 3 | 4 | 5 | # 截图 6 | ![](https://i.imgur.com/YNv6tcQ.png) 7 | 8 | ![](https://i.imgur.com/ucigXw8.png) 9 | 10 | ![](https://i.imgur.com/sJdZXxr.png) 11 | 12 | ![](https://i.imgur.com/Hhj3DLS.png) 13 | 14 | ![](https://i.imgur.com/7pk3Gw6.png) 15 | 16 | ![](https://i.imgur.com/snKJ2Bb.png) 17 | 18 | ![](https://i.imgur.com/NEpdT3b.png) 19 | 20 | 21 | # 使用说明 22 | **按照以下**文件地址进行**替换** 23 | 24 | | 文件名 | 文件位置 | 25 | | ------ | ------ | 26 | | _gameboard.scss | /path/to/fbctf/src/static/css/scss | 27 | | fb-ctf.css | /path/to/fbctf/src/static/css | 28 | | AdminController.php| /var/www/fbctf/src/controllers | 29 | | IndexController.php | /var/www/fbctf/src/controllers | 30 | |Custombranding.php | /var/www/fbctf/src/xhp | 31 | | lang_zh-cn.php | /var/www/fbctf/src/language | 32 | 33 | 其中`lang_zh-cn.php`为新增文件,使用简中时需要删除原有的`lang_zh-tw.php` 34 | 35 | # 缓存更新 36 | 37 | 替换完成后在`shell`中更新缓存【重新编译】 38 | ```shell 39 | rm /var/cache/hhvm/hhvm.hhbc 40 | hhvm-repo-mode enable "/var/www/fbctf" 41 | chown www-data:www-data /var/cache/hhvm/hhvm.hhbc 42 | service hhvm start 43 | service nginx restart 44 | ``` 45 | -------------------------------------------------------------------------------- /lang_zh-cn.php: -------------------------------------------------------------------------------- 1 | 5 | 'H:i:s D m/d/Y', //used by date() function 6 | //Translations for IndexController 7 | 'Facebook CTF' => 8 | 'Facebook CTF', 9 | 'Conquer the world' => 10 | '征服世界', 11 | 'Play' => 12 | '开始', 13 | 'Welcome to the Facebook Capture the Flag Competition. By clicking "Play," you will be entered into the official CTF challenge. Good luck in your conquest.' => 14 | '欢迎来到 Facebook CTF竞赛。立即点击 "开始" 进入正式的 CTF 挑战。祝君征途愉快。', 15 | 'Get ready for the CTF to start and access the gameboard now!' => 16 | '立即查看游戏版图并准备开始进行 CTF!', 17 | 'Gameboard' => 18 | '游戏版图', 19 | 'Register Team' => 20 | '注册队伍', 21 | 'Get ready for the CTF to start and register your team now!' => 22 | '立即注册队伍并准备开始进行 CTF!', 23 | 'Login' => 24 | '登录', 25 | 'Soon' => 26 | '即将开始', 27 | 'Upcoming Game' => 28 | '待开始', 29 | '_days' => 30 | '天', 31 | '_hours' => 32 | '小时', 33 | '_minutes' => 34 | '分钟', 35 | '_seconds' => 36 | '秒', 37 | 'Official CTF Rules' => 38 | '官方 CTF 规则', 39 | 'Following actions are prohibited, unless explicitly told otherwise by event Admins.' => 40 | '除非管理员明确说明,否则禁止以下行为。', 41 | 'Rule' => 42 | '规则', 43 | 'Cooperation' => 44 | '互助', 45 | 'No cooperation between teams with independent accounts. Sharing of keys or providing revealing hints to other teams is cheating, don’t do it.' => 46 | '队伍之间禁止互助的行为。谨记,分享密钥或是提供提示给其它的队伍将被视为作弊!', 47 | 'Attacking Scoreboard' => 48 | '攻击记分板', 49 | 'No attacking the competition infrastructure. If bugs or vulns are found, please alert the competition organizers immediately.' => 50 | '禁止攻击竞赛相关的基础设施。如发现错误或是漏洞,请立即通报管理员。', 51 | 'Sabotage' => 52 | '妨碍', 53 | 'Absolutely no sabotaging of other competing teams, or in any way hindering their independent progress.' => 54 | '强烈禁止妨碍或阻挠其他队伍解题。', 55 | 'Bruteforcing' => 56 | '暴力攻击', 57 | 'No brute forcing of challenge flag/ keys against the scoring site.' => 58 | '禁止以暴力方式尝试取得记分网站的密钥。', 59 | 'Denial Of Service' => 60 | '阻断服务攻击', 61 | 'DoSing the CTF platform or any of the challenges is forbidden.' => 62 | '禁止对 CTF 平台或题目实行阻断服务攻击。', 63 | 'Legal' => 64 | '法律', 65 | 'Disclaimer' => 66 | '免责声明', 67 | 'By participating in the contest, you agree to release Facebook and its employees, and the hosting organization from any and all liability, claims or actions of any kind whatsoever for injuries, damages or losses to persons and property which may be sustained in connection with the contest. You acknowledge and agree that Facebook et al is not responsible for technical, hardware or software failures, or other errors or problems which may occur in connection with the contest.' => 68 | '当您参与比赛时,您同意放弃任何及所有对 Facebook 和其员工以及主办方在竞赛连线期间对人身和财产的任何损失或损害产生的与其相关的任何损害或损失责任的索赔。', 69 | 'If you have any questions about what is or is not allowed, please ask an organizer.' => 70 | '如果您对上述问题有疑虑,请立即向主办方反映。', 71 | 'Have fun!' => 72 | '玩得愉快!', 73 | 'Name' => 74 | '姓名', 75 | 'Email' => 76 | '邮箱', 77 | 'Token' => 78 | 'Token', 79 | 'Team Registration' => 80 | '队伍注册', 81 | 'Team Name' => 82 | '队伍名称', 83 | 'Password' => 84 | '密码', 85 | 'Choose an Emblem' => 86 | '选择一个队徽', 87 | 'or upload your own' => 88 | '或上传自己的队徽', 89 | 'Clear your custom emblem to use a default emblem.' => 90 | '清除您的自定义队徽以使用默认队徽。', 91 | 'Password is too simple' => 92 | '密码太简单了', 93 | 'Sign Up' => 94 | '注册', 95 | 'Register to play Capture The Flag here. Once you have registered, you will be logged in.' => 96 | '注册以进行CTF 。注册完毕时,会将您自动登入。', 97 | 'Not Available' => 98 | '服务不可用', 99 | 'Team Registration will be open soon, stay tuned!' => 100 | '队伍注册即将开放。', 101 | 'Try Again' => 102 | '再试一遍', 103 | 'Select' => 104 | '选择', 105 | 'Team Login' => 106 | '队伍登入', 107 | 'Please login here. If you have not registered, you may do so by clicking "Sign Up" below. ' => 108 | '请登入。如果您尚未注册,请点击下方的 "注册" 。', 109 | 'Team Login will be open soon, stay tuned!' => 110 | '队伍登入即将开放,请静候佳音。', 111 | 'ERROR' => 112 | '错误', 113 | 'Start Over' => 114 | '重新来过', 115 | 'Window is too small' => 116 | '窗口过小', 117 | 'For the best CTF experience, please make window size bigger.' => 118 | '为了体验最佳的 CTF 游戏,请放大窗口。', 119 | 'Thank you.' => 120 | '谢谢。', 121 | 'Logout' => 122 | '登出', 123 | 'Registration' => 124 | '注册', 125 | 'Play CTF' => 126 | '进行 CTF', 127 | 'Rules' => 128 | '规则', 129 | //Translations for GameboardController 130 | 'Admin' => 131 | '后台', 132 | 'ADMIN' => 133 | '后台', 134 | 'Navigation' => 135 | '导航', 136 | 'View Mode' => 137 | '观看模式', 138 | 'View mode' => 139 | '观看模式', 140 | 'Tutorial' => 141 | '教学', 142 | 'Scoreboard' => 143 | '记分板', 144 | 'You' => 145 | '您', 146 | 'Others' => 147 | '其他', 148 | 'All' => 149 | '所有', 150 | 'Leaderboard' => 151 | '积分榜', 152 | 'Announcements' => 153 | '公告', 154 | 'Teams' => 155 | '队伍', 156 | 'Filter' => 157 | '筛选', 158 | 'Activity' => 159 | '活动', 160 | 'Game Clock' => 161 | '游戏时间', 162 | //Translations for AdminController 163 | 'Auto' => 164 | '自动', 165 | 'All Categories' => 166 | '所有类别', 167 | 'Open' => 168 | '开放', 169 | 'Tokenized' => 170 | 'Token', 171 | 'Hour' => 172 | '小时', 173 | 'Hours' => 174 | '小时', 175 | 'Used by' => 176 | '已被使用于', 177 | 'Used By' => 178 | '已被使用于', 179 | 'Available' => 180 | '可用的', 181 | 'Registration Tokens' => 182 | '注册 Tokens', 183 | 'Create More' => 184 | '建立更多', 185 | 'Export Available' => 186 | '导出可用', 187 | 'Not started yet' => 188 | '尚未开始', 189 | 'Configuration' => 190 | '设定', 191 | 'Tokens' => 192 | 'Tokens', 193 | 'Game Configuration' => 194 | '游戏设定', 195 | 'OK' => 196 | '正常', 197 | 'status_' => 198 | '状态', 199 | 'On' => 200 | '开', 201 | 'Off' => 202 | '关', 203 | 'Player Names' => 204 | '玩家名称', 205 | 'Players Per Team' => 206 | '各队人数', 207 | 'Registration Type' => 208 | '注册类别', 209 | 'Strong Passwords' => 210 | '增强式密码', 211 | 'Team Selection' => 212 | '队伍选择', 213 | 'Game' => 214 | '游戏', 215 | 'Scoring' => 216 | '记分板', 217 | 'Progressive Cycle (s)' => 218 | '计分版更新频率 (秒)', 219 | 'Refresh Gameboard' => 220 | '重置记分板', 221 | 'Default Bonus' => 222 | '默认积分', 223 | 'Bases Cycle (s)' => 224 | '基地占领计分更新频率 (秒)', 225 | 'Default Bonus Dec' => 226 | '默认积分损失', 227 | 'Timer' => 228 | '计时器', 229 | 'Server Time' => 230 | '服务器时间', 231 | 'Game Duration' => 232 | '游戏时间', 233 | 'Begin Time' => 234 | '开始时间', 235 | 'Expected End Time' => 236 | '预计结束时间', 237 | 'Internationalization' => 238 | '国际化设置', 239 | 'Language' => 240 | '语言', 241 | 'Branding' => 242 | '团队设置', 243 | 'Custom Logo' => 244 | '自定义队徽', 245 | 'Custom Text' => 246 | '自定义文本', 247 | 'DELETE' => 248 | '删除', 249 | 'Delete' => 250 | '删除', 251 | 'No Announcements' => 252 | '无公告', 253 | 'Game Controls' => 254 | '游戏控制', 255 | 'Write New Announcement here' => 256 | '在此新增公告', 257 | 'Create' => 258 | '提交', 259 | 'General' => 260 | '一般', 261 | 'Back Up Database' => 262 | '资料库备份', 263 | 'Export Full Game' => 264 | '导出游戏进度', 265 | 'Import Full Game' => 266 | '导入游戏进度', 267 | 'Import Teams' => 268 | '导入队伍资料', 269 | 'Export Teams' => 270 | '导出队伍资料', 271 | 'Import Logos' => 272 | '导入所有标志', 273 | 'Export Logos' => 274 | '导出所有标志', 275 | 'Import Levels' => 276 | '导入所有关卡', 277 | 'Export Levels' => 278 | '导出所有关卡', 279 | 'Import Categories' => 280 | '导入所有类别', 281 | 'Export Categories' => 282 | '导出所有类别', 283 | 'Levels' => 284 | '关卡', 285 | 'New Quiz Level' => 286 | '新增测验型关卡', 287 | 'Title' => 288 | '标题', 289 | 'Question' => 290 | '问题', 291 | 'Level title' => 292 | '关卡标题', 293 | 'Quiz question' => 294 | '测验问题', 295 | 'Country' => 296 | '国家', 297 | 'Answer' => 298 | '答案', 299 | 'Points' => 300 | '分数', 301 | 'Hint' => 302 | '提示', 303 | 'Hint Penalty' => 304 | '提示所消耗的积分', 305 | 'EDIT' => 306 | '修改', 307 | 'All Quiz Levels' => 308 | '所有测验型关卡', 309 | 'Filter By:' => 310 | '过滤选项:', 311 | 'All Status' => 312 | '所有状态', 313 | 'Enabled' => 314 | '开启', 315 | 'Disabled' => 316 | '关闭', 317 | 'Quiz Level' => 318 | '测验型关卡', 319 | 'Show Answer' => 320 | '显示答案', 321 | 'Bonus' => 322 | '积分', 323 | '-Dec' => 324 | '-积分', 325 | 'Save' => 326 | '保存', 327 | 'Quiz Management' => 328 | '测验管理', 329 | 'Add Quiz Level' => 330 | '增加测验型关卡', 331 | 'New Flag Level' => 332 | '新增 Flag 型关卡', 333 | 'Description' => 334 | '叙述', 335 | 'Level description' => 336 | '关卡叙述', 337 | 'Category' => 338 | '类别', 339 | 'Flag' => 340 | 'Flag', 341 | 'flag' => 342 | 'Flag', 343 | 'All Flag Levels' => 344 | '所有Flag型关卡', 345 | 'New Attachment:' => 346 | '新增附件:', 347 | 'Attachment' => 348 | '附件', 349 | 'Link' => 350 | '链接', 351 | 'New Link:' => 352 | '新增链接:', 353 | 'Flag Level' => 354 | 'Flag关卡', 355 | 'Categories' => 356 | '所有类别', 357 | '+ Attachment' => 358 | '+ 附件', 359 | '+ Link' => 360 | '+ 链接', 361 | 'Flags Management' => 362 | 'Flag管理', 363 | 'Add Flag Level' => 364 | '增加Flag型关卡', 365 | 'New Base Level' => 366 | '新增基地关卡', 367 | 'Keep Points' => 368 | '保留得分', 369 | 'Capture points' => 370 | '夺取得分', 371 | 'All Base Levels' => 372 | '所有基地关卡', 373 | 'Base Level' => 374 | '基地关卡', 375 | 'Bases Management' => 376 | '基地管理', 377 | 'Add Base Level' => 378 | '增加基地关卡', 379 | 'New Category' => 380 | '新增类别', 381 | 'Category: ' => 382 | '类别:', 383 | 'Categories Management' => 384 | '类别管理', 385 | 'Add Category' => 386 | '增加类别', 387 | 'All Countries' => 388 | '所有国家', 389 | 'In Use' => 390 | '使用中', 391 | 'In use' => 392 | '使用中', 393 | 'Not Used' => 394 | '未使用', 395 | 'Yes' => 396 | '是', 397 | 'No' => 398 | '否', 399 | 'ISO Code' => 400 | 'ISO 代码', 401 | 'Countries Management' => 402 | '国家管理', 403 | 'No Team Names' => 404 | '没有队伍名称', 405 | 'time' => 406 | '时间', 407 | 'type' => 408 | '类型', 409 | 'pts' => 410 | '分数(pts)', 411 | 'Level' => 412 | '关卡', 413 | 'level' => 414 | '关卡', 415 | 'No Scores' => 416 | '没有分数', 417 | 'Attempt' => 418 | '尝试', 419 | 'No Failures' => 420 | '没有错误', 421 | 'Team' => 422 | '队伍', 423 | 'team' => 424 | '队伍', 425 | 'Names' => 426 | '名称', 427 | 'Scores' => 428 | '分数', 429 | 'Failures' => 430 | '错误', 431 | 'New Team' => 432 | '新增队伍', 433 | 'Team Logo' => 434 | '队伍标志', 435 | 'Selected Logo:' => 436 | '已选择的标志:', 437 | 'Select Logo' => 438 | '选择标志', 439 | 'All Teams' => 440 | '所有队伍', 441 | 'Protected' => 442 | '已保护', 443 | 'Score' => 444 | '分数', 445 | 'Change Password' => 446 | '更改密码', 447 | 'Admin Level' => 448 | '管理员', 449 | 'Visibility' => 450 | '队伍可见', 451 | 'Team Management' => 452 | '队伍管理', 453 | 'Add Team' => 454 | '增加队伍', 455 | 'None' => 456 | '无', 457 | 'Logo Name' => 458 | '标志名称', 459 | 'Logo Management' => 460 | '标志管理', 461 | 'Session' => 462 | 'Session', 463 | 'Cookie' => 464 | 'Cookie', 465 | 'Creation Time' => 466 | '建立时间', 467 | 'Last Access' => 468 | '最后登入', 469 | 'Last Page Access' => 470 | '最后访问页面', 471 | 'Data' => 472 | '资料', 473 | 'Sessions' => 474 | 'Sessions', 475 | 'entry' => 476 | '项目', 477 | 'No Entries' => 478 | '无任何项目', 479 | 'Game Logs' => 480 | '游戏记录', 481 | 'Game Logs Timeline' => 482 | '游戏记录时间轴', 483 | 'End Game' => 484 | '结束游戏', 485 | 'Begin Game' => 486 | '开始游戏', 487 | 'Game Admin' => 488 | '游戏后台', 489 | 'Controls' => 490 | '控制', 491 | 'Quiz' => 492 | '测验', 493 | 'Flags' => 494 | 'Flag', 495 | 'Bases' => 496 | '基地', 497 | 'Countries' => 498 | '国家', 499 | 'Logos' => 500 | '标志', 501 | //Translations for inc/* and inc/gameboard/* 502 | 'captured' => 503 | '夺得', 504 | 'Status' => 505 | '状态', 506 | 'Completed' => 507 | '已完成', 508 | 'Remaining' => 509 | '剩余', 510 | 'Start' => 511 | '开始', 512 | 'End' => 513 | '结束', 514 | 'Rank' => 515 | '排名', 516 | 'pts' => 517 | '分(pts)', //points 518 | 'Your Rank' => 519 | '您的排名', 520 | 'Your Score' => 521 | '您的分数', 522 | 'Everyone' => 523 | '每个人', 524 | 'Your Team' => 525 | '您的队伍', 526 | 'Captured' => 527 | '夺得', 528 | 'Initiating' => 529 | '初始化', 530 | 'run : > boot_sequence' => 531 | '进度:> 加载进度', 532 | 'Extracting' => 533 | '处理中', 534 | //Translations for Utils.php's time_ago() function 535 | 'just now' => 536 | '刚刚', 537 | 'd' => 538 | '天', //day 539 | 'hr' => 540 | '小时', //hour 541 | 'min' => 542 | '分钟', //minute 543 | 'sec' => 544 | '秒', //second 545 | 'ds' => 546 | '天', //days 547 | 'hrs' => 548 | '小时', //hours 549 | 'mins' => 550 | '分钟', //minutes 551 | 'secs' => 552 | '秒', //seconds 553 | 'ago' => 554 | '前', 555 | //Translations for ModalControllers 556 | 'begin_' => 557 | '开始', 558 | 'Are you sure you want to kick off the game? Logs will be cleared and progressive scoreboard will start' => 559 | '您确定要开始游戏了吗?记录将会被清空且记分板将会重新开始记录。', 560 | 'end_' => 561 | '结束', 562 | 'Are you sure you want to finish the current game?' => 563 | '您确定要立即结束现在的游戏?', 564 | 'Are you sure you want to logout from the game?' => 565 | '您确定要立即登出?', 566 | 'Saved' => 567 | '已保存', 568 | 'All changes have been successfully saved.' => 569 | '所有变更已成功保存。', 570 | 'Error' => 571 | '错误', 572 | 'Sorry your form was not saved. Please correct the all errors and save again.' => 573 | '很抱歉我們无法保存您的表单。请修正所有错误并再次尝试。', 574 | 'cancel_' => 575 | '取消', 576 | 'Are you sure you want to cancel? You have unsaved changes that will be reverted.' => 577 | '您确定要取消?未保存的变更将会恢复至上次的状态。', 578 | 'choose_logo' => 579 | '选择标志', 580 | 'captured_' => 581 | '夺得', 582 | 'flag_owner_' => 583 | 'Flag 拥有者', 584 | 'INACTIVE' => 585 | '未启动', 586 | 'PTS' => 587 | '分', 588 | 'category' => 589 | '类别', 590 | 'capture_' => 591 | '夺取', 592 | 'Insert your answer' => 593 | '输入您的答案', 594 | 'Request Hint' => 595 | '要求提示', 596 | 'Submit' => 597 | '提交', 598 | 'hint_' => 599 | '提示', 600 | 'first_capture' => 601 | '一血', 602 | 'completed_by' => 603 | '已完成:', 604 | 'scoreboard_' => 605 | '记分板', 606 | 'filter_' => 607 | '筛选', 608 | 'rank_' => 609 | '排名', 610 | 'team_name_' => 611 | '队伍名称', 612 | 'quiz_pts_' => 613 | '测验型得分', 614 | 'flag_pts_' => 615 | 'Flag型得分', 616 | 'base_pts_' => 617 | '基地型得分', 618 | 'total_pts_' => 619 | '总得分', 620 | 'team_' => 621 | '队伍', 622 | 'team_members' => 623 | '队伍 成员', 624 | 'base_pts' => 625 | '基地型得分', 626 | 'quiz_pts' => 627 | '测验型得分', 628 | 'flag_pts' => 629 | 'Flag型得分', 630 | 'total_pts' => 631 | '总得分', 632 | 'Tool bars are located on all edges of the gameboard. Tap a category to expand and close each tool bar.' => 633 | '工具栏位于游戏版图的四周。点击任一分类以展开或关闭各工具栏。', 634 | 'Tool_Bars' => 635 | '工具栏', 636 | 'Tap the "Game Clock" to keep track of time during gameplay. Don’t let time get the best of you.' => 637 | '点击 "游戏时间" 以随时查看剩余时间。别败给时间了。', 638 | 'Game_Clock' => 639 | '游戏 时间', 640 | 'Countries marked with an ' => 641 | '有着', 642 | 'are captured by you.' => 643 | '符号代表您夺得的国家。', 644 | ' are owned by others.' => 645 | '符号代表被其他人占有的国家。', 646 | 'Captures' => 647 | '夺取', 648 | 'Tap Plus[+] to Zoom In. Tap Minus[-] to Zoom Out.' => 649 | '按 + [+] 以放大. 按 - [-] 以缩小。', 650 | 'Click and Drag to move left, right, up and down.' => 651 | '点击并拖拽来移动版图。', 652 | 'Zoom' => 653 | '缩放', 654 | 'Tap Forward Slash [/] to activate computer commands. A list of commands can be found under "Rules".' => 655 | '按 / [/] 以执行命令。在 "规则" 底下找到可用的指令。', 656 | 'Command_Line' => 657 | '命令行', 658 | 'Click "Nav" to access main navigation links like Rules of Play, Registration, Blog, Jobs & more.' => 659 | '点击 "导航" 来获取更多信息,例如: 游戏规则、注册、博客、工作或更多。', 660 | 'Track your competition by clicking "scoreboard" to access real-time game statistics and graphs.' => 661 | '点击 "记分板" 以查看您目前游戏的进度,并查看即时的統计资料与图形。', 662 | 'Have fun, be the best and conquer the world.' => 663 | '成为最棒的征服者吧,祝君玩的愉快。', 664 | 'Game_On' => 665 | '游戏 开始', 666 | 'tutorial_' => 667 | '教学', 668 | 'Next' => 669 | '下一步', 670 | 'Skip to play' => 671 | '跳过教学', 672 | 'Powered By Facebook' => 673 | 'Powered By Facebook', 674 | 'Active Directory / LDAP' => 675 | '活动目录 / LDAP', 676 | 'LDAP Server' => 677 | 'LDAP服务器', 678 | 'LDAP Port' => 679 | 'LDAP端口', 680 | 'LDAP Domain' => 681 | 'LDAP域', 682 | //*********************************** 683 | //Add Something Missed 684 | //By:Sp4ce Mutou 685 | //2018.11.13 686 | //游戏后台部分翻译遗漏项 687 | //*********************************** 688 | //全局部分 689 | 'Pause Game'=> 690 | '暂停游戏', 691 | 'pause_'=> 692 | '暂停', 693 | 'Are you sure you want to pause the current game?'=> 694 | '你确定要暂停当前游戏吗?', 695 | 'Unpause Game'=> 696 | '恢复游戏', 697 | 'unpause_'=> 698 | '恢复', 699 | 'Are you sure you want to unpause the current game?'=> 700 | '你确定要取消暂停当前游戏吗?', 701 | 702 | 703 | //游戏设定页面 704 | 'Automatic Team Name Prefix'=> 705 | '队伍名前缀自动补全', 706 | 'Google Registration'=> 707 | '用Google账户注册', 708 | 'Google Login'=> 709 | 'Google账户登录', 710 | 'Facebook Login'=> 711 | 'Facebook帐号登录', 712 | 'Facebook Registration'=> 713 | 'Facebook帐号注册', 714 | 'Auto Announcements'=> 715 | '自动通知', 716 | 'Autorun Cycle (s)'=> 717 | '自动运行周期(秒)', 718 | 'Leaderboard Limit'=> 719 | '排行榜显示数量限制', 720 | 'Gameboard Cycle (s)'=> 721 | '游戏面板更新周期(秒)', 722 | 'Configuration Cycle (s)'=> 723 | '配置更新周期(秒)', 724 | 'Game Schedule'=> 725 | '游戏时间表', 726 | 'Game Start Year'=> 727 | '游戏开始(年)', 728 | 'Month'=> 729 | '月', 730 | 'Day'=> 731 | '日', 732 | 'Minute'=> 733 | '分', 734 | 'Game Running'=> 735 | '游戏进行中', 736 | 'LiveSync'=> 737 | 'LiveSync同步', 738 | 'Optional LiveSync Auth Key'=> 739 | 'LiveSync密钥', 740 | 'Custom Organization'=> 741 | '自定义比赛名称(主标题)', 742 | 'Custom Byline'=> 743 | '自定义比赛名称(副标题)', 744 | 'Reset Schedule'=> 745 | '重置时间表', 746 | ' Hours '=> 747 | '小时', 748 | ' Minutes '=> 749 | '分钟', 750 | ' Days '=> 751 | '天', 752 | 'Change'=> 753 | '更改', 754 | 755 | //控制页面 756 | 'Game Controls'=> 757 | '游戏控制', 758 | 'Utilities' => 759 | '公共', 760 | 'Database'=> 761 | '数据库', 762 | 'reset_'=> 763 | '重置', 764 | 'Are you sure you want to reset the database? This will destroy ALL data! Admin accounts will remain.'=> 765 | '您确定要重置数据库吗? 这会破坏所有数据! 管理员帐户将保留。', 766 | 'restore_'=> 767 | '恢复', 768 | 'Are you sure you want to restore the database? This will overwrite ALL existing data!'=> 769 | '您确定要还原数据库吗? 这将覆盖所有现有数据!', 770 | 'Reset Database' => 771 | '重置数据库', 772 | 'Flush Memcached'=> 773 | '清理缓存', 774 | 'Restore Database'=> 775 | '恢复数据库', 776 | 'Backup Database'=> 777 | '备份数据库', 778 | 'Import Attachments'=> 779 | '导入附件', 780 | 'Export Attachments'=> 781 | '导出附件', 782 | 'Integration'=> 783 | '社交', 784 | 785 | //公告页面 786 | 'Announcement Controls'=> 787 | '公告控制', 788 | 789 | //关卡页面 790 | 'Hide Answer'=> 791 | '隐藏答案', 792 | 'Show Answer'=> 793 | '显示答案', 794 | 'delete_'=> 795 | '删除', 796 | 'Are you sure you want to delete this level? All data for this level will be irreversibly removed, including scores.'=> 797 | '您确定要删除此关卡吗? 此关卡的所有数据将被删除不可逆的,包括成绩。', 798 | 799 | //类别 800 | 'Quiz'=> 801 | '测验', 802 | 803 | 804 | //队伍 805 | 'Are you sure you want to delete this team? All data for this team will be irreversibly removed, including scoring logs. If you prefer to retain data, you can disable the team instead.'=> 806 | '您确定要删除此团队吗? 该团队的所有数据都将被不可逆转地删除,包括评分日志。 如果您希望保留数据,则可以禁用该团队。', 807 | 'Enabled'=> 808 | '启用', 809 | 'Disabled'=> 810 | '禁用', 811 | 'Please login here with username and password.'=> 812 | '请输入账号密码', 813 | 814 | //游戏版图部分 815 | 'Account'=> 816 | '帐号', 817 | 'Game has been paused!'=> 818 | '游戏已暂停!', 819 | 'Game has resumed!'=> 820 | '游戏已经恢复!', 821 | 'Need more points'=> 822 | '需要更多积分', 823 | 'Free Hint'=> 824 | '免费提示', 825 | 'Uncaptured'=> 826 | '未被夺取', 827 | 'account_'=> 828 | '帐号', 829 | 'Settings'=> 830 | '设置', 831 | 'Change your team name.'=> 832 | '更改队名', 833 | 'Update'=> 834 | '更新', 835 | 'Close'=> 836 | '关闭', 837 | 'Close'=> 838 | '关闭', 839 | 'Captured!'=> 840 | '已夺取!', 841 | 'Level already captured!'=> 842 | '关卡已夺取!', 843 | 'Free Hint'=> 844 | '免费提示', 845 | 'Welcome to the '=> 846 | '欢迎来到 ', 847 | ' Capture the Flag Competition. By clicking "Play," you will be entered into the official CTF challenge. Good luck in your conquest.'=> 848 | ' CTF比赛.点击“开始”,您将进入正式的CTF挑战赛。 祝你好运。', 849 | 850 | //注册 851 | 'Or register to use username and password here. Once you have registered, you will be logged in.'=> 852 | '或者在这里注册使用用户名和密码。 注册后,您将登录。', 853 | 854 | 855 | ); 856 | -------------------------------------------------------------------------------- /_gameboard.scss: -------------------------------------------------------------------------------- 1 | /* ============================================================== 2 | * gameboard 3 | * 4 | * all the gameboard styles 5 | ============================================================== */ 6 | 7 | $gameboard-margin: 10px; 8 | $module-margin: 20px; 9 | 10 | .fb-gameboard { 11 | height: 100vh; 12 | position: relative; 13 | 14 | // main header 15 | .gameboard-header { 16 | margin-left: 24%; 17 | margin-right: 24%; 18 | text-align: center; 19 | position: relative; 20 | z-index: 2; 21 | 22 | .tutorial-active & { 23 | position: static; 24 | } 25 | 26 | .fb-map-select { 27 | text-align: center; 28 | display: inline-block; 29 | 30 | .click-effect > span { 31 | position: relative; 32 | display: inline-block; 33 | 34 | &:before, 35 | &:after { 36 | margin-top: -50px; 37 | } 38 | } 39 | 40 | label.listview-only { 41 | display: none; 42 | } 43 | } 44 | } 45 | 46 | // module containers 47 | .fb-module-container { 48 | position: absolute; 49 | 50 | .tutorial-step--tool-bars & { 51 | z-index: 11; 52 | } 53 | 54 | &.container--column { 55 | width: 23%; 56 | max-width: 360px; 57 | top: $module-margin; 58 | 59 | &.column-left { 60 | left: $module-margin; 61 | } 62 | 63 | &.column-right { 64 | right: $module-margin; 65 | } 66 | 67 | aside + aside { 68 | margin-top: 20px; 69 | } 70 | } 71 | 72 | &.container--row { 73 | position: absolute; 74 | bottom: $module-margin; 75 | left: $module-margin; 76 | right: $module-margin; 77 | 78 | & > aside { 79 | position: absolute; 80 | 81 | .module-content { 82 | overflow: auto; 83 | padding-top: 10px; 84 | padding-right: 14px; 85 | } 86 | } 87 | 88 | .module--outer-left { 89 | bottom: 0; 90 | left: 0; 91 | width: 29%; 92 | padding-right: 10px; 93 | } 94 | 95 | .module--inner { 96 | bottom: 0; 97 | left: 29%; 98 | right: 29%; 99 | padding-left: 10px; 100 | padding-right: 10px; 101 | z-index: 2; 102 | } 103 | 104 | .module--inner-left { 105 | bottom: 0; 106 | left: 29%; 107 | right: 50%; 108 | padding-left: 10px; 109 | padding-right: 10px; 110 | z-index: 2; 111 | } 112 | 113 | .module--inner-right { 114 | &:not(.activity-viewmode) { 115 | left: 50%; 116 | } 117 | &.activity-viewmode { 118 | left: 31%; 119 | } 120 | bottom: 0; 121 | right: 29%; 122 | padding-left: 10px; 123 | padding-right: 10px; 124 | z-index: 2; 125 | } 126 | 127 | .module--outer-right { 128 | bottom: 0; 129 | right: 0; 130 | width: 29%; 131 | padding-left: 10px; 132 | } 133 | } 134 | } 135 | 136 | // modules 137 | aside[data-module] { 138 | /*background: $main-blue;*/ 139 | 140 | .radio-tab-content { 141 | padding-top: 10px; 142 | } 143 | 144 | .module-header { 145 | border-top: 1px solid $teal-blue; 146 | border-bottom: 1px solid $teal-blue; 147 | padding: 6px 10px 6px 26px; 148 | position: relative; 149 | cursor: pointer; 150 | 151 | &:before { 152 | content: ""; 153 | width: 8px; 154 | height: 8px; 155 | position: absolute; 156 | left: 6px; 157 | top: 50%; 158 | margin-top: -4px; 159 | border: 1px solid $light-blue; 160 | } 161 | 162 | &:after { 163 | content: ""; 164 | position: absolute; 165 | right: 6px; 166 | width: 14px; 167 | height: 8px; 168 | top: 50%; 169 | margin-top: -4px; 170 | background-repeat: no-repeat; 171 | background-position: center center; 172 | background-position: contain; 173 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2011.2%205.6%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%237ED0E0%22%20stroke-miterlimit%3D%2210%22%20d%3D%22M10.9.4L5.9%205c-.2.2-.4.2-.5%200L.4.3%22%2F%3E%3C%2Fsvg%3E"); 174 | 175 | body[data-section="viewer-mode"] & { 176 | display: none; 177 | } 178 | } 179 | } 180 | 181 | .module-content { 182 | @include flexbox; 183 | 184 | @include transition(all 0.4s); 185 | 186 | flex-direction: column; 187 | height: 0; 188 | opacity: 0; 189 | visibility: hidden; 190 | overflow: hidden; 191 | position: relative; 192 | 193 | .fb-section-border { 194 | position: absolute; 195 | top: 0; 196 | left: 0; 197 | right: 0; 198 | bottom: 0; 199 | 200 | @include flexbox; 201 | 202 | flex-direction: column; 203 | } 204 | 205 | .module-top { 206 | @include flex(0 0 auto); 207 | } 208 | 209 | .module-scrollable { 210 | @include flex(1 1 auto); 211 | 212 | overflow: auto; 213 | padding-left: 8px; 214 | padding-right: 8px; 215 | } 216 | } 217 | 218 | &.individual-chat-active, 219 | &.active { 220 | .module-header:before { 221 | background: $light-blue; 222 | } 223 | 224 | .module-content { 225 | margin-top: 10px; 226 | visibility: visible; 227 | opacity: 1; 228 | 229 | &:not(.leaderboard-viewmode) { 230 | height: calc(33vh - 62px); 231 | } 232 | &.leaderboard-viewmode { 233 | height: calc(97vh - 62px); 234 | } 235 | } 236 | } 237 | 238 | /** 239 | * --specific modules 240 | */ 241 | 242 | // teams module 243 | &[data-module="teams"] { 244 | .grid-list { 245 | padding-top: 20px; 246 | } 247 | 248 | li { 249 | text-align: center; 250 | 251 | &.alert { 252 | position: relative; 253 | 254 | &:before { 255 | position: absolute; 256 | right: 0px; 257 | top: 2px; 258 | background-color: $main-blue; 259 | } 260 | } 261 | } 262 | 263 | a:hover .icon--badge { 264 | stroke: $yellow; 265 | } 266 | } 267 | 268 | // data-module = teams 269 | 270 | // leaderboard module 271 | &[data-module="leaderboard"] { 272 | .module-top { 273 | padding: 10px; 274 | border-bottom: 1px solid $teal-blue; 275 | } 276 | } 277 | 278 | // data-module = leaderboard 279 | 280 | // announcements module 281 | &[data-module="announcements"] { 282 | .activity-stream li { 283 | position: relative; 284 | padding: 3px 0 3px 5px; 285 | border-bottom: 1px solid $teal-blue; 286 | } 287 | } 288 | 289 | // data-module = announcements 290 | 291 | // activity module 292 | &[data-module="activity"] { 293 | .activity-stream li { 294 | position: relative; 295 | padding: 4px 0 4px 18px; 296 | 297 | &:before { 298 | content: ""; 299 | position: absolute; 300 | top: 6px; 301 | left: 0; 302 | width: 10px; 303 | height: 10px; 304 | background-repeat: no-repeat; 305 | background-position: center center; 306 | background-size: contain; 307 | } 308 | 309 | &.opponent-team:before { 310 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-300%20391.6%2010%2010.4%22%3E%3Cstyle%3E.st0%7Bfill%3A%23ED2224%3B%7D%3C%2Fstyle%3E%3Cpath%20class%3D%22st0%22%20d%3D%22M-299.7%20391.9l9.1%204.8v.1l-9.1%204.8v-9.7z%22%2F%3E%3C%2Fsvg%3E"); 311 | } 312 | 313 | &.your-team:before { 314 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-300%20391.6%2010%2010.4%22%3E%3Cstyle%3E.st0%7Bfill%3A%23EAD44D%3B%7D%3C%2Fstyle%3E%3Cpath%20class%3D%22st0%22%20d%3D%22M-299.7%20391.9l9.1%204.8v.1l-9.1%204.8v-9.7z%22%2F%3E%3C%2Fsvg%3E"); 315 | } 316 | 317 | span { 318 | @include source-code-bold; 319 | } 320 | } 321 | } 322 | 323 | // data-module = activity 324 | 325 | &[data-module="under-attack"] { 326 | .activity-stream { 327 | li { 328 | margin: 8px 0; 329 | } 330 | 331 | .team-name { 332 | position: relative; 333 | display: inline-block; 334 | color: $red; 335 | padding-left: 18px; 336 | 337 | &:before { 338 | content: ""; 339 | position: absolute; 340 | top: 50%; 341 | left: 0; 342 | width: 10px; 343 | height: 10px; 344 | margin-top: -5px; 345 | background-repeat: no-repeat; 346 | background-position: center center; 347 | background-size: contain; 348 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-300%20391.6%2010%2010.4%22%3E%3Cstyle%3E.st0%7Bfill%3A%23ED2224%3B%7D%3C%2Fstyle%3E%3Cpath%20class%3D%22st0%22%20d%3D%22M-299.7%20391.9l9.1%204.8v.1l-9.1%204.8v-9.7z%22%2F%3E%3C%2Fsvg%3E"); 349 | } 350 | } 351 | } 352 | } 353 | 354 | &[data-module="world-domination"] { 355 | .domination-status { 356 | position: relative; 357 | padding-right: 94px; 358 | 359 | .captured-info { 360 | font-size: .875em; 361 | display: inline-block; 362 | vertical-align: middle; 363 | 364 | span { 365 | display: block; 366 | 367 | &.country { 368 | color: $yellow; 369 | text-transform: uppercase; 370 | } 371 | } 372 | } 373 | 374 | .progress { 375 | @include flexbox; 376 | 377 | align-items: flex-end; 378 | 379 | @include break-min { 380 | margin-top: 10px; 381 | } 382 | 383 | 384 | .progress-pct { 385 | @include flex(0 0 auto); 386 | 387 | font-size: 1.5em; 388 | padding: 0 10px; 389 | } 390 | 391 | .progress-graph { 392 | @include flex(1 0 auto); 393 | 394 | .filled { 395 | stroke: $rate-bar-fill; 396 | } 397 | } 398 | } 399 | 400 | .icon--globe { 401 | position: absolute; 402 | right: 0; 403 | top: 50%; 404 | 405 | @include translate(0, -50%); 406 | } 407 | } 408 | 409 | // domination status 410 | 411 | .game-statuses > div { 412 | margin-top: 10px; 413 | } 414 | } 415 | 416 | // data-module = world domination 417 | 418 | &[data-module="world-chat"] { 419 | &.individual-chat-active .module-content, 420 | &.individual-chat-active .module-header { 421 | visibility: hidden; 422 | } 423 | 424 | .chat-box { 425 | padding: 8px 16px 8px 6px; 426 | 427 | .message-time:before, 428 | .message-author:before { 429 | content: "["; 430 | } 431 | 432 | .message-time:after, 433 | .message-author:after { 434 | content: "]"; 435 | } 436 | 437 | li + li { 438 | margin-top: 6px; 439 | } 440 | } 441 | 442 | .chat-input { 443 | @include flexbox; 444 | 445 | padding-top: 10px; 446 | 447 | .input-container { 448 | @include flex(1 0 auto); 449 | 450 | padding-right: 10px; 451 | 452 | input { 453 | width: 100%; 454 | } 455 | } 456 | 457 | button { 458 | @include flex(0 0 auto); 459 | 460 | font-size: .9em; 461 | } 462 | } 463 | 464 | .world-chat-box { 465 | position: relative; 466 | } 467 | } 468 | 469 | // data-module = world chat 470 | 471 | &[data-module="game-clock"] { 472 | .tutorial-step--game-clock & { 473 | z-index: 11; 474 | 475 | .module-content { 476 | height: calc(33vh - 62px); 477 | margin-top: 10px; 478 | visibility: visible; 479 | opacity: 1; 480 | } 481 | } 482 | 483 | .game-clock { 484 | margin-bottom: 10px; 485 | border-bottom: 1px solid $teal-blue; 486 | font-size: 2.5em; 487 | text-align: center; 488 | padding-bottom: 20px; 489 | 490 | span { 491 | padding-bottom: 10px; 492 | position: relative; 493 | display: inline-block; 494 | width: 50px; 495 | letter-spacing: .1em; 496 | } 497 | 498 | span.three-digit{ 499 | width: 80px; 500 | letter-spacing: 0; 501 | } 502 | 503 | span:before { 504 | content: "00"; 505 | position: absolute; 506 | opacity: .2; 507 | left: 0; 508 | right: 0; 509 | top: 0; 510 | } 511 | 512 | span.three-digit:before { 513 | content: "000" 514 | } 515 | 516 | span:after { 517 | @include purista-bold; 518 | 519 | position: absolute; 520 | font-size: 9px; 521 | bottom: 0; 522 | text-align: center; 523 | left: 50%; 524 | 525 | @include translate(-50%, 0); 526 | 527 | text-transform: uppercase; 528 | } 529 | 530 | .clock-days:after { 531 | content: "day"; 532 | } 533 | 534 | .clock-hours:after { 535 | content: "hr"; 536 | } 537 | 538 | .clock-minutes:after { 539 | content: "min"; 540 | } 541 | 542 | .clock-seconds:after { 543 | content: "sec"; 544 | } 545 | 546 | .clock-milliseconds { 547 | color: darken($teal-blue, 10%); 548 | 549 | &:after { 550 | content: "m.sec"; 551 | } 552 | } 553 | 554 | @include break-min(1074px) { 555 | font-size: 3em; 556 | 557 | span { 558 | width: 60px; 559 | } 560 | } 561 | 562 | 563 | @include break-min(1180px) { 564 | font-size: 4em; 565 | 566 | span { 567 | width: 66px; 568 | } 569 | } 570 | 571 | 572 | @include break-min { 573 | span { 574 | width: 80px; 575 | } 576 | } 577 | } 578 | } 579 | 580 | // data-module = game clock 581 | 582 | } 583 | } 584 | 585 | /* -------------------------------------------- 586 | * --map specific 587 | * -------------------------------------------- */ 588 | 589 | body[data-section="gameboard"] .fb-page { 590 | overflow: hidden; 591 | } 592 | 593 | .fb-map { 594 | position: absolute; 595 | top: 0; 596 | left: 0; 597 | right: 0; 598 | bottom: 0; 599 | overflow: hidden; 600 | 601 | .gameboard--viewmode & { 602 | bottom: 33%; 603 | } 604 | 605 | svg { 606 | height: 100vh; 607 | width: 100%; 608 | 609 | .gameboard--viewmode & { 610 | height: calc(100vh - 33.3%); 611 | } 612 | 613 | // the indicator 614 | .map-indicator { 615 | opacity: 0; 616 | pointer-events: none; 617 | 618 | &.captured--you { 619 | fill: $yellow; 620 | } 621 | 622 | &.captured--opponent { 623 | stroke: $red; 624 | stroke-width: 2; 625 | } 626 | } 627 | 628 | // land paths 629 | .land { 630 | fill: $black; 631 | fill-opacity: 1; 632 | stroke: $main-blue; 633 | stroke-opacity: 1; 634 | stroke-width: 1.5; 635 | 636 | &.active { 637 | fill: #1f3b47; 638 | stroke: #36687c; 639 | stroke-width: 1; 640 | } 641 | } 642 | 643 | .country-hover .land { 644 | fill: transparent; 645 | } 646 | 647 | .countries { 648 | g[data-captured] .map-indicator { 649 | opacity: 1; 650 | } 651 | 652 | g.inactive { 653 | opacity: .2; 654 | } 655 | 656 | g.highlighted .land { 657 | stroke-width: 2; 658 | } 659 | } 660 | 661 | .country-clicked { 662 | .map-indicator[class*="captured--"] { 663 | opacity: 1; 664 | } 665 | 666 | .latitude-focus, 667 | .longitude-focus, 668 | .crosshairs { 669 | @include transition(all 0.3s); 670 | } 671 | 672 | .crosshairs { 673 | @include animation(crosshairs-flicker 0.3s linear 0.3s 1); 674 | 675 | .crosshairs-rotate { 676 | @include animation(crosshairs-rotate 0.3s linear 0.3s 1); 677 | } 678 | } 679 | } 680 | 681 | // country - active state 682 | .country-clicked .land, 683 | .country-hover .land { 684 | stroke: $yellow; 685 | stroke-width: 1; 686 | } 687 | } 688 | } 689 | 690 | /* -------------------------------------------- 691 | * --alerts 692 | * -------------------------------------------- */ 693 | .alerts { 694 | position: absolute; 695 | left: -20px; 696 | right: -20px; 697 | bottom: 100%; 698 | padding-bottom: 20px; 699 | 700 | .tutorial-active &, 701 | .listview-enabled & { 702 | display: none; 703 | } 704 | 705 | .alert-placeholder { 706 | display: none; 707 | } 708 | 709 | & > li { 710 | background: rgba($main-blue, 0.85); 711 | border: 2px solid $red; 712 | border-radius: 20px; 713 | opacity: 1; 714 | visibility: visible; 715 | 716 | @include animation(alert-pulse 1s linear 0s infinite); 717 | 718 | @include transition(opacity 0.3s, visibility 0.3s); 719 | 720 | &.removing, 721 | &.loadin { 722 | opacity: 0; 723 | visibility: hidden; 724 | } 725 | 726 | & + li { 727 | margin-top: 10px; 728 | } 729 | 730 | & > div { 731 | padding: 6px 10px; 732 | } 733 | } 734 | 735 | .alert-main { 736 | @include flexbox; 737 | 738 | @include align-center; 739 | } 740 | 741 | .alert { 742 | @include flex(1 0 auto); 743 | 744 | &:before { 745 | margin-right: 10px; 746 | } 747 | } 748 | 749 | .alert--actionable { 750 | @include flex(0 0 auto); 751 | 752 | .fb-cta { 753 | font-size: .8em; 754 | display: none; 755 | } 756 | 757 | &.out-of-time { 758 | .fb-cta { 759 | display: inline-block; 760 | } 761 | 762 | .fb-numbers { 763 | display: none; 764 | } 765 | } 766 | 767 | .icon { 768 | width: 14px; 769 | height: 14px; 770 | margin: 0 4px; 771 | stroke: $red; 772 | fill: none; 773 | } 774 | } 775 | 776 | .individual-chat { 777 | display: none; 778 | padding-top: 20px; 779 | overflow: hidden; 780 | } 781 | 782 | // the enabled state 783 | &.individual-chat-enabled { 784 | top: 0; 785 | bottom: 0; 786 | left: -10px; 787 | right: -10px; 788 | padding: 0; 789 | 790 | & > li { 791 | display: none; 792 | margin: 0; 793 | border-radius: $border-radius; 794 | 795 | &.active { 796 | @include flexbox; 797 | 798 | flex-direction: column; 799 | position: absolute; 800 | top: 0; 801 | bottom: 0; 802 | left: 0; 803 | right: 0; 804 | 805 | .alert-main { 806 | @include flex(0 0 auto); 807 | 808 | font-size: 1.5em; 809 | border-bottom: 1px solid $red; 810 | 811 | .alert:before { 812 | display: none; 813 | } 814 | } 815 | 816 | .individual-chat { 817 | @include flex(1 1 auto); 818 | 819 | @include flexbox; 820 | 821 | background-color: $main-blue-darken; 822 | } 823 | } 824 | } 825 | } 826 | } 827 | 828 | /* -------------------------------------------- 829 | * --loading 830 | * -------------------------------------------- */ 831 | 832 | .fb-loading { 833 | @include flexbox; 834 | 835 | @include align-center; 836 | 837 | position: absolute; 838 | top: 0; 839 | bottom: 0; 840 | right: 0; 841 | left: 0; 842 | background: $main-blue; 843 | z-index: 10; 844 | visibility: hidden; 845 | opacity: 0; 846 | 847 | .gameboard-loading { 848 | max-width: 520px; 849 | } 850 | 851 | .loading & { 852 | visibility: visible; 853 | opacity: 1; 854 | } 855 | 856 | h2 { 857 | color: $yellow; 858 | 859 | @include purista-bold; 860 | 861 | padding: 0; 862 | } 863 | 864 | .game-progress { 865 | margin-top: 2em; 866 | } 867 | 868 | .boot-sequence { 869 | margin-top: 2em; 870 | font-size: .8em; 871 | } 872 | } 873 | 874 | /* -------------------------------------------- 875 | * --capture country popup 876 | * -------------------------------------------- */ 877 | 878 | .capture-hints-and-help { 879 | & > div { 880 | display: none; 881 | padding: 0 40px 40px 40px; 882 | } 883 | 884 | .hint-enabled & .capture-hint, 885 | .help-enabled & .capture-help { 886 | display: block; 887 | } 888 | 889 | h4 { 890 | margin-bottom: 10px; 891 | } 892 | } 893 | 894 | .country-capture-form { 895 | .help--time-left { 896 | font-size: 2em; 897 | display: none; 898 | } 899 | 900 | .help-enabled & { 901 | .help--time-left { 902 | display: inline-block; 903 | } 904 | 905 | .actions--right .fb-cta { 906 | display: none; 907 | } 908 | } 909 | } 910 | 911 | .country-capture-stats .country-stats { 912 | padding-left: 20px; 913 | } 914 | 915 | .country-capture-completed { 916 | span { 917 | color: $yellow; 918 | } 919 | 920 | .completed-list { 921 | position: absolute; 922 | top: 0; 923 | left: 50%; 924 | right: 0; 925 | bottom: 0; 926 | padding: 4px 20px 4px 0; 927 | padding-right: 20px; 928 | overflow: auto; 929 | 930 | li { 931 | @extend .opponent-name; 932 | 933 | @include purista-medium; 934 | 935 | margin: 4px 0; 936 | } 937 | } 938 | } 939 | 940 | /* -------------------------------------------- 941 | * --scoreboard 942 | * -------------------------------------------- */ 943 | 944 | .modal--default[class*="modal--scoreboard"] .fb-modal-content { 945 | max-width: 860px; 946 | max-height: 100vh; 947 | padding-top: 20px; 948 | padding-bottom: 20px; 949 | 950 | .fb-centered-main { 951 | padding-bottom: 40px; 952 | } 953 | } 954 | 955 | .game-scoreboard { 956 | @include flex(0 1 auto); 957 | 958 | overflow: hidden; 959 | padding-top: 40px; 960 | 961 | .main-data { 962 | max-height: 300px; 963 | border-top: 1px solid $teal-blue; 964 | 965 | .el--radio { 966 | text-align: center; 967 | } 968 | } 969 | } 970 | 971 | /** 972 | * --graphic 973 | */ 974 | .scoreboard-graphic { 975 | margin: 20px 0; 976 | 977 | svg .axis { 978 | line, path { 979 | fill: none; 980 | stroke: $light-blue; 981 | stroke-width: 2; 982 | shape-rendering: crispEdges; 983 | } 984 | 985 | text { 986 | stroke: #75a5ba; 987 | } 988 | } 989 | } 990 | 991 | /* ------------------------------------------ 992 | * --listview 993 | * ------------------------------------------ */ 994 | 995 | .fb-listview { 996 | position: absolute; 997 | top: 0; 998 | bottom: 0; 999 | left: 0; 1000 | right: 0; 1001 | overflow: hidden; 1002 | background: rgba($main-blue, 0.8); 1003 | display: none; 1004 | 1005 | .listview-container { 1006 | position: absolute; 1007 | top: 180px; 1008 | bottom: 100px; 1009 | left: $gameboard-margin; 1010 | right: $gameboard-margin; 1011 | overflow: auto; 1012 | 1013 | @include transition(all 0.4s); 1014 | } 1015 | 1016 | table { 1017 | border-collapse: separate; 1018 | padding: 4px; 1019 | 1020 | tr:nth-child(even) td { 1021 | background: rgba(#20364b, 0.5); 1022 | } 1023 | 1024 | tr:hover { 1025 | outline: 1px solid $yellow; 1026 | } 1027 | 1028 | td { 1029 | padding: 20px; 1030 | 1031 | // country name 1032 | &:nth-child(1) { 1033 | @include purista-bold; 1034 | 1035 | color: $light-blue; 1036 | } 1037 | 1038 | // country points 1039 | &:nth-child(2) { 1040 | color: $yellow; 1041 | } 1042 | 1043 | // country category 1044 | &:nth-child(3) { 1045 | color: $teal-blue; 1046 | } 1047 | 1048 | // status 1049 | &:nth-child(4) { 1050 | @include source-code-regular; 1051 | 1052 | text-transform: none; 1053 | letter-spacing: 0; 1054 | } 1055 | } 1056 | 1057 | tr:not(.help-enabled):not(.captured--you) td, 1058 | tr.help-enabled td:nth-child(4) { 1059 | cursor: pointer; 1060 | } 1061 | 1062 | tr.captured--you td:nth-child(-n+3), 1063 | tr.help-enabled td:nth-child(-n+3), 1064 | tr.inactive td { 1065 | opacity: .4; 1066 | } 1067 | 1068 | tr.captured--you:nth-child(even) td:nth-child(4), 1069 | tr:nth-child(even).help-enabled td:nth-child(4) { 1070 | background: rgba(#20364b, 0.2); 1071 | } 1072 | } 1073 | } 1074 | 1075 | .listview-enabled { 1076 | .fb-listview { 1077 | display: block; 1078 | } 1079 | 1080 | .fb-module-container.container--column.column-left aside { 1081 | background: transparent; 1082 | } 1083 | 1084 | .gameboard-header .fb-map-select label.listview-only { 1085 | display: inline-block; 1086 | } 1087 | 1088 | &.fb-gameboard aside[data-module="teams"] { 1089 | display: none; 1090 | } 1091 | 1092 | &.fb-gameboard aside[data-module="world-domination"] { 1093 | .module-header, 1094 | .game-statuses, 1095 | .recently-captured { 1096 | display: none; 1097 | } 1098 | 1099 | .module-content { 1100 | height: 132px; 1101 | visibility: visible; 1102 | opacity: 1; 1103 | } 1104 | 1105 | .domination-status .progress { 1106 | margin-top: 0; 1107 | padding-top: 20px; 1108 | padding-bottom: 20px; 1109 | } 1110 | } 1111 | 1112 | .alerts.individual-chat-enabled { 1113 | display: block; 1114 | } 1115 | } 1116 | -------------------------------------------------------------------------------- /IndexController.php: -------------------------------------------------------------------------------- 1 | > 5 | public function getTitle(): string { 6 | $custom_org = \HH\Asio\join(Configuration::gen('custom_org')); 7 | return tr($custom_org->getValue()).' '.tr('CTF'); 8 | } 9 | 10 | <<__Override>> 11 | protected function getFilters(): array { 12 | return array( 13 | 'GET' => array( 14 | 'page' => array( 15 | 'filter' => FILTER_VALIDATE_REGEXP, 16 | 'options' => array('regexp' => '/^[\w-]+$/'), 17 | ), 18 | 'action' => array( 19 | 'filter' => FILTER_VALIDATE_REGEXP, 20 | 'options' => array('regexp' => '/^[\w-]+$/'), 21 | ), 22 | ), 23 | ); 24 | } 25 | 26 | <<__Override>> 27 | protected function getPages(): array { 28 | return array( 29 | 'main', 30 | 'countdown', 31 | 'rules', 32 | 'registration', 33 | 'login', 34 | 'error', 35 | 'mobile', 36 | 'game', 37 | 'admin', 38 | ); 39 | } 40 | 41 | public function renderMainContent(): :xhp { 42 | $custom_org = \HH\Asio\join(Configuration::gen('custom_org')); 43 | if ($custom_org->getValue() === 'Facebook') { 44 | $welcome_msg = 45 | tr( 46 | 'Welcome to the Facebook Capture the Flag Competition. By clicking "Play," you will be entered into the official CTF challenge. Good luck in your conquest.', 47 | ); 48 | } else { 49 | $welcome_msg = 50 | tr('Welcome to the '). 51 | $custom_org->getValue(). 52 | tr(' Capture the Flag Competition. By clicking "Play," you will be entered into the official CTF challenge. Good luck in your conquest.'); 53 | } 54 | return 55 |
56 |
60 |
61 |

62 | {tr('Conquer the world')} 63 |

64 |

65 | {$welcome_msg} 66 |

67 | 72 |
73 |
74 |
; 75 | } 76 | 77 | public async function genRenderCountdownContent(): Awaitable<:xhp> { 78 | if (SessionUtils::sessionActive()) { 79 | $play_nav = 80 |
81 |

82 | {tr( 83 | 'Get ready for the CTF to start and access the gameboard now!', 84 | )} 85 |

86 | 91 |
; 92 | } else { 93 | $registration = await Configuration::gen('registration'); 94 | if ($registration->getValue() === '1') { 95 | $registration_button = 96 | 100 | {tr('Register Team')} 101 | ; 102 | } else { 103 | $registration_button = ; 104 | } 105 | $play_nav = 106 |
107 |

108 | {tr('Get ready for the CTF to start and register your team now!')} 109 |

110 |
111 | {$registration_button} 112 | 116 | {tr('Login')} 117 | 118 |
119 |
; 120 | } 121 | list($config_game, $config_next_game) = await \HH\Asio\va( 122 | Configuration::gen('game'), 123 | Configuration::gen('next_game'), 124 | ); 125 | $game = $config_game->getValue(); 126 | $next_game = $config_next_game->getValue(); 127 | if ($game === '1') { 128 | $next_game_text = tr('In Progress'); 129 | $countdown = array('--', '--', '--', '--'); 130 | } else if ($next_game === '0' || intval($next_game) < time()) { 131 | $next_game_text = tr('Soon'); 132 | $countdown = array('--', '--', '--', '--'); 133 | } else { 134 | $next_game_text = date(tr('date and time format'), $next_game); 135 | $game_start = new DateTime(); 136 | $game_start->setTimestamp(intval($next_game)); 137 | $now = new DateTime('now'); 138 | $countdown_diff = $now->diff($game_start); 139 | $countdown = explode('-', $countdown_diff->format('%d-%h-%i-%s')); 140 | } 141 | return 142 |
143 |
147 |
148 |

{tr('Upcoming Game')}

149 |

150 | {$next_game_text} 151 |

152 |
    153 |
  • 154 | {$countdown[0]} 155 | {tr('_days')} 156 |
  • 157 |
  • 158 | {$countdown[1]} 159 | {tr('_hours')} 160 |
  • 161 |
  • 162 | {$countdown[2]} 163 | {tr('_minutes')} 164 |
  • 165 |
  • 166 | {$countdown[3]} 167 | {tr('_seconds')} 168 |
  • 169 |
170 | {$play_nav} 171 |
172 |
173 |
; 174 | } 175 | 176 | public function renderRulesContent(): :xhp { 177 | return 178 |
179 |
180 |
181 |

182 | {tr('Official CTF Rules')} 183 |

184 |

185 | {tr( 186 | 'Following actions are prohibited, unless explicitly told otherwise by event Admins.', 187 | )} 188 |

189 |
190 |
191 |
192 |
193 |

{tr('Rule')} 1

194 |
{tr('Cooperation')}
195 |
196 |
197 |

198 | {tr( 199 | 'No cooperation between teams with independent accounts. Sharing of keys or providing revealing hints to other teams is cheating, don’t do it.', 200 | )} 201 |

202 |

203 |
204 |
205 |
206 |
207 |

{tr('Rule')} 2

208 |
{tr('Attacking Scoreboard')}
209 |
210 |
211 |

212 | {tr( 213 | 'No attacking the competition infrastructure. If bugs or vulns are found, please alert the competition organizers immediately.', 214 | )} 215 |

216 |

217 |
218 |
219 |
220 |
221 |

{tr('Rule')} 3

222 |
{tr('Sabotage')}
223 |
224 |
225 |

226 | {tr( 227 | 'Absolutely no sabotaging of other competing teams, or in any way hindering their independent progress.', 228 | )} 229 |

230 |

231 |
232 |
233 |
234 |
235 |

{tr('Rule')} 4

236 |
{tr('Bruteforcing')}
237 |
238 |
239 |

240 | {tr( 241 | 'No brute forcing of challenge flag/ keys against the scoring site.', 242 | )} 243 |

244 |

245 |
246 |
247 |
248 |
249 |

{tr('Rule')} 5

250 |
{tr('Denial Of Service')}
251 |
252 |
253 |

254 | {tr( 255 | 'DoSing the CTF platform or any of the challenges is forbidden.', 256 | )} 257 |

258 |

259 |
260 |
261 |
262 |
263 |

{tr('Legal')}

264 |
{tr('Disclaimer')}
265 |
266 |
267 |

268 | {tr( 269 | 'By participating in the contest, you agree to release Facebook and its employees, and the hosting organization from any and all liability, claims or actions of any kind whatsoever for injuries, damages or losses to persons and property which may be sustained in connection with the contest. You acknowledge and agree that Facebook et al is not responsible for technical, hardware or software failures, or other errors or problems which may occur in connection with the contest.', 270 | )} 271 |

272 |
273 |
274 |

275 | {tr( 276 | 'If you have any questions about what is or is not allowed, please ask an organizer.', 277 | )} 278 |

279 |

280 |

{tr('Have fun!')}

281 |

282 |
283 |
284 |
; 285 | } 286 | 287 | public async function genRenderLogosSelection(): Awaitable<:xhp> { 288 | return ; 289 | } 290 | 291 | public async function genRenderRegistrationNames(): Awaitable<:xhp> { 292 | $awaitables = Map { 293 | 'login_facebook' => Configuration::gen('login_facebook'), 294 | 'login_google' => Configuration::gen('login_google'), 295 | 'registration_players' => Configuration::gen('registration_players'), 296 | 'registration_facebook' => Configuration::gen('registration_facebook'), 297 | 'registration_google' => Configuration::gen('registration_google'), 298 | 'registration_type' => Configuration::gen('registration_type'), 299 | 'ldap' => Configuration::gen('ldap'), 300 | 'logos_selection' => $this->genRenderLogosSelection(), 301 | 'facebook_enabled' => Integration::facebookOAuthEnabled(), 302 | 'google_enabled' => Integration::googleOAuthEnabled(), 303 | }; 304 | $awaitables_results = await \HH\Asio\m($awaitables); 305 | 306 | $login_facebook = $awaitables_results['login_facebook']; 307 | $login_google = $awaitables_results['login_google']; 308 | $registration_players = $awaitables_results['registration_players']; 309 | $registration_facebook = $awaitables_results['registration_facebook']; 310 | $registration_google = $awaitables_results['registration_google']; 311 | $registration_type = $awaitables_results['registration_type']; 312 | $ldap = $awaitables_results['ldap']; 313 | $logos_selection = $awaitables_results['logos_selection']; 314 | $facebook_enabled = $awaitables_results['facebook_enabled']; 315 | $google_enabled = $awaitables_results['google_enabled']; 316 | 317 | invariant( 318 | $login_facebook instanceof Configuration, 319 | 'login_facebook should be of type Configuration', 320 | ); 321 | invariant( 322 | $login_google instanceof Configuration, 323 | 'login_google should be of type Configuration', 324 | ); 325 | invariant( 326 | $registration_players instanceof Configuration, 327 | 'registration_players should be of type Configuration', 328 | ); 329 | invariant( 330 | $registration_facebook instanceof Configuration, 331 | 'registration_facebook should be of type Configuration', 332 | ); 333 | invariant( 334 | $registration_google instanceof Configuration, 335 | 'registration_google should be of type Configuration', 336 | ); 337 | invariant( 338 | $registration_type instanceof Configuration, 339 | 'registration_type should be of type Configuration', 340 | ); 341 | invariant( 342 | $ldap instanceof Configuration, 343 | 'ldap should be of type Configuration', 344 | ); 345 | 346 | $players = intval($registration_players->getValue()); 347 | $names_ul =
    ; 348 | 349 | for ($i = 1; $i <= $players; $i++) { 350 | $name_ = 'registration_name_'.$i; 351 | $email_ = 'registration_email_'.$i; 352 | $names_ul->appendChild( 353 |
  • 354 |
    355 | 356 | 357 |
    358 |
    359 | 360 | 361 |
    362 |
  • 363 | ); 364 | } 365 | 366 | if ($registration_type->getValue() === '2') { 367 | $token_field = 368 |
    369 | 370 | 371 |
    ; 372 | } else { 373 | $token_field =
    ; 374 | } 375 | 376 | $ldap_domain_suffix = ""; 377 | if ($ldap->getValue() === '1') { 378 | $ldap_domain_suffix = await Configuration::gen('ldap_domain_suffix'); 379 | $ldap_domain_suffix = $ldap_domain_suffix->getValue(); 380 | } 381 | 382 | $page_header = 383 |
    384 |

    385 | {tr('Team Registration')} 386 |

    387 |
    ; 388 | 389 | $oauth_header = ''; 390 | $oauth_form = ''; 391 | 392 | if ((($facebook_enabled === true) || ($google_enabled === true)) && 393 | (($registration_facebook->getValue() === '1') || 394 | ($registration_google->getValue() === '1'))) { 395 | $oauth_header = 396 |
    397 |

    398 | {tr( 399 | 'Register to play Capture The Flag with one of the options below. Once you select an option, you will be registered and logged in.', 400 | )} 401 |

    402 |
    ; 403 | if (($facebook_enabled === true) && 404 | (($login_facebook->getValue() === '1') || 405 | ($registration_facebook->getValue() === '1'))) { 406 | $facebook_button = 407 | ; 414 | } else { 415 | $facebook_button = ''; 416 | } 417 | if (($google_enabled === true) && 418 | (($login_google->getValue() === '1') || 419 | ($registration_google->getValue() === '1'))) { 420 | $google_button = 421 | ; 428 | } else { 429 | $google_button = ''; 430 | } 431 | $oauth_form = 432 |
    433 | {$facebook_button} 434 | {$google_button} 435 |
    ; 436 | } 437 | 438 | $registration_header = 439 |
    440 |

    441 | {tr( 442 | 'Or register to use username and password here. Once you have registered, you will be logged in.', 443 | )} 444 |

    445 |
    ; 446 | 447 | $registration_form = 448 |
    449 |
    450 | 451 |
    452 | {$names_ul} 453 |
    454 |

    455 |
    456 |
    457 | 458 | 465 | {$ldap_domain_suffix} 466 |
    467 |
    468 | 469 | 470 |
    471 | {$token_field} 472 |
    473 |
    474 |
    {tr('Choose an Emblem')}
    475 |
    476 | 477 | {tr('or upload your own')} 478 | 479 |
    480 |
    481 | 488 | 489 | 490 |
    491 | 505 |
    506 |
    507 |

    508 | 514 |

    515 |
    516 |
    517 |
    ; 518 | 519 | return 520 |
    523 | {$page_header} 524 | {$oauth_header} 525 | {$oauth_form} 526 | {$registration_header} 527 | {$registration_form} 528 |
    ; 529 | } 530 | 531 | public async function genRenderRegistrationNoNames(): Awaitable<:xhp> { 532 | $awaitables = Map { 533 | 'login_facebook' => Configuration::gen('login_facebook'), 534 | 'login_google' => Configuration::gen('login_google'), 535 | 'registration_facebook' => Configuration::gen('registration_facebook'), 536 | 'registration_google' => Configuration::gen('registration_google'), 537 | 'registration_type' => Configuration::gen('registration_type'), 538 | 'ldap' => Configuration::gen('ldap'), 539 | 'logos_selection' => $this->genRenderLogosSelection(), 540 | 'facebook_enabled' => Integration::facebookOAuthEnabled(), 541 | 'google_enabled' => Integration::googleOAuthEnabled(), 542 | }; 543 | $awaitables_results = await \HH\Asio\m($awaitables); 544 | 545 | $login_facebook = $awaitables_results['login_facebook']; 546 | $login_google = $awaitables_results['login_google']; 547 | $registration_facebook = $awaitables_results['registration_facebook']; 548 | $registration_google = $awaitables_results['registration_google']; 549 | $registration_type = $awaitables_results['registration_type']; 550 | $ldap = $awaitables_results['ldap']; 551 | $logos_selection = $awaitables_results['logos_selection']; 552 | $facebook_enabled = $awaitables_results['facebook_enabled']; 553 | $google_enabled = $awaitables_results['google_enabled']; 554 | 555 | invariant( 556 | $login_facebook instanceof Configuration, 557 | 'login_facebook should be of type Configuration', 558 | ); 559 | invariant( 560 | $login_google instanceof Configuration, 561 | 'login_google should be of type Configuration', 562 | ); 563 | invariant( 564 | $registration_facebook instanceof Configuration, 565 | 'registration_facebook should be of type Configuration', 566 | ); 567 | invariant( 568 | $registration_google instanceof Configuration, 569 | 'registration_google should be of type Configuration', 570 | ); 571 | invariant( 572 | $registration_type instanceof Configuration, 573 | 'registration_type should be of type Configuration', 574 | ); 575 | invariant( 576 | $ldap instanceof Configuration, 577 | 'ldap should be of type Configuration', 578 | ); 579 | 580 | if ($registration_type->getValue() === '2') { 581 | $token_field = 582 |
    583 | 584 | 585 |
    ; 586 | } else { 587 | $token_field =
    ; 588 | } 589 | 590 | $ldap_domain_suffix = ""; 591 | if ($ldap->getValue() === '1') { 592 | $ldap_domain_suffix = await Configuration::gen('ldap_domain_suffix'); 593 | $ldap_domain_suffix = $ldap_domain_suffix->getValue(); 594 | } 595 | 596 | $page_header = 597 |
    598 |

    599 | {tr('Team Registration')} 600 |

    601 |
    ; 602 | 603 | $oauth_header = ''; 604 | $oauth_form = ''; 605 | 606 | if ((($facebook_enabled === true) || ($google_enabled === true)) && 607 | (($registration_facebook->getValue() === '1') || 608 | ($registration_google->getValue() === '1'))) { 609 | $oauth_header = 610 |
    611 |

    612 | {tr( 613 | 'Register to play Capture The Flag with one of the options below. Once you select an option, you will be registered and logged in.', 614 | )} 615 |

    616 |
    ; 617 | if (($facebook_enabled === true) && 618 | (($login_facebook->getValue() === '1') || 619 | ($registration_facebook->getValue() === '1'))) { 620 | $facebook_button = 621 | ; 628 | } else { 629 | $facebook_button = ''; 630 | } 631 | if (($google_enabled === true) && 632 | (($login_google->getValue() === '1') || 633 | ($registration_google->getValue() === '1'))) { 634 | $google_button = 635 | ; 642 | } else { 643 | $google_button = ''; 644 | } 645 | $oauth_form = 646 |
    647 | {$facebook_button} 648 | {$google_button} 649 |
    ; 650 | } 651 | 652 | $registration_header = 653 |
    654 |

    655 | {tr( 656 | 'Or register to use username and password here. Once you have registered, you will be logged in.', 657 | )} 658 |

    659 |
    ; 660 | 661 | $registration_form = 662 |
    663 |
    664 | 665 |
    666 |
    667 | 668 | 675 | {$ldap_domain_suffix} 676 |
    677 |
    678 | 679 | 680 |
    681 |
    682 | 683 |
    {tr('Password is too simple')}
    684 |
    685 | {$token_field} 686 |
    687 |
    688 |
    {tr('Choose an Emblem')}
    689 |
    690 | 691 | {tr('or upload your own')} 692 | 693 |
    694 |
    695 | 702 | 703 | 704 |
    705 | 719 |
    720 |
    721 |

    722 | 728 |

    729 |
    730 |
    731 |
    ; 732 | 733 | return 734 |
    737 | {$page_header} 738 | {$oauth_header} 739 | {$oauth_form} 740 | {$registration_header} 741 | {$registration_form} 742 |
    ; 743 | } 744 | 745 | public async function genRenderRegistrationContent(): Awaitable<:xhp> { 746 | $awaitables = Map { 747 | 'registration' => Configuration::gen('registration'), 748 | 'registration_facebook' => Configuration::gen('registration_facebook'), 749 | 'registration_google' => Configuration::gen('registration_google'), 750 | 'registration_names' => Configuration::gen('registration_names'), 751 | 'facebook_enabled' => Integration::facebookOAuthEnabled(), 752 | 'google_enabled' => Integration::googleOAuthEnabled(), 753 | }; 754 | $awaitables_results = await \HH\Asio\m($awaitables); 755 | 756 | $registration = $awaitables_results['registration']; 757 | $registration_facebook = $awaitables_results['registration_facebook']; 758 | $registration_google = $awaitables_results['registration_google']; 759 | $registration_names = $awaitables_results['registration_names']; 760 | $facebook_enabled = $awaitables_results['facebook_enabled']; 761 | $google_enabled = $awaitables_results['google_enabled']; 762 | 763 | invariant( 764 | $registration instanceof Configuration, 765 | 'registration should be of type Configuration', 766 | ); 767 | invariant( 768 | $registration_facebook instanceof Configuration, 769 | 'registration_facebook should be of type Configuration', 770 | ); 771 | invariant( 772 | $registration_google instanceof Configuration, 773 | 'registration_google should be of type Configuration', 774 | ); 775 | invariant( 776 | $registration_names instanceof Configuration, 777 | 'registration_names should be of type Configuration', 778 | ); 779 | 780 | if ($registration->getValue() === '1') { 781 | if ($registration_names->getValue() === '1') { 782 | return await $this->genRenderRegistrationNames(); 783 | } else { 784 | return await $this->genRenderRegistrationNoNames(); 785 | } 786 | } else if ((($facebook_enabled === true) || ($google_enabled === true)) && 787 | (($registration_facebook->getValue() === '1') || 788 | ($registration_google->getValue() === '1'))) { 789 | if (($facebook_enabled === true) && 790 | ($registration_facebook->getValue() === '1')) { 791 | $facebook_button = 792 | ; 799 | } else { 800 | $facebook_button = ''; 801 | } 802 | if (($google_enabled === true) && 803 | ($registration_google->getValue() === '1')) { 804 | $google_button = 805 | ; 812 | } else { 813 | $google_button = ''; 814 | } 815 | return 816 |
    819 |
    820 |

    821 | {tr('Team Registration')} 822 |

    823 |

    824 | {tr( 825 | 'Register to play Capture The Flag with one of the options below. Once you have registered, you will be logged in.', 826 | )} 827 |

    828 |
    829 |
    830 | {$facebook_button} 831 | {$google_button} 832 |
    833 |
    ; 834 | } else { 835 | return 836 |
    837 |
    841 |
    842 |

    {tr('Team Registration')}

    843 |

    844 | {tr('Not Available')} 845 |

    846 |
    847 |

    848 | {tr('Team Registration will be open soon, stay tuned!')} 849 |

    850 | 857 |
    858 |
    859 |
    860 |
    ; 861 | } 862 | } 863 | 864 | public async function genRenderLoginContent(): Awaitable<:xhp> { 865 | header('Login-Page: true'); 866 | if (SessionUtils::sessionActive()) { 867 | throw new IndexRedirectException(); 868 | } 869 | $awaitables = Map { 870 | 'login' => Configuration::gen('login'), 871 | 'login_facebook' => Configuration::gen('login_facebook'), 872 | 'login_google' => Configuration::gen('login_google'), 873 | 'registration' => Configuration::gen('registration'), 874 | 'registration_facebook' => Configuration::gen('registration_facebook'), 875 | 'registration_google' => Configuration::gen('registration_google'), 876 | 'ldap' => Configuration::gen('ldap'), 877 | 'facebook_enabled' => Integration::facebookOAuthEnabled(), 878 | 'google_enabled' => Integration::googleOAuthEnabled(), 879 | }; 880 | $awaitables_results = await \HH\Asio\m($awaitables); 881 | 882 | $login = $awaitables_results['login']; 883 | $login_facebook = $awaitables_results['login_facebook']; 884 | $login_google = $awaitables_results['login_google']; 885 | $registration = $awaitables_results['registration']; 886 | $registration_facebook = $awaitables_results['registration_facebook']; 887 | $registration_google = $awaitables_results['registration_google']; 888 | $ldap = $awaitables_results['ldap']; 889 | $facebook_enabled = $awaitables_results['facebook_enabled']; 890 | $google_enabled = $awaitables_results['google_enabled']; 891 | 892 | invariant( 893 | $login instanceof Configuration, 894 | 'login should be of type Configuration', 895 | ); 896 | invariant( 897 | $login_facebook instanceof Configuration, 898 | 'login_facebook should be of type Configuration', 899 | ); 900 | invariant( 901 | $login_google instanceof Configuration, 902 | 'login_google should be of type Configuration', 903 | ); 904 | invariant( 905 | $registration instanceof Configuration, 906 | 'registration should be of type Configuration', 907 | ); 908 | invariant( 909 | $registration_facebook instanceof Configuration, 910 | 'registration_facebook should be of type Configuration', 911 | ); 912 | invariant( 913 | $registration_google instanceof Configuration, 914 | 'registration_google should be of type Configuration', 915 | ); 916 | invariant( 917 | $ldap instanceof Configuration, 918 | 'ldap should be of type Configuration', 919 | ); 920 | 921 | $ldap_domain_suffix = ""; 922 | if ($ldap->getValue() === '1') { 923 | $ldap_domain_suffix = await Configuration::gen('ldap_domain_suffix'); 924 | $ldap_domain_suffix = $ldap_domain_suffix->getValue(); 925 | } 926 | 927 | if ((($facebook_enabled === true) || ($google_enabled === true)) && 928 | (($login_facebook->getValue() === '1') || 929 | ($login_google->getValue() === '1'))) { 930 | if (($facebook_enabled === true) && 931 | ($login_facebook->getValue() === '1')) { 932 | $facebook_button_text = tr('Login with Facebook Account'); 933 | if ($registration_facebook->getValue() === '1') { 934 | $facebook_button_text = 935 | tr('Login or Register with Facebook Account'); 936 | } 937 | $facebook_button = 938 | ; 945 | } else { 946 | $facebook_button = ''; 947 | } 948 | if (($google_enabled === true) && ($login_google->getValue() === '1')) { 949 | $google_button_text = tr('Login with Google Account'); 950 | if ($registration_google->getValue() === '1') { 951 | $google_button_text = tr('Login or Register with Google Account'); 952 | } 953 | $google_button = 954 | ; 961 | } else { 962 | $google_button = ''; 963 | } 964 | $oauth_header_message = 965 | tr( 966 | 'Or login with these one of these options (existing account is required):', 967 | ); 968 | if (($registration_facebook->getValue() === '1') || 969 | ($registration_google->getValue() === '1')) { 970 | $oauth_header_message = 971 | tr('Or login/register with these one of these options:'); 972 | } 973 | $oauth_header = 974 |
    975 |

    976 | {$oauth_header_message} 977 |

    978 |
    ; 979 | $oauth_form = 980 | ; 984 | } else { 985 | $oauth_header = ''; 986 | $oauth_form = ''; 987 | } 988 | 989 | if ($login->getValue() === '1') { 990 | $login_team = 991 | ; 998 | $login_select = "off"; 999 | $login_select_config = await Configuration::gen('login_select'); 1000 | if ($login_select_config->getValue() === '1') { 1001 | $login_select = "on"; 1002 | $login_team = 1043 | 1044 |
    1045 |
    1046 | 1047 | {$login_team} {$ldap_domain_suffix} 1048 |
    1049 |
    1050 | 1051 | 1052 |
    1053 |
    1054 |
    1055 | 1061 |
    1062 | 1065 | 1066 | ; 1067 | 1068 | $login_form = 1069 |
    1070 | {$login_header} 1071 | {$login_form} 1072 | {$oauth_header} 1073 | {$oauth_form} 1074 |
    ; 1075 | 1076 | return $login_form; 1077 | } else if (Utils::getGET()->get('admin') === 'true') { 1078 | return 1079 |
    1080 |
    1081 |

    1082 | {tr('Admin Login')} 1083 |

    1084 |

    1085 | {tr( 1086 | 'Team login is disabled. Only admins can login at this time. ', 1087 | )} 1088 |

    1089 |
    1090 | 1123 |
    ; 1124 | } else if ((($facebook_enabled === true) || ($google_enabled === true)) && 1125 | (($login_facebook->getValue() === '1') || 1126 | ($login_google->getValue() === '1'))) { 1127 | 1128 | if (($registration_facebook->getValue() === '1') || 1129 | ($registration_google->getValue() === '1')) { 1130 | $header_message = 1131 | tr('Login/Register with these one of these options:'); 1132 | } else { 1133 | $header_message = 1134 | tr( 1135 | 'Login with these one of these options (existing account is required):', 1136 | ); 1137 | } 1138 | 1139 | $login_header = 1140 |
    1141 |

    1142 | {tr('Team Login')} 1143 |

    1144 |

    1145 | {$header_message} 1146 |

    1147 |
    ; 1148 | 1149 | return 1150 |
    1151 | {$login_header} 1152 | {$oauth_form} 1153 | 1158 |
    ; 1159 | } else { 1160 | return 1161 |
    1162 |
    1166 |
    1167 |

    {tr('Team Login')}

    1168 |

    1169 | {tr('Not Available')} 1170 |

    1171 |
    1172 |

    {tr('Team Login will be open soon, stay tuned!')}

    1173 | 1178 | 1183 |
    1184 |
    1185 |
    1186 |
    ; 1187 | } 1188 | } 1189 | 1190 | public function renderErrorPage(): :xhp { 1191 | return 1192 |
    1193 |
    1194 |

    {tr('ERROR')}

    1195 |
    1196 |
    1197 |

    ¯\_(ツ)_/¯

    1198 | 1199 | {tr('Start Over')} 1200 | 1201 |
    1202 |
    ; 1203 | } 1204 | 1205 | public async function genRenderMobilePage(): Awaitable<:xhp> { 1206 | $branding_xhp = await $this->genRenderBranding(); 1207 | return 1208 |
    1209 |
    1212 |
    1213 |

    1214 | {tr('Window is too small')} 1215 |

    1216 |

    1217 | {tr( 1218 | 'For the best CTF experience, please make window size bigger.', 1219 | )} 1220 |

    1221 |

    {tr('Thank you.')}

    1222 | 1227 |
    1228 |
    1229 |
    1230 | {$branding_xhp} 1231 |
    1232 |
    ; 1233 | } 1234 | 1235 | public async function genRenderMainNav(): Awaitable<:xhp> { 1236 | if (SessionUtils::sessionActive()) { 1237 | $right_nav = 1238 | ; 1251 | } else { 1252 | $right_nav = 1253 | ; 1268 | } 1269 | $left_nav = 1270 | ; 1283 | $branding_gen = await $this->genRenderBranding(); 1284 | $branding = 1285 | ; 1292 | 1293 | return 1294 | ; 1299 | } 1300 | 1301 | public async function genRenderPage(string $page): Awaitable<:xhp> { 1302 | switch ($page) { 1303 | case 'main': 1304 | return $this->renderMainContent(); 1305 | case 'error': 1306 | return $this->renderErrorPage(); 1307 | case 'mobile': 1308 | return await $this->genRenderMobilePage(); 1309 | case 'login': 1310 | return await $this->genRenderLoginContent(); 1311 | case 'registration': 1312 | return await $this->genRenderRegistrationContent(); 1313 | case 'rules': 1314 | return $this->renderRulesContent(); 1315 | case 'countdown': 1316 | return await $this->genRenderCountdownContent(); 1317 | case 'game': 1318 | throw new GameRedirectException(); 1319 | case 'admin': 1320 | throw new AdminRedirectException(); 1321 | default: 1322 | return $this->renderMainContent(); 1323 | } 1324 | } 1325 | 1326 | <<__Override>> 1327 | public async function genRenderBody(string $page): Awaitable<:xhp> { 1328 | list($rendered_page, $rendered_nav) = await \HH\Asio\va( 1329 | $this->genRenderPage($page), 1330 | $this->genRenderMainNav(), 1331 | ); 1332 | return 1333 | 1334 |
    1335 |
    1336 |
    {$rendered_nav}
    1337 |
    {$rendered_page}
    1338 |
    1339 | 1340 | ; 1341 | } 1342 | } 1343 | --------------------------------------------------------------------------------