├── .gitignore ├── .htaccess ├── @import ├── apis │ ├── challenges │ │ └── authentication.php │ ├── error │ │ └── error.php │ ├── users │ │ ├── settings.php │ │ ├── sign-in.php │ │ ├── sign-out.php │ │ ├── sign-up.php │ │ ├── verify-email.php │ │ └── verify-name.php │ └── wechall │ │ ├── user-score.php │ │ └── validate-mail.php ├── confs │ ├── common.php │ └── init.sql ├── init.php └── views │ ├── challenges │ └── challenges.php │ ├── common │ ├── foot.php │ └── head.php │ ├── error │ └── error.php │ ├── home │ └── home.php │ ├── notifications │ └── notifications.php │ ├── scoreboard │ └── scoreboard.php │ ├── solves │ └── solves.php │ └── users │ ├── profile.php │ ├── settings.php │ ├── sign-in.php │ ├── sign-up.php │ └── users.php ├── LICENSE ├── README.md ├── assets ├── fonts │ ├── Play-Regular.ttf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── images │ ├── brand.svg │ ├── favicon.ico │ ├── favicon.png │ ├── forbidden.png │ └── thumbnail.png ├── scripts │ ├── bootstrap.min.js │ ├── challenges.js │ ├── common.js │ ├── html5shiv.min.js │ ├── jquery.min.js │ ├── popper.min.js │ ├── respond.min.js │ └── users.js └── styles │ ├── bootstrap.min.css │ ├── common.css │ └── font-awesome.min.css ├── index.php └── robots.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .*.txt 2 | .*.db 3 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | Options -Indexes 2 | ServerSignature Off 3 | 4 | ErrorDocument 403 /index.php 5 | ErrorDocument 404 /index.php 6 | 7 | AddDefaultCharset utf-8 8 | 9 | 10 | RewriteEngine On 11 | RewriteBase / 12 | 13 | RewriteCond %{REQUEST_FILENAME} !-f 14 | RewriteCond %{REQUEST_FILENAME} !-d 15 | RewriteRule . /index.php [L] 16 | 17 | RewriteRule ^\@import - [F,NC] 18 | 19 | -------------------------------------------------------------------------------- /@import/apis/challenges/authentication.php: -------------------------------------------------------------------------------- 1 | 'error']; 5 | goto tail; 6 | } 7 | 8 | if(is_use_recaptcha()){ 9 | if(!isset($_POST['recaptcha-token']) || !is_valid_recaptcha_token($_POST['recaptcha-token'])){ 10 | $res = ['result' => 'invalid_token']; 11 | goto tail; 12 | } 13 | } 14 | 15 | if(!Users::is_signed()){ 16 | $res = ['result' => 'unsigned']; 17 | goto tail; 18 | } 19 | 20 | if(!Challenges::is_valid_chal_flag($_POST['flag']) || ($chal = Challenges::get_chal_by_chal_flag($_POST['flag'])) === false){ 21 | $res = ['result' => 'invalid_flag']; 22 | goto tail; 23 | } 24 | 25 | if(Challenges::is_solved_chal($chal['chal_no'])){ 26 | $res = ['result' => 'already_solved', 'chal_name' => $chal['chal_name'], 'chal_title' => $chal['chal_title']]; 27 | goto tail; 28 | } 29 | 30 | if(!Challenges::do_solve_chal($chal['chal_no'], $chal['chal_score'])){ 31 | $res = ['result' => 'error']; 32 | goto tail; 33 | } 34 | 35 | $res = ['result' => 'solved', 'chal_name' => $chal['chal_name'], 'chal_title' => $chal['chal_title'], 'chal_score' => $chal['chal_score']]; 36 | 37 | tail: 38 | Templater::json($res); -------------------------------------------------------------------------------- /@import/apis/error/error.php: -------------------------------------------------------------------------------- 1 | 'access_denied']; 8 | break; 9 | case 404: 10 | default: 11 | header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found'); 12 | $res = ['result' => 'page_not_found']; 13 | break; 14 | } 15 | 16 | Templater::json($res); 17 | -------------------------------------------------------------------------------- /@import/apis/users/settings.php: -------------------------------------------------------------------------------- 1 | 'error']; 4 | goto tail; 5 | } 6 | 7 | if(is_use_recaptcha()){ 8 | if(!isset($_POST['recaptcha-token']) || !is_valid_recaptcha_token($_POST['recaptcha-token'])){ 9 | $res = ['result' => 'invalid_token']; 10 | goto tail; 11 | } 12 | } 13 | 14 | if(!Users::is_signed()){ 15 | $res = ['result' => 'not_signed']; 16 | goto tail; 17 | } 18 | 19 | if(!Users::is_valid_user_name($_POST['name'])){ 20 | $res = ['result' => 'invalid_name']; 21 | goto tail; 22 | } 23 | 24 | if(Users::is_exists_user_name($_POST['name'])){ 25 | $res = ['result' => 'already_exists_name']; 26 | goto tail; 27 | } 28 | 29 | if(!Users::is_valid_user_email($_POST['email'])){ 30 | $res = ['result' => 'invalid_email']; 31 | goto tail; 32 | } 33 | 34 | if(Users::is_exists_user_email($_POST['email'])){ 35 | $res = ['result' => 'already_exists_email']; 36 | goto tail; 37 | } 38 | 39 | if(!Users::is_valid_user_password($_POST['password'])){ 40 | $res = ['result' => 'invalid_password']; 41 | goto tail; 42 | } 43 | 44 | if(!Users::is_valid_user_comment($_POST['comment'])){ 45 | $res = ['result' => 'invalid_comment']; 46 | goto tail; 47 | } 48 | 49 | if(!Users::update_my_user($_POST['name'], $_POST['email'], $_POST['password'], $_POST['comment'])){ 50 | $res = ['result' => 'error']; 51 | goto tail; 52 | } 53 | 54 | $res = ['result' => 'valid', 'redirect' => get_user_profile_page_url($_POST['name'])]; 55 | 56 | tail: 57 | Templater::json($res); 58 | -------------------------------------------------------------------------------- /@import/apis/users/sign-in.php: -------------------------------------------------------------------------------- 1 | 'error']; 4 | goto tail; 5 | } 6 | 7 | if(is_use_recaptcha()){ 8 | if(!isset($_POST['recaptcha-token']) || !is_valid_recaptcha_token($_POST['recaptcha-token'])){ 9 | $res = ['result' => 'invalid_token']; 10 | goto tail; 11 | } 12 | } 13 | 14 | if(Users::is_signed()){ 15 | $res = ['result' => 'already_signed']; 16 | goto tail; 17 | } 18 | 19 | if(!Users::is_valid_user_name($_POST['name']) || 20 | !Users::is_valid_user_password($_POST['password']) || 21 | !Users::do_sign_in($_POST['name'], $_POST['password'])){ 22 | $res = ['result' => 'invalid_account']; 23 | goto tail; 24 | } 25 | 26 | $res = ['result' => 'valid']; 27 | 28 | tail: 29 | Templater::json($res); 30 | -------------------------------------------------------------------------------- /@import/apis/users/sign-out.php: -------------------------------------------------------------------------------- 1 | 'error']; 4 | goto tail; 5 | } 6 | 7 | if(is_use_recaptcha()){ 8 | if(!isset($_POST['recaptcha-token']) || !is_valid_recaptcha_token($_POST['recaptcha-token'])){ 9 | $res = ['result' => 'invalid_token']; 10 | goto tail; 11 | } 12 | } 13 | 14 | if(Users::is_signed()){ 15 | $res = ['result' => 'already_signed']; 16 | goto tail; 17 | } 18 | 19 | if(!Users::is_valid_user_name($_POST['name'])){ 20 | $res = ['result' => 'invalid_name']; 21 | goto tail; 22 | } 23 | 24 | if(Users::is_exists_user_name($_POST['name'])){ 25 | $res = ['result' => 'already_exists_name']; 26 | goto tail; 27 | } 28 | 29 | if(!Users::is_valid_user_email($_POST['email'])){ 30 | $res = ['result' => 'invalid_email']; 31 | goto tail; 32 | } 33 | 34 | if(Users::is_exists_user_email($_POST['email'])){ 35 | $res = ['result' => 'already_exists_email']; 36 | goto tail; 37 | } 38 | 39 | if(!Users::is_valid_user_password($_POST['password'])){ 40 | $res = ['result' => 'invalid_password']; 41 | goto tail; 42 | } 43 | 44 | if(!Users::is_valid_user_comment($_POST['comment'])){ 45 | $res = ['result' => 'invalid_comment']; 46 | goto tail; 47 | } 48 | 49 | if(!Users::do_sign_up($_POST['name'], $_POST['email'], $_POST['password'], $_POST['comment'])){ 50 | $res = ['result' => 'error']; 51 | goto tail; 52 | } 53 | 54 | $res = ['result' => 'valid']; 55 | 56 | tail: 57 | Templater::json($res); 58 | -------------------------------------------------------------------------------- /@import/apis/users/verify-email.php: -------------------------------------------------------------------------------- 1 | 'error']; 4 | goto tail; 5 | } 6 | 7 | if(!Users::is_valid_user_email($_POST['email'])){ 8 | $res = ['result' => 'invalid']; 9 | goto tail; 10 | } 11 | 12 | if(Users::is_exists_user_email($_POST['email'])){ 13 | $res = ['result' => 'exists']; 14 | goto tail; 15 | } 16 | 17 | $res = ['result' => 'valid']; 18 | 19 | tail: 20 | Templater::json($res); 21 | -------------------------------------------------------------------------------- /@import/apis/users/verify-name.php: -------------------------------------------------------------------------------- 1 | 'error']; 4 | goto tail; 5 | } 6 | 7 | if(!Users::is_valid_user_name($_POST['name'])){ 8 | $res = ['result' => 'invalid']; 9 | goto tail; 10 | } 11 | 12 | if(Users::is_exists_user_name($_POST['name'])){ 13 | $res = ['result' => 'exists']; 14 | goto tail; 15 | } 16 | 17 | $res = ['result' => 'valid']; 18 | 19 | tail: 20 | Templater::json($res); 21 | -------------------------------------------------------------------------------- /@import/apis/wechall/user-score.php: -------------------------------------------------------------------------------- 1 | 'CanHackMe', 7 | 'description' => 'Have fun challenges!', 8 | 'keyword' => 'CanHackMe, Jeopardy, CTF, Wargame, Hacking, Security, Flag', 9 | 'url' => 'https://canhack.me/', 10 | 'facebook_app_id' => file_get_contents(__DIR__.'/.facebook_app_id.txt'), 11 | 'twitter_account' => file_get_contents(__DIR__.'/.twitter_account.txt'), 12 | 13 | 'use_recaptcha' => false, 14 | 'recaptcha_sitekey' => file_get_contents(__DIR__.'/.recaptcha_sitekey.txt'), 15 | 'recaptcha_secretkey' => file_get_contents(__DIR__.'/.recaptcha_secretkey.txt'), 16 | 17 | 'wechall_authkey' => file_get_contents(__DIR__.'/.wechall_authkey.txt'), 18 | ]); 19 | 20 | define('__AUTHOR__', [ 21 | 'name' => 'Safflower', 22 | 'email' => 'plzdonotsay@gmail.com', 23 | 'website' => 'https://safflower.pw/', 24 | ]); 25 | 26 | define('__ADMIN__', [ 27 | 'admin', 28 | 'safflower', 29 | ]); 30 | 31 | define('__HASH_SALT__', file_get_contents(__DIR__.'/.hash_salt.txt')); 32 | -------------------------------------------------------------------------------- /@import/confs/init.sql: -------------------------------------------------------------------------------- 1 | 2 | # users 3 | CREATE TABLE `users` ( 4 | `user_no` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 5 | `user_name` TEXT NOT NULL UNIQUE, 6 | `user_email` TEXT NOT NULL UNIQUE, 7 | `user_password` TEXT NOT NULL, 8 | `user_comment` TEXT NOT NULL, 9 | `user_score` INTEGER NOT NULL, 10 | `user_signed_up_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 11 | ); 12 | 13 | # challenges 14 | CREATE TABLE `chals` ( 15 | `chal_no` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 16 | `chal_name` TEXT NOT NULL UNIQUE, 17 | `chal_title` TEXT NOT NULL UNIQUE, 18 | `chal_contents` TEXT NOT NULL, 19 | `chal_score` INTEGER NOT NULL, 20 | `chal_flag` TEXT NOT NULL UNIQUE, 21 | `chal_tags` TEXT NOT NULL, 22 | `chal_user_no` INTEGER NOT NULL, 23 | `chal_uploaded_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 24 | ); 25 | 26 | # solve logs 27 | CREATE TABLE `solvs` ( 28 | `solv_no` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 29 | `solv_user_no` INTEGER NOT NULL, 30 | `solv_chal_no` INTEGER NOT NULL, 31 | `solv_solved_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 32 | ); 33 | 34 | # notifications 35 | CREATE TABLE `notis` ( 36 | `noti_no` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 37 | `noti_contents` TEXT NOT NULL, 38 | `noti_user_no` INTEGER NOT NULL, 39 | `noti_uploaded_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 40 | ); 41 | -------------------------------------------------------------------------------- /@import/init.php: -------------------------------------------------------------------------------- 1 | =') or die('Not supported PHP version (now: '.PHP_VERSION.'). Please install PHP 7.3.'); 3 | 4 | ini_set('display_errors', 'off'); 5 | 6 | ini_set('session.name', 'session'); 7 | ini_set('session.cookie_httponly', '1'); 8 | ini_set('session.sid_length', '50'); 9 | ini_set('session.sid_bits_per_character', '6'); 10 | session_start(); 11 | 12 | define('__CSP_NONCE__', base64_encode(random_bytes(20))); 13 | header('X-Content-Type-Options: nosniff'); 14 | header('X-Frame-Options: deny'); 15 | header('X-XSS-Protection: 1; mode=block'); 16 | header('Content-Security-Policy: base-uri \'self\'; script-src \'nonce-'.__CSP_NONCE__.'\';'); 17 | 18 | require __DIR__.'/confs/common.php'; 19 | 20 | ###################################################################################################################### 21 | 22 | if(__IS_DEBUG__){ 23 | error_reporting(E_ALL); 24 | ini_set('display_errors', 'on'); 25 | } 26 | 27 | date_default_timezone_set('UTC'); 28 | 29 | $need_init = !is_file(__DIR__.'/confs/.common.db'); 30 | 31 | $db = new SQLite3(__DIR__.'/confs/.common.db'); 32 | $db->createFunction('HASH', function(string $value){ 33 | return hash('sha256', $value.__HASH_SALT__); 34 | }); 35 | 36 | if($need_init){ 37 | if(($init_sql = file_get_contents(__DIR__.'/confs/init.sql')) === false){ 38 | die('Can\'t found SQL file for initialize database. (path: /confs/init.sql)'); 39 | } 40 | $db->query($init_sql); 41 | unset($init_sql); 42 | } 43 | unset($need_init); 44 | 45 | Templater::init(); 46 | Users::init(); 47 | Challenges::init(); 48 | 49 | ###################################################################################################################### 50 | 51 | function is_use_recaptcha(): bool{ 52 | return isset(__SITE__['use_recaptcha'], __SITE__['recaptcha_sitekey'], __SITE__['recaptcha_secretkey']) && 53 | __SITE__['use_recaptcha'] === true && 54 | is_string(__SITE__['recaptcha_sitekey']) && 55 | is_string(__SITE__['recaptcha_secretkey']); 56 | } 57 | function is_valid_recaptcha_token($token): bool{ 58 | if(!is_string($token) || !isset($token{0})){ 59 | return false; 60 | } 61 | $url = 'https://www.google.com/recaptcha/api/siteverify'; 62 | $data = [ 63 | 'secret' => __SITE__['recaptcha_secretkey'], 64 | 'response' => $token, 65 | ]; 66 | $options = [ 67 | 'http' => [ 68 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 69 | 'method' => 'POST', 70 | 'content' => http_build_query($data), 71 | ] 72 | ]; 73 | $context = stream_context_create($options); 74 | $response = file_get_contents($url, false, $context); 75 | $responseKeys = json_decode($response, true); 76 | return isset($responseKeys['success']) && is_bool($responseKeys['success']) ? $responseKeys['success'] : false; 77 | } 78 | function email_encode(string $email): string{ 79 | // html encode some characters (at, dot). 80 | return strtr(htmlentities($email), ['@' => '@', '.' => '.']); 81 | } 82 | function get_challenge_shortcut_page_url(string $chal_name): string{ 83 | return '/challenges/@'.urlencode(strtolower($chal_name)); 84 | } 85 | function get_challenge_tag_page_url(string $chal_name): string{ 86 | return '/challenges/tag/'.urlencode(strtolower($chal_name)); 87 | } 88 | function get_user_profile_page_url(string $user_name): string{ 89 | return '/users/@'.urlencode(strtolower($user_name)); 90 | } 91 | function get_user_profile_image_url(string $user_email, int $size = 64): string{ 92 | $token = md5($user_email); 93 | return 'https://www.gravatar.com/avatar/'.$token.'?'. 94 | http_build_query([ 95 | 'd' => 'https://github.com/identicons/'.$token.'.png', 96 | 's' => $size, 97 | ]); 98 | } 99 | function is_admin_user_name(string $user_name): bool{ 100 | return in_array(strtolower($user_name), __ADMIN__, true); 101 | } 102 | function highlight_keyword(string $content, string $keyword): string{ 103 | if(!isset($keyword{0})){ 104 | return $content; 105 | } 106 | return preg_replace('/('.preg_quote($keyword, '/').')/i', '$1', $content); 107 | } 108 | 109 | ###################################################################################################################### 110 | 111 | class Templater{ 112 | private static $url_path; 113 | public static function init(){ 114 | self::$url_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); 115 | } 116 | public static function get_url_path(): string{ 117 | return self::$url_path; 118 | } 119 | public static function route(string $regex, array $methods, &$args = null): bool{ 120 | return in_array($_SERVER['REQUEST_METHOD'], $methods, true) && preg_match($regex, self::get_url_path(), $args); 121 | } 122 | public static function import(string $file, $args = null){ 123 | include __DIR__.'/'.$file.'.php'; 124 | } 125 | public static function error(int $status = 404){ 126 | $_SERVER['REDIRECT_STATUS'] = $status; 127 | if($_SERVER['REQUEST_METHOD'] === 'GET'){ 128 | self::import('views/error/error'); 129 | }else{ 130 | self::import('apis/error/error'); 131 | } 132 | die; 133 | } 134 | public static function redirect(string $url){ 135 | if(!headers_sent()){ 136 | header('Location: '.$url); 137 | } 138 | die(''); 139 | } 140 | public static function json(array $data){ 141 | if(!headers_sent()){ 142 | header('Content-Type: application/json; charset=utf-8'); 143 | } 144 | die(json_encode($data)); 145 | } 146 | } 147 | 148 | ###################################################################################################################### 149 | 150 | class Data{ 151 | public static function resource(string $link, bool $is_full_url = false): string{ 152 | $parsed_url = parse_url($link); 153 | 154 | // If the link is external URL, return it as it is. 155 | if(isset($parsed_url['scheme']) || isset($parsed_url['host'])) return $link; 156 | 157 | $url_prefix = $is_full_url === true ? rtrim(__SITE__['url'], '/') : ''; 158 | $url_path = $parsed_url['path']; 159 | $file_path = realpath($_SERVER['DOCUMENT_ROOT'].'/'.$url_path); 160 | $url_query = $file_path !== false ? '?v='.filemtime($file_path) : ''; 161 | return $url_prefix.$url_path.$url_query; 162 | } 163 | public static function markbb(string $value): string{ 164 | $value = htmlentities($value); 165 | $value = strtr($value, ["\r" => '', "\n" => '
']); 166 | 167 | $value = preg_replace('#\[b\](.*?)\[/b\]#s', '$1', $value); 168 | $value = preg_replace('#__(.*?)__#s', '$1', $value); 169 | $value = preg_replace('#\*\*(.*?)\*\*#s', '$1', $value); 170 | 171 | $value = preg_replace('#\[u\](.*?)\[/u\]#s', '$1', $value); 172 | $value = preg_replace('#\+\+(.*?)\+\+#s', '$1', $value); 173 | 174 | $value = preg_replace('#\[i\](.*?)\[/i\]#s', '$1', $value); 175 | $value = preg_replace('#\[em\](.*?)\[/em\]#s', '$1', $value); 176 | $value = preg_replace('#\*(.*?)\*#s', '$1', $value); 177 | 178 | $value = preg_replace('#\[s\](.*?)\[/s\]#s', '$1', $value); 179 | $value = preg_replace('#~~(.*?)~~#s', '$1', $value); 180 | 181 | $value = preg_replace('#\[quote\](.*?)\[/quote\]#s', '
$1
', $value); 182 | $value = preg_replace('#\>(.*?)(\r?\n)#s', '
$1
$2', $value); 183 | 184 | $value = preg_replace('#\[mark\](.*?)\[/mark\]#s', '$1', $value); 185 | $value = preg_replace('#==(.*?)==#s', '$1', $value); 186 | 187 | $value = preg_replace('#```(.*?)```#s', '
$1
', $value); 188 | 189 | $value = preg_replace('#\[code\](.*?)\[/code\]#s', '$1', $value); 190 | $value = preg_replace('#`(.*?)`#s', '$1', $value); 191 | 192 | $value = preg_replace('#\[pre\](.*?)\[/pre\]#s', '
$1
', $value); 193 | 194 | $value = preg_replace('#\[file\](.*?)\[/file\]#s', '$1', $value); 195 | $value = preg_replace('#\[file=(.*?)\](.*?)\[/file\]#s', '$2', $value); 196 | 197 | $value = preg_replace('#\[img\](.*?)\[/img\]#s', '', $value); 198 | $value = preg_replace('#\!\[(.*?)\]\((.*?)\)#s', '$1', $value); 199 | $value = preg_replace('#\[img=(.*?)\](.*?)\[/img\]#s', '$1', $value); 200 | 201 | $value = preg_replace('#\[(.*?)\]\((\/.*?)\)#s', '$1', $value); 202 | $value = preg_replace('#\[(.*?)\]\((.*?)\)#s', '$1', $value); 203 | $value = preg_replace('#\[url=(\/.*?)\](.*?)\[/url\]#s', '$2', $value); 204 | $value = preg_replace('#\[url=(.*?)\](.*?)\[/url\]#s', '$2', $value); 205 | 206 | $value = preg_replace('#\<(\/.*?)\>#s', '$1', $value); 207 | $value = preg_replace('#\<(.*?)\>#s', '$1', $value); 208 | $value = preg_replace('#\[url\](\/.*?)\[/url\]#s', '$1', $value); 209 | $value = preg_replace('#\[url\](.*?)\[/url\]#s', '$1', $value); 210 | return $value; 211 | } 212 | } 213 | 214 | ###################################################################################################################### 215 | 216 | class Users{ 217 | private static $is_signed; 218 | private static $my_user; 219 | public static function init(){ 220 | self::$is_signed = isset($_SESSION['user_no'], $_SESSION['signed_token']) && $_SESSION['signed_token'] === self::get_signed_token(); 221 | if(self::$is_signed){ 222 | if((self::$my_user = self::get_user_by_user_no($_SESSION['user_no'])) === false){ 223 | self::$is_signed = self::$my_user = false; 224 | } 225 | }else{ 226 | self::$my_user = false; 227 | } 228 | } 229 | public static function is_signed(): bool{ 230 | return self::$is_signed; 231 | } 232 | public static function get_my_user(string $column = '*'){ 233 | if(self::$is_signed !== true){ 234 | return false; 235 | } 236 | if($column === '*'){ 237 | return self::$my_user; 238 | }else if(isset(self::$my_user[$column])){ 239 | return self::$my_user[$column]; 240 | }else{ 241 | return false; 242 | } 243 | } 244 | public static function get_unsigned_token(): string{ 245 | return base64_encode(sha1(json_encode([$_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']]), true)); 246 | } 247 | public static function get_signed_token(){ 248 | return isset($_SESSION['user_no']) ? 249 | base64_encode(sha1(json_encode([$_SESSION['user_no'], $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT'], __HASH_SALT__]), true)) : 250 | false; 251 | } 252 | public static function get_user_by_name(string $user_name, string $column = '*', bool $is_case_sensitive = false){ 253 | $where_option = $is_case_sensitive ? '' : 'COLLATE NOCASE'; 254 | global $db; 255 | $stmt = $db->prepare(" 256 | SELECT 257 | * 258 | FROM 259 | `users` 260 | WHERE 261 | `user_name`=:user_name {$where_option} 262 | LIMIT 263 | 1 264 | "); 265 | $stmt->bindParam(':user_name', $user_name); 266 | $res = $stmt->execute(); 267 | if($res === false) return false; 268 | $user = $res->fetchArray(SQLITE3_ASSOC); 269 | if($user === false) return false; 270 | 271 | if($column === '*'){ 272 | return $user; 273 | }else if(isset($user[$column])){ 274 | return $user[$column]; 275 | }else{ 276 | return false; 277 | } 278 | } 279 | public static function get_user_by_user_no(int $user_no, string $column = '*'){ 280 | global $db; 281 | $stmt = $db->prepare(' 282 | SELECT 283 | * 284 | FROM 285 | `users` 286 | WHERE 287 | `user_no`=:user_no 288 | LIMIT 289 | 1 290 | '); 291 | $stmt->bindParam(':user_no', $user_no); 292 | $res = $stmt->execute(); 293 | if($res === false) return false; 294 | $user = $res->fetchArray(SQLITE3_ASSOC); 295 | if($user === false) return false; 296 | 297 | if($column === '*'){ 298 | return $user; 299 | }else if(isset($user[$column])){ 300 | return $user[$column]; 301 | }else{ 302 | return false; 303 | } 304 | } 305 | public static function get_solv_count_by_user_no(int $user_no){ 306 | global $db; 307 | $stmt = $db->prepare(" 308 | SELECT 309 | COUNT(*) AS `solv_count` 310 | FROM 311 | `solvs` 312 | WHERE 313 | `solv_user_no`=:user_no 314 | "); 315 | $stmt->bindParam(':user_no', $user_no); 316 | $res = $stmt->execute(); 317 | if($res === false) return false; 318 | $solv = $res->fetchArray(SQLITE3_ASSOC); 319 | if($solv === false) return false; 320 | return $solv['solv_count']; 321 | } 322 | public static function get_users(int $limit_start = 0, int $limit_end = 24){ 323 | global $db; 324 | $stmt = $db->prepare(' 325 | SELECT 326 | * 327 | FROM 328 | `users` 329 | ORDER BY 330 | `user_no` DESC 331 | LIMIT 332 | :limit_start, :limit_end 333 | '); 334 | $stmt->bindParam(':limit_start', $limit_start); 335 | $stmt->bindParam(':limit_end', $limit_end); 336 | $res = $stmt->execute(); 337 | if($res === false) return false; 338 | $users = []; 339 | while($user = $res->fetchArray(SQLITE3_ASSOC)) $users[] = $user; 340 | return $users; 341 | } 342 | public static function is_valid_user_name($user_name): bool{ 343 | return is_string($user_name) && preg_match('/\A[a-zA-Z0-9_-]{5,20}\z/', $user_name); 344 | } 345 | public static function is_valid_user_url($user_url): bool{ 346 | return is_string($user_url) && preg_match('/\A(https?\:\/\/|\/\/).+\z/i', $user_url) && filter_var($user_url, FILTER_VALIDATE_URL); 347 | } 348 | public static function is_valid_user_email($user_email): bool{ 349 | return is_string($user_email) && filter_var($user_email, FILTER_VALIDATE_EMAIL); 350 | } 351 | public static function is_valid_user_comment($user_comment): bool{ 352 | return is_string($user_comment) && ($len = mb_strlen($user_comment)) !== false && 0 <= $len && $len <= 50; 353 | } 354 | public static function is_valid_user_password($user_password): bool{ 355 | return is_string($user_password) && ($len = mb_strlen($user_password)) !== false && 6 <= $len && $len <= 50; 356 | } 357 | public static function is_exists_user_name(string $user_name): bool{ 358 | global $db; 359 | $stmt = $db->prepare(' 360 | SELECT 361 | 1 362 | FROM 363 | `users` 364 | WHERE 365 | `user_name`=:user_name COLLATE NOCASE AND `user_no`!=:user_no 366 | LIMIT 367 | 1 368 | '); 369 | $stmt->bindParam(':user_name', $user_name); 370 | $stmt->bindValue(':user_no', self::get_my_user('user_no')); 371 | $res = $stmt->execute(); 372 | if($res === false) return false; 373 | $user = $res->fetchArray(SQLITE3_ASSOC); 374 | if($user === false) return false; 375 | return true; 376 | } 377 | public static function is_exists_user_email(string $user_email): bool{ 378 | global $db; 379 | $stmt = $db->prepare(' 380 | SELECT 381 | 1 382 | FROM 383 | `users` 384 | WHERE 385 | `user_email`=:user_email COLLATE NOCASE AND `user_no`!=:user_no 386 | LIMIT 387 | 1 388 | '); 389 | $stmt->bindParam(':user_email', $user_email); 390 | $stmt->bindValue(':user_no', self::get_my_user('user_no')); 391 | $res = $stmt->execute(); 392 | if($res === false) return false; 393 | $user = $res->fetchArray(SQLITE3_ASSOC); 394 | if($user === false) return false; 395 | return true; 396 | } 397 | public static function do_sign_in(string $user_name, string $user_password): bool{ 398 | global $db; 399 | $stmt = $db->prepare(' 400 | SELECT 401 | `user_no` 402 | FROM 403 | `users` 404 | WHERE 405 | `user_name`=:user_name COLLATE NOCASE AND `user_password`=HASH(:user_password) 406 | LIMIT 407 | 1 408 | '); 409 | $stmt->bindParam(':user_name', $user_name); 410 | $stmt->bindParam(':user_password', $user_password); 411 | $res = $stmt->execute(); 412 | if($res === false) return false; 413 | $user = $res->fetchArray(SQLITE3_ASSOC); 414 | if($user === false) return false; 415 | if(session_regenerate_id(true) === false) return false; 416 | $_SESSION['user_no'] = $user['user_no']; 417 | $_SESSION['signed_token'] = self::get_signed_token(); 418 | self::init(); 419 | return true; 420 | } 421 | public static function do_sign_up(string $user_name, string $user_email, string $user_password, string $user_comment): bool{ 422 | global $db; 423 | $stmt = $db->prepare(' 424 | INSERT INTO 425 | `users` 426 | ( 427 | `user_name`, `user_email`, `user_password`, `user_comment`, `user_score` 428 | ) 429 | VALUES 430 | ( 431 | :user_name, :user_email, HASH(:user_password), :user_comment, 0 432 | ) 433 | '); 434 | $stmt->bindParam(':user_name', $user_name); 435 | $stmt->bindParam(':user_email', $user_email); 436 | $stmt->bindParam(':user_password', $user_password); 437 | $stmt->bindParam(':user_comment', $user_comment); 438 | $res = $stmt->execute(); 439 | if($res === false) return false; 440 | return true; 441 | } 442 | public static function do_sign_out(){ 443 | unset($_SESSION['user_no'], $_SESSION['signed_token']); 444 | } 445 | public static function get_user_count(string $keyword = ''){ 446 | global $db; 447 | $stmt = $db->prepare(' 448 | SELECT 449 | COUNT(*) AS `count` 450 | FROM 451 | `users` 452 | WHERE 453 | INSTR(LOWER(`user_name`), LOWER(:keyword)) OR INSTR(LOWER(`user_comment`), LOWER(:keyword)) 454 | '); 455 | $stmt->bindParam(':keyword', $keyword); 456 | $res = $stmt->execute(); 457 | if($res === false) return false; 458 | $user = $res->fetchArray(SQLITE3_ASSOC); 459 | if($user === false) return false; 460 | return $user['count']; 461 | } 462 | public static function update_my_user(string $user_name, string $user_email, string $user_password, string $user_comment): bool{ 463 | global $db; 464 | $stmt = $db->prepare(' 465 | UPDATE 466 | `users` 467 | SET 468 | `user_name`=:user_name, 469 | `user_email`=:user_email, 470 | `user_password`=HASH(:user_password), 471 | `user_comment`=:user_comment 472 | WHERE 473 | `user_no`=:user_no 474 | '); 475 | $stmt->bindParam(':user_name', $user_name); 476 | $stmt->bindParam(':user_email', $user_email); 477 | $stmt->bindParam(':user_password', $user_password); 478 | $stmt->bindParam(':user_comment', $user_comment); 479 | $stmt->bindValue(':user_no', Users::get_my_user('user_no')); 480 | $res = $stmt->execute(); 481 | if($res === false) return false; 482 | self::init(); 483 | return true; 484 | } 485 | } 486 | 487 | ###################################################################################################################### 488 | 489 | class Challenges{ 490 | private static $chal_tags; 491 | public static function init(){ 492 | global $db; 493 | $stmt = $db->prepare(' 494 | SELECT 495 | GROUP_CONCAT(`chal_tags`, ",") AS `chal_tags` 496 | FROM 497 | `chals` 498 | '); 499 | $res = $stmt->execute(); 500 | if($res !== false){ 501 | $chal = $res->fetchArray(SQLITE3_ASSOC); 502 | if($chal !== false){ 503 | self::$chal_tags = array_unique(explode(',', $chal['chal_tags'])); 504 | }else{ 505 | self::$chal_tags = false; 506 | } 507 | }else{ 508 | self::$chal_tags = false; 509 | } 510 | } 511 | public static function get_chal_tags(){ 512 | return self::$chal_tags; 513 | } 514 | public static function is_valid_chal_flag($chal_flag): bool{ 515 | return is_string($chal_flag) && preg_match('/\A[a-zA-Z0-9_]+?\{[a-zA-Z0-9_]{10,50}\}\z/', $chal_flag); 516 | } 517 | public static function get_chal_by_chal_flag(string $chal_flag){ 518 | global $db; 519 | $stmt = $db->prepare(' 520 | SELECT 521 | * 522 | FROM 523 | `chals` 524 | WHERE 525 | `chal_flag`=:chal_flag 526 | LIMIT 527 | 1 528 | '); 529 | $stmt->bindParam(':chal_flag', $chal_flag); 530 | $res = $stmt->execute(); 531 | if($res === false) return false; 532 | $chal = $res->fetchArray(SQLITE3_ASSOC); 533 | if($chal === false) return false; 534 | return $chal; 535 | } 536 | public static function get_solved_chals(int $user_no, bool $get_first_solver = false){ 537 | global $db; 538 | $select = $get_first_solver ? '(SELECT `u`.`user_no` FROM `solvs`, `users` AS `u` WHERE `solv_chal_no`=`chal_no` AND `solv_user_no`=`u`.`user_no` ORDER BY `solv_no` ASC LIMIT 1) AS `chal_first_solver`,' : '0 AS `chal_first_solver`,'; 539 | $stmt = $db->prepare(" 540 | SELECT 541 | `chal_name`, 542 | `chal_title`, 543 | `chal_score`, 544 | {$select} 545 | `solv_solved_at` AS `chal_solved_at` 546 | FROM 547 | `solvs`, 548 | `chals` 549 | WHERE 550 | `solv_chal_no`=`chal_no` AND `solv_user_no`=:solv_user_no 551 | ORDER BY 552 | `solv_no` ASC, 553 | `chal_score` ASC, 554 | `chal_no` ASC 555 | "); 556 | $stmt->bindParam(':solv_user_no', $user_no); 557 | $res = $stmt->execute(); 558 | if($res === false) return false; 559 | $chals = []; 560 | while($chal = $res->fetchArray(SQLITE3_ASSOC)) $chals[] = $chal; 561 | return $chals; 562 | } 563 | public static function is_solved_chal(int $chal_no): bool{ 564 | global $db; 565 | $stmt = $db->prepare(' 566 | SELECT 567 | 1 568 | FROM 569 | `solvs` 570 | WHERE 571 | `solv_user_no`=:solv_user_no AND `solv_chal_no`=:solv_chal_no 572 | LIMIT 573 | 1 574 | '); 575 | $stmt->bindValue(':solv_user_no', Users::get_my_user('user_no')); 576 | $stmt->bindParam(':solv_chal_no', $chal_no); 577 | $res = $stmt->execute(); 578 | if($res === false) return false; 579 | $chal = $res->fetchArray(SQLITE3_ASSOC); 580 | if($chal === false) return false; 581 | return true; 582 | } 583 | public static function do_solve_chal(int $chal_no, int $chal_score): bool{ 584 | global $db; 585 | $stmt = $db->prepare(' 586 | INSERT INTO `solvs` 587 | ( 588 | `solv_user_no`, `solv_chal_no` 589 | ) 590 | VALUES 591 | ( 592 | :solv_user_no, :solv_chal_no 593 | ) 594 | '); 595 | $stmt->bindValue(':solv_user_no', Users::get_my_user('user_no')); 596 | $stmt->bindParam(':solv_chal_no', $chal_no); 597 | $res = $stmt->execute(); 598 | if($res === false) return false; 599 | 600 | $stmt = $db->prepare(' 601 | UPDATE 602 | `users` 603 | SET 604 | `user_score`=`user_score`+:score 605 | WHERE 606 | `user_no`=:user_no 607 | '); 608 | $stmt->bindValue(':user_no', Users::get_my_user('user_no')); 609 | $stmt->bindParam(':score', $chal_score); 610 | $res = $stmt->execute(); 611 | if($res === false) return false; 612 | return true; 613 | } 614 | public static function get_chal_count_and_score(){ 615 | global $db; 616 | $stmt = $db->prepare(" 617 | SELECT 618 | COUNT(*) AS `count`, 619 | SUM(`chal_score`) AS `score` 620 | FROM 621 | `chals` 622 | "); 623 | $res = $stmt->execute(); 624 | if($res === false) return false; 625 | $solv = $res->fetchArray(SQLITE3_ASSOC); 626 | if($solv === false) return false; 627 | return $solv; 628 | } 629 | public static function get_chals(string $chal_tag = 'all'){ 630 | $query_column = Users::is_signed() ? 'EXISTS(SELECT 1 FROM `solvs` WHERE `solv_chal_no`=`chal_no` AND `solv_user_no`='.Users::get_my_user('user_no').' LIMIT 1)' : '0'; 631 | $query_where = strcasecmp($chal_tag, 'all') ? 'AND INSTR(","||`chal_tags`||",", ",'.$chal_tag.',")' : ''; 632 | global $db; 633 | $stmt = $db->prepare(" 634 | SELECT 635 | `chal_name`, 636 | `chal_title`, 637 | `chal_contents`, 638 | `chal_score`, 639 | `chal_tags`, 640 | `chal_uploaded_at`, 641 | `user_name` AS `chal_author`, 642 | {$query_column} AS `chal_is_solved`, 643 | (SELECT COUNT(*) FROM `solvs` WHERE `solv_chal_no`=`chal_no`) AS `chal_solvers`, 644 | (SELECT `u`.`user_name` FROM `solvs`, `users` AS `u` WHERE `solv_chal_no`=`chal_no` AND `solv_user_no`=`u`.`user_no` ORDER BY `solv_no` ASC LIMIT 1) AS `chal_first_solver` 645 | FROM 646 | `chals`, 647 | `users` 648 | WHERE 649 | `chal_user_no`=`user_no` 650 | {$query_where} 651 | ORDER BY 652 | `chal_score` ASC, 653 | `chal_no` ASC 654 | "); 655 | $res = $stmt->execute(); 656 | if($res === false) return false; 657 | $chals = []; 658 | while($chal = $res->fetchArray(SQLITE3_ASSOC)) $chals[] = $chal; 659 | return $chals; 660 | } 661 | public static function get_rank_by_user_no(int $user_no){ 662 | global $db; 663 | $stmt = $db->prepare(' 664 | SELECT 665 | `user_no` 666 | FROM 667 | `users` 668 | ORDER BY 669 | `user_score` DESC, 670 | (SELECT `solv_solved_at` FROM `solvs` WHERE `solv_user_no`=`user_no` ORDER BY `solv_no` DESC LIMIT 1) ASC 671 | '); 672 | $res = $stmt->execute(); 673 | if($res === false) return false; 674 | $i = 0; 675 | $ranks = []; 676 | while($rank = $res->fetchArray(SQLITE3_ASSOC)){ 677 | ++$i; 678 | if($rank['user_no'] === $user_no) return $i; 679 | } 680 | return false; 681 | } 682 | public static function get_ranks(string $keyword = '', int $limit_start = 0, int $limit_end = 30){ 683 | global $db; 684 | $stmt = $db->prepare(' 685 | SELECT 686 | `user_no`, 687 | `user_name`, 688 | `user_comment`, 689 | `user_score`, 690 | (SELECT `solv_solved_at` FROM `solvs` WHERE `solv_user_no`=`user_no` ORDER BY `solv_no` DESC LIMIT 1) AS `user_last_solved_at` 691 | FROM 692 | `users` 693 | WHERE 694 | INSTR(LOWER(`user_name`), LOWER(:keyword)) OR INSTR(LOWER(`user_comment`), LOWER(:keyword)) 695 | ORDER BY 696 | `user_score` DESC, 697 | `user_last_solved_at` ASC 698 | LIMIT 699 | :limit_start, :limit_end 700 | '); 701 | $stmt->bindParam(':keyword', $keyword); 702 | $stmt->bindParam(':limit_start', $limit_start); 703 | $stmt->bindParam(':limit_end', $limit_end); 704 | $res = $stmt->execute(); 705 | if($res === false) return false; 706 | $ranks = []; 707 | while($rank = $res->fetchArray(SQLITE3_ASSOC)) $ranks[] = $rank; 708 | return $ranks; 709 | } 710 | public static function get_solvs(string $keyword = '', int $limit_start = 0, int $limit_end = 30){ 711 | global $db; 712 | $stmt = $db->prepare(' 713 | SELECT 714 | `solv_no`, 715 | `user_name` AS `solv_user_name`, 716 | `chal_name` AS `solv_chal_name`, 717 | `chal_title` AS `solv_chal_title`, 718 | `chal_score` AS `solv_chal_score`, 719 | `solv_solved_at` 720 | FROM 721 | `solvs`, 722 | `chals`, 723 | `users` 724 | WHERE 725 | `user_no`=`solv_user_no` AND `chal_no`=`solv_chal_no` AND 726 | (INSTR(LOWER(`user_name`), LOWER(:keyword)) OR INSTR(LOWER(`chal_title`), LOWER(:keyword))) 727 | ORDER BY 728 | `solv_no` DESC 729 | LIMIT 730 | :limit_start, :limit_end 731 | '); 732 | $stmt->bindParam(':keyword', $keyword); 733 | $stmt->bindParam(':limit_start', $limit_start); 734 | $stmt->bindParam(':limit_end', $limit_end); 735 | $res = $stmt->execute(); 736 | if($res === false) return false; 737 | $solvs = []; 738 | while($solv = $res->fetchArray(SQLITE3_ASSOC)) $solvs[] = $solv; 739 | return $solvs; 740 | } 741 | public static function get_chal_count(){ 742 | global $db; 743 | $stmt = $db->prepare(' 744 | SELECT 745 | COUNT(*) AS `chal_count` 746 | FROM 747 | `chals` 748 | '); 749 | $res = $stmt->execute(); 750 | if($res === false) return false; 751 | $chal = $res->fetchArray(SQLITE3_ASSOC); 752 | if($chal === false) return false; 753 | return $chal['chal_count']; 754 | } 755 | public static function get_solv_count(string $keyword = ''){ 756 | global $db; 757 | $stmt = $db->prepare(' 758 | SELECT 759 | COUNT(*) AS `solv_count` 760 | FROM 761 | `solvs`, 762 | `chals`, 763 | `users` 764 | WHERE 765 | `user_no`=`solv_user_no` AND `chal_no`=`solv_chal_no` AND 766 | (INSTR(LOWER(`user_name`), LOWER(:keyword)) OR INSTR(LOWER(`chal_title`), LOWER(:keyword))) 767 | '); 768 | $stmt->bindParam(':keyword', $keyword); 769 | $res = $stmt->execute(); 770 | if($res === false) return false; 771 | $solv = $res->fetchArray(SQLITE3_ASSOC); 772 | if($solv === false) return false; 773 | return $solv['solv_count']; 774 | } 775 | } 776 | 777 | class Notifications{ 778 | public static function get_notis(int $limit_start = 0, int $limit_end = 24){ 779 | global $db; 780 | $stmt = $db->prepare(' 781 | SELECT 782 | * 783 | FROM 784 | `notis` 785 | ORDER BY 786 | `noti_no` DESC 787 | LIMIT 788 | :limit_start, :limit_end 789 | '); 790 | $stmt->bindParam(':limit_start', $limit_start); 791 | $stmt->bindParam(':limit_end', $limit_end); 792 | $res = $stmt->execute(); 793 | if($res === false) return false; 794 | $notis = []; 795 | while($noti = $res->fetchArray(SQLITE3_ASSOC)) $notis[] = $noti; 796 | return $notis; 797 | } 798 | 799 | public static function get_noti_count(){ 800 | global $db; 801 | $stmt = $db->prepare(' 802 | SELECT 803 | COUNT(*) AS `noti_count` 804 | FROM 805 | `notis` 806 | '); 807 | $res = $stmt->execute(); 808 | if($res === false) return false; 809 | $noti = $res->fetchArray(SQLITE3_ASSOC); 810 | if($noti === false) return false; 811 | return $noti['noti_count']; 812 | } 813 | } -------------------------------------------------------------------------------- /@import/views/challenges/challenges.php: -------------------------------------------------------------------------------- 1 | 'Challenges - '.__SITE__['title'], 21 | 'active' => 'challenges', 22 | 'scripts' => is_use_recaptcha() ? [ 23 | '/assets/scripts/challenges.js', 24 | 'https://www.google.com/recaptcha/api.js?render='.urlencode(__SITE__['recaptcha_sitekey']), 25 | ] : [ 26 | '/assets/scripts/challenges.js', 27 | ], 28 | ]; 29 | $args_foot = [ 30 | 'active' => 'challenges', 31 | ]; 32 | 33 | if(isset($args['chal_name'])){ 34 | foreach($chals as $chal){ 35 | if(!strcasecmp($chal['chal_name'], $args['chal_name'])){ 36 | $selected_name = $chal['chal_name']; 37 | break; 38 | } 39 | } 40 | if(!isset($selected_name)){ 41 | Templater::error(404); 42 | } 43 | }else{ 44 | $selected_name = null; 45 | } 46 | 47 | ?> 48 | 49 |
50 |
51 | 52 |

Challenges

53 |
54 | Nothing opened yet. 55 |
56 | 57 |
58 |

Challenges

59 |
60 | 71 |
72 |
73 |
76 | data-recaptcha-sitekey="" 77 | 78 | autocomplete="off"> 79 | 80 | 81 | 82 |
83 | 84 |
85 |
86 | 87 |
88 | 91 |
92 | 93 | 96 | 97 | 100 | 101 |
102 |
103 |
104 |
105 |
106 | 107 |
108 |
109 | 114 | = time() - 3600 * 24 * 3): ?> 115 | New 116 | 117 | pt 118 |
119 |
120 |
121 |
122 |
    123 |
  • Publisher:
  • 124 |
  • |
  • 125 |
  • Published at:
  • 126 |
  • |
  • 127 |
  • Solvers:
  • 128 | 129 |
  • |
  • 130 |
  • First Solver:
  • 131 | 132 |
133 |
134 |
135 |
136 | 137 | 138 | # 139 | 140 |
141 |
142 |
143 |
144 | 145 |
146 | 147 |
148 | Currently opened challenges. 149 |
150 |
151 |
152 | -------------------------------------------------------------------------------- /@import/views/common/foot.php: -------------------------------------------------------------------------------- 1 |
2 | Back to top 3 |
4 | 5 | 6 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /@import/views/common/head.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <?= htmlentities($args['title']) ?> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 |
67 |
68 | 134 |
135 |
136 |
137 |
138 | 143 |
144 | -------------------------------------------------------------------------------- /@import/views/error/error.php: -------------------------------------------------------------------------------- 1 | 'Error - '.__SITE__['title'], 4 | 'active' => 'error', 5 | ]; 6 | $args_foot = [ 7 | 'active' => 'error', 8 | ]; 9 | 10 | # error 11 | isset($_SERVER['REDIRECT_STATUS']) or $_SERVER['REDIRECT_STATUS'] = 404; 12 | switch($_SERVER['REDIRECT_STATUS']){ 13 | case 403: 14 | header($_SERVER['SERVER_PROTOCOL'].' 403 Forbidden'); 15 | $err_title = 'Access Denied'; 16 | $err_contents = 'You don\'t have permission to access this page.'; 17 | break; 18 | case 404: 19 | default: 20 | header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found'); 21 | $err_title = 'Page Not Found'; 22 | $err_contents = 'The requested URL was not found on this website,
please verify that the URL is spelled correctly.'; 23 | break; 24 | } 25 | 26 | ?> 27 | 28 |
29 |
30 |

31 | 🚫 32 |
33 |
34 | Go back to the home page. 35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /@import/views/home/home.php: -------------------------------------------------------------------------------- 1 | __SITE__['title'], 4 | 'active' => 'home', 5 | ]; 6 | $args_foot = [ 7 | 'active' => 'home', 8 | ]; 9 | ?> 10 | 11 |
12 |
13 |

Introduction

14 |
15 | This service offers challenges to improve your hacking skills.
16 | Capture the flag to resolve the challenge. 17 | The default format for all flags is CanHackMe{...}.
18 | And authenticate the flag you captured. 19 | If you've done it, you get points for the challenge.
20 | You can see the ranking on the scoreboard and compete with other users.
21 | If you don't give up, you can hack me. Have fun challenges!
22 |
23 |
24 |
25 |

Rules

26 |
    27 |
  • DO NOT sign up more than once. If you forget your account, please contact to admin.
  • 28 |
  • DO NOT play as a team. Only solo play is allowed.
  • 29 |
  • DO NOT bruteforce challenges authentication. It's meaningless attempt.
  • 30 |
  • DO NOT share publicly with the flag and the solution of the challenge.
  • 31 |
  • If you find an unintended bug, please contact to admin directly.
  • 32 |
33 |
34 |
35 |

Contact

36 |
37 | If you have any questions or problems, please feel free to contact at <>.
38 | Admin may not be able to reply immediately.
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /@import/views/notifications/notifications.php: -------------------------------------------------------------------------------- 1 | $first_page, 16 | ])); 17 | }else if($last_page < $page){ 18 | Templater::redirect('/notifications?'.http_build_query([ 19 | 'p' => $last_page, 20 | ])); 21 | } 22 | 23 | $notis = Notifications::get_notis((int)(($page - 1) * $limit), (int)$limit); 24 | 25 | $args_head = [ 26 | 'title' => 'Notifications - '.__SITE__['title'], 27 | 'active' => 'notifications', 28 | ]; 29 | $args_foot = [ 30 | 'active' => 'notifications', 31 | ]; 32 | 33 | ?> 34 | 35 |
36 |
37 |

Notifications

38 | 39 | 40 |
41 | Nothing notified yet. 42 |
43 | 44 |
    45 | 46 |
  • 47 | 48 | 49 |
  • 50 | 51 |
52 | 53 | 98 |
99 | Currently notified messages. 100 |
101 |
102 |
103 | -------------------------------------------------------------------------------- /@import/views/scoreboard/scoreboard.php: -------------------------------------------------------------------------------- 1 | $keyword, 17 | 'p' => $first_page, 18 | ])); 19 | }else if($last_page < $page){ 20 | Templater::redirect('/scoreboard?'.http_build_query([ 21 | 'q' => $keyword, 22 | 'p' => $last_page, 23 | ])); 24 | } 25 | 26 | $ranks = Challenges::get_ranks($keyword, (int)(($page - 1) * $limit), (int)$limit); 27 | 28 | 29 | $args_head = [ 30 | 'title' => 'Scoreboard - '.__SITE__['title'], 31 | 'active' => 'scoreboard', 32 | 'scripts' => ['/assets/scripts/chart.min.js'], 33 | ]; 34 | $args_foot = [ 35 | 'active' => 'scoreboard', 36 | ]; 37 | 38 | ?> 39 | 40 |
41 |
42 |
43 |

Scoreboard

44 |
45 |
46 | 47 |
48 | 51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 | 59 |
60 | Nobody signed up yet. 61 |
62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
#UserCommentScoreLast solved at
91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ptNothing solved yet
109 |
110 | 111 | 156 |
157 | Currently signed up peoples. 158 |
159 |
160 |
161 | 162 | 163 | -------------------------------------------------------------------------------- /@import/views/solves/solves.php: -------------------------------------------------------------------------------- 1 | $keyword, 17 | 'p' => $first_page, 18 | ])); 19 | }else if($last_page < $page){ 20 | Templater::redirect('/solves?'.http_build_query([ 21 | 'q' => $keyword, 22 | 'p' => $last_page, 23 | ])); 24 | } 25 | 26 | $solvs = Challenges::get_solvs($keyword, (int)(($page - 1) * $limit), (int)$limit); 27 | 28 | 29 | $args_head = [ 30 | 'title' => 'Solves - '.__SITE__['title'], 31 | 'active' => 'solves', 32 | 'scripts' => ['/assets/scripts/chart.min.js'], 33 | ]; 34 | $args_foot = [ 35 | 'active' => 'solves', 36 | ]; 37 | 38 | ?> 39 | 40 |
41 |
42 |
43 |

Solves

44 |
45 |
46 | 47 |
48 | 51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 | 59 |
60 | Nobody solved yet. 61 |
62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
#UserChallengeScoreSolved at
pt
97 |
98 | 99 | 144 |
145 | Currently solved times. 146 |
147 |
148 |
149 | 150 | -------------------------------------------------------------------------------- /@import/views/users/profile.php: -------------------------------------------------------------------------------- 1 | $user['user_name'].'\'s Profile - '.__SITE__['title'], 13 | 'active' => 'users', 14 | ]; 15 | $args_foot = [ 16 | 'active' => 'users', 17 | ]; 18 | ?> 19 | 20 |
21 |
22 |

Profile

23 |
24 |
25 | <?= htmlentities($user['user_name']) ?> 26 |
27 |
28 |
29 | 30 |

31 |
32 |
#
33 |
pt
34 | 35 |
Admin
36 | 37 |
38 | 39 |
40 | 41 |
42 | 43 |
44 | Signed up at . 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 |
53 |
54 |
55 |
56 |
57 |

Solve Progress

58 |
59 |
%
60 |
61 | 62 |
63 | Nothing solved yet. 64 |
65 | 66 |
    67 | 68 |
  • 69 | 70 | 71 | 72 | (pt) 73 | 74 | First Solved 75 | 76 | 77 | 78 |
  • 79 | 80 |
81 | 82 |
83 | 84 |
85 | Settings 86 |
87 | 88 |
89 | -------------------------------------------------------------------------------- /@import/views/users/settings.php: -------------------------------------------------------------------------------- 1 | 'Settings - '.__SITE__['title'], 8 | 'active' => 'users', 9 | 'scripts' => is_use_recaptcha() ? [ 10 | '/assets/scripts/users.js', 11 | 'https://www.google.com/recaptcha/api.js?render='.urlencode(__SITE__['recaptcha_sitekey']), 12 | ] : [ 13 | '/assets/scripts/users.js', 14 | ], 15 | ]; 16 | $args_foot = [ 17 | 'active' => 'users', 18 | ]; 19 | 20 | $user = Users::get_my_user(); 21 | 22 | ?> 23 | 24 |
25 |
26 |

Settings

27 |
30 | data-recaptcha-sitekey="" 31 | 32 | > 33 | 34 | 35 | 36 |
37 | 38 |
39 | 43 |
44 | 46 |
47 |
48 | 49 | Name is used for sign in, or let you know your identity. 50 | 51 |
52 |
53 | 54 |
55 | 59 |
60 | 62 |
63 |
64 | 65 | Email is used for reset your password and generate your profile picture in here. 66 | 67 |
68 |
69 | 70 |
71 | 75 |
76 | 78 |
79 |
80 | 81 | Password is used for sign in, and all passwords are encrypted with long salt. 82 | 83 |
84 |
85 | 86 | 90 | 91 | Comment is used to express what you want to say. 92 | 93 |
94 |
95 | 98 |
99 | 100 | 103 | 104 | 107 | 108 |
109 |
110 |
111 |
112 |
113 | -------------------------------------------------------------------------------- /@import/views/users/sign-in.php: -------------------------------------------------------------------------------- 1 | 'Sign in - '.__SITE__['title'], 8 | 'active' => 'users', 9 | 'scripts' => is_use_recaptcha() ? [ 10 | '/assets/scripts/users.js', 11 | 'https://www.google.com/recaptcha/api.js?render='.urlencode(__SITE__['recaptcha_sitekey']), 12 | ] : [ 13 | '/assets/scripts/users.js', 14 | ], 15 | ]; 16 | $args_foot = [ 17 | 'active' => 'users', 18 | ]; 19 | 20 | ?> 21 | 22 |
23 |
24 |

Sign in

25 |
28 | data-recaptcha-sitekey="" 29 | 30 | > 31 | 32 | 33 | 34 |
35 | 36 | 38 |
39 |
40 | 41 | 42 | Forgot password? 43 | 44 | 46 |
47 |
48 | 51 |
52 | 53 | 56 | 57 | 60 | 61 |
62 |
63 |
64 |
65 |
66 | -------------------------------------------------------------------------------- /@import/views/users/sign-up.php: -------------------------------------------------------------------------------- 1 | 'Sign up - '.__SITE__['title'], 8 | 'active' => 'users', 9 | 'scripts' => is_use_recaptcha() ? [ 10 | '/assets/scripts/users.js', 11 | 'https://www.google.com/recaptcha/api.js?render='.urlencode(__SITE__['recaptcha_sitekey']), 12 | ] : [ 13 | '/assets/scripts/users.js', 14 | ], 15 | ]; 16 | $args_foot = [ 17 | 'active' => 'users', 18 | ]; 19 | 20 | ?> 21 | 22 |
23 |
24 |

Sign up

25 |
28 | data-recaptcha-sitekey="" 29 | 30 | > 31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 |
39 | 41 |
42 |
43 | 44 | Name is used for sign in, or let you know your identity. 45 | 46 |
47 |
48 | 49 |
50 | 51 |
52 | 54 |
55 |
56 | 57 | Email is used for reset your password and generate your profile picture in here. 58 | 59 |
60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 |
68 | 69 | Password is used for sign in, and all passwords are encrypted with long salt. 70 | 71 |
72 |
73 | 74 | 75 | 76 | Comment is used to express what you want to say. 77 | 78 |
79 |
80 | 83 |
84 | 85 | 88 | 89 | 92 | 93 |
94 |
95 |
96 |
97 |
98 | -------------------------------------------------------------------------------- /@import/views/users/users.php: -------------------------------------------------------------------------------- 1 | $first_page, 16 | ])); 17 | }else if($last_page < $page){ 18 | Templater::redirect('/users?'.http_build_query([ 19 | 'p' => $last_page, 20 | ])); 21 | } 22 | 23 | $users = Users::get_users((int)(($page - 1) * $limit), (int)$limit); 24 | 25 | $args_head = [ 26 | 'title' => 'Users - '.__SITE__['title'], 27 | 'active' => 'users', 28 | ]; 29 | $args_foot = [ 30 | 'active' => 'users', 31 | ]; 32 | 33 | ?> 34 | 35 |
36 |
37 |

Users

38 | 39 | 40 |
41 | Nobody signed up yet. 42 |
43 | 44 |
45 | 46 | 57 | 58 |
59 | 60 | 105 |
106 | Currently signed up peoples. 107 |
108 |
109 |
110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Safflower 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 | [![Rawsec's CyberSecurity Inventory](https://inventory.raw.pm/img/badges/Rawsec-inventoried-FF5050_flat.svg)](https://inventory.raw.pm/ctf_platforms.html#CanHackMe) 2 | [![GitHub stars](https://img.shields.io/github/stars/safflower/canhackme.svg)](https://github.com/safflower/canhackme/stargazers) 3 | [![GitHub license](https://img.shields.io/github/license/safflower/canhackme.svg)](https://github.com/safflower/canhackme/blob/master/LICENSE) 4 | 5 | # CanHackMe 6 | 7 | ## What's this? 8 | 9 | ![main](https://i.imgur.com/ItpTYc2.png) 10 | 11 | CanHackMe is jeopardy CTF platform. 12 | 13 | This platform tested on `Ubuntu 16.04` + `Apache 2.4` + `PHP 7.3`. 14 | 15 | 16 | 17 | ## How to install? 18 | 19 | 1. Install `Apache 2.4`. 20 | `.htaccess` file is not available with other software. 21 | 22 | 2. Install `PHP 7.3`. 23 | Lower versions are not supported. 24 | 25 | 3. Install `php-sqlite3` and `php-mbstrings` modules. 26 | 27 | 4. Set permission to access SQLite database file (default: `/@import/confs/.common.db`). 28 | 29 | 5. Modify `/@import/confs/common.php`, `.facebook_app_id.txt`, `.twitter_account.txt`, `.recaptcha_sitekey.txt`, `.recaptcha_secretkey.txt`, `.wechall_authkey.txt`, `.hash_salt.txt` file. 30 | Make sure to change the hash salt to a long random string. Don't make it public. 31 | 32 | 6. Register an account of administrator at the website. 33 | And modify `__ADMIN__` constant in `/@import/confs/common.php` file. 34 | 35 | 7. You must access the sqlite database directly to add notifications and challenges. 36 | 37 | 8. If you have any questions, feel free to contact me. 38 | -------------------------------------------------------------------------------- /assets/fonts/Play-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/fonts/Play-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /assets/images/brand.svg: -------------------------------------------------------------------------------- 1 | Layer 1 -------------------------------------------------------------------------------- /assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/images/favicon.ico -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/forbidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/images/forbidden.png -------------------------------------------------------------------------------- /assets/images/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengbomb/canhackme/b386fc6fd9e127f2caedf84a5e734e5fa2ba6de2/assets/images/thumbnail.png -------------------------------------------------------------------------------- /assets/scripts/challenges.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | // flag auth 3 | $('#auth-flag-form').submit(function(){ 4 | var form = $(this); 5 | $.ajax({ 6 | method: 'POST', 7 | url: form.attr('action'), 8 | data: form.serialize(), 9 | dataType: 'json', 10 | }).done(function(res) { 11 | switch (res['result']) { 12 | case 'solved': 13 | $.alert('success', 'Congratulations! You solved the '+res['chal_title']+' challenge, and you got a '+res['chal_score']+'pt.'); 14 | $('#flag').val(''); 15 | var chal_head = $('#chal-head-'+res['chal_name']); 16 | chal_head.find('a.text-dark') 17 | .removeClass('text-dark') 18 | .addClass('text-success') 19 | .find('i.fa-lock') 20 | .removeClass('fa-lock') 21 | .addClass('fa-unlock-alt'); 22 | chal_head.find('span.text-dark') 23 | .removeClass('text-dark') 24 | .addClass('text-success'); 25 | chal_head.find('span.badge-secondary') 26 | .removeClass('badge-secondary') 27 | .addClass('badge-success'); 28 | var chal_solvers = $('#chal-body-'+res['chal_name']).find('span.solvers'); 29 | chal_solvers.html(Number(chal_solvers.html()) + 1); 30 | break; 31 | case 'already_solved': 32 | $.alert('info', 'Correct! You already solved the '+res['chal_title']+' challenge.'); 33 | $('#flag').val(''); 34 | break; 35 | case 'invalid_flag': 36 | $.alert('danger', 'Incorrect! You have entered an invalid flag, please make sure you have entered it correctly.'); 37 | $('#flag').focus(); 38 | break; 39 | case 'unsigned': 40 | $.alert('warning', 'Failed! You have not signed this website, please sign in through here.'); 41 | break; 42 | case 'invalid_token': 43 | $.alert("danger", "Error! The captcha token is invalid, please try again."); 44 | break; 45 | default: 46 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 47 | } 48 | }).fail(function() { 49 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 50 | }).always(function() { 51 | form.trigger('refresh-recaptcha'); 52 | }); 53 | return false; 54 | }); 55 | }); -------------------------------------------------------------------------------- /assets/scripts/common.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $('[data-toggle="tooltip"]').tooltip(); 3 | 4 | // scroll to top 5 | $('[data-scroll-top]').click(function(){ 6 | $('html,body').animate({ 7 | scrollTop: 0, 8 | }, 300); 9 | return false; 10 | }); 11 | 12 | // scroll to bottom 13 | $('[data-scroll-bottom]').click(function(){ 14 | $('html,body').animate({ 15 | scrollTop: $(document).height(), 16 | }, 300); 17 | return false; 18 | }); 19 | 20 | // refresh recaptcha token in forms 21 | $('form[data-recaptcha-sitekey]').each(function(index){ 22 | var form = $(this); 23 | form.on('refresh-recaptcha', function(){ 24 | var sitekey = form.data('recaptcha-sitekey'); 25 | var submit = form.find('[type=submit]'); 26 | if(!submit.attr('disabled')){ 27 | submit.attr('disabled', true).html(' Waiting...'); 28 | } 29 | grecaptcha.ready(function(){ 30 | grecaptcha.execute(sitekey, { action: 'homepage' }).then(function(token){ 31 | form.find('input[name=recaptcha-token]').val(token); 32 | submit.attr('disabled', false).html(' Submit'); 33 | }); 34 | }); 35 | return; 36 | }); 37 | form.trigger('refresh-recaptcha'); 38 | return; 39 | }); 40 | 41 | // alert 42 | $.alert = function(level, message){ 43 | var alert_area = $('#alert-area'), 44 | alert = alert_area.find('.alert'), 45 | alert_class, icon_class; 46 | // select html tag class 47 | switch(level){ 48 | case 'success': 49 | alert_class = 'alert-success'; 50 | icon_class = 'fa-check'; 51 | break; 52 | case 'warning': 53 | alert_class = 'alert-warning'; 54 | icon_class = 'fa-exclamation-circle'; 55 | break; 56 | case 'info': 57 | alert_class = 'alert-info'; 58 | icon_class = 'fa-info-circle'; 59 | break; 60 | case 'danger': 61 | default: 62 | alert_class = 'alert-danger'; 63 | icon_class = 'fa-exclamation-triangle'; 64 | break; 65 | } 66 | // generate html tag 67 | var html = 68 | ''; 74 | // pop alert (fifo) 75 | if(alert.length == 3){ 76 | alert[0].remove(); 77 | } 78 | // push alert (fifo) 79 | if(alert_area.find('.alert').length === 0){ 80 | alert_area.prepend(html); 81 | }else{ 82 | alert_area.find('.alert:last').after(html); 83 | } 84 | // scroll to top 85 | $('html,body').animate({ 86 | scrollTop: 0, 87 | }, 300); 88 | }; 89 | 90 | $('.view-password').click(function(){ 91 | // convert input type 92 | var input = $($(this).toggleClass('active').data('target')); 93 | input.attr('type', input.attr('type') === 'text' ? 'password' : 'text'); 94 | }); 95 | 96 | // go back webpage 97 | $('.go-back').click(function() { 98 | var href = $(this).data('href'); 99 | if(document.referrer.indexOf(location.protocol + '//' + location.host + href) == 0){ 100 | history.back(); 101 | }else{ 102 | location.href = href; 103 | } 104 | }); 105 | $('[data-timestamp]').each(function(){ 106 | var localize_time = function(timestamp, timeformat){ 107 | var t = new Date(timestamp * 1000); 108 | if(t.toString() === 'Invalid Date') return; 109 | 110 | var zerofill = function(num, zero_count){ 111 | return num < (10 ** (zero_count - 1)) ? '0' + num.toString() : num.toString(); 112 | } 113 | 114 | var y = zerofill(t.getFullYear(), 4); 115 | var m = zerofill(t.getMonth() + 1, 2); 116 | var d = zerofill(t.getDate(), 2); 117 | if(timeformat === 'Y-m-d'){ 118 | return y + '-' + m + '-' + d; 119 | }else{ 120 | var h = zerofill(t.getHours(), 2); 121 | var i = zerofill(t.getMinutes(), 2); 122 | var s = zerofill(t.getSeconds(), 2); 123 | return y + '-' + m + '-' + d + ' ' + h + ':' + i + ':' + s; 124 | } 125 | } 126 | var timestamp = $(this).data('timestamp'); 127 | var timeformat = $(this).data('timeformat'); 128 | var time = localize_time(timestamp, timeformat); 129 | if(time != undefined){ 130 | $(this).html(time); 131 | } 132 | }); 133 | 134 | }); -------------------------------------------------------------------------------- /assets/scripts/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /assets/scripts/popper.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2018 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=J(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!q(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,y=t(e.instance.popper),w=parseFloat(y['margin'+f],10),E=parseFloat(y['border'+f+'Width'],10),v=b-e.offsets.popper[m]-w-E;return v=$(J(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,Q(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case he.FLIP:p=[n,i];break;case he.CLOCKWISE:p=z(n);break;case he.COUNTERCLOCKWISE:p=z(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,y=-1!==['top','bottom'].indexOf(n),w=!!t.flipVariations&&(y&&'start'===r&&h||y&&'end'===r&&c||!y&&'start'===r&&g||!y&&'end'===r&&u);(m||b||w)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),w&&(r=G(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!q(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.right #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;bSucceed! This name can be used.'); 15 | break; 16 | case 'invalid': 17 | $.alert('danger', 'Failed! This name format is invalid.'); 18 | name.focus(); 19 | break; 20 | case 'exists': 21 | $.alert('danger', 'Failed! This name already exists, please enter a different name.'); 22 | name.focus(); 23 | break; 24 | default: 25 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 26 | } 27 | }).fail(function() { 28 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 29 | }); 30 | return false; 31 | }); 32 | $('.verify-email').click(function() { 33 | var email = $($(this).data('target')); 34 | $.ajax({ 35 | method: 'POST', 36 | url: '/users/verify-email', 37 | data: { 38 | 'email': email.val() 39 | }, 40 | dataType: 'json', 41 | }).done(function(res) { 42 | switch (res['result']) { 43 | case 'valid': 44 | $.alert('success', 'Succeed! This email can be used.'); 45 | break; 46 | case 'invalid': 47 | $.alert('danger', 'Failed! This email format is invalid.'); 48 | email.focus(); 49 | break; 50 | case 'exists': 51 | $.alert('danger', 'Failed! This email already exists, please enter a different email.'); 52 | email.focus(); 53 | break; 54 | default: 55 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 56 | } 57 | }).fail(function() { 58 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 59 | }); 60 | return false; 61 | }); 62 | $('#sign-in-form').submit(function(){ 63 | var form = $(this); 64 | $.ajax({ 65 | method: form.attr('method'), 66 | url: form.attr('action'), 67 | data: form.serialize(), 68 | dataType: 'json', 69 | }).done(function(res) { 70 | switch (res['result']) { 71 | case 'valid': 72 | case 'already_signed': 73 | location.href = '/'; 74 | break; 75 | case 'invalid_account': 76 | $.alert('danger', 'Failed! You have entered an invalid name or password, please make sure you have entered it correctly.'); 77 | $('#password').focus(); 78 | break; 79 | case 'invalid_token': 80 | $.alert("danger", "Error! This captcha token is invalid, please try again."); 81 | break; 82 | default: 83 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 84 | } 85 | }).fail(function() { 86 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 87 | }).always(function() { 88 | form.trigger('refresh-recaptcha'); 89 | }); 90 | return false; 91 | }); 92 | $('#sign-up-form').submit(function(){ 93 | var form = $(this); 94 | $.ajax({ 95 | method: form.attr('method'), 96 | url: form.attr('action'), 97 | data: form.serialize(), 98 | dataType: 'json', 99 | }).done(function(res) { 100 | switch (res['result']) { 101 | case 'valid': 102 | location.href = '/users/sign-in'; 103 | break; 104 | case 'already_signed': 105 | location.href = '/'; 106 | break; 107 | case 'invalid_name': 108 | $.alert("danger", "Failed! This name format is invalid."); 109 | form.find('#name').focus(); 110 | break; 111 | case 'already_exists_name': 112 | $.alert("danger", "Failed! This name already exists, please enter a different name."); 113 | form.find('#name').focus(); 114 | break; 115 | case 'invalid_email': 116 | $.alert("danger", "Failed! This name format is invalid."); 117 | form.find('#email').focus(); 118 | break; 119 | case 'already_exists_email': 120 | $.alert("danger", "Failed! This email already exists, please enter a different email."); 121 | form.find('#email').focus(); 122 | break; 123 | case 'invalid_password': 124 | $.alert("danger", "Failed! This password format is invalid."); 125 | form.find('#password').focus(); 126 | break; 127 | case 'invalid_comment': 128 | $.alert("danger", "Failed! This comment format is invalid."); 129 | form.find('#comment').focus(); 130 | break; 131 | case 'invalid_token': 132 | $.alert("danger", "Error! This captcha token is invalid, please try again."); 133 | break; 134 | default: 135 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 136 | } 137 | }).fail(function() { 138 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 139 | }).always(function() { 140 | form.trigger('refresh-recaptcha'); 141 | }); 142 | return false; 143 | }); 144 | $('#settings-form').submit(function(){ 145 | var form = $(this); 146 | $.ajax({ 147 | method: form.attr('method'), 148 | url: form.attr('action'), 149 | data: form.serialize(), 150 | dataType: 'json', 151 | }).done(function(res) { 152 | switch (res['result']) { 153 | case 'valid': 154 | location.href = res['redirect']; 155 | break; 156 | case 'unsigned': 157 | location.href = '/users/sign-in'; 158 | break; 159 | case 'invalid_name': 160 | $.alert("danger", "Failed! This name format is invalid."); 161 | form.find('#name').focus(); 162 | break; 163 | case 'already_exists_name': 164 | $.alert("danger", "Failed! This name already exists, please enter a different name."); 165 | form.find('#name').focus(); 166 | break; 167 | case 'invalid_email': 168 | $.alert("danger", "Failed! This name format is invalid."); 169 | form.find('#email').focus(); 170 | break; 171 | case 'already_exists_email': 172 | $.alert("danger", "Failed! This email already exists, please enter a different email."); 173 | form.find('#email').focus(); 174 | break; 175 | case 'invalid_password': 176 | $.alert("danger", "Failed! This password format is invalid."); 177 | form.find('#password').focus(); 178 | break; 179 | case 'invalid_comment': 180 | $.alert("danger", "Failed! This comment format is invalid."); 181 | form.find('#comment').focus(); 182 | break; 183 | case 'invalid_token': 184 | $.alert("danger", "Error! This captcha token is invalid, please try again."); 185 | break; 186 | default: 187 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 188 | } 189 | }).fail(function() { 190 | $.alert('danger', 'Error! An unexpected error occurred, please try again.'); 191 | }).always(function() { 192 | form.trigger('refresh-recaptcha'); 193 | }); 194 | return false; 195 | }); 196 | }); -------------------------------------------------------------------------------- /assets/styles/common.css: -------------------------------------------------------------------------------- 1 | /* cyrillic */ 2 | @font-face { 3 | font-family: 'Play'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Play Regular'), local('Play-Regular'), url('/assets/fonts/Play-Regular.ttf') format('truetype'); 7 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 8 | } 9 | /* greek */ 10 | @font-face { 11 | font-family: 'Play'; 12 | font-style: normal; 13 | font-weight: 400; 14 | src: local('Play Regular'), local('Play-Regular'), url('/assets/fonts/Play-Regular.ttf') format('truetype'); 15 | unicode-range: U+0370-03FF; 16 | } 17 | /* vietnamese */ 18 | @font-face { 19 | font-family: 'Play'; 20 | font-style: normal; 21 | font-weight: 400; 22 | src: local('Play Regular'), local('Play-Regular'), url('/assets/fonts/Play-Regular.ttf') format('truetype'); 23 | unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; 24 | } 25 | /* latin-ext */ 26 | @font-face { 27 | font-family: 'Play'; 28 | font-style: normal; 29 | font-weight: 400; 30 | src: local('Play Regular'), local('Play-Regular'), url('/assets/fonts/Play-Regular.ttf') format('truetype'); 31 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 32 | } 33 | /* latin */ 34 | @font-face { 35 | font-family: 'Play'; 36 | font-style: normal; 37 | font-weight: 400; 38 | src: local('Play Regular'), local('Play-Regular'), url('/assets/fonts/Play-Regular.ttf') format('truetype'); 39 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 40 | } 41 | 42 | *{ 43 | font-family: 'Play', 'sans-serif'; 44 | word-break: break-all; 45 | } 46 | a:hover{ 47 | text-decoration: none; 48 | } 49 | .accordion .card:last-of-type { 50 | border-bottom: 1px solid rgba(0,0,0,.125); 51 | } 52 | .table { 53 | border: 1px solid #ddd; 54 | } 55 | .custom-checkbox input, 56 | .custom-checkbox label{ 57 | cursor: pointer; 58 | } -------------------------------------------------------------------------------- /assets/styles/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | route('/\A\/\z/', ['GET'])){ 8 | $tpl->import('views/home/home'); 9 | 10 | 11 | }else if($tpl->route('/\A\/notifications\z/i', ['GET'])){ 12 | $tpl->import('views/notifications/notifications'); 13 | 14 | 15 | }else if($tpl->route('/\A\/challenges\z/i', ['GET'])){ 16 | $tpl->import('views/challenges/challenges'); 17 | 18 | }else if($tpl->route('/\A\/challenges\/\@(?[a-zA-Z0-9_-]{1,10})\z/i', ['GET'], $args)){ 19 | $tpl->import('views/challenges/challenges', $args); 20 | 21 | }else if($tpl->route('/\A\/challenges\/tag\/(?[a-zA-Z0-9_-]{1,10})\z/i', ['GET'], $args)){ 22 | $tpl->import('views/challenges/challenges', $args); 23 | 24 | }else if($tpl->route('/\A\/challenges\/authentication\z/i', ['POST'])){ 25 | $tpl->import('apis/challenges/authentication'); 26 | 27 | 28 | }else if($tpl->route('/\A\/scoreboard\z/i', ['GET'])){ 29 | $tpl->import('views/scoreboard/scoreboard'); 30 | 31 | 32 | }else if($tpl->route('/\A\/solves\z/i', ['GET'])){ 33 | $tpl->import('views/solves/solves'); 34 | 35 | 36 | }else if($tpl->route('/\A\/users\z/i', ['GET'])){ 37 | $tpl->import('views/users/users'); 38 | 39 | }else if($tpl->route('/\A\/users\/sign-in\z/i', ['GET'])){ 40 | $tpl->import('views/users/sign-in'); 41 | 42 | }else if($tpl->route('/\A\/users\/sign-in\z/i', ['POST'])){ 43 | $tpl->import('apis/users/sign-in'); 44 | 45 | }else if($tpl->route('/\A\/users\/sign-up\z/i', ['GET'])){ 46 | $tpl->import('views/users/sign-up'); 47 | 48 | }else if($tpl->route('/\A\/users\/sign-up\z/i', ['POST'])){ 49 | $tpl->import('apis/users/sign-up'); 50 | 51 | }else if($tpl->route('/\A\/users\/verify-name\z/i', ['POST'])){ 52 | $tpl->import('apis/users/verify-name'); 53 | 54 | }else if($tpl->route('/\A\/users\/verify-email\z/i', ['POST'])){ 55 | $tpl->import('apis/users/verify-email'); 56 | 57 | }else if($tpl->route('/\A\/users\/sign-out\z/i', ['GET'])){ 58 | $tpl->import('apis/users/sign-out'); 59 | 60 | }else if($tpl->route('/\A\/users\/\@(?[a-zA-Z0-9_-]{5,20})\z/i', ['GET'], $args)){ 61 | $tpl->import('views/users/profile', $args); 62 | 63 | }else if($tpl->route('/\A\/users\/settings\z/i', ['GET'], $args)){ 64 | $tpl->import('views/users/settings', $args); 65 | 66 | }else if($tpl->route('/\A\/users\/settings\z/i', ['POST'], $args)){ 67 | $tpl->import('apis/users/settings', $args); 68 | 69 | 70 | }else if($tpl->route('/\A\/wechall\/user-score\z/i', ['GET'])){ 71 | $tpl->import('apis/wechall/user-score'); 72 | 73 | }else if($tpl->route('/\A\/wechall\/validate-mail\z/i', ['GET'])){ 74 | $tpl->import('apis/wechall/validate-mail'); 75 | 76 | 77 | }else{ 78 | $tpl->error(404); 79 | 80 | } -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Allow: / --------------------------------------------------------------------------------