diff --git a/composer.json b/composer.json index 0dc4d333..70eb026d 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "license": "CC-BY-NC-4.0", "require": { "facebook/xhp-lib": "2.x", + "facebook/graph-sdk": "5.x", "google/apiclient": "^2.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index ce54b8ed..64d2fa25 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,67 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "7fa4023de48d9c453ad8eaa93913ae96", - "content-hash": "a8b3ade722987ad56f29a0fc697a5c70", + "hash": "98f4d4750954ef68e0ab70174023f199", + "content-hash": "d462af7ff5694e2192b54e1ffd67c264", "packages": [ + { + "name": "facebook/graph-sdk", + "version": "5.6.1", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-graph-sdk.git", + "reference": "2f9639c15ae043911f40ffe44080b32bac2c5280" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-graph-sdk/zipball/2f9639c15ae043911f40ffe44080b32bac2c5280", + "reference": "2f9639c15ae043911f40ffe44080b32bac2c5280", + "shasum": "" + }, + "require": { + "php": "^5.4|^7.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "~5.0", + "mockery/mockery": "~0.8", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "guzzlehttp/guzzle": "Allows for implementation of the Guzzle HTTP client", + "paragonie/random_compat": "Provides a better CSPRNG option in PHP 5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\": "src/Facebook/" + }, + "files": [ + "src/Facebook/polyfills.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Facebook Platform" + ], + "authors": [ + { + "name": "Facebook", + "homepage": "https://github.com/facebook/php-graph-sdk/contributors" + } + ], + "description": "Facebook SDK for PHP", + "homepage": "https://github.com/facebook/php-graph-sdk", + "keywords": [ + "facebook", + "sdk" + ], + "time": "2017-08-16 17:28:07" + }, { "name": "facebook/xhp-lib", "version": "v2.3.2", @@ -133,6 +191,7 @@ "TypeAssert", "hack" ], + "abandoned": "hhvm/type-assert", "time": "2017-02-15 02:26:23" }, { diff --git a/database/countries.sql b/database/countries.sql index 8bf03ef4..4dba1bba 100644 --- a/database/countries.sql +++ b/database/countries.sql @@ -12,7 +12,9 @@ CREATE TABLE `countries` ( `enabled` tinyint(1) DEFAULT 0, `d` text DEFAULT NULL, `transform` text DEFAULT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `iso_code` (`iso_code`), + KEY `enabled` (`enabled`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -298,4 +300,4 @@ UPDATE `countries` SET enabled = 1 WHERE iso_code = "ZA"; UPDATE `countries` SET enabled = 1 WHERE iso_code = "ZM"; UPDATE `countries` SET enabled = 1 WHERE iso_code = "ZW"; /*!40000 ALTER TABLE `countries` ENABLE KEYS */; -UNLOCK TABLES; +UNLOCK TABLES; \ No newline at end of file diff --git a/database/schema.sql b/database/schema.sql index 82a2966b..3c80338e 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -36,7 +36,7 @@ CREATE TABLE `levels` ( `id` int(11) NOT NULL AUTO_INCREMENT, `active` tinyint(1) NOT NULL, `type` varchar(4) NOT NULL, - `title` text NOT NULL, + `title` varchar(255) NOT NULL, `description` text NOT NULL, `entity_id` int(11) NOT NULL, `category_id` int(11) NOT NULL, @@ -48,7 +48,9 @@ CREATE TABLE `levels` ( `hint` text NOT NULL, `penalty` int(11) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `entity_id` (`entity_id`), + KEY `active` (`active`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -61,7 +63,7 @@ DROP TABLE IF EXISTS `categories`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `categories` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `category` text NOT NULL, + `category` varchar(255) NOT NULL, `protected` tinyint(1) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, PRIMARY KEY (`id`) @@ -116,8 +118,8 @@ DROP TABLE IF EXISTS `teams`; CREATE TABLE `teams` ( `id` int(11) NOT NULL AUTO_INCREMENT, `active` tinyint(1) NOT NULL DEFAULT 1, - `name` text NOT NULL, - `password_hash` text NOT NULL, + `name` varchar(255) NOT NULL, + `password_hash` varchar(255) NOT NULL, `points` int(11) NOT NULL DEFAULT 0, `last_score` timestamp NOT NULL, `logo` text NOT NULL, @@ -125,7 +127,9 @@ CREATE TABLE `teams` ( `protected` tinyint(1) NOT NULL DEFAULT 0, `visible` tinyint(1) NOT NULL DEFAULT 1, `created_ts` timestamp NOT NULL DEFAULT 0, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `visible` (`visible`), + KEY `active` (`active`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -146,6 +150,22 @@ CREATE TABLE `livesync` ( ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `teams_oauth` +-- + +DROP TABLE IF EXISTS `teams_oauth`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `teams_oauth` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` text NOT NULL, + `team_id` int(11) NOT NULL, + `token` text NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `teams_data` -- @@ -156,10 +176,11 @@ DROP TABLE IF EXISTS `teams_data`; CREATE TABLE `teams_data` ( `id` int(11) NOT NULL AUTO_INCREMENT, `team_id` int(11) NOT NULL, - `name` text NOT NULL, - `email` text NOT NULL, + `name` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `team_id` (`team_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -172,13 +193,14 @@ DROP TABLE IF EXISTS `sessions`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sessions` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `cookie` text NOT NULL, + `cookie` varchar(200) NOT NULL, `data` text NOT NULL, `team_id` int(11) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, `last_access_ts` timestamp NOT NULL, - `last_page_access` text NOT NULL, - PRIMARY KEY (`id`) + `last_page_access` varchar(200) NOT NULL, + PRIMARY KEY (`id`), + KEY `cookie` (`cookie`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -215,10 +237,16 @@ INSERT INTO `configuration` (field, value, description) VALUES("auto_announce", INSERT INTO `configuration` (field, value, description) VALUES("progressive_cycle", "300", "(Integer) Frequency to take progressive scoreboard in seconds"); INSERT INTO `configuration` (field, value, description) VALUES("bases_cycle", "5", "(Integer) Frequency to score base levels in seconds"); INSERT INTO `configuration` (field, value, description) VALUES("autorun_cycle", "30", "(Integer) Frequency to cycle autorun in seconds"); +INSERT INTO `configuration` (field, value, description) VALUES("gameboard_cycle", "5", "(Integer) Frequency to cycle gameboard in seconds"); +INSERT INTO `configuration` (field, value, description) VALUES("conf_cycle", "10", "(Integer) Frequency to cycle configuration and commandline in seconds"); +INSERT INTO `configuration` (field, value, description) VALUES("leaderboard_limit", "50", "(Integer) Maximum number of teams to show on the leaderboard"); INSERT INTO `configuration` (field, value, description) VALUES("registration", "0", "(Boolean) Ability to register teams"); INSERT INTO `configuration` (field, value, description) VALUES("registration_names", "0", "(Boolean) Registration will ask for names"); INSERT INTO `configuration` (field, value, description) VALUES("registration_type", "1", "(Integer) Type of registration: 1 - Open; 2 - Tokenized;"); INSERT INTO `configuration` (field, value, description) VALUES("registration_players", "3", "(Integer) Number of players per team"); +INSERT INTO `configuration` (field, value, description) VALUES("registration_facebook", "0", "(Boolean) Allow Facebook Registration"); +INSERT INTO `configuration` (field, value, description) VALUES("registration_google", "0", "(Boolean) Allow Google Registration"); +INSERT INTO `configuration` (field, value, description) VALUES("registration_prefix", "Hacker", "(String) Automated Team Registation Name Prefix"); INSERT INTO `configuration` (field, value, description) VALUES("ldap", "0", "(Boolean) Ability to use LDAP to login"); INSERT INTO `configuration` (field, value, description) VALUES("ldap_server", "ldap://localhost", "(String) LDAP Server"); INSERT INTO `configuration` (field, value, description) VALUES("ldap_port", "389", "(Integer) LDAP Port"); @@ -226,6 +254,8 @@ INSERT INTO `configuration` (field, value, description) VALUES("ldap_domain_suff INSERT INTO `configuration` (field, value, description) VALUES("login", "1", "(Boolean) Ability to login"); INSERT INTO `configuration` (field, value, description) VALUES("login_select", "0", "(Boolean) Login selecting the team"); INSERT INTO `configuration` (field, value, description) VALUES("login_strongpasswords", "0", "(Boolean) Enforce using strong passwords"); +INSERT INTO `configuration` (field, value, description) VALUES("login_facebook", "0", "(Boolean) Allow Facebook Login"); +INSERT INTO `configuration` (field, value, description) VALUES("login_google", "0", "(Boolean) Allow Google Login"); INSERT INTO `configuration` (field, value, description) VALUES("password_type", "1", "(Integer) Type of passwords: See table password_types"); INSERT INTO `configuration` (field, value, description) VALUES("default_bonus", "30", "(Integer) Default value for bonus in levels"); INSERT INTO `configuration` (field, value, description) VALUES("default_bonusdec", "10", "(Integer) Default bonus decrement in levels"); @@ -291,12 +321,13 @@ DROP TABLE IF EXISTS `registration_tokens`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `registration_tokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `token` text NOT NULL, + `token` varchar(250) NOT NULL, `used` tinyint(1) NOT NULL, `team_id` int(11) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, `use_ts` timestamp NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `token` (`token`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -314,7 +345,9 @@ CREATE TABLE `scores_log` ( `points` int(11) NOT NULL, `level_id` int(11) NOT NULL, `type` varchar(4) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `level_id` (`level_id`), + KEY `team_id` (`team_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -331,7 +364,8 @@ CREATE TABLE `bases_log` ( `code` int(11) NOT NULL, `response` text NOT NULL, `level_id` int(11) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `level_id` (`level_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -344,12 +378,16 @@ DROP TABLE IF EXISTS `scripts`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `scripts` ( `id` int(11) NOT NULL AUTO_INCREMENT, + `host` varchar(1024) NOT NULL, `ts` timestamp NULL, `pid` int(11) NOT NULL, - `name` text NOT NULL, + `name` varchar(255) NOT NULL, `cmd` text NOT NULL, `status` tinyint(1) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `host` (`host`), + KEY `status` (`status`), + KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -366,7 +404,9 @@ CREATE TABLE `failures_log` ( `team_id` int(11) NOT NULL, `level_id` int(11) NOT NULL, `flag` text NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `team_id` (`team_id`), + KEY `level_id` (`level_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -383,7 +423,9 @@ CREATE TABLE `hints_log` ( `level_id` int(11) NOT NULL, `team_id` int(11) NOT NULL, `penalty` int(11) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `level_id` (`level_id`), + KEY `team_id` (`team_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/database/test_schema.sql b/database/test_schema.sql index 5beadecb..e91e2057 100644 --- a/database/test_schema.sql +++ b/database/test_schema.sql @@ -36,7 +36,7 @@ CREATE TABLE `levels` ( `id` int(11) NOT NULL AUTO_INCREMENT, `active` tinyint(1) NOT NULL, `type` varchar(4) NOT NULL, - `title` text NOT NULL, + `title` varchar(255) NOT NULL DEFAULT '', `description` text NOT NULL, `entity_id` int(11) NOT NULL, `category_id` int(11) NOT NULL, @@ -47,9 +47,11 @@ CREATE TABLE `levels` ( `flag` text NOT NULL, `hint` text NOT NULL, `penalty` int(11) NOT NULL, - `created_ts` timestamp NOT NULL DEFAULT 0, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; + `created_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `entity_id` (`entity_id`), + KEY `active` (`active`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -61,7 +63,7 @@ DROP TABLE IF EXISTS `categories`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `categories` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `category` text NOT NULL, + `category` varchar(255) NOT NULL, `protected` tinyint(1) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, PRIMARY KEY (`id`) @@ -116,8 +118,8 @@ DROP TABLE IF EXISTS `teams`; CREATE TABLE `teams` ( `id` int(11) NOT NULL AUTO_INCREMENT, `active` tinyint(1) NOT NULL DEFAULT 1, - `name` text NOT NULL, - `password_hash` text NOT NULL, + `name` varchar(255) NOT NULL, + `password_hash` varchar(255) NOT NULL, `points` int(11) NOT NULL DEFAULT 0, `last_score` timestamp NOT NULL, `logo` text NOT NULL, @@ -125,7 +127,9 @@ CREATE TABLE `teams` ( `protected` tinyint(1) NOT NULL DEFAULT 0, `visible` tinyint(1) NOT NULL DEFAULT 1, `created_ts` timestamp NOT NULL DEFAULT 0, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `visible` (`visible`), + KEY `active` (`active`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -146,6 +150,22 @@ CREATE TABLE `livesync` ( ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `teams_oauth` +-- + +DROP TABLE IF EXISTS `teams_oauth`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `teams_oauth` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` text NOT NULL, + `team_id` int(11) NOT NULL, + `token` text NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `teams_data` -- @@ -156,10 +176,11 @@ DROP TABLE IF EXISTS `teams_data`; CREATE TABLE `teams_data` ( `id` int(11) NOT NULL AUTO_INCREMENT, `team_id` int(11) NOT NULL, - `name` text NOT NULL, - `email` text NOT NULL, + `name` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `team_id` (`team_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -172,14 +193,15 @@ DROP TABLE IF EXISTS `sessions`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sessions` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `cookie` text NOT NULL, + `cookie` varchar(200) NOT NULL, `data` text NOT NULL, `team_id` int(11) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, `last_access_ts` timestamp NOT NULL, - `last_page_access` text NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; + `last_page_access` varchar(200) NOT NULL, + PRIMARY KEY (`id`), + KEY `cookie` (`cookie`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- @@ -215,10 +237,16 @@ INSERT INTO `configuration` (field, value, description) VALUES("auto_announce", INSERT INTO `configuration` (field, value, description) VALUES("progressive_cycle", "300", "(Integer) Frequency to take progressive scoreboard in seconds"); INSERT INTO `configuration` (field, value, description) VALUES("bases_cycle", "5", "(Integer) Frequency to score base levels in seconds"); INSERT INTO `configuration` (field, value, description) VALUES("autorun_cycle", "30", "(Integer) Frequency to cycle autorun in seconds"); +INSERT INTO `configuration` (field, value, description) VALUES("gameboard_cycle", "5", "(Integer) Frequency to cycle gameboard in seconds"); +INSERT INTO `configuration` (field, value, description) VALUES("conf_cycle", "10", "(Integer) Frequency to cycle configuration and commandline in seconds"); +INSERT INTO `configuration` (field, value, description) VALUES("leaderboard_limit", "50", "(Integer) Maximum number of teams to show on the leaderboard"); INSERT INTO `configuration` (field, value, description) VALUES("registration", "0", "(Boolean) Ability to register teams"); INSERT INTO `configuration` (field, value, description) VALUES("registration_names", "0", "(Boolean) Registration will ask for names"); INSERT INTO `configuration` (field, value, description) VALUES("registration_type", "1", "(Integer) Type of registration: 1 - Open; 2 - Tokenized;"); INSERT INTO `configuration` (field, value, description) VALUES("registration_players", "3", "(Integer) Number of players per team"); +INSERT INTO `configuration` (field, value, description) VALUES("registration_facebook", "0", "(Boolean) Allow Facebook Registration"); +INSERT INTO `configuration` (field, value, description) VALUES("registration_google", "0", "(Boolean) Allow Google Registration"); +INSERT INTO `configuration` (field, value, description) VALUES("registration_prefix", "Hacker", "(String) Automated Team Registation Name Prefix"); INSERT INTO `configuration` (field, value, description) VALUES("ldap", "0", "(Boolean) Ability to use LDAP to login"); INSERT INTO `configuration` (field, value, description) VALUES("ldap_server", "ldap://localhost", "(String) LDAP Server"); INSERT INTO `configuration` (field, value, description) VALUES("ldap_port", "389", "(Integer) LDAP Port"); @@ -226,6 +254,8 @@ INSERT INTO `configuration` (field, value, description) VALUES("ldap_domain_suff INSERT INTO `configuration` (field, value, description) VALUES("login", "1", "(Boolean) Ability to login"); INSERT INTO `configuration` (field, value, description) VALUES("login_select", "0", "(Boolean) Login selecting the team"); INSERT INTO `configuration` (field, value, description) VALUES("login_strongpasswords", "0", "(Boolean) Enforce using strong passwords"); +INSERT INTO `configuration` (field, value, description) VALUES("login_facebook", "0", "(Boolean) Allow Facebook Login"); +INSERT INTO `configuration` (field, value, description) VALUES("login_google", "0", "(Boolean) Allow Google Login"); INSERT INTO `configuration` (field, value, description) VALUES("password_type", "1", "(Integer) Type of passwords: See table password_types"); INSERT INTO `configuration` (field, value, description) VALUES("default_bonus", "30", "(Integer) Default value for bonus in levels"); INSERT INTO `configuration` (field, value, description) VALUES("default_bonusdec", "10", "(Integer) Default bonus decrement in levels"); @@ -291,12 +321,13 @@ DROP TABLE IF EXISTS `registration_tokens`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `registration_tokens` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `token` text NOT NULL, + `token` varchar(250) NOT NULL, `used` tinyint(1) NOT NULL, `team_id` int(11) NOT NULL, `created_ts` timestamp NOT NULL DEFAULT 0, `use_ts` timestamp NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `token` (`token`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -314,7 +345,9 @@ CREATE TABLE `scores_log` ( `points` int(11) NOT NULL, `level_id` int(11) NOT NULL, `type` varchar(4) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `level_id` (`level_id`), + KEY `team_id` (`team_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -331,7 +364,8 @@ CREATE TABLE `bases_log` ( `code` int(11) NOT NULL, `response` text NOT NULL, `level_id` int(11) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `level_id` (`level_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -344,12 +378,16 @@ DROP TABLE IF EXISTS `scripts`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `scripts` ( `id` int(11) NOT NULL AUTO_INCREMENT, + `host` varchar(1024) NOT NULL, `ts` timestamp NULL, `pid` int(11) NOT NULL, - `name` text NOT NULL, + `name` varchar(255) NOT NULL, `cmd` text NOT NULL, `status` tinyint(1) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `host` (`host`), + KEY `status` (`status`), + KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -366,7 +404,9 @@ CREATE TABLE `failures_log` ( `team_id` int(11) NOT NULL, `level_id` int(11) NOT NULL, `flag` text NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `team_id` (`team_id`), + KEY `level_id` (`level_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -383,7 +423,9 @@ CREATE TABLE `hints_log` ( `level_id` int(11) NOT NULL, `team_id` int(11) NOT NULL, `penalty` int(11) NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + KEY `level_id` (`level_id`), + KEY `team_id` (`team_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/extra/nginx.conf b/extra/nginx.conf index cc305a47..c2a8b55a 100644 --- a/extra/nginx.conf +++ b/extra/nginx.conf @@ -40,6 +40,7 @@ server { location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/hhvm/sock; + fastcgi_intercept_errors on; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; diff --git a/extra/nginx/nginx.conf b/extra/nginx/nginx.conf index 61a8da02..c5edb099 100644 --- a/extra/nginx/nginx.conf +++ b/extra/nginx/nginx.conf @@ -39,6 +39,7 @@ server { index index.php; location /data/customlogos/ { + fastcgi_intercept_errors on; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_pass HHVMSERVER:9000; @@ -47,6 +48,7 @@ server { location ~ \.php$ { try_files $uri =404; fastcgi_pass HHVMSERVER:9000; + fastcgi_intercept_errors on; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; diff --git a/extra/settings.ini.example b/extra/settings.ini.example index 5b0cb752..3b65cd38 100644 --- a/extra/settings.ini.example +++ b/extra/settings.ini.example @@ -6,7 +6,9 @@ DB_NAME = 'DATABASE' DB_USERNAME = 'MYUSER' DB_PASSWORD = 'MYPWD' -MC_HOST = 'MCHOST' +MC_HOST[] = 'MCHOST' MC_PORT = '11211' +FACEBOOK_OAUTH_APP_ID = '' +FACEBOOK_OAUTH_APP_SECRET = '' GOOGLE_OAUTH_FILE = '' diff --git a/src/Db.php b/src/Db.php index c1a3bf28..7e101906 100644 --- a/src/Db.php +++ b/src/Db.php @@ -25,6 +25,11 @@ private function __construct() { private function __clone(): void {} + public static function getDatabaseStats(): array { + $db = self::getInstance(); + return $db->pool->getPoolStats(); + } + public function getBackupCmd(): string { $usr = must_have_idx($this->config, 'DB_USERNAME'); $pwd = must_have_idx($this->config, 'DB_PASSWORD'); diff --git a/src/Router.php b/src/Router.php index 22fad642..bffaf9a9 100644 --- a/src/Router.php +++ b/src/Router.php @@ -106,6 +106,6 @@ public static function isRequestModal(): bool { // Check to see if the request is going through the router public static function isRequestRouter(): bool { - return self::getRequestedPage() != "index"; + return self::getRequestedPage() !== "index"; } } diff --git a/src/SessionUtils.php b/src/SessionUtils.php index 0cadfa10..fe0d0301 100644 --- a/src/SessionUtils.php +++ b/src/SessionUtils.php @@ -110,14 +110,14 @@ public static function sessionActive(): bool { public static function enforceLogin(): void { /* HH_IGNORE_ERROR[2050] */ if (!self::sessionActive()) { - throw new IndexRedirectException(); + throw new LoginRedirectException(); } } public static function enforceAdmin(): void { /* HH_IGNORE_ERROR[2050] */ if (!array_key_exists('admin', $_SESSION)) { - throw new IndexRedirectException(); + throw new LoginRedirectException(); } } diff --git a/src/controllers/AdminController.php b/src/controllers/AdminController.php index 55cf8d4c..d8c002be 100644 --- a/src/controllers/AdminController.php +++ b/src/controllers/AdminController.php @@ -152,8 +152,10 @@ class="fb--conf--registration_type" // TODO: Translate password types private async function genStrongPasswordsSelect(): Awaitable<:xhp> { - $types = await Configuration::genAllPasswordTypes(); - $config = await Configuration::genCurrentPasswordType(); + list($types, $config) = await \HH\Asio\va( + Configuration::genAllPasswordTypes(), + Configuration::genCurrentPasswordType(), + ); $select = ; foreach ($types as $type) { $select->appendChild( @@ -170,10 +172,12 @@ class="fb--conf--password_type" } private async function genConfigurationDurationSelect(): Awaitable<:xhp> { - $config = await Configuration::gen('game_duration_unit'); - $duration_unit = $config->getValue(); - $config = await Configuration::gen('game_duration_value'); - $duration_value = $config->getValue(); + list($config_duration_unit, $config_duration_value) = await \HH\Asio\va( + Configuration::gen('game_duration_unit'), + Configuration::gen('game_duration_value'), + ); + $duration_unit = $config_duration_unit->getValue(); + $duration_value = $config_duration_value->getValue(); $minute_selected = $duration_unit === 'm'; $hour_selected = $duration_unit === 'h'; @@ -244,7 +248,7 @@ class="fb--conf--language" $tokens = await Token::genAllTokens(); foreach ($tokens as $token) { if ($token->getUsed()) { - $team = await MultiTeam::genTeam($token->getTeamId()); + $team = await MultiTeam::genTeam($token->getTeamId()); // TODO: Combine Awaits $token_status = {tr('Used by')} {$team->getName()} @@ -298,7 +302,12 @@ class="fb-cta cta--yellow" 'login' => Configuration::gen('login'), 'login_select' => Configuration::gen('login_select'), 'login_strongpasswords' => Configuration::gen('login_strongpasswords'), + 'login_facebook' => Configuration::gen('login_facebook'), + 'login_google' => Configuration::gen('login_google'), 'registration_names' => Configuration::gen('registration_names'), + 'registration_facebook' => Configuration::gen('registration_facebook'), + 'registration_google' => Configuration::gen('registration_google'), + 'registration_prefix' => Configuration::gen('registration_prefix'), 'ldap' => Configuration::gen('ldap'), 'ldap_server' => Configuration::gen('ldap_server'), 'ldap_port' => Configuration::gen('ldap_port'), @@ -310,6 +319,9 @@ class="fb-cta cta--yellow" 'progressive_cycle' => Configuration::gen('progressive_cycle'), 'default_bonus' => Configuration::gen('default_bonus'), 'default_bonusdec' => Configuration::gen('default_bonusdec'), + 'gameboard_cycle' => Configuration::gen('gameboard_cycle'), + 'conf_cycle' => Configuration::gen('conf_cycle'), + 'leaderboard_limit' => Configuration::gen('leaderboard_limit'), 'bases_cycle' => Configuration::gen('bases_cycle'), 'autorun_cycle' => Configuration::gen('autorun_cycle'), 'start_ts' => Configuration::gen('start_ts'), @@ -330,7 +342,13 @@ class="fb-cta cta--yellow" $login = $results['login']; $login_select = $results['login_select']; $login_strongpasswords = $results['login_strongpasswords']; + $login_facebook = $results['login_facebook']; + $login_google = $results['login_google']; $registration_names = $results['registration_names']; + $registration_facebook = $results['registration_facebook']; + $registration_google = $results['registration_google']; + $registration_prefix = $results['registration_prefix']; + $login_google = $results['login_google']; $ldap = $results['ldap']; $ldap_server = $results['ldap_server']; $ldap_port = $results['ldap_port']; @@ -342,6 +360,9 @@ class="fb-cta cta--yellow" $progressive_cycle = $results['progressive_cycle']; $default_bonus = $results['default_bonus']; $default_bonusdec = $results['default_bonusdec']; + $gameboard_cycle = $results['gameboard_cycle']; + $conf_cycle = $results['conf_cycle']; + $leaderboard_limit = $results['leaderboard_limit']; $bases_cycle = $results['bases_cycle']; $autorun_cycle = $results['autorun_cycle']; $start_ts = $results['start_ts']; @@ -352,13 +373,20 @@ class="fb-cta cta--yellow" $custom_org = $results['custom_org']; $custom_byline = $results['custom_byline']; $custom_logo_image = $results['custom_logo_image']; - $registration_on = $registration->getValue() === '1'; $registration_off = $registration->getValue() === '0'; $login_on = $login->getValue() === '1'; $login_off = $login->getValue() === '0'; $login_select_on = $login_select->getValue() === '1'; $login_select_off = $login_select->getValue() === '0'; + $login_facebook_on = $login_facebook->getValue() === '1'; + $login_facebook_off = $login_facebook->getValue() === '0'; + $login_google_on = $login_google->getValue() === '1'; + $login_google_off = $login_google->getValue() === '0'; + $registration_facebook_on = $registration_facebook->getValue() === '1'; + $registration_facebook_off = $registration_facebook->getValue() === '0'; + $registration_google_on = $registration_google->getValue() === '1'; + $registration_google_off = $registration_google->getValue() === '0'; $ldap_on = $ldap->getValue() === '1'; $ldap_off = $ldap->getValue() === '0'; $strong_passwords_on = $login_strongpasswords->getValue() === '1'; @@ -381,7 +409,12 @@ class="fb-cta cta--yellow" $game_start_array = array(); if ($start_ts->getValue() !== '0' && $start_ts->getValue() !== 'NaN') { $game_start_ts = $start_ts->getValue(); - $game_start_array = getdate($game_start_ts); + $game_start_array = array(); + $game_start_array['year'] = gmdate('Y', $game_start_ts); + $game_start_array['mon'] = gmdate('m', $game_start_ts); + $game_start_array['mday'] = gmdate('d', $game_start_ts); + $game_start_array['hours'] = gmdate('H', $game_start_ts); + $game_start_array['minutes'] = gmdate('i', $game_start_ts); } else { $game_start_ts = '0'; $game_start_array['year'] = '0'; @@ -394,7 +427,12 @@ class="fb-cta cta--yellow" $game_end_array = array(); if ($end_ts->getValue() !== '0' && $end_ts->getValue() !== 'NaN') { $game_end_ts = $end_ts->getValue(); - $game_end_array = getdate($game_end_ts); + $game_end_array = array(); + $game_end_array['year'] = gmdate('Y', $game_end_ts); + $game_end_array['mon'] = gmdate('m', $game_end_ts); + $game_end_array['mday'] = gmdate('d', $game_end_ts); + $game_end_array['hours'] = gmdate('H', $game_end_ts); + $game_end_array['minutes'] = gmdate('i', $game_end_ts); } else { $game_end_ts = '0'; $game_end_array['year'] = '0'; @@ -407,10 +445,16 @@ class="fb-cta cta--yellow" if ($game->getValue() === '0') { $timer_start_ts = tr('Not started yet'); $timer_end_ts = tr('Not started yet'); + $game_schedule_reset_text = tr('Reset Schedule'); + $game_schedule_reset_class = 'fb-cta cta--red'; + $game_schedule_reset_action = 'reset-game-schedule'; } else { $timer_start_ts = date(tr('date and time format'), $start_ts->getValue()); $timer_end_ts = date(tr('date and time format'), $end_ts->getValue()); + $game_schedule_reset_text = tr('Game Running'); + $game_schedule_reset_class = 'fb-cta cta--yellowe'; + $game_schedule_reset_action = ''; } $registration_type = await Configuration::gen('registration_type'); @@ -655,6 +699,119 @@ class="icon--badge" +
+
+

{tr('Integration')}

+
+
+
+
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+
+
+
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+
+
+
+ + getValue()} + name="fb--conf--registration_prefix" + /> +
+
+
+

{tr('Active Directory / LDAP')}

@@ -745,6 +902,14 @@ class="icon--badge" name="fb--conf--progressive_cycle" /> +
+ + getValue()} + name="fb--conf--bases_cycle" + /> +
@@ -778,34 +943,16 @@ class="icon--badge" name="fb--conf--default_bonus" />
-
-
-
- - getValue()} - name="fb--conf--bases_cycle" - /> -
- + getValue()} - name="fb--conf--default_bonusdec" + value={$gameboard_cycle->getValue()} + name="fb--conf--gameboard_cycle" />
-
-
- - getValue()} - name="fb--conf--autorun_cycle" - /> -
+
@@ -829,6 +976,40 @@ class="icon--badge"
+
+ + getValue()} + name="fb--conf--default_bonusdec" + /> +
+
+ + getValue()} + name="fb--conf--conf_cycle" + /> +
+
+
+
+ + getValue()} + name="fb--conf--autorun_cycle" + /> +
+
+ + getValue()} + name="fb--conf--leaderboard_limit" + /> +
@@ -836,6 +1017,13 @@ class="icon--badge"

{tr('Game Schedule')}

+
+ +
@@ -1597,7 +1785,7 @@ class= $quiz_id_txt = 'quiz_id'.strval($quiz->getId()); $countries_select = - await $this->genGenerateCountriesSelect($quiz->getEntityId()); + await $this->genGenerateCountriesSelect($quiz->getEntityId()); // TODO: Combine Awaits $delete_button =
@@ -1771,20 +1959,15 @@ class= } public async function genRenderFlagsContent(): Awaitable<:xhp> { - $awaitables = Map { - 'countries_select' => $this->genGenerateCountriesSelect(0), - 'level_categories_select' => $this->genGenerateLevelCategoriesSelect( - 0, - ), - 'filter_categories_select' => - $this->genGenerateFilterCategoriesSelect(), - }; - - $results = await \HH\Asio\m($awaitables); - - $countries_select = $results['countries_select']; - $level_categories_select = $results['level_categories_select']; - $filter_categories_select = $results['filter_categories_select']; + list( + $countries_select, + $level_categories_select, + $filter_categories_select, + ) = await \HH\Asio\va( + $this->genGenerateCountriesSelect(0), + $this->genGenerateLevelCategoriesSelect(0), + $this->genGenerateFilterCategoriesSelect(), + ); $adminsections =
@@ -1990,11 +2173,11 @@ class="fb-cta cta--yellow"
; - $attachments = await Attachment::genHasAttachments($flag->getId()); + $attachments = await Attachment::genHasAttachments($flag->getId()); // TODO: Combine Awaits if ($attachments) { $a_c = 1; $all_attachments = - await Attachment::genAllAttachments($flag->getId()); + await Attachment::genAllAttachments($flag->getId()); // TODO: Combine Awaits foreach ($all_attachments as $attachment) { $attachments_div->appendChild(
@@ -2072,10 +2255,10 @@ class="fb-cta cta--red"
; - $links = await Link::genHasLinks($flag->getId()); + $links = await Link::genHasLinks($flag->getId()); // TODO: Combine Awaits if ($links) { $l_c = 1; - $all_links = await Link::genAllLinks($flag->getId()); + $all_links = await Link::genAllLinks($flag->getId()); // TODO: Combine Awaits foreach ($all_links as $link) { $links_div->appendChild(
; - $has_attachments = await Attachment::genHasAttachments($base->getId()); + $has_attachments = await Attachment::genHasAttachments($base->getId()); // TODO: Combine Awaits if ($has_attachments) { $a_c = 1; $all_attachments = - await Attachment::genAllAttachments($base->getId()); + await Attachment::genAllAttachments($base->getId()); // TODO: Combine Awaits foreach ($all_attachments as $attachment) { $attachments_div->appendChild(
@@ -2605,10 +2775,10 @@ class="fb-cta cta--red"
; - $has_links = await Link::genHasLinks($base->getId()); + $has_links = await Link::genHasLinks($base->getId()); // TODO: Combine Awaits if ($has_links) { $l_c = 1; - $all_links = await Link::genAllLinks($base->getId()); + $all_links = await Link::genAllLinks($base->getId()); // TODO: Combine Awaits foreach ($all_links as $link) { if (filter_var($link->getLink(), FILTER_VALIDATE_URL)) { $link_a = @@ -2652,18 +2822,10 @@ class="fb-cta cta--red" $l_c++; } - $awaitables = Map { - 'countries_select' => $this->genGenerateCountriesSelect( - $base->getEntityId(), - ), - 'level_categories_select' => - $this->genGenerateLevelCategoriesSelect($base->getCategoryId()), - }; - - $results = await HH\Asio\m($awaitables); - - $countries_select = $results['countries_select']; - $level_categories_select = $results['level_categories_select']; + list($countries_select, $level_categories_select) = await \HH\Asio\va( + $this->genGenerateCountriesSelect($base->getEntityId()), + $this->genGenerateLevelCategoriesSelect($base->getCategoryId()), + ); // TODO: Combine Awaits $adminsections->appendChild(
@@ -2870,7 +3032,7 @@ class="highlighted--yellow" ; } - $is_used = await Category::genIsUsed($category->getId()); + $is_used = await Category::genIsUsed($category->getId()); // TODO: Combine Awaits ; if ($is_used || $category->getProtected()) { $delete_action = ; @@ -2968,7 +3130,7 @@ class="not_configuration" $all_countries = await Country::genAllCountries(); foreach ($all_countries as $country) { - $using_country = await Level::genWhoUses($country->getId()); + $using_country = await Level::genWhoUses($country->getId()); // TODO: Combine Awaits $current_use = ($using_country) ? tr('Yes') : tr('No'); if ($country->getEnabled()) { $highlighted_action = 'disable_country'; @@ -3092,12 +3254,12 @@ class={$highlighted_color} private async function genGenerateTeamScores(int $team_id): Awaitable<:xhp> { $scores_div =
; - $scores = await ScoreLog::genAllScoresByTeam($team_id); + $scores = await ScoreLog::genAllScoresByTeam($team_id, true); if (count($scores) > 0) { $scores_tbody = ; foreach ($scores as $score) { - $level = await Level::gen($score->getLevelId()); - $country = await Country::gen($level->getEntityId()); + $level = await Level::gen($score->getLevelId()); // TODO: Combine Awaits + $country = await Country::gen($level->getEntityId()); // TODO: Combine Awaits $level_str = $country->getName().' - '.$level->getTitle(); $scores_tbody->appendChild( @@ -3142,7 +3304,7 @@ class={$highlighted_color} if (count($failures) > 0) { $failures_tbody = ; foreach ($failures as $failure) { - $check_status = await Level::genCheckStatus($failure->getLevelId()); + $check_status = await Level::genCheckStatus($failure->getLevelId()); // TODO: Combine Awaits if (!$check_status) { continue; } @@ -3340,7 +3502,7 @@ class="fb-cta cta--yellow js-confirm-save" $c = 1; $all_teams = await Team::genAllTeams(); foreach ($all_teams as $team) { - $logo_model = await $team->getLogoModel(); + $logo_model = await $team->getLogoModel(); // TODO: Combine Awaits if ($logo_model->getCustom()) { $image = getLogo()}>; } else { @@ -3461,7 +3623,7 @@ class="fb-cta cta--red js-delete-team" 'team_failures' => $this->genGenerateTeamFailures($team->getId()), }; - $results = await \HH\Asio\m($awaitables); + $results = await \HH\Asio\m($awaitables); // TODO: Combine Awaits $team_tabs = $results['team_tabs']; $team_names = $results['team_names']; @@ -3654,7 +3816,7 @@ class="fb-cta cta--yellow js-confirm-save" ; } - $using_logo = await MultiTeam::genWhoUses($logo->getName()); + $using_logo = await MultiTeam::genWhoUses($logo->getName()); // TODO: Combine Awaits $current_use = (count($using_logo) > 0) ? tr('Yes') : tr('No'); if ($logo->getEnabled()) { $highlighted_action = 'disable_logo'; @@ -3745,8 +3907,17 @@ class={$highlighted_color} $c = 1; $all_sessions = await Session::genAllSessions(); foreach ($all_sessions as $session) { + /* HH_IGNORE_ERROR[2050] */ + $cookie = $_COOKIE['FBCTF']; + if ($cookie === $session->getCookie()) { + $session_data = await Session::genSessionDataIfExist($cookie); // TODO: Combine Awaits + await Session::genSetTeamId($cookie, $session_data); // TODO: Combine Awaits + $session = await Session::gen($cookie); // TODO: Combine Awaits + } else if ($session->getTeamId() === 0) { + continue; + } $session_id = 'session_'.strval($session->getId()); - $team = await MultiTeam::genTeam($session->getTeamId()); + $team = await MultiTeam::genTeam($session->getTeamId()); // TODO: Combine Awaits $adminsections->appendChild(
@@ -3859,43 +4030,37 @@ class={$highlighted_color} {$gamelog->getEntry()}; } - $awaitables = Map { - 'team' => MultiTeam::genTeam($gamelog->getTeamId()), - 'level' => Level::gen($gamelog->getLevelId()), - }; - - $results = await \HH\Asio\m($awaitables); + list($team, $level) = await \HH\Asio\va( + MultiTeam::genTeam($gamelog->getTeamId()), + Level::gen($gamelog->getLevelId()), + ); // TODO: Combine Awaits - if ($results->contains('team') && $results->contains('level')) { - $team = $results->get('team'); - invariant($team !== null, 'Team should not be null'); - invariant($team instanceof Team, 'team should be of type Team'); + invariant($team !== null, 'Team should not be null'); + invariant($team instanceof Team, 'team should be of type Team'); - $level = $results->get('level'); - invariant($level !== null, 'Level should not be null'); - invariant($level instanceof Level, 'level should be of type Level'); + invariant($level !== null, 'Level should not be null'); + invariant($level instanceof Level, 'level should be of type Level'); - $country = await Country::gen($level->getEntityId()); + $country = await Country::gen($level->getEntityId()); // TODO: Combine Awaits - $team_name = $team->getName(); + $team_name = $team->getName(); - $level_str = - $country->getName(). - ' - '. - $level->getTitle(). - ' - '. - $level->getType(); - $logs_tbody->appendChild( - - {time_ago($gamelog->getTs())} - {$log_entry} - {$level_str} - {strval($gamelog->getPoints())} - {$team_name} - {$gamelog->getFlag()} - - ); - } + $level_str = + $country->getName(). + ' - '. + $level->getTitle(). + ' - '. + $level->getType(); + $logs_tbody->appendChild( + + {time_ago($gamelog->getTs())} + {$log_entry} + {$level_str} + {strval($gamelog->getPoints())} + {$team_name} + {$gamelog->getFlag()} + + ); $logs_table = @@ -4038,10 +4203,11 @@ public function renderMainContent(): :xhp { {tr('Game Logs')} + +
{$game_action} -


{$pause_action} - +
diff --git a/src/data/announcements.php b/src/data/announcements.php index 02b4aec0..7e6cbf21 100644 --- a/src/data/announcements.php +++ b/src/data/announcements.php @@ -2,12 +2,13 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - class AnnouncementsDataController extends DataController { public async function genGenerateData(): Awaitable { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + $data = array(); $all_announcements = await Announcement::genAllAnnouncements(); @@ -19,5 +20,6 @@ class AnnouncementsDataController extends DataController { } } +/* HH_IGNORE_ERROR[1002] */ $announcementsData = new AnnouncementsDataController(); -\HH\Asio\join($announcementsData->genGenerateData()); +$announcementsData->sendData(); diff --git a/src/data/attachment.php b/src/data/attachment.php index 811d9493..c7a3d5b7 100644 --- a/src/data/attachment.php +++ b/src/data/attachment.php @@ -29,13 +29,10 @@ class AttachmentDataController extends DataController { } } - header('Content-Type: application/octet-stream'); - header("Content-Transfer-Encoding: Binary"); - header('Content-disposition: attachment; filename="'.$filename.'"'); - print $data; + $this->downloadSend($filename, $data); } } /* HH_IGNORE_ERROR[1002] */ -$attachment_file = new AttachmentDataController(); -\HH\Asio\join($attachment_file->genGenerateData()); +$attachmentData = new AttachmentDataController(); +$attachmentData->sendData(); diff --git a/src/data/captures.php b/src/data/captures.php new file mode 100644 index 00000000..b30472f4 --- /dev/null +++ b/src/data/captures.php @@ -0,0 +1,28 @@ + { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + + $data = array(); + + $my_team_id = SessionUtils::sessionTeam(); + + $captures = await ScoreLog::genAllScoresByTeam($my_team_id); + + foreach ($captures as $capture) { + $data[] = $capture->getLevelId(); + } + + $this->jsonSend($data); + } +} + +/* HH_IGNORE_ERROR[1002] */ +$capturesData = new CapturesController(); +$capturesData->sendData(); diff --git a/src/data/command-line.php b/src/data/command-line.php index c1b8932f..c3644e73 100644 --- a/src/data/command-line.php +++ b/src/data/command-line.php @@ -2,12 +2,13 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - class CommandsController extends DataController { public async function genGenerateData(): Awaitable { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + // Object to hold all the data. $commands_line_data = (object) array(); @@ -15,7 +16,18 @@ class CommandsController extends DataController { $results_library = (object) array(); $results_library_key = "results_library"; - $all_levels = await Level::genAllLevels(); + list( + $all_levels, + $all_enabled_countries, + $all_visible_teams, + $all_categories, + ) = await \HH\Asio\va( + Level::genAllLevels(), + Country::genAllEnabledCountries(), + MultiTeam::genAllVisibleTeams(), + Category::genAllCategories(), + ); + $levels_map = Map {}; foreach ($all_levels as $level) { $levels_map[$level->getEntityId()] = $level; @@ -24,7 +36,6 @@ class CommandsController extends DataController { // List of active countries. $countries_results = array(); $countries_key = "country_list"; - $all_enabled_countries = await Country::genAllEnabledCountries(); foreach ($all_enabled_countries as $country) { $level = $levels_map->get($country->getId()); $is_active_level = $level !== null && $level->getActive(); @@ -48,7 +59,6 @@ class CommandsController extends DataController { // List of active teams. $teams_results = array(); $teams_key = "teams"; - $all_visible_teams = await MultiTeam::genAllVisibleTeams(); foreach ($all_visible_teams as $team) { array_push($teams_results, $team->getName()); } @@ -56,7 +66,6 @@ class CommandsController extends DataController { // List of level categories. $categories_results = array(); $categories_key = "categories"; - $all_categories = await Category::genAllCategories(); foreach ($all_categories as $category) { array_push($categories_results, $category->getCategory()); } @@ -128,4 +137,4 @@ class CommandsController extends DataController { } $cmd = new CommandsController(); -\HH\Asio\join($cmd->genGenerateData()); +$cmd->sendData(); diff --git a/src/data/configuration.php b/src/data/configuration.php index 4522cef8..6f7a71a4 100644 --- a/src/data/configuration.php +++ b/src/data/configuration.php @@ -2,35 +2,41 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - class ConfigurationController extends DataController { - // Refresh rate for teams/leaderboard in milliseconds - private string $teams_cycle = "5000"; - // Refresh rate for map/announcements in milliseconds - private string $map_cycle = "5000"; - // Refresh rate for configuration values in milliseconds - private string $conf_cycle = "10000"; - // Refresh rate for commands in milliseconds - private string $cmd_cycle = "10000"; public async function genGenerateData(): Awaitable { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + $conf_data = (object) array(); $control = new Control(); - $gameboard = await Configuration::gen('gameboard'); + $awaitables = Map { + 'gameboard' => Configuration::gen('gameboard'), + 'gameboard_cycle' => Configuration::gen('gameboard_cycle'), + 'conf_cycle' => Configuration::gen('conf_cycle'), + }; + $awaitables_results = await \HH\Asio\m($awaitables); + + $gameboard = $awaitables_results['gameboard']; + // Refresh rate for teams/leaderboard in milliseconds + // Refresh rate for map/announcements in milliseconds + $gameboard_cycle = $awaitables_results['gameboard_cycle']; + // Refresh rate for configuration values in milliseconds + // Refresh rate for commands in milliseconds + $conf_cycle = $awaitables_results['conf_cycle']; /* HH_FIXME[1002] */ /* HH_FIXME[2011] */ $conf_data->{'currentTeam'} = SessionUtils::sessionTeamName(); $conf_data->{'gameboard'} = $gameboard->getValue(); - $conf_data->{'refreshTeams'} = $this->teams_cycle; - $conf_data->{'refreshMap'} = $this->map_cycle; - $conf_data->{'refreshConf'} = $this->conf_cycle; - $conf_data->{'refreshCmd'} = $this->cmd_cycle; + $conf_data->{'refreshTeams'} = ($gameboard_cycle->getValue()) * 1000; + $conf_data->{'refreshMap'} = ($gameboard_cycle->getValue()) * 1000; + $conf_data->{'refreshConf'} = ($conf_cycle->getValue()) * 1000; + $conf_data->{'refreshCmd'} = ($conf_cycle->getValue()) * 1000; $conf_data->{'progressiveCount'} = await Progressive::genCount(); $this->jsonSend($conf_data); @@ -38,4 +44,4 @@ class ConfigurationController extends DataController { } $confController = new ConfigurationController(); -\HH\Asio\join($confController->genGenerateData()); +$confController->sendData(); diff --git a/src/data/controller.php b/src/data/controller.php index 278684cc..9c7f8aa4 100644 --- a/src/data/controller.php +++ b/src/data/controller.php @@ -4,8 +4,31 @@ abstract class DataController { abstract public function genGenerateData(): Awaitable; + public function sendData(): void { + try { + \HH\Asio\join($this->genGenerateData()); + } catch (RedirectException $e) { + if (get_class($this) === "SessionController") { + error_log( + 'RedirectException: ('.get_class($e).') '.$e->getTraceAsString(), + ); + http_response_code($e->getStatusCode()); + Utils::redirect($e->getPath()); + } else { + $this->jsonSend(array()); + } + } + } + public function jsonSend(mixed $data): void { header('Content-Type: application/json'); print json_encode($data); } + + public function downloadSend(string $name, mixed $data): void { + header('Content-Type: application/octet-stream'); + header("Content-Transfer-Encoding: Binary"); + header('Content-disposition: attachment; filename="'.$name.'"'); + print $data; + } } diff --git a/src/data/country-data.php b/src/data/country-data.php index e3e2a632..204fbe36 100644 --- a/src/data/country-data.php +++ b/src/data/country-data.php @@ -2,47 +2,79 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - class CountryDataController extends DataController { public async function genGenerateData(): Awaitable { - $my_team = await MultiTeam::genTeam(SessionUtils::sessionTeam()); + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + + list($my_team, $gameboard, $all_active_levels) = await \HH\Asio\va( + MultiTeam::genTeam(SessionUtils::sessionTeam()), + Configuration::gen('gameboard'), + Level::genAllActiveLevels(), + ); $countries_data = (object) array(); // If gameboard refresing is disabled, exit - $gameboard = await Configuration::gen('gameboard'); if ($gameboard->getValue() === '0') { $this->jsonSend($countries_data); exit(1); } - $all_active_levels = await Level::genAllActiveLevels(); foreach ($all_active_levels as $level) { - $country = await Country::gen(intval($level->getEntityId())); + $awaitables = Map { + 'country' => Country::gen(intval($level->getEntityId())), + 'category' => Category::genSingleCategory($level->getCategoryId()), + 'attachments_list' => Attachment::genAllAttachmentsFileNamesLinks( + $level->getId(), + ), + 'links_list' => Link::genAllLinksValues($level->getId()), + 'completed_by' => MultiTeam::genCompletedLevelTeamNames( + $level->getId(), + ), + }; + $awaitables_results = await \HH\Asio\m($awaitables); // TODO: Combine Awaits + + $country = $awaitables_results['country']; + $category = $awaitables_results['category']; + $attachments_list = $awaitables_results['attachments_list']; + $links_list = $awaitables_results['links_list']; + $completed_by = $awaitables_results['completed_by']; + + invariant( + $country instanceof Country, + 'country should be of type Country', + ); + invariant( + $category instanceof Category, + 'category should be of type Category', + ); + if (!$country) { continue; } - $category = await Category::genSingleCategory($level->getCategoryId()); if ($level->getHint() !== '') { // There is hint, can this team afford it? if ($level->getPenalty() > $my_team->getPoints()) { // Not enough points $hint_cost = -2; $hint = 'no'; } else { - $hint = await HintLog::genPreviousHint( - $level->getId(), - $my_team->getId(), - false, - ); - $score = await ScoreLog::genPreviousScore( - $level->getId(), - $my_team->getId(), - false, - ); + list($hint, $score) = await \HH\Asio\va( + HintLog::genPreviousHint( + $level->getId(), + $my_team->getId(), + false, + ), + ScoreLog::genPreviousScore( + $level->getId(), + $my_team->getId(), + false, + ), + ); // TODO: Combine Awaits + // Has this team requested this hint or scored this level before? if ($hint || $score) { $hint_cost = 0; @@ -56,40 +88,9 @@ class CountryDataController extends DataController { $hint = 'no'; } - // All attachments for this level - $attachments_list = array(); - $has_attachments = await Attachment::genHasAttachments($level->getId()); - if ($has_attachments) { - $all_attachments = - await Attachment::genAllAttachments($level->getId()); - foreach ($all_attachments as $attachment) { - $attachment_details = array(); - $attachment_details['filename'] = $attachment->getFilename(); - $attachment_details['file_link'] = $attachment->getFileLink(); - array_push($attachments_list, $attachment_details); - } - } - - // All links for this level - $links_list = array(); - $has_links = await Link::genHasLinks($level->getId()); - if ($has_links) { - $all_links = await Link::genAllLinks($level->getId()); - foreach ($all_links as $link) { - array_push($links_list, $link->getLink()); - } - } - - // All teams that have completed this level - $completed_by = array(); - $completed_level = await MultiTeam::genCompletedLevel($level->getId()); - foreach ($completed_level as $c) { - array_push($completed_by, $c->getName()); - } - // Who is the first owner of this level - if ($completed_level) { - $owner = await MultiTeam::genFirstCapture($level->getId()); + if ($completed_by) { + $owner = await MultiTeam::genFirstCapture($level->getId()); // TODO: Combine Awaits $owner = $owner->getName(); } else { $owner = 'Uncaptured'; @@ -119,4 +120,4 @@ class CountryDataController extends DataController { } $countryData = new CountryDataController(); -\HH\Asio\join($countryData->genGenerateData()); +$countryData->sendData(); diff --git a/src/data/google_oauth.php b/src/data/google_oauth.php deleted file mode 100644 index 959ff0b1..00000000 --- a/src/data/google_oauth.php +++ /dev/null @@ -1,128 +0,0 @@ -setAuthConfig($google_oauth_file); - $client->setAccessType('offline'); - $client->setScopes(['profile email']); - $client->setRedirectUri( - 'https://'.$_SERVER['HTTP_HOST'].'/data/google_oauth.php', - ); - - $integration_csrf_token = base64_encode(random_bytes(100)); - // Cookie is sent with headers, and therefore not set until after the PHP code executes - this allows us to reset the cookie on each request without clobbering the state - setcookie( - 'integration_csrf_token', - strval($integration_csrf_token), - 0, - '/data/', - must_have_string(Utils::getSERVER(), 'SERVER_NAME'), - true, - true, - ); - $client->setState(strval($integration_csrf_token)); - - if ($code !== false) { - $integration_csrf_token = /* HH_IGNORE_ERROR[2050] */ - idx($_COOKIE, 'integration_csrf_token', false); - if (strval($integration_csrf_token) === '' || - strval($state) === '' || - strval($integration_csrf_token) != strval($state)) { - $code = false; - $error = false; - } - } - - if ($code !== false) { - $client->authenticate($code); - $access_token = $client->getAccessToken(); - $oauth_client = new Google_Service_Oauth2($client); - $profile = $oauth_client->userinfo->get(); - $livesync_password_update = \HH\Asio\join( - Team::genSetLiveSyncPassword( - SessionUtils::sessionTeam(), - "google_oauth", - $profile->email, - $profile->id, - ), - ); - if ($livesync_password_update === true) { - $message = - tr('Your FBCTF account was successfully linked with Google.'); - $javascript_status = - 'window.opener.document.getElementsByClassName("google-link-response")[0].innerHTML = "'. - tr('Your FBCTF account was successfully linked with Google.'). - '"'; - } else { - $message = - tr( - 'There was an error connecting your account to Google, please try again later.', - ); - $javascript_status = - 'window.opener.document.getElementsByClassName("google-link-response")[0].innerHTML = "'. - tr( - 'There was an error connecting your account to Google, please try again later.', - ). - '"'; - } - $javascript_close = "window.open('', '_self', ''); window.close();"; - } else if ($error === true) { - $message = - tr( - 'There was an error connecting your account to Google, please try again later.', - ); - $javascript_status = - 'window.opener.document.getElementsByClassName("google-link-response")[0].innerHTML = "'. - tr( - 'There was an error connecting your account to Google, please try again later.', - ). - '"'; - $javascript_close = "window.open('', '_self', ''); window.close();"; - } else { - $auth_url = $client->createAuthUrl(); - header('Location: '.filter_var($auth_url, FILTER_SANITIZE_URL)); - exit; - } -} else { - $message = tr('Google OAuth is disabled.'); - $javascript_status = - 'window.opener.document.getElementsByClassName("google-link-response")[0].innerHTML = "'. - tr('Google OAuth is disabled.'). - '"'; - $javascript_close = "window.open('', '_self', ''); window.close();"; -} - -$output = -
- - - - {$message} -
- - - -
; - -print $output; diff --git a/src/data/integration_login.php b/src/data/integration_login.php new file mode 100644 index 00000000..57b923e6 --- /dev/null +++ b/src/data/integration_login.php @@ -0,0 +1,57 @@ + { + $type = idx(Utils::getGET(), 'type'); + + if (!is_string($type)) { + $type = "none"; + } + + switch ($type) { + case "facebook": + await self::genProcessFacebookLogin(); + break; + case "google": + await self::genProcessGoogleLogin(); + break; + // FALLTHROUGH + default: + header('Location: /index.php?page=login'); + exit; + break; + } + } + + public static async function genProcessFacebookLogin(): Awaitable { + $enabled = await Integration::facebookLoginEnabled(); + if ($enabled === true) { + $url = await Integration::genFacebookLogin(); + header('Location: '.filter_var($url, FILTER_SANITIZE_URL)); + exit; + } else { + header('Location: /index.php?page=login'); + exit; + } + } + + public static async function genProcessGoogleLogin(): Awaitable { + $enabled = await Integration::googleLoginEnabled(); + if ($enabled === true) { + $url = await Integration::genGoogleLogin(); + header('Location: '.filter_var($url, FILTER_SANITIZE_URL)); + exit; + } else { + header('Location: /index.php?page=login'); + exit; + } + } +} + +$integration_login = new IntegrationLogin(); +\HH\Asio\join($integration_login->genProcessLogin()); diff --git a/src/data/integration_oauth.php b/src/data/integration_oauth.php new file mode 100644 index 00000000..245f7cb8 --- /dev/null +++ b/src/data/integration_oauth.php @@ -0,0 +1,143 @@ + { + + try { + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + } catch (RedirectException $e) { + error_log( + 'RedirectException: ('.get_class($e).') '.$e->getTraceAsString(), + ); + http_response_code($e->getStatusCode()); + Utils::redirect($e->getPath()); + } + + $type = idx(Utils::getGET(), 'type'); + + if (!is_string($type)) { + $type = "none"; + } + + $status = false; + + switch ($type) { + case "facebook": + $status = await self::genProcessFacebookOAuth(); + $provider = "Facebook"; + $container = "facebook-link-response"; + $button = "facebook-oauth-button"; + break; + case "google": + $status = await self::genProcessGoogleOAuth(); + $provider = "Google"; + $container = "google-link-response"; + $button = "google-oauth-button"; + break; + // FALLTHROUGH + default: + $provider = ''; + $container = ''; + $button = ''; + break; + } + + await self::genOutput($status, $provider, $container, $button); + } + + public static async function genOutput( + bool $status, + string $provider, + string $container, + string $button, + ): Awaitable { + await tr_start(); + if ($status === true) { //facebook-link-response + $message = + tr('Your FBCTF account was successfully linked with '.$provider.'.'); + $javascript_status = + 'window.opener.document.getElementsByClassName("'. + $container. + '")[0].innerHTML = "'. + tr('Your FBCTF account was successfully linked with '.$provider.'.'). + '"'; + $javascript_button = + 'window.opener.document.getElementsByName("'. + $button. + '")[0].style.backgroundColor="#1f7a1f"'; + } else { + $message = tr( + 'There was an error connecting your account to '. + $provider. + ', please try again later.', + ); + $javascript_status = + 'window.opener.document.getElementsByClassName("'. + $container. + '")[0].innerHTML = "'. + tr( + 'There was an error connecting your account to '. + $provider. + ', please try again later.', + ). + '"'; + $javascript_button = + 'window.opener.document.getElementsByName("'. + $button. + '")[0].style.backgroundColor="#800000"'; + } + + $javascript_close = "window.open('', '_self', ''); window.close();"; + + $output = +
+ + + + + {$message} +
+ + + +
; + + print $output; + } + + public static async function genProcessFacebookOAuth(): Awaitable { + $enabled = await Integration::facebookOAuthEnabled(); + if ($enabled === true) { + $status = await Integration::genFacebookOAuth(); + return $status; + } + return false; + } + + public static async function genProcessGoogleOAuth(): Awaitable { + $enabled = await Integration::googleOAuthEnabled(); + if ($enabled === true) { + $status = await Integration::genGoogleOAuth(); + return $status; + } + return false; + } + +} + +/* HH_IGNORE_ERROR[1002] */ +$integration_oauth = new IntegrationOAuth(); +\HH\Asio\join($integration_oauth->genProcessOAuth()); diff --git a/src/data/leaderboard.php b/src/data/leaderboard.php index f75e4e74..81cf3645 100644 --- a/src/data/leaderboard.php +++ b/src/data/leaderboard.php @@ -2,12 +2,13 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - class LeaderboardDataController extends DataController { public async function genGenerateData(): Awaitable { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + $leaderboard_data = (object) array(); // If refresing is disabled, exit @@ -17,9 +18,18 @@ class LeaderboardDataController extends DataController { exit(1); } - $leaders = await MultiTeam::genLeaderboard(); - $my_team = await MultiTeam::genTeam(SessionUtils::sessionTeam()); - $my_rank = await Team::genMyRank(SessionUtils::sessionTeam()); + list($leaders, list($my_team, $my_rank), $leaderboard_limit) = + await \HH\Asio\va( + MultiTeam::genLeaderboard(), + MultiTeam::genMyTeamRank(SessionUtils::sessionTeam()), + Configuration::gen('leaderboard_limit'), + ); + + $leaderboard_limit_value = intval($leaderboard_limit->getValue()); + if ($my_rank >= $leaderboard_limit_value) { + $my_rank = $leaderboard_limit_value."+"; + } + $my_team_data = (object) array( 'badge' => $my_team->getLogo(), 'points' => $my_team->getPoints(), @@ -51,4 +61,4 @@ class LeaderboardDataController extends DataController { } $leaderboardData = new LeaderboardDataController(); -\HH\Asio\join($leaderboardData->genGenerateData()); +$leaderboardData->sendData(); diff --git a/src/data/livesync.php b/src/data/livesync.php index 433fcef4..4b5983c1 100644 --- a/src/data/livesync.php +++ b/src/data/livesync.php @@ -8,13 +8,10 @@ class LiveSyncDataController extends DataController { $data = array(); await tr_start(); $input_auth_key = idx(Utils::getGET(), 'auth', ''); - $livesync_awaits = Map { - 'livesync_enabled' => Configuration::gen('livesync'), - 'livesync_auth_key' => Configuration::gen('livesync_auth_key'), - }; - $livesync_awaits_results = await \HH\Asio\m($livesync_awaits); - $livesync_enabled = $livesync_awaits_results['livesync_enabled']; - $livesync_auth_key = $livesync_awaits_results['livesync_auth_key']; + list($livesync_enabled, $livesync_auth_key) = await \HH\Asio\va( + Configuration::gen('livesync'), + Configuration::gen('livesync_auth_key'), + ); if ($livesync_enabled->getValue() === '1' && hash_equals( @@ -59,21 +56,49 @@ class LiveSyncDataController extends DataController { $team_livesync_exists = Map {}; $team_livesync_key = Map {}; foreach ($all_teams as $team) { + $team_livesync_types = Map {}; $team_id = $team->getId(); - $team_livesync_exists->add( - Pair {$team_id, Team::genLiveSyncExists($team_id, "fbctf")}, + + $team_livesync_types->add( + Pair {'fbctf', Team::genLiveSyncExists($team_id, 'fbctf')}, + ); + $team_livesync_types->add( + Pair { + 'facebook_oauth', + Team::genLiveSyncExists($team_id, 'facebook_oauth'), + }, + ); + $team_livesync_types->add( + Pair { + 'google_oauth', + Team::genLiveSyncExists($team_id, 'google_oauth'), + }, ); + + $team_livesync_exists->add(Pair {$team_id, $team_livesync_types}); } - $team_livesync_exists_results = await \HH\Asio\m($team_livesync_exists); - foreach ($team_livesync_exists_results as $team_id => $livesync_exists) { - if ($livesync_exists === true) { - $team_livesync_key->add( - Pair {$team_id, Team::genGetLiveSyncKey($team_id, "fbctf")}, - ); + + foreach ($team_livesync_exists as $team_id => $livesync_types) { + $team_livesync_keys = Map {}; + $team_livesync_exists_results = await \HH\Asio\m($livesync_types); // TODO: Combine Awaits + foreach ($team_livesync_exists_results as + $livesync_type => $livesync_exists) { + if (boolval($livesync_exists) === true) { + $team_livesync_keys->add( + Pair { + $livesync_type, + Team::genGetLiveSyncKey($team_id, $livesync_type), + }, + ); + } } + $team_livesync_keys->add( + Pair {'general', Team::genGetLiveSyncKey($team_id, 'general')}, + ); + $team_livesync_keys_results = await \HH\Asio\m($team_livesync_keys); // TODO: Combine Awaits + $team_livesync_key->add(Pair {$team_id, $team_livesync_keys_results}); } - $team_livesync_key_results = await \HH\Asio\m($team_livesync_key); - $teams_array = $team_livesync_key_results->toArray(); + $teams_array = $team_livesync_key->toArray(); $scores_array = array(); $scored_teams = array(); @@ -83,13 +108,21 @@ class LiveSyncDataController extends DataController { false) { continue; } - $scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['timestamp'] = - $score->getTs(); - $scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['capture'] = - true; - $scores_array[$score->getLevelId()][$teams_array[$score->getTeamId()]]['hint'] = - false; - $scored_teams[$score->getLevelId()][] = $score->getTeamId(); + $team_livesync_array_scores = + $team_livesync_key->get($score->getTeamId()); + invariant( + $team_livesync_array_scores instanceof Map, + 'team_livesync_array_scores should of type Map and not null', + ); + foreach ($team_livesync_array_scores as + $livesync_type => $livesync_key) { + $scores_array[$score->getLevelId()][$livesync_key]['timestamp'] = + $score->getTs(); + $scores_array[$score->getLevelId()][$livesync_key]['capture'] = + true; + $scores_array[$score->getLevelId()][$livesync_key]['hint'] = false; + $scored_teams[$score->getLevelId()][] = $score->getTeamId(); + } } foreach ($all_hints as $hint) { if ($hint->getPenalty()) { @@ -97,17 +130,25 @@ class LiveSyncDataController extends DataController { false) { continue; } - $scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['hint'] = - true; - if (in_array( - $hint->getTeamId(), - $scored_teams[$hint->getLevelId()], - ) === - false) { - $scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['capture'] = - false; - $scores_array[$hint->getLevelId()][$teams_array[$hint->getTeamId()]]['timestamp'] = - $hint->getTs(); + $team_livesync_array_hints = + $team_livesync_key->get($hint->getTeamId()); + invariant( + $team_livesync_array_hints instanceof Map, + 'team_livesync_array_hints should of type Map and not null', + ); + foreach ($team_livesync_array_hints as + $livesync_type => $livesync_key) { + $scores_array[$hint->getLevelId()][$livesync_key]['hint'] = true; + if (in_array( + $hint->getTeamId(), + $scored_teams[$hint->getLevelId()], + ) === + false) { + $scores_array[$hint->getLevelId()][$livesync_key]['capture'] = + false; + $scores_array[$hint->getLevelId()][$livesync_key]['timestamp'] = + $hint->getTs(); + } } } } diff --git a/src/data/map-data.php b/src/data/map-data.php index e781fda3..c3f8a9f2 100644 --- a/src/data/map-data.php +++ b/src/data/map-data.php @@ -2,41 +2,41 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - class MapDataController extends DataController { public async function genGenerateData(): Awaitable { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + $map_data = (object) array(); $my_team_id = SessionUtils::sessionTeam(); $my_name = SessionUtils::sessionTeamName(); - $all_levels = await Level::genAllLevels(); - $enabled_countries = await Country::genAllEnabledCountriesForMap(); - - $levels_map = Map {}; - foreach ($all_levels as $level) { - $levels_map[$level->getEntityId()] = $level; - } + list($all_levels, $enabled_countries) = await \HH\Asio\va( + Level::genAllLevelsCountryMap(), + Country::genAllEnabledCountriesForMap(), + ); foreach ($enabled_countries as $country) { - $country_level = $levels_map->get($country->getId()); + $country_level = $all_levels->get($country->getId()); $is_active_level = $country_level !== null && $country_level->getActive(); $active = ($country->getUsed() && $is_active_level) ? 'active' : ''; if ($country_level) { - $my_previous_score = await ScoreLog::genPreviousScore( - $country_level->getId(), - $my_team_id, - false, - ); - $other_previous_score = await ScoreLog::genPreviousScore( - $country_level->getId(), - $my_team_id, - true, - ); + list($my_previous_score, $other_previous_score) = await \HH\Asio\va( + ScoreLog::genAllPreviousScore( + $country_level->getId(), + $my_team_id, + false, + ), + ScoreLog::genPreviousScore( + $country_level->getId(), + $my_team_id, + true, + ), + ); // TODO: Combine Awaits // If my team has scored if ($my_previous_score) { @@ -46,7 +46,7 @@ class MapDataController extends DataController { } else if ($other_previous_score) { $captured_by = 'opponent'; $completed_by = - await MultiTeam::genCompletedLevel($country_level->getId()); + await MultiTeam::genCompletedLevel($country_level->getId()); // TODO: Combine Awaits $data_captured = ''; foreach ($completed_by as $c) { $data_captured .= ' '.$c->getName(); @@ -74,4 +74,4 @@ class MapDataController extends DataController { } $map = new MapDataController(); -\HH\Asio\join($map->genGenerateData()); +$map->sendData(); diff --git a/src/data/scores.php b/src/data/scores.php index f7d95a88..b29300e4 100644 --- a/src/data/scores.php +++ b/src/data/scores.php @@ -2,20 +2,21 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - class ScoresDataController extends DataController { public async function genGenerateData(): Awaitable { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + $data = array(); - $leaderboard = await MultiTeam::genLeaderboard(); + $leaderboard = await MultiTeam::genLeaderboard(false); foreach ($leaderboard as $team) { $values = array(); $i = 1; $progressive_scoreboard = - await Progressive::genProgressiveScoreboard($team->getName()); + await Progressive::genProgressiveScoreboard($team->getName()); // TODO: Combine Awaits foreach ($progressive_scoreboard as $progress) { $score = (object) array('time' => $i, 'score' => $progress->getPoints()); @@ -35,5 +36,6 @@ class ScoresDataController extends DataController { } } +/* HH_IGNORE_ERROR[1002] */ $scoresData = new ScoresDataController(); -\HH\Asio\join($scoresData->genGenerateData()); +$scoresData->sendData(); diff --git a/src/data/session.php b/src/data/session.php new file mode 100644 index 00000000..fc45d31d --- /dev/null +++ b/src/data/session.php @@ -0,0 +1,19 @@ + { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + + $data = array('true'); + $this->jsonSend($data); + } +} + +/* HH_IGNORE_ERROR[1002] */ +$sessionControler = new SessionController(); +$sessionControler->sendData(); diff --git a/src/data/stats.php b/src/data/stats.php new file mode 100644 index 00000000..12017229 --- /dev/null +++ b/src/data/stats.php @@ -0,0 +1,83 @@ + { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + SessionUtils::enforceAdmin(); + + $stats = array(); + + $awaitables = Map { + 'team_stats' => MultiTeam::genAllTeamsCache(), + 'session_stats' => Session::genAllSessions(), + 'level_stats' => Level::genAllLevels(), + 'active_level_stats' => Level::genAllActiveLevels(), + 'hint_stats' => HintLog::genAllHints(), + 'capture_stats' => ScoreLog::genAllScores(), + }; + $awaitables_results = await \HH\Asio\m($awaitables); + + $team_stats = $awaitables['team_stats']; + $session_stats = $awaitables['session_stats']; + $level_stats = $awaitables['level_stats']; + $active_level_stats = $awaitables['active_level_stats']; + $hint_stats = $awaitables['hint_stats']; + $capture_stats = $awaitables['capture_stats']; + + // Number of teams + $stats['teams'] = count($team_stats); + + // Number of active sessions + $stats['sessions'] = count($session_stats); + + // Number of levels + $stats['levels'] = count($level_stats); + + // Number of active levels + $stats['active_levels'] = count($active_level_stats); + + // Number of captures + $stats['hints'] = count($hint_stats); + + // Number of hints + $stats['captures'] = count($capture_stats); + + // AsyncMysqlConnectionPool Stats + $stats['database'] = Db::getDatabaseStats(); + + // Memcached Stats + $stats['memcached'] = Model::getMemcachedStats(); + + // System load average + $stats['load'] = sys_getloadavg(); + + // System CPU stats + $cpu_stats_1 = file('/proc/stat'); + sleep(1); + $cpu_stats_2 = file('/proc/stat'); + $cpu_info_1 = explode(" ", preg_replace("!cpu +!", "", $cpu_stats_1[0])); + $cpu_info_2 = explode(" ", preg_replace("!cpu +!", "", $cpu_stats_2[0])); + $cpu_diff = array(); + $cpu_diff['user'] = $cpu_info_2[0] - $cpu_info_1[0]; + $cpu_diff['nice'] = $cpu_info_2[1] - $cpu_info_1[1]; + $cpu_diff['sys'] = $cpu_info_2[2] - $cpu_info_1[2]; + $cpu_diff['idle'] = $cpu_info_2[3] - $cpu_info_1[3]; + $cpu_total = array_sum($cpu_diff); + $cpu_stats = array(); + foreach ($cpu_diff as $x => $y) + $cpu_stats[$x] = round($y / $cpu_total * 100, 1); + $stats['cpu'] = $cpu_stats; + + $this->jsonSend($stats); + } +} + +/* HH_IGNORE_ERROR[1002] */ +$statsController = new StatsController(); +$statsController->sendData(); diff --git a/src/data/teams.php b/src/data/teams.php index b4b176cd..a904295a 100644 --- a/src/data/teams.php +++ b/src/data/teams.php @@ -2,29 +2,45 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - class TeamDataController extends DataController { public async function genGenerateData(): Awaitable { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + $rank = 1; - $leaderboard = await MultiTeam::genLeaderboard(); + list($leaderboard, $gameboard, $leaderboard_limit) = await \HH\Asio\va( + MultiTeam::genLeaderboard(), + Configuration::gen('gameboard'), + Configuration::gen('leaderboard_limit'), + ); + $teams_data = (object) array(); // If refresing is disabled, exit - $gameboard = await Configuration::gen('gameboard'); if ($gameboard->getValue() === '0') { $this->jsonSend($teams_data); exit(1); } - foreach ($leaderboard as $team) { - $base = await MultiTeam::genPointsByType($team->getId(), 'base'); - $quiz = await MultiTeam::genPointsByType($team->getId(), 'quiz'); - $flag = await MultiTeam::genPointsByType($team->getId(), 'flag'); + $leaderboard_size = count($leaderboard); + $leaderboard_limit_value = intval($leaderboard_limit->getValue()); + if (($leaderboard_size <= $leaderboard_limit_value) || + ($leaderboard_limit_value === 0)) { + $leaderboard_count = $leaderboard_size; + } else { + $leaderboard_count = $leaderboard_limit_value; + } + for ($i = 0; $i < $leaderboard_count; $i++) { + $team = $leaderboard[$i]; + list($base, $quiz, $flag) = await \HH\Asio\va( + MultiTeam::genPointsByType($team->getId(), 'base'), + MultiTeam::genPointsByType($team->getId(), 'quiz'), + MultiTeam::genPointsByType($team->getId(), 'flag'), + ); - $logo_model = await $team->getLogoModel(); + $logo_model = await $team->getLogoModel(); // TODO: Combine Awaits $team_data = (object) array( 'logo' => array( @@ -54,4 +70,4 @@ class TeamDataController extends DataController { } $teamsData = new TeamDataController(); -\HH\Asio\join($teamsData->genGenerateData()); +$teamsData->sendData(); diff --git a/src/error.php b/src/error.php index 942892bd..eb264021 100644 --- a/src/error.php +++ b/src/error.php @@ -1,4 +1,42 @@ + + + An Error Occured + + + + + + +

An Error Occured!

+
+

Please check your request and try again.

+

If the problem persists, you should contact an admin.

+
+ +
+ + +END +; diff --git a/src/inc/gameboard/listview.php b/src/inc/gameboard/listview.php index b88d01e3..d2d769dc 100644 --- a/src/inc/gameboard/listview.php +++ b/src/inc/gameboard/listview.php @@ -2,25 +2,28 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - -class ListviewController { +class ListviewController extends ModuleController { public async function genRender(): Awaitable<:xhp> { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + await tr_start(); $listview_div =
; $listview_table =
; $active_levels = await Level::genAllActiveLevels(); foreach ($active_levels as $level) { - $country = await Country::gen(intval($level->getEntityId())); - $category = await Category::genSingleCategory($level->getCategoryId()); - $previous_score = await ScoreLog::genPreviousScore( - $level->getId(), - SessionUtils::sessionTeam(), - false, - ); + list($country, $category, $previous_score) = await \HH\Asio\va( + Country::gen(intval($level->getEntityId())), + Category::genSingleCategory($level->getCategoryId()), + ScoreLog::genAllPreviousScore( + $level->getId(), + SessionUtils::sessionTeam(), + false, + ), + ); // TODO: Combine Awaits if ($previous_score) { $span_status = {tr('Captured')}; @@ -45,5 +48,6 @@ class ListviewController { } } +/* HH_IGNORE_ERROR[1002] */ $listview_generated = new ListviewController(); -echo \HH\Asio\join($listview_generated->genRender()); +$listview_generated->sendRender(); diff --git a/src/inc/gameboard/modules/activity-viewmode.php b/src/inc/gameboard/modules/activity-viewmode.php index 9b7af0c3..6f414513 100644 --- a/src/inc/gameboard/modules/activity-viewmode.php +++ b/src/inc/gameboard/modules/activity-viewmode.php @@ -2,13 +2,15 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -class ActivityViewModeModuleController { +class ActivityViewModeModuleController extends ModuleController { public async function genRender(): Awaitable<:xhp> { await tr_start(); $activity_ul =
    ; - $all_activity = await Control::genAllActivity(); - $config = await Configuration::gen('language'); + list($all_activity, $config) = await \HH\Asio\va( + Control::genAllActivity(), + Configuration::gen('language'), + ); $language = $config->getValue(); foreach ($all_activity as $score) { $translated_country = @@ -40,4 +42,4 @@ class ActivityViewModeModuleController { /* HH_IGNORE_ERROR[1002] */ $activity_generated = new ActivityViewModeModuleController(); -echo \HH\Asio\join($activity_generated->genRender()); +$activity_generated->sendRender(); diff --git a/src/inc/gameboard/modules/activity.php b/src/inc/gameboard/modules/activity.php index c98bc5fd..1564cfa9 100644 --- a/src/inc/gameboard/modules/activity.php +++ b/src/inc/gameboard/modules/activity.php @@ -2,22 +2,32 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - -class ActivityModuleController { +class ActivityModuleController extends ModuleController { public async function genRender(): Awaitable<:xhp> { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + await tr_start(); $activity_ul =
      ; - $all_activity = await ActivityLog::genAllActivity(); - $config = await Configuration::gen('language'); + list($all_activity, $config) = await \HH\Asio\va( + ActivityLog::genAllActivity(), + Configuration::gen('language'), + ); $language = $config->getValue(); - foreach ($all_activity as $activity) { + $activity_count = count($all_activity); + $activity_limit = ($activity_count > 100) ? 100 : $activity_count; + for ($i = 0; $i < $activity_limit; $i++) { + $activity = $all_activity[$i]; $subject = $activity->getSubject(); $entity = $activity->getEntity(); $ts = $activity->getTs(); + $visible = $activity->getVisible(); + if ($visible === false) { + continue; + } if (($subject !== '') && ($entity !== '')) { $class_li = ''; $class_span = ''; @@ -78,5 +88,6 @@ class ActivityModuleController { } } +/* HH_IGNORE_ERROR[1002] */ $activity_generated = new ActivityModuleController(); -echo \HH\Asio\join($activity_generated->genRender()); +$activity_generated->sendRender(); diff --git a/src/inc/gameboard/modules/announcements.php b/src/inc/gameboard/modules/announcements.php index a7b3b2d4..673d8379 100644 --- a/src/inc/gameboard/modules/announcements.php +++ b/src/inc/gameboard/modules/announcements.php @@ -2,12 +2,13 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - -class AnnouncementsModuleController { +class AnnouncementsModuleController extends ModuleController { public async function genRender(): Awaitable<:xhp> { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + await tr_start(); $announcements = await Announcement::genAllAnnouncements(); $announcements_ul =
        ; @@ -41,5 +42,6 @@ class AnnouncementsModuleController { } } +/* HH_IGNORE_ERROR[1002] */ $announcements_generated = new AnnouncementsModuleController(); -echo \HH\Asio\join($announcements_generated->genRender()); +$announcements_generated->sendRender(); diff --git a/src/inc/gameboard/modules/controller.php b/src/inc/gameboard/modules/controller.php new file mode 100644 index 00000000..368f3f2f --- /dev/null +++ b/src/inc/gameboard/modules/controller.php @@ -0,0 +1,14 @@ +; + + public function sendRender(): void { + try { + echo \HH\Asio\join($this->genRender()); + } catch (RedirectException $e) { + echo ''; + } + } +} diff --git a/src/inc/gameboard/modules/filter.php b/src/inc/gameboard/modules/filter.php index 739381d6..95ff9c93 100644 --- a/src/inc/gameboard/modules/filter.php +++ b/src/inc/gameboard/modules/filter.php @@ -2,12 +2,13 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - -class FilterModuleController { +class FilterModuleController extends ModuleController { public async function genRender(): Awaitable<:xhp> { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + await tr_start(); $categories_ul =
          ; $categories_ul->appendChild( @@ -134,5 +135,6 @@ class="click-effect"> } } +/* HH_IGNORE_ERROR[1002] */ $filter_generated = new FilterModuleController(); -echo \HH\Asio\join($filter_generated->genRender()); +$filter_generated->sendRender(); diff --git a/src/inc/gameboard/modules/game-clock.php b/src/inc/gameboard/modules/game-clock.php index 48098d9c..69027b77 100644 --- a/src/inc/gameboard/modules/game-clock.php +++ b/src/inc/gameboard/modules/game-clock.php @@ -2,10 +2,7 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); - -class ClockModuleController { +class ClockModuleController extends ModuleController { private async function genGenerateIndicator( string $start_ts, string $end_ts, @@ -44,13 +41,20 @@ class ClockModuleController { } public async function genRender(): Awaitable<:xhp> { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + await tr_start(); - $timer = await Configuration::gen('timer'); - $start_ts = await Configuration::gen('start_ts'); - $end_ts = await Configuration::gen('end_ts'); - $timer = $timer->getValue(); - $start_ts = $start_ts->getValue(); - $end_ts = $end_ts->getValue(); + list($config_timer, $config_start_ts, $config_end_ts) = + await \HH\Asio\va( + Configuration::gen('timer'), + Configuration::gen('start_ts'), + Configuration::gen('end_ts'), + ); + $timer = $config_timer->getValue(); + $start_ts = $config_start_ts->getValue(); + $end_ts = $config_end_ts->getValue(); $now = time(); $init = intval($end_ts) - $now; @@ -142,5 +146,6 @@ class ClockModuleController { } } +/* HH_IGNORE_ERROR[1002] */ $clock_generated = new ClockModuleController(); -echo \HH\Asio\join($clock_generated->genRender()); +$clock_generated->sendRender(); diff --git a/src/inc/gameboard/modules/leaderboard-viewmode.php b/src/inc/gameboard/modules/leaderboard-viewmode.php index 3947114d..67658c8f 100644 --- a/src/inc/gameboard/modules/leaderboard-viewmode.php +++ b/src/inc/gameboard/modules/leaderboard-viewmode.php @@ -2,7 +2,7 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -class LeaderboardModuleViewController { +class LeaderboardModuleViewController extends ModuleController { public async function genRender(): Awaitable<:xhp> { await tr_start(); $leaderboard_ul =
            ; @@ -46,4 +46,4 @@ class LeaderboardModuleViewController { /* HH_IGNORE_ERROR[1002] */ $leaderboard_generated = new LeaderboardModuleViewController(); -echo \HH\Asio\join($leaderboard_generated->genRender()); +$leaderboard_generated->sendRender(); diff --git a/src/inc/gameboard/modules/leaderboard.php b/src/inc/gameboard/modules/leaderboard.php index ee67b599..bc634f0a 100644 --- a/src/inc/gameboard/modules/leaderboard.php +++ b/src/inc/gameboard/modules/leaderboard.php @@ -2,20 +2,23 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - -class LeaderboardModuleController { +class LeaderboardModuleController extends ModuleController { public async function genRender(): Awaitable<:xhp> { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + await tr_start(); $leaderboard_ul =
              ; - $my_team = await MultiTeam::genTeam(SessionUtils::sessionTeam()); - $my_rank = await Team::genMyRank(SessionUtils::sessionTeam()); + list($my_team, $my_rank, $gameboard) = await \HH\Asio\va( + MultiTeam::genTeam(SessionUtils::sessionTeam()), + Team::genMyRank(SessionUtils::sessionTeam()), + Configuration::gen('gameboard'), + ); // If refresing is enabled, do the needful - $gameboard = await Configuration::gen('gameboard'); if ($gameboard->getValue() === '1') { $leaders = await MultiTeam::genLeaderboard(); $rank = 1; @@ -24,7 +27,7 @@ class LeaderboardModuleController { $team = $leaders[$i]; // TODO also duplicated in modules/teams.php. Needs to be un-duplicated. - $logo_model = await $team->getLogoModel(); + $logo_model = await $team->getLogoModel(); // TODO: Combine Awaits if ($logo_model->getCustom()) { $image = getLogo()}>; @@ -56,6 +59,15 @@ class LeaderboardModuleController { } } + if ($my_team->getVisible() === true) { + $leaderboard_limit = await Configuration::gen('leaderboard_limit'); + if ($my_rank >= intval($leaderboard_limit->getValue())) { + $my_rank = intval($leaderboard_limit->getValue()) + 1 ."+"; + } + } else { + $my_rank = "N/A"; + } + return
              @@ -81,5 +93,6 @@ class LeaderboardModuleController { } } +/* HH_IGNORE_ERROR[1002] */ $leaderboard_generated = new LeaderboardModuleController(); -echo \HH\Asio\join($leaderboard_generated->genRender()); +$leaderboard_generated->sendRender(); diff --git a/src/inc/gameboard/modules/teams.php b/src/inc/gameboard/modules/teams.php index f8328308..88e32a2e 100644 --- a/src/inc/gameboard/modules/teams.php +++ b/src/inc/gameboard/modules/teams.php @@ -2,12 +2,13 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - -class TeamModuleController { +class TeamModuleController extends ModuleController { public async function genRender(): Awaitable<:xhp> { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + await tr_start(); $leaderboard = await MultiTeam::genLeaderboard(); $rank = 1; @@ -16,8 +17,19 @@ class TeamModuleController { $gameboard = await Configuration::gen('gameboard'); if ($gameboard->getValue() === '1') { - foreach ($leaderboard as $leader) { - $logo_model = await $leader->getLogoModel(); + $leaderboard_size = count($leaderboard); + $leaderboard_limit = await Configuration::gen('leaderboard_limit'); + $leaderboard_limit_value = intval($leaderboard_limit->getValue()); + + if (($leaderboard_size <= $leaderboard_limit_value) || + ($leaderboard_limit_value === 0)) { + $leaderboard_count = $leaderboard_size; + } else { + $leaderboard_count = $leaderboard_limit_value; + } + for ($i = 0; $i < $leaderboard_count; $i++) { + $leader = $leaderboard[$i]; + $logo_model = await $leader->getLogoModel(); // TODO: Combine Awaits if ($logo_model->getCustom()) { $image = getLogo()}>; @@ -66,5 +78,6 @@ class="click-effect">{tr('Everyone')} < } } +/* HH_IGNORE_ERROR[1002] */ $teams_generated = new TeamModuleController(); -echo \HH\Asio\join($teams_generated->genRender()); +$teams_generated->sendRender(); diff --git a/src/index.php b/src/index.php index c34a3428..e7be2691 100644 --- a/src/index.php +++ b/src/index.php @@ -7,7 +7,9 @@ $response = await Router::genRoute(); echo $response; } catch (RedirectException $e) { - error_log($e->getTraceAsString()); + error_log( + 'RedirectException: ('.get_class($e).') '.$e->getTraceAsString(), + ); http_response_code($e->getStatusCode()); Utils::redirect($e->getPath()); } diff --git a/src/models/ActivityLog.php b/src/models/ActivityLog.php index 9f860cb2..2cbcc7a3 100644 --- a/src/models/ActivityLog.php +++ b/src/models/ActivityLog.php @@ -18,6 +18,7 @@ private function __construct( private string $formatted_subject = '', private string $formatted_entity = '', private string $formatted_message = '', + private bool $visible = true, ) { $formatted_subject = \HH\Asio\join(self::genFormatString("%s", $this->subject)); @@ -28,6 +29,9 @@ private function __construct( $formatted_message = \HH\Asio\join(self::genFormatString($this->message, $this->arguments)); $this->formatted_message = $formatted_message; + $visible = + \HH\Asio\join(self::genLogEntryVisible($this->subject, $this->action)); + $this->visible = $visible; } public function getId(): int { @@ -70,6 +74,10 @@ public function getTs(): string { return $this->ts; } + public function getVisible(): bool { + return $this->visible; + } + private static function activitylogFromRow( Map $row, ): ActivityLog { @@ -97,7 +105,7 @@ private static function activitylogFromRow( case "Team": $team_exists = await Team::genTeamExistById(intval($id)); if ($team_exists === true) { - $team = await Team::genTeam(intval($id)); + $team = await MultiTeam::genTeam(intval($id)); $variables[] = $team->getName(); } else { return ''; @@ -133,6 +141,25 @@ private static function activitylogFromRow( return $string; } + public static async function genLogEntryVisible( + string $subject, + string $action, + ): Awaitable { + if ($subject === '') { + return true; + } + list($class, $id) = explode(':', $subject); + if ($class === 'Team' && $action === 'captured') { + $team_exists = await Team::genTeamExistById(intval($id)); + if ($team_exists === true) { + $team = await MultiTeam::genTeam(intval($id)); + return $team->getVisible(); + } else + return false; + } + return true; + } + public static async function genCreate( string $subject, string $action, @@ -191,8 +218,10 @@ private static function activitylogFromRow( string $entity_class, int $entity_id, ): Awaitable { - $config_game = await Configuration::gen('game'); - $config_pause = await Configuration::gen('game_paused'); + list($config_game, $config_pause) = await \HH\Asio\va( + Configuration::gen('game'), + Configuration::gen('game_paused'), + ); if ((intval($config_game->getValue()) === 1) && (intval($config_pause->getValue()) === 0)) { await self::genCreate( @@ -255,8 +284,9 @@ private static function activitylogFromRow( if (!$mc_result || count($mc_result) === 0 || $refresh) { $db = await self::genDb(); $activity_log_lines = array(); - $result = - await $db->queryf('SELECT * FROM activity_log ORDER BY ts DESC'); + $result = await $db->query( + 'SELECT * FROM activity_log ORDER BY ts DESC LIMIT 100', + ); foreach ($result->mapRows() as $row) { $activity_log = self::activitylogFromRow($row); if (($activity_log->getFormattedMessage() !== '') || diff --git a/src/models/Announcement.php b/src/models/Announcement.php index cd2b87ca..e7e3014d 100644 --- a/src/models/Announcement.php +++ b/src/models/Announcement.php @@ -18,7 +18,7 @@ public function getId(): int { } public function getAnnouncement(): string { - return $this->announcement; + return mb_convert_encoding($this->announcement, 'UTF-8'); } public function getTs(): string { @@ -50,8 +50,10 @@ private static function announcementFromRow( public static async function genCreateAuto( string $announcement, ): Awaitable { - $config_game = await Configuration::gen('game'); - $config_pause = await Configuration::gen('game_paused'); + list($config_game, $config_pause) = await \HH\Asio\va( + Configuration::gen('game'), + Configuration::gen('game_paused'), + ); if ((intval($config_game->getValue()) === 1) && (intval($config_pause->getValue()) === 0)) { $auto_announce = await Configuration::gen('auto_announce'); diff --git a/src/models/Attachment.php b/src/models/Attachment.php index fbb151f9..585ed233 100644 --- a/src/models/Attachment.php +++ b/src/models/Attachment.php @@ -11,6 +11,8 @@ class Attachment extends Model { 'LEVELS_COUNT' => 'attachment_levels_count', 'LEVEL_ATTACHMENTS' => 'attachment_levels', 'ATTACHMENTS' => 'attachments_by_id', + 'LEVEL_ATTACHMENTS_NAMES' => 'attachment_file_names', + 'LEVEL_ATTACHMENTS_LINKS' => 'attachment_file_links', }; private function __construct( @@ -62,14 +64,15 @@ public function getLevelId(): int { // Extract extension and name $parts = explode('.', $filename, 2); - $local_filename .= firstx($parts).'_'.$md5_str; + $local_filename .= + mb_convert_encoding(firstx($parts), 'UTF-8').'_'.$md5_str; $extension = idx($parts, 1); if ($extension !== null) { - $local_filename .= '.'.$extension; + $local_filename .= '.'.mb_convert_encoding($extension, 'UTF-8'); } - // Remove all non alpahnum characters from filename - allow international chars, dash, underscore, and period + // Remove all non alphanum characters from filename - allow international chars, dash, underscore, and period $local_filename = preg_replace('/[^\p{L}\p{N}_\-.]+/u', '_', $local_filename); @@ -197,6 +200,168 @@ public function getLevelId(): int { } } + public static async function genAllAttachmentsForGame( + bool $refresh = false, + ): Awaitable> { + $mc_result = self::getMCRecords('LEVEL_ATTACHMENTS'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $db = await self::genDb(); + $attachments = array(); + $result = await $db->queryf('SELECT * FROM attachments'); + foreach ($result->mapRows() as $row) { + $attachments[intval($row->get('level_id'))][] = + self::attachmentFromRow($row); + } + self::setMCRecords('LEVEL_ATTACHMENTS', new Map($attachments)); + $attachments = new Map($attachments); + invariant( + $attachments instanceof Map, + 'attachments should be a Map of Attachment', + ); + return $attachments; + } else { + invariant( + $mc_result instanceof Map, + 'cache return should be of type Map', + ); + return $mc_result; + } + } + + public static async function genAllAttachmentsFileNames( + int $level_id, + bool $refresh = false, + ): Awaitable> { + $mc_result = self::getMCRecords('LEVEL_ATTACHMENTS_NAMES'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $db = await self::genDb(); + $filenames = array(); + $attachments = await self::genAllAttachmentsForGame(); + invariant( + $attachments instanceof Map, + 'attachments should be a Map of Attachment', + ); + foreach ($attachments as $level => $attachment_arr) { + invariant( + is_array($attachment_arr), + 'attachment_arr should be an array of Attachment', + ); + foreach ($attachment_arr as $attach_obj) { + invariant( + $attach_obj instanceof Attachment, + 'link_obj should be of type Attachment', + ); + $filenames[$level][] = $attach_obj->getFilename(); + } + } + self::setMCRecords('LEVEL_ATTACHMENTS_NAMES', new Map($filenames)); + $filenames = new Map($filenames); + if ($filenames->contains($level_id)) { + $filename = $filenames->get($level_id); + invariant( + is_array($filename), + 'filename should be an array of string', + ); + return $filename; + } else { + return array(); + } + } else { + invariant( + $mc_result instanceof Map, + 'cache return should be of type Map', + ); + if ($mc_result->contains($level_id)) { + $filename = $mc_result->get($level_id); + invariant( + is_array($filename), + 'filename should be an array of string', + ); + return $filename; + } else { + return array(); + } + } + } + + public static async function genAllAttachmentsFileLinks( + int $level_id, + bool $refresh = false, + ): Awaitable> { + $mc_result = self::getMCRecords('LEVEL_ATTACHMENTS_LINKS'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $db = await self::genDb(); + $attachment_links = array(); + $attachments = await self::genAllAttachmentsForGame(); + invariant( + $attachments instanceof Map, + 'attachments should be a Map of Attachment', + ); + foreach ($attachments as $level => $attachment_arr) { + invariant( + is_array($attachment_arr), + 'attachment_arr should be an array of Attachment', + ); + foreach ($attachment_arr as $attach_obj) { + invariant( + $attach_obj instanceof Attachment, + 'link_obj should be of type Attachment', + ); + $attachment_links[$level][] = $attach_obj->getFileLink(); + } + } + self::setMCRecords( + 'LEVEL_ATTACHMENTS_LINKS', + new Map($attachment_links), + ); + $attachment_links = new Map($attachment_links); + if ($attachment_links->contains($level_id)) { + $attachment_link = $attachment_links->get($level_id); + invariant( + is_array($attachment_link), + 'attachment link should be an array of string', + ); + return $attachment_link; + } else { + return array(); + } + } else { + invariant( + $mc_result instanceof Map, + 'cache return should be of type Map', + ); + if ($mc_result->contains($level_id)) { + $attachment_link = $mc_result->get($level_id); + invariant( + is_array($attachment_link), + 'attachment_link should be an array of string', + ); + return $attachment_link; + } else { + return array(); + } + } + } + + public static async function genAllAttachmentsFileNamesLinks( + int $level_id, + bool $refresh = false, + ): Awaitable>> { + $filenames_links = array(); + list($file_names, $file_links) = await \HH\Asio\va( + self::genAllAttachmentsFileNames($level_id), + self::genAllAttachmentsFileLinks($level_id), + ); + + foreach ($file_names as $idx => $file_name) { + if (idx($file_links, $idx) !== null) { + $filenames_links[$idx]['filename'] = $file_name; + $filenames_links[$idx]['file_link'] = $file_links[$idx]; + } + } + return $filenames_links; + } + // Get a single attachment. /* HH_IGNORE_ERROR[4110]: Claims - It is incompatible with void because this async function implicitly returns Awaitable, yet this returns Awaitable and the type is checked on line 185 */ public static async function gen( diff --git a/src/models/Cache.php b/src/models/Cache.php new file mode 100644 index 00000000..08aa0d63 --- /dev/null +++ b/src/models/Cache.php @@ -0,0 +1,31 @@ + $CACHE = Map {}; + + public function __construct() {} + + public function setCache(string $key, mixed $value): void { + $this->CACHE->add(Pair {strval($key), $value}); + } + + public function getCache(string $key): mixed { + if ($this->CACHE->contains($key)) { + return $this->CACHE->get($key); + } else { + return false; + } + } + + public function deleteCache(string $key): void { + if ($this->CACHE->contains($key)) { + $this->CACHE->remove($key); + } + } + + public function flushCache(): void { + $this->CACHE = Map {}; + } + +} diff --git a/src/models/Configuration.php b/src/models/Configuration.php index dc344e40..25f1623f 100644 --- a/src/models/Configuration.php +++ b/src/models/Configuration.php @@ -2,7 +2,16 @@ class Configuration extends Model { - const string MC_KEY = 'configuration:'; + protected static string $MC_KEY = 'configuration:'; + + protected static Map + $MC_KEYS = Map { + 'CONFIGURATION' => 'config_field', + 'FACEBOOK_INTEGRATION_APP_ID' => 'integration_facebook_app_id', + 'FACEBOOK_INTEGRATION_APP_SECRET' => + 'integration_facebook_app_secret', + 'GOOGLE_INTEGRATION_FILE' => 'integration_google_file', + }; private function __construct( private int $id, @@ -28,23 +37,52 @@ public function getDescription(): string { } // Get configuration entry. - public static async function gen(string $field): Awaitable { - $mc = self::getMc(); - $key = self::MC_KEY.$field; - $mc_result = $mc->get($key); - if ($mc_result) { - $result = $mc_result; - } else { + public static async function gen( + string $field, + bool $refresh = false, + ): Awaitable { + $mc_result = self::getMCRecords('CONFIGURATION'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { $db = await self::genDb(); - $db_result = await $db->queryf( - 'SELECT * FROM configuration WHERE field = %s LIMIT 1', + $config_values = Map {}; + $result = await $db->queryf('SELECT * FROM configuration'); + foreach ($result->mapRows() as $row) { + $config_values->add( + Pair { + strval($row->get('field')), + self::configurationFromRow($row->toArray()), + }, + ); + } + self::setMCRecords('CONFIGURATION', $config_values); + invariant( + $config_values->contains($field) !== false, + 'config value not found (db): %s', $field, ); - invariant($db_result->numRows() === 1, 'Expected exactly one result'); - $result = firstx($db_result->mapRows())->toArray(); - $mc->set($key, $result); + $config = $config_values->get($field); + invariant( + $config instanceof Configuration, + 'config cache value should of type Configuration and not null', + ); + return $config; + } else { + invariant( + $mc_result instanceof Map, + 'config cache return should be of type Map and not null', + ); + invariant( + $mc_result->contains($field) !== false, + 'config value not found (cache): %s', + $field, + ); + $config = $mc_result->get($field); + invariant( + $config instanceof Configuration, + 'config cache value should of type Configuration and not null', + ); + return $config; } - return self::configurationFromRow($result); } // Change configuration field. @@ -62,7 +100,7 @@ public function getDescription(): string { await Session::genDeleteAllUnprotected(); } - self::getMc()->delete(self::MC_KEY.$field); + self::invalidateMCRecords(); // Invalidate Configuration data. } // Check if field is valid. @@ -96,10 +134,11 @@ public function getDescription(): string { public static async function genCurrentPasswordType( ): Awaitable { $db = await self::genDb(); - $db_result = await $db->queryf( - 'SELECT * FROM password_types WHERE field = (SELECT value FROM configuration WHERE field = %s) LIMIT 1', - 'password_type' - ); + $db_result = + await $db->queryf( + 'SELECT * FROM password_types WHERE field = (SELECT value FROM configuration WHERE field = %s) LIMIT 1', + 'password_type', + ); invariant($db_result->numRows() === 1, 'Expected exactly one result'); $result = firstx($db_result->mapRows())->toArray(); @@ -132,26 +171,69 @@ private static function configurationFromRow( ); } - public static function genGoogleOAuthFileExists(): bool { - $settings_file = '../../settings.ini'; - $config = parse_ini_file($settings_file); + public static function getFacebookOAuthSettingsExists( + bool $refresh = false, + ): bool { + return (self::getFacebookOAuthSettingsAppId($refresh) !== '' && + self::getFacebookOAuthSettingsAppSecret($refresh) !== ''); + } - if ((array_key_exists('GOOGLE_OAUTH_FILE', $config) === true) && - (file_exists($config['GOOGLE_OAUTH_FILE']) === true)) { - return true; + public static function getFacebookOAuthSettingsAppId( + bool $refresh = false, + ): string { + $mc_result = self::getMCRecords('FACEBOOK_INTEGRATION_APP_ID'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $settings_file = '../../settings.ini'; + $config = parse_ini_file($settings_file); + $app_id = ''; + if (array_key_exists('FACEBOOK_OAUTH_APP_ID', $config) === true) { + $app_id = strval($config['FACEBOOK_OAUTH_APP_ID']); + } + self::setMCRecords('FACEBOOK_INTEGRATION_APP_ID', $app_id); + return $app_id; + } else { + return strval($mc_result); } - return false; } - public static function genGoogleOAuthFile(): string { - $settings_file = '../../settings.ini'; - $config = parse_ini_file($settings_file); - - if ((array_key_exists('GOOGLE_OAUTH_FILE', $config) === true) && - (file_exists($config['GOOGLE_OAUTH_FILE']) === true)) { - return strval($config['GOOGLE_OAUTH_FILE']); + public static function getFacebookOAuthSettingsAppSecret( + bool $refresh = false, + ): string { + $mc_result = self::getMCRecords('FACEBOOK_INTEGRATION_APP_SECRET'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $settings_file = '../../settings.ini'; + $config = parse_ini_file($settings_file); + $app_secret = ''; + if (array_key_exists('FACEBOOK_OAUTH_APP_SECRET', $config) === true) { + $app_secret = strval($config['FACEBOOK_OAUTH_APP_SECRET']); + } + self::setMCRecords('FACEBOOK_INTEGRATION_APP_SECRET', $app_secret); + return $app_secret; + } else { + return strval($mc_result); } + } + + public static function getGoogleOAuthFileExists( + bool $refresh = false, + ): bool { + return (self::getGoogleOAuthFile($refresh) !== ''); + } - return ''; + public static function getGoogleOAuthFile(bool $refresh = false): string { + $mc_result = self::getMCRecords('GOOGLE_INTEGRATION_FILE'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $settings_file = '../../settings.ini'; + $config = parse_ini_file($settings_file); + $oauth_file = ''; + if ((array_key_exists('GOOGLE_OAUTH_FILE', $config) === true) && + (file_exists($config['GOOGLE_OAUTH_FILE']) === true)) { + $oauth_file = strval($config['GOOGLE_OAUTH_FILE']); + } + self::setMCRecords('GOOGLE_INTEGRATION_FILE', $oauth_file); + return $oauth_file; + } else { + return strval($mc_result); + } } } diff --git a/src/models/Control.php b/src/models/Control.php index 57d0aa32..93c6b7a1 100644 --- a/src/models/Control.php +++ b/src/models/Control.php @@ -7,93 +7,97 @@ class Control extends Model { protected static Map $MC_KEYS = Map {'ALL_ACTIVITY' => 'activity'}; + public static async function genServerAddr(): Awaitable { + $host = gethostname(); + $ip = gethostbyname($host); + return strval($ip); + } + public static async function genStartScriptLog( int $pid, string $name, string $cmd, ): Awaitable { $db = await self::genDb(); + $host = await Control::genServerAddr(); await $db->queryf( - 'INSERT INTO scripts (ts, pid, name, cmd, status) VALUES (NOW(), %d, %s, %s, 1)', + 'INSERT INTO scripts (ts, pid, name, host, cmd, status) VALUES (NOW(), %d, %s, %s, %s, 1)', $pid, $name, + $host, $cmd, ); } public static async function genStopScriptLog(int $pid): Awaitable { $db = await self::genDb(); + $host = await Control::genServerAddr(); await $db->queryf( - 'UPDATE scripts SET status = 0 WHERE pid = %d LIMIT 1', + 'UPDATE scripts SET status = 0, host = %s WHERE pid = %d LIMIT 1', + $host, $pid, ); } public static async function genScriptPid(string $name): Awaitable { $db = await self::genDb(); - $result = await $db->queryf( - 'SELECT pid FROM scripts WHERE name = %s AND status = 1 LIMIT 1', - $name, - ); - return intval(must_have_idx($result->mapRows()[0], 'pid')); + $host = await Control::genServerAddr(); + $result = + await $db->queryf( + 'SELECT pid FROM scripts WHERE name = %s AND host = %s AND status = 1 LIMIT 1', + $name, + $host, + ); + $pid = 0; + if ($result->numRows() > 0) { + $pid = intval(must_have_idx($result->mapRows()[0], 'pid')); + } + return $pid; } public static async function genClearScriptLog(): Awaitable { $db = await self::genDb(); - await $db->queryf('DELETE FROM scripts WHERE id > 0 AND status = 0'); + $host = await Control::genServerAddr(); + await $db->queryf( + 'DELETE FROM scripts WHERE id > 0 AND status = 0 AND host = %s', + $host, + ); } public static async function genBegin(): Awaitable { - // Disable registration - await Configuration::genUpdate('registration', '0'); - - // Clear announcements log - await Announcement::genDeleteAll(); - - // Clear activity log - await ActivityLog::genDeleteAll(); - - // Announce game starting - await Announcement::genCreateAuto('Game has started!'); - - // Log game starting - await ActivityLog::genCreateGenericLog('Game has started!'); - - // Reset all points - await Team::genResetAllPoints(); - - // Clear scores log - await ScoreLog::genResetScores(); - - // Clear hints log - await HintLog::genResetHints(); - - // Clear failures log - await FailureLog::genResetFailures(); - - // Clear bases log - await self::genResetBases(); - await self::genClearScriptLog(); - - // Mark game as started - await Configuration::genUpdate('game', '1'); + await \HH\Asio\va( + Announcement::genDeleteAll(), // Clear announcements log + ActivityLog::genDeleteAll(), // Clear activity log + Team::genResetAllPoints(), // Reset all points + ScoreLog::genResetScores(), // Clear scores log + HintLog::genResetHints(), // Clear hints log + FailureLog::genResetFailures(), // Clear failures log + self::genResetBases(), // Clear bases log + self::genClearScriptLog(), + Configuration::genUpdate('registration', '0'), // Disable registration + ); - // Enable scoring - await Configuration::genUpdate('scoring', '1'); + await \HH\Asio\va( + Announcement::genCreateAuto('Game has started!'), // Announce game starting + ActivityLog::genCreateGenericLog('Game has started!'), // Log game starting + Configuration::genUpdate('game', '1'), // Mark game as started + Configuration::genUpdate('scoring', '1'), // Enable scoring + ); // Take timestamp of start $start_ts = time(); await Configuration::genUpdate('start_ts', strval($start_ts)); // Calculate timestamp of the end or game duration - $config = await Configuration::gen('end_ts'); - $end_ts = intval($config->getValue()); - + $config_end_ts = await Configuration::gen('end_ts'); + $end_ts = intval($config_end_ts->getValue()); if ($end_ts === 0) { - $config = await Configuration::gen('game_duration_value'); - $duration_value = intval($config->getValue()); - $config = await Configuration::gen('game_duration_unit'); - $duration_unit = $config->getValue(); + list($config_value, $config_unit) = await \HH\Asio\va( + Configuration::gen('game_duration_value'), + Configuration::gen('game_duration_unit'), + ); + $duration_value = intval($config_value->getValue()); + $duration_unit = $config_unit->getValue(); switch ($duration_unit) { case 'd': $duration = $duration_value * 60 * 60 * 24; @@ -109,63 +113,48 @@ class Control extends Model { await Configuration::genUpdate('end_ts', strval($end_ts)); } else { $duration_length = ($end_ts - $start_ts) / 60; - await Configuration::genUpdate( - 'game_duration_value', - strval($duration_length), + await \HH\Asio\va( + Configuration::genUpdate( + 'game_duration_value', + strval($duration_length), + ), + Configuration::genUpdate('game_duration_unit', 'm'), ); - await Configuration::genUpdate('game_duration_unit', 'm'); } - // Set pause to zero - await Configuration::genUpdate('pause_ts', '0'); - - // Set game to not paused - await Configuration::genUpdate('game_paused', '0'); - - // Kick off timer - await Configuration::genUpdate('timer', '1'); - - // Reset and kick off progressive scoreboard - await Progressive::genReset(); - await Progressive::genRun(); + await \HH\Asio\va( + Configuration::genUpdate('pause_ts', '0'), // Set pause to zero + Configuration::genUpdate('game_paused', '0'), // Set game to not paused + Configuration::genUpdate('timer', '1'), // Kick off timer + Progressive::genReset(), // Reset and kick off progressive scoreboard + ); - // Kick off scoring for bases - await Level::genBaseScoring(); + await \HH\Asio\va( + Progressive::genRun(), + Level::genBaseScoring(), // Kick off scoring for bases + ); } public static async function genEnd(): Awaitable { - // Announce game ending - await Announcement::genCreateAuto('Game has ended!'); - - // Log game ending - await ActivityLog::genCreateGenericLog('Game has ended!'); - - // Mark game as finished and it stops progressive scoreboard - await Configuration::genUpdate('game', '0'); - - // Disable scoring - await Configuration::genUpdate('scoring', '0'); - - // Put timestampts to zero - await Configuration::genUpdate('start_ts', '0'); - await Configuration::genUpdate('end_ts', '0'); - await Configuration::genUpdate('next_game', '0'); - - // Set pause to zero - await Configuration::genUpdate('pause_ts', '0'); - - // Stop timer - await Configuration::genUpdate('timer', '0'); + await \HH\Asio\va( + Announcement::genCreateAuto('Game has ended!'), // Announce game ending + ActivityLog::genCreateGenericLog('Game has ended!'), // Log game ending + Configuration::genUpdate('game', '0'), // Mark game as finished and stop progressive scoreboard + Configuration::genUpdate('scoring', '0'), // Disable scoring + Configuration::genUpdate('start_ts', '0'), // Put timestamps to zero + Configuration::genUpdate('end_ts', '0'), + Configuration::genUpdate('next_game', '0'), + Configuration::genUpdate('pause_ts', '0'), // Set pause to zero + Configuration::genUpdate('timer', '0'), // Stop timer + ); $pause = await Configuration::gen('game_paused'); $game_paused = $pause->getValue() === '1'; if (!$game_paused) { // Stop bases scoring process - await Level::genStopBaseScoring(); - // Stop progressive scoreboard process - await Progressive::genStop(); + await \HH\Asio\va(Level::genStopBaseScoring(), Progressive::genStop()); } else { // Set game to not paused await Configuration::genUpdate('game_paused', '0'); @@ -173,46 +162,32 @@ class Control extends Model { } public static async function genPause(): Awaitable { - // Announce game starting - await Announcement::genCreateAuto('Game has been paused!'); - - // Log game paused - await ActivityLog::genCreateGenericLog('Game has been paused!'); - - // Disable scoring - await Configuration::genUpdate('scoring', '0'); + await \HH\Asio\va( + Announcement::genCreateAuto('Game has been paused!'), // Announce game paused + ActivityLog::genCreateGenericLog('Game has been paused!'), // Log game paused + Configuration::genUpdate('scoring', '0'), // Disable scoring + ); - // Set pause timestamp $pause_ts = time(); - await Configuration::genUpdate('pause_ts', strval($pause_ts)); - - // Set gane to paused - await Configuration::genUpdate('game_paused', '1'); - - // Stop timer - await Configuration::genUpdate('timer', '0'); - - // Stop bases scoring process - await Level::genStopBaseScoring(); - - // Stop progressive scoreboard process - await Progressive::genStop(); + await \HH\Asio\va( + Configuration::genUpdate('pause_ts', strval($pause_ts)), // Set pause timestamp + Configuration::genUpdate('game_paused', '1'), // Set gane to paused + Configuration::genUpdate('timer', '0'), // Stop timer + Level::genStopBaseScoring(), // Stop bases scoring process + Progressive::genStop(), // Stop progressive scoreboard process + ); } public static async function genUnpause(): Awaitable { - // Enable scoring - await Configuration::genUpdate('scoring', '1'); - - // Get pause time - $config_pause_ts = await Configuration::gen('pause_ts'); + await Configuration::genUpdate('scoring', '1'); // Enable scoring + list($config_pause_ts, $config_start_ts, $config_end_ts) = + await \HH\Asio\va( + Configuration::gen('pause_ts'), // Get pause time + Configuration::gen('start_ts'), // Get start time + Configuration::gen('end_ts'), // Get end time + ); $pause_ts = intval($config_pause_ts->getValue()); - - // Get start time - $config_start_ts = await Configuration::gen('start_ts'); $start_ts = intval($config_start_ts->getValue()); - - // Get end time - $config_end_ts = await Configuration::gen('end_ts'); $end_ts = intval($config_end_ts->getValue()); // Calulcate game remaining @@ -221,80 +196,64 @@ class Control extends Model { $remaining_duration = $game_duration - $game_played_duration; $end_ts = time() + $remaining_duration; - // Set new endtime - await Configuration::genUpdate('end_ts', strval($end_ts)); - - // Set pause to zero - await Configuration::genUpdate('pause_ts', '0'); - - // Set gane to not paused - await Configuration::genUpdate('game_paused', '0'); - - // Start timer - await Configuration::genUpdate('timer', '1'); - - // Kick off progressive scoreboard - await Progressive::genRun(); - - // Kick off scoring for bases - await Level::genBaseScoring(); - - // Announce game resumed - await Announcement::genCreateAuto('Game has resumed!'); - - // Log game paused - await ActivityLog::genCreateGenericLog('Game has resumed!'); + await \HH\Asio\va( + Configuration::genUpdate('end_ts', strval($end_ts)), // Set new endtime + Configuration::genUpdate('pause_ts', '0'), // Set pause to zero + Configuration::genUpdate('game_paused', '0'), // Set gane to not paused + Configuration::genUpdate('timer', '1'), // Start timer + Progressive::genRun(), // Kick off progressive scoreboard + Level::genBaseScoring(), // Kick off scoring for bases + Announcement::genCreateAuto('Game has resumed!'), // Announce game resumed + ActivityLog::genCreateGenericLog('Game has resumed!'), // Log game resumed + ); } public static async function genAutoBegin(): Awaitable { - // Get start time - $config_start_ts = await Configuration::gen('start_ts'); + // Prevent autorun.php from storing timestamps in local cache, forever (the script runs continuously). + Configuration::deleteLocalCache('CONFIGURATION'); + list($config_start_ts, $config_end_ts, $config_game_paused) = + await \HH\Asio\va( + Configuration::gen('start_ts'), // Get start time + Configuration::gen('end_ts'), // Get end time + Configuration::gen('game_paused'), // Get paused status + ); $start_ts = intval($config_start_ts->getValue()); - - // Get end time - $config_end_ts = await Configuration::gen('end_ts'); $end_ts = intval($config_end_ts->getValue()); - - // Get paused status - $config_game_paused = await Configuration::gen('game_paused'); $game_paused = intval($config_game_paused->getValue()); if (($game_paused === 0) && ($start_ts <= time()) && ($end_ts > time())) { - // Start the game - await Control::genBegin(); + await Control::genBegin(); // Start the game } } public static async function genAutoEnd(): Awaitable { - // Get start time - $config_start_ts = await Configuration::gen('start_ts'); + // Prevent autorun.php from storing timestamps in local cache, forever (the script runs continuously). + Configuration::deleteLocalCache('CONFIGURATION'); + list($config_start_ts, $config_end_ts, $config_game_paused) = + await \HH\Asio\va( + Configuration::gen('start_ts'), // Get start time + Configuration::gen('end_ts'), // Get end time + Configuration::gen('game_paused'), // Get paused status + ); $start_ts = intval($config_start_ts->getValue()); - - // Get end time - $config_end_ts = await Configuration::gen('end_ts'); $end_ts = intval($config_end_ts->getValue()); - - // Get paused status - $config_game_paused = await Configuration::gen('game_paused'); $game_paused = intval($config_game_paused->getValue()); if (($game_paused === 0) && ($end_ts <= time())) { - // Start the game - await Control::genEnd(); + await Control::genEnd(); // End the game } } public static async function genAutoRun(): Awaitable { - // Get start time - $config_game = await Configuration::gen('game'); + // Prevent autorun.php from storing timestamps in local cache, forever (the script runs continuously). + Configuration::deleteLocalCache('CONFIGURATION'); + $config_game = await Configuration::gen('game'); // Get start time $game = intval($config_game->getValue()); if ($game === 0) { - // Check and start the game - await Control::genAutoBegin(); + await Control::genAutoBegin(); // Check and start the game } else { - // Check and stop the game - await Control::genAutoEnd(); + await Control::genAutoEnd(); // Check and stop the game } } @@ -318,16 +277,21 @@ class Control extends Model { string $name, ): Awaitable { $db = await self::genDb(); + $host = await Control::genServerAddr(); $result = await $db->queryf( - 'SELECT pid FROM scripts WHERE name = %s AND status = 1 LIMIT 1', + 'SELECT pid FROM scripts WHERE name = %s AND host = %s AND status = 1', $name, + $host, ); + $status = false; if ($result->numRows() >= 1) { - $pid = intval(must_have_idx($result->mapRows()[0], 'pid')); - $status = file_exists("/proc/$pid"); - if ($status === false) { - await Control::genStopScriptLog($pid); - await Control::genClearScriptLog(); + foreach ($result->mapRows() as $row) { + $pid = intval(must_have_idx($row, 'pid')); + $status = file_exists("/proc/$pid"); + if ($status === false) { + await Control::genStopScriptLog($pid); + await Control::genClearScriptLog(); + } } return $status; } else { @@ -464,14 +428,19 @@ class Control extends Model { public static async function exportGame(): Awaitable { $game = array(); - $logos = await Logo::exportAll(); - $game['logos'] = $logos; - $teams = await Team::exportAll(); - $game['teams'] = $teams; - $categories = await Category::exportAll(); - $game['categories'] = $categories; - $levels = await Level::exportAll(); - $game['levels'] = $levels; + $awaitables = Map { + 'logos' => Logo::exportAll(), + 'teams' => Team::exportAll(), + 'categories' => Category::exportAll(), + 'levels' => Level::exportAll(), + }; + $awaitables_results = await \HH\Asio\m($awaitables); + + $game['logos'] = $awaitables['logos']; + $game['teams'] = $awaitables['teams']; + $game['categories'] = $awaitables['categories']; + $game['levels'] = $awaitables['levels']; + $output_file = 'fbctf_game.json'; JSONExporterController::sendJSON($game, $output_file); exit(); @@ -551,8 +520,7 @@ class Control extends Model { } public static async function genFlushMemcached(): Awaitable { - $mc = self::getMc(); - return $mc->flush(0); + return self::flushMCCluster(); } private static async function genLoadDatabaseFile( diff --git a/src/models/Country.php b/src/models/Country.php index 748584b4..75fad5cc 100644 --- a/src/models/Country.php +++ b/src/models/Country.php @@ -55,14 +55,12 @@ public function getTransform(): string { // Make sure all the countries used field is good public static async function genUsedAdjust(): Awaitable { $db = await self::genDb(); - await $db->queryf( + $queries = Vector { 'UPDATE countries SET used = 1 WHERE id IN (SELECT entity_id FROM levels)', - ); - await $db->queryf( 'UPDATE countries SET used = 0 WHERE id NOT IN (SELECT entity_id FROM levels)', - ); - - self::invalidateMCRecords(); // Invalidate Memcached Country data. + }; + await $db->multiQuery($queries); + self::invalidateMCRecords(); } // Enable or disable a country @@ -76,7 +74,7 @@ public function getTransform(): string { $status ? 1 : 0, $country_id, ); - self::invalidateMCRecords(); // Invalidate Memcached Country data. + self::invalidateMCRecords(); } // Set the used flag for a country @@ -90,7 +88,7 @@ public function getTransform(): string { $status ? 1 : 0, $country_id, ); - self::invalidateMCRecords(); // Invalidate Memcached Country data. + self::invalidateMCRecords(); } private static async function genAll( @@ -219,7 +217,7 @@ function($a, $b) { public static async function genIsActiveLevel( int $country_id, ): Awaitable { - return Level::genWhoUses($country_id) != null; + return Level::genWhoUses($country_id) !== null; } // Get a country by id. diff --git a/src/models/Integration.php b/src/models/Integration.php new file mode 100644 index 00000000..1af20e2f --- /dev/null +++ b/src/models/Integration.php @@ -0,0 +1,534 @@ +type; + } + + public static function getIntegrationCacheObject(): Cache { + if (self::$INTEGRATION_CACHE === MUST_MODIFY) { + self::$INTEGRATION_CACHE = new Cache(); + } + invariant( + self::$INTEGRATION_CACHE instanceof Cache, + 'Integration::$INTEGRATION_CACHE should of type Map and not null', + ); + return self::$INTEGRATION_CACHE; + } + + public static async function facebookOAuthEnabled(): Awaitable { + return Configuration::getFacebookOAuthSettingsExists(); + } + + public static async function googleOAuthEnabled(): Awaitable { + return Configuration::getGoogleOAuthFileExists(); + } + + public static async function facebookLoginEnabled(): Awaitable { + $login_facebook = await Configuration::gen('login_facebook'); + $oauth = Configuration::getFacebookOAuthSettingsExists(); + + $login_facebook_enabled = + $login_facebook->getValue() === '1' ? true : false; + + if ($oauth && $login_facebook_enabled) { + return true; + } else { + return false; + } + } + + public static async function googleLoginEnabled(): Awaitable { + $login_google = await Configuration::gen('login_google'); + $oauth = Configuration::getGoogleOAuthFileExists(); + + $login_google_enabled = $login_google->getValue() === '1' ? true : false; + + if (($oauth) && ($login_google_enabled)) { + return true; + } else { + return false; + } + } + + public static async function genFacebookAuthURL( + string $redirect, + bool $rerequest = false, + ): Awaitable<(Facebook, string)> { + $host = strval(idx(Utils::getSERVER(), 'HTTP_HOST')); + $app_id = Configuration::getFacebookOAuthSettingsAppId(); + $app_secret = Configuration::getFacebookOAuthSettingsAppSecret(); + $client = new Facebook\Facebook( + [ + 'app_id' => $app_id, + 'app_secret' => $app_secret, + 'default_graph_version' => 'v2.2', + ], + ); + + $helper = $client->getRedirectLoginHelper(); + + $permissions = ['email']; + $auth_url = $helper->getLoginUrl( + 'https://'.$host.'/data/integration_'.$redirect.'.php?type=facebook', + $permissions, + ); + + if ($rerequest === true) { + $auth_url .= '&auth_type=rerequest'; + } + return tuple($client, $auth_url); + } + + public static async function genFacebookAPIClient( + ): Awaitable<(Facebook, string)> { + $host = strval(idx(Utils::getSERVER(), 'HTTP_HOST')); + $app_id = Configuration::getFacebookOAuthSettingsAppId(); + $app_secret = Configuration::getFacebookOAuthSettingsAppSecret(); + $client = new Facebook\Facebook( + [ + 'app_id' => $app_id, + 'app_secret' => $app_secret, + 'default_graph_version' => 'v2.2', + ], + ); + + $access_token = $app_id.'|'.$app_secret; + + return tuple($client, $access_token); + } + + public static async function genGoogleAuthURL( + string $redirect, + ): Awaitable<(Google_Client, string)> { + $host = strval(idx(Utils::getSERVER(), 'HTTP_HOST')); + $google_oauth_file = Configuration::getGoogleOAuthFile(); + $client = new Google_Client(); + $client->setAuthConfig($google_oauth_file); + $client->setAccessType('offline'); + $client->setScopes(['profile email']); + $client->setRedirectUri( + 'https://'.$host.'/data/integration_'.$redirect.'.php?type=google', + ); + + $integration_csrf_token = bin2hex(random_bytes(100)); + setcookie( + 'integration_csrf_token', + strval($integration_csrf_token), + 0, + '/data/', + must_have_string(Utils::getSERVER(), 'SERVER_NAME'), + true, + true, + ); + $client->setState(strval($integration_csrf_token)); + + $auth_url = $client->createAuthUrl(); + + return tuple($client, $auth_url); + } + + public static async function genFacebookLogin(): Awaitable { + list($client, $url) = await self::genFacebookAuthURL("login"); + $helper = $client->getRedirectLoginHelper(); + + $code = idx(Utils::getGET(), 'code', false); + $error = idx(Utils::getGET(), 'error', false); + + $accessToken = ''; + + if ($code !== false) { + $graph_error = false; + try { + $accessToken = $helper->getAccessToken(); + } catch (Facebook\Exceptions\FacebookResponseException $e) { + $graph_error = true; + } catch (Facebook\Exceptions\FacebookSDKException $e) { + $graph_error = true; + } + + $url = '/index.php?page=login'; + if ($graph_error !== true) { + + $response = false; + try { + $response = + $client->get('/me?fields=id,third_party_id,email', $accessToken); + } catch (Facebook\Exceptions\FacebookResponseException $e) { + error_log("Facebook OAuth Failed - Missing fields"); + $url = '/index.php?page=error'; + return $url; + } catch (Facebook\Exceptions\FacebookSDKException $e) { + error_log("Facebook OAuth Failed - Missing fields"); + $url = '/index.php?page=error'; + return $url; + } + $profile = $response->getGraphUser(); + $email = $profile['email']; + $id = $profile['third_party_id']; + + if ($id === null) { + error_log("Facebook OAuth Failed - Missing id Field"); + list($client, $url) = await self::genFacebookAuthURL("login", true); + return $url; + } + + if ($email === null) { + error_log("Facebook OAuth Failed - Missing email Field - $id"); + list($client, $url) = await self::genFacebookAuthURL("login", true); + return $url; + } + + list($oauth_token_exists, $registration_facebook) = + await \HH\Asio\va( + Team::genAuthTokenExists('facebook_oauth', strval($email)), + Configuration::gen('registration_facebook'), + ); + + if ($oauth_token_exists === true) { + $url = await self::genLoginURL("facebook_oauth", $email); + } else if ($registration_facebook->getValue() === '1') { + $team_id = await self::genRegisterTeam($email, $id); + + if (is_int($team_id) === true) { + $set_integrations = await self::genSetTeamIntegrations( + $team_id, + 'facebook_oauth', + $email, + $id, + ); + if ($set_integrations === true) { + $url = await self::genLoginURL('facebook_oauth', $email); + } + } + } + } + } else if ($error !== false) { + $url = '/index.php?page=login'; + } + + return $url; + } + + public static async function genGoogleLogin(): Awaitable { + list($client, $url) = await self::genGoogleAuthURL("login"); + + $code = idx(Utils::getGET(), 'code', false); + $error = idx(Utils::getGET(), 'error', false); + $state = idx(Utils::getGET(), 'state', false); + + if ($code !== false) { + $integration_csrf_token = /* HH_IGNORE_ERROR[2050] */ + idx($_COOKIE, 'integration_csrf_token', false); + if (strval($integration_csrf_token) === '' || + strval($state) === '' || + strval($integration_csrf_token) !== strval($state)) { + $code = false; + $error = true; + } + } + + if ($code !== false) { + $url = '/index.php?page=login'; + $client->authenticate($code); + $access_token = $client->getAccessToken(); + $oauth_client = new Google_Service_Oauth2($client); + $profile = $oauth_client->userinfo->get(); + $email = $profile->email; + $id = $profile->id; + + list($oauth_token_exists, $registration_google) = await \HH\Asio\va( + Team::genAuthTokenExists('google_oauth', strval($email)), + Configuration::gen('registration_google'), + ); + + if ($oauth_token_exists === true) { + $url = await self::genLoginURL('google_oauth', $email); + } else if ($registration_google->getValue() === '1') { + $team_id = await self::genRegisterTeam($email, $id); + if (is_int($team_id) === true) { + $set_integrations = await self::genSetTeamIntegrations( + $team_id, + 'google_oauth', + $email, + $id, + ); + if ($set_integrations === true) { + $url = await self::genLoginURL('google_oauth', $email); + } + } + } + } else if ($error !== false) { + $url = '/index.php?page=login'; + } + + return $url; + } + + public static async function genLoginURL( + string $type, + string $token, + ): Awaitable { + $team = await Team::genTeamFromOAuthToken($type, $token); + + SessionUtils::sessionRefresh(); + if (!SessionUtils::sessionActive()) { + SessionUtils::sessionSet('team_id', strval($team->getId())); + SessionUtils::sessionSet('name', $team->getName()); + SessionUtils::sessionSet( + 'csrf_token', + (string) strval(bin2hex(random_bytes(100))), + ); + SessionUtils::sessionSet( + 'IP', + must_have_string(Utils::getSERVER(), 'REMOTE_ADDR'), + ); + if ($team->getAdmin()) { + SessionUtils::sessionSet('admin', strval($team->getAdmin())); + } + } + if ($team->getAdmin()) { + $redirect = 'admin'; + } else { + $redirect = 'game'; + } + + $login_url = '/index.php?p='.$redirect; + + return $login_url; + } + + public static async function genFacebookOAuth(): Awaitable { + list($client, $url) = await self::genFacebookAuthURL("oauth"); + $helper = $client->getRedirectLoginHelper(); + + $code = idx(Utils::getGET(), 'code'); + $error = idx(Utils::getGET(), 'error'); + + if (!is_string($code)) { + $code = false; + } + + if (!is_string($error)) { + $error = false; + } + + $accessToken = ''; + + if ($code !== false) { + $graph_error = false; + try { + $accessToken = $helper->getAccessToken(); + } catch (Facebook\Exceptions\FacebookResponseException $e) { + $graph_error = true; + } catch (Facebook\Exceptions\FacebookSDKException $e) { + $graph_error = true; + } + + if ($graph_error === true) { + return false; + } else { + $response = + $client->get('/me?fields=id,third_party_id,email', $accessToken); + $profile = $response->getGraphUser(); + $email = $profile['email']; + $id = $profile['third_party_id']; + + if ($email === null) { + list($client, $url) = await self::genFacebookAuthURL("oauth", true); + header('Location: '.filter_var($url, FILTER_SANITIZE_URL)); + exit; + } + + $set_integrations = await self::genSetTeamIntegrations( + SessionUtils::sessionTeam(), + 'facebook_oauth', + $email, + $id, + ); + return $set_integrations; + } + } else if ($error !== false) { + return false; + } + + header('Location: '.filter_var($url, FILTER_SANITIZE_URL)); + exit; + return false; + } + + public static async function genGoogleOAuth(): Awaitable { + list($client, $url) = await self::genGoogleAuthURL("oauth"); + + $code = idx(Utils::getGET(), 'code', false); + $error = idx(Utils::getGET(), 'error', false); + $state = idx(Utils::getGET(), 'state', false); + + if ($code !== false) { + $integration_csrf_token = /* HH_IGNORE_ERROR[2050] */ + idx($_COOKIE, 'integration_csrf_token', false); + if (strval($integration_csrf_token) === '' || + strval($state) === '' || + strval($integration_csrf_token) !== strval($state)) { + $code = false; + $error = true; + } + } + + if ($code !== false) { + $client->authenticate($code); + $access_token = $client->getAccessToken(); + $oauth_client = new Google_Service_Oauth2($client); + $profile = $oauth_client->userinfo->get(); + $email = $profile->email; + $id = $profile->id; + + $set_integrations = await self::genSetTeamIntegrations( + SessionUtils::sessionTeam(), + 'google_oauth', + $email, + $id, + ); + return $set_integrations; + } else if ($error !== false) { + return false; + } + + header('Location: '.filter_var($url, FILTER_SANITIZE_URL)); + exit; + return false; + } + + public static async function genSetTeamIntegrations( + int $team_id, + string $type, + string $email, + string $id, + ): Awaitable { + list($livesync_password_update, $oauth_token_update) = await \HH\Asio\va( + Team::genSetLiveSyncPassword($team_id, $type, $email, $id), + Team::genSetOAuthToken($team_id, $type, $email), + ); + + if (($livesync_password_update === true) && + ($oauth_token_update === true)) { + return true; + } else { + return false; + } + } + + public static async function genRegisterTeam( + string $email, + string $id, + string $name = '', + ): Awaitable { + list($registration_prefix, $logo_name) = await \HH\Asio\va( + Configuration::gen('registration_prefix'), + Logo::genRandomLogo(), + ); + + $team_password = Team::generateHash(random_bytes(100)); + $team_name = substr( + substr($registration_prefix->getValue(), 0, 14). + "-". + bin2hex(random_bytes(12)), + 0, + 20, + ); + if ($name !== '') { + $team_name = substr(strval($name), 0, 20); + } + $team_id = await Team::genCreate($team_name, $team_password, $logo_name); + return $team_id; + } + + public static async function genFacebookThirdPartyExists( + string $third_party_id, + ): Awaitable { + self::getIntegrationCacheObject(); + if (self::$INTEGRATION_CACHE->getCache( + 'facebook_exists:'.$third_party_id, + ) === + true) { + return true; + } else { + list($client, $access_token) = await self::genFacebookAPIClient(); + + try { + $response = + $client->get('/'.$third_party_id.'?fields=email', $access_token); + } catch (FacebookExceptionsFacebookResponseException $e) { + print "error 1\n\n\n"; + return false; + } catch (FacebookExceptionsFacebookSDKException $e) { + print "error 2\n\n\n"; + return false; + } + $graphNode = $response->getGraphNode(); + $graphNode->getField('email'); + $profile = $response->getGraphUser(); + $email = strval($profile->getEmail()); + if ($email !== null) { + self::$INTEGRATION_CACHE->setCache( + 'facebook_exists:'.$third_party_id, + true, + ); + self::$INTEGRATION_CACHE->setCache( + 'facebook_email:'.$third_party_id, + $email, + ); + return true; + } else { + return false; + } + } + } + + public static async function genFacebookThirdPartyEmail( + string $third_party_id, + ): Awaitable { + self::getIntegrationCacheObject(); + $integration_local_cache = + self::$INTEGRATION_CACHE->getCache('facebook_email:'.$third_party_id); + if ($integration_local_cache !== false) { + return strval($integration_local_cache); + } else { + list($client, $access_token) = await self::genFacebookAPIClient(); + + try { + $response = + $client->get('/'.$third_party_id.'?fields=email', $access_token); + } catch (FacebookExceptionsFacebookResponseException $e) { + return ''; + } catch (FacebookExceptionsFacebookSDKException $e) { + return ''; + } + $graphNode = $response->getGraphNode(); + $graphNode->getField('email'); + $profile = $response->getGraphUser(); + $email = $profile->getEmail(); + if ($email !== null) { + self::$INTEGRATION_CACHE->setCache( + 'facebook_exists:'.$third_party_id, + true, + ); + self::$INTEGRATION_CACHE->setCache( + 'facebook_email:'.$third_party_id, + $email, + ); + return $email; + } else { + return ''; + } + } + } + +} diff --git a/src/models/Level.php b/src/models/Level.php index 0a10fd60..e559621b 100644 --- a/src/models/Level.php +++ b/src/models/Level.php @@ -9,6 +9,7 @@ class Level extends Model implements Importable, Exportable { 'LEVEL_BY_COUNTRY' => 'level_by_country', 'ALL_LEVELS' => 'all_levels', 'ALL_ACTIVE_LEVELS' => 'active_levels', + 'ALL_LEVELS_COUNTRY_MAP' => 'all_levels_country_map', }; private function __construct( @@ -42,11 +43,11 @@ public function getType(): string { } public function getTitle(): string { - return $this->title; + return mb_convert_encoding($this->title, 'UTF-8'); } public function getDescription(): string { - return $this->description; + return mb_convert_encoding($this->description, 'UTF-8'); } public function getEntityId(): int { @@ -153,11 +154,15 @@ private static function levelFromRow(Map $row): Level { $entity_iso_code = must_have_string($level, 'entity_iso_code'); $c = must_have_string($level, 'category'); $exist = await self::genAlreadyExist($type, $title, $entity_iso_code); - $entity_exist = await Country::genCheckExists($entity_iso_code); - $category_exist = await Category::genCheckExists($c); + list($entity_exist, $category_exist) = await \HH\Asio\va( + Country::genCheckExists($entity_iso_code), + Category::genCheckExists($c), + ); // TODO: Combine Awaits if (!$exist && $entity_exist && $category_exist) { - $entity = await Country::genCountry($entity_iso_code); - $category = await Category::genSingleCategoryByName($c); + list($entity, $category) = await \HH\Asio\va( + Country::genCountry($entity_iso_code), + Category::genSingleCategoryByName($c), + ); // TODO: Combine Awaits $level_id = await self::genCreate( $type, $title, @@ -176,7 +181,7 @@ private static function levelFromRow(Map $row): Level { $links = must_have_idx($level, 'links'); invariant(is_array($links), 'links must be of type array'); foreach ($links as $link) { - await Link::genCreate($link, $level_id); + await Link::genCreate($link, $level_id); // TODO: Combine Awaits } } if (array_key_exists('attachments', $level)) { @@ -190,7 +195,7 @@ private static function levelFromRow(Map $row): Level { $level_id, $attachment['filename'], $attachment['type'], - ); + ); // TODO: Combine Awaits } } } @@ -205,14 +210,17 @@ private static function levelFromRow(Map $row): Level { $all_levels = await self::genAllLevels(); foreach ($all_levels as $level) { - $entity = await Country::gen($level->getEntityId()); - $category = await Category::genSingleCategory($level->getCategoryId()); - $links = await Link::genAllLinks($level->getId()); + list($entity, $category, $links, $attachments) = await \HH\Asio\va( + Country::gen($level->getEntityId()), + Category::genSingleCategory($level->getCategoryId()), + Link::genAllLinks($level->getId()), + Attachment::genAllAttachments($level->getId()), + ); // TODO: Combine Awaits + $link_array = array(); foreach ($links as $link) { $link_array[] = $link->getLink(); } - $attachments = await Attachment::genAllAttachments($level->getId()); $attachment_array = array(); foreach ($attachments as $attachment) { $attachment_array[] = [ @@ -381,16 +389,19 @@ private static function levelFromRow(Map $row): Level { $category_id, ); - self::invalidateMCRecords(); // Invalidate Memcached Level data. + self::invalidateMCRecords(); invariant($result->numRows() === 1, 'Expected exactly one result'); $country_id = await self::genCountryIdForLevel( intval(must_have_idx($result->mapRows()[0], 'id')), ); - await ActivityLog::genAdminLog("added", "Country", $country_id); $country = await Country::gen($country_id); - await Announcement::genCreateAuto($country->getName()." added!"); - ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached ActivityLog data. + await \HH\Asio\va( + Announcement::genCreateAuto($country->getName()." added!"), + ActivityLog::genAdminLog("added", "Country", $country_id), + ); + + ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); return intval(must_have_idx($result->mapRows()[0], 'id')); } @@ -629,9 +640,11 @@ private static function levelFromRow(Map $row): Level { if ($result->numRowsAffected() > 0) { $country_id = await self::genCountryIdForLevel($level_id); - await ActivityLog::genAdminLog("updated", "Country", $country_id); $country = await Country::gen($country_id); - await Announcement::genCreateAuto($country->getName()." updated!"); + await \HH\Asio\va( + ActivityLog::genAdminLog("updated", "Country", $country_id), + Announcement::genCreateAuto($country->getName()." updated!"), + ); self::invalidateMCRecords(); // Invalidate Memcached Level data. ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached ActivityLog data. } @@ -708,11 +721,13 @@ private static function levelFromRow(Map $row): Level { if ($result->numRowsAffected() > 0) { $action = ($active === true) ? "enabled" : "disabled"; $country_id = await self::genCountryIdForLevel($level_id); - await ActivityLog::genAdminLog($action, "Country", $country_id); $country = await Country::gen($country_id); - await Announcement::genCreateAuto($country->getName().' '.$action.'!'); - self::invalidateMCRecords(); // Invalidate Memcached Level data. - ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached ActivityLog data. + await \HH\Asio\va( + ActivityLog::genAdminLog($action, "Country", $country_id), + Announcement::genCreateAuto($country->getName().' '.$action.'!'), + ); + self::invalidateMCRecords(); + ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); } } @@ -730,7 +745,7 @@ private static function levelFromRow(Map $row): Level { ); if ($results->numRowsAffected() > 0) { - self::invalidateMCRecords(); // Invalidate Memcached Level data. + self::invalidateMCRecords(); } } @@ -754,7 +769,7 @@ private static function levelFromRow(Map $row): Level { ); } foreach ($result->mapRows() as $row) { - await self::genSetStatus(intval($row->get('id')), $active); + await self::genSetStatus(intval($row->get('id')), $active); // TODO: Combine Awaits } } @@ -787,6 +802,31 @@ private static function levelFromRow(Map $row): Level { } } + public static async function genAllLevelsCountryMap( + bool $refresh = false, + ): Awaitable> { + $mc_result = self::getMCRecords('ALL_LEVELS_COUNTRY_MAP'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $db = await self::genDb(); + $all_levels = Map {}; + $result = await $db->queryf('SELECT * FROM levels ORDER BY id'); + foreach ($result->mapRows() as $row) { + $all_levels->add( + Pair {intval($row->get('entity_id')), self::levelFromRow($row)}, + ); + } + self::setMCRecords('ALL_LEVELS_COUNTRY_MAP', new Map($all_levels)); + return $all_levels; + } else { + $levels = array(); + invariant( + $mc_result instanceof Map, + 'cache return should be of type Map', + ); + return $mc_result; + } + } + // All levels by status. public static async function genAllActiveLevels( bool $refresh = false, @@ -1043,7 +1083,7 @@ private static function levelFromRow(Map $row): Level { // Check if team has already scored this level $previous_score = - await ScoreLog::genPreviousScore($level_id, $team_id, false); + await ScoreLog::genAllPreviousScore($level_id, $team_id, false); if ($previous_score) { return false; } @@ -1053,24 +1093,26 @@ private static function levelFromRow(Map $row): Level { // Calculate points to give $points = $level->getPoints() + $level->getBonus(); - // Adjust bonus - await self::genAdjustBonus($level_id); - - // Score! - await $db->queryf( - 'UPDATE teams SET points = points + %d, last_score = NOW() WHERE id = %d LIMIT 1', - $points, - $team_id, - ); - // Log the score - await ScoreLog::genLogValidScore( + $captured = await ScoreLog::genLogValidScore( $level_id, $team_id, $points, $level->getType(), ); + if ($captured === true) { + // Adjust bonus + await self::genAdjustBonus($level_id); + + // Score! + await $db->queryf( + 'UPDATE teams SET points = points + %d, last_score = NOW() WHERE id = %d LIMIT 1', + $points, + $team_id, + ); + } + self::invalidateMCRecords(); // Invalidate Memcached Level data. return true; @@ -1096,7 +1138,7 @@ private static function levelFromRow(Map $row): Level { // Calculate points to give $score = - await ScoreLog::genPreviousScore($level_id, $team_id, false); + await ScoreLog::genAllPreviousScore($level_id, $team_id, false); if ($score) { $points = $level->getPoints(); } else { @@ -1118,7 +1160,7 @@ private static function levelFromRow(Map $row): Level { $level->getType(), ); - self::invalidateMCRecords(); // Invalidate Memcached Level data. + self::invalidateMCRecords(); return true; }, @@ -1144,9 +1186,10 @@ private static function levelFromRow(Map $row): Level { // Check if team has already gotten this hint or if the team has scored this already // If so, hint is free - $hint = await HintLog::genPreviousHint($level_id, $team_id, false); - $score = - await ScoreLog::genPreviousScore($level_id, $team_id, false); + list($hint, $score) = await \HH\Asio\va( + HintLog::genPreviousHint($level_id, $team_id, false), + ScoreLog::genAllPreviousScore($level_id, $team_id, false), + ); if ($hint || $score) { $penalty = 0; } @@ -1157,22 +1200,25 @@ private static function levelFromRow(Map $row): Level { return null; } - // Adjust points - await $db->queryf( - 'UPDATE teams SET points = points - %d WHERE id = %d LIMIT 1', - $penalty, - $team_id, + // Adjust points and log the hint + await \HH\Asio\va( + $db->queryf( + 'UPDATE teams SET points = points - %d WHERE id = %d LIMIT 1', + $penalty, + $team_id, + ), + HintLog::genLogGetHint($level_id, $team_id, $penalty), ); - // Log the hint - await HintLog::genLogGetHint($level_id, $team_id, $penalty); - ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached ActivityLog data. MultiTeam::invalidateMCRecords('ALL_TEAMS'); // Invalidate Memcached MultiTeam data. MultiTeam::invalidateMCRecords('POINTS_BY_TYPE'); // Invalidate Memcached MultiTeam data. MultiTeam::invalidateMCRecords('LEADERBOARD'); // Invalidate Memcached MultiTeam data. + $completed_level = await MultiTeam::genCompletedLevel($level_id); + if (count($completed_level) === 0) { + MultiTeam::invalidateMCRecords('TEAMS_FIRST_CAP'); // Invalidate Memcached MultiTeam data. + } MultiTeam::invalidateMCRecords('TEAMS_BY_LEVEL'); // Invalidate Memcached MultiTeam data. - MultiTeam::invalidateMCRecords('TEAMS_FIRST_CAP'); // Invalidate Memcached MultiTeam data. // Hint! return $level->getHint(); diff --git a/src/models/Link.php b/src/models/Link.php index ac30680e..9c514c6c 100644 --- a/src/models/Link.php +++ b/src/models/Link.php @@ -9,6 +9,7 @@ class Link extends Model { 'LEVELS_COUNT' => 'link_levels_count', 'LEVEL_LINKS' => 'link_levels', 'LINKS' => 'link_by_id', + 'LEVEL_LINKS_VALUES' => 'link_level_values', }; private function __construct( @@ -26,7 +27,7 @@ public function getLevelId(): int { } public function getLink(): string { - return $this->link; + return mb_convert_encoding($this->link, 'UTF-8'); } // Create link for a given level. @@ -106,6 +107,75 @@ public function getLink(): string { } } + public static async function genAllLinksForGame( + bool $refresh = false, + ): Awaitable> { + $mc_result = self::getMCRecords('LEVEL_LINKS'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $db = await self::genDb(); + $links = array(); + $result = await $db->queryf('SELECT * FROM links'); + foreach ($result->mapRows() as $row) { + $links[intval($row->get('level_id'))][] = self::linkFromRow($row); + } + self::setMCRecords('LEVEL_LINKS', new Map($links)); + $links = new Map($links); + invariant($links instanceof Map, 'links should be a Map of Link'); + return $links; + } else { + invariant($mc_result instanceof Map, 'links should be of type Map'); + invariant($mc_result instanceof Map, 'cache should be a Map of Link'); + return $mc_result; + } + } + + public static async function genAllLinksValues( + int $level_id, + bool $refresh = false, + ): Awaitable> { + $mc_result = self::getMCRecords('LEVEL_LINKS_VALUES'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $db = await self::genDb(); + $link_values = array(); + $links = await self::genAllLinksForGame(); + invariant($links instanceof Map, 'link should be a Map of Link'); + foreach ($links as $level => $link_arr) { + invariant(is_array($link_arr), 'link_arr should be an array of Link'); + foreach ($link_arr as $link_obj) { + invariant( + $link_obj instanceof Link, + 'link_obj should be of type Link', + ); + $link_values[$level][] = $link_obj->getLink(); + } + } + self::setMCRecords('LEVEL_LINKS_VALUES', new Map($link_values)); + $link_values = new Map($link_values); + if ($link_values->contains($level_id)) { + $link_array = $link_values->get($level_id); + invariant( + is_array($link_array), + 'link_array should be an array of string', + ); + return $link_array; + } else { + return array(); + } + } else { + invariant($mc_result instanceof Map, 'links should be of type Map'); + if ($mc_result->contains($level_id)) { + $link_array = $mc_result->get($level_id); + invariant( + is_array($link_array), + 'link_array should be an array of string', + ); + return $link_array; + } else { + return array(); + } + } + } + // Get a single link. public static async function gen( int $link_id, diff --git a/src/models/Logo.php b/src/models/Logo.php index 0d28726a..633be8ec 100644 --- a/src/models/Logo.php +++ b/src/models/Logo.php @@ -164,11 +164,20 @@ public function getCustom(): bool { await $db->queryf( 'SELECT * FROM logos WHERE enabled = 1 AND used = 0 AND protected = 0 AND custom = 0', ); - - foreach ($result->mapRows() as $row) { - $all_enabled_logos[] = self::logoFromRow($row); + // If all logos are used, provide all logos with no restriction on unused. + if ($result->numRows() === 0) { + $all_logos = await self::genAllLogos(); + $all_enabled_logos = $all_logos->toArray(); + } else { + foreach ($result->mapRows() as $row) { + $all_enabled_logos[] = self::logoFromRow($row); + } } self::setMCRecords('ALL_ENABLED_LOGOS', $all_enabled_logos); + invariant( + is_array($all_enabled_logos), + 'all_enabled_logos should be an array of Logo', + ); return $all_enabled_logos; } else { invariant( @@ -197,7 +206,7 @@ private static function logoFromRow(Map $row): Logo { ): Awaitable { foreach ($elements as $logo) { $name = must_have_string($logo, 'name'); - $exist = await self::genCheckExists($name); + $exist = await self::genCheckExists($name); // TODO: Combine Awaits if (!$exist) { await self::genCreate( (bool) must_have_idx($logo, 'used'), @@ -206,7 +215,7 @@ private static function logoFromRow(Map $row): Logo { (bool) must_have_idx($logo, 'custom'), $name, must_have_string($logo, 'logo'), - ); + ); // TODO: Combine Awaits } } return true; diff --git a/src/models/Model.php b/src/models/Model.php index 2e096e24..b8a808b4 100644 --- a/src/models/Model.php +++ b/src/models/Model.php @@ -1,11 +1,16 @@ $MC_KEYS = Map {}; protected static async function genDb(): Awaitable { @@ -21,37 +26,143 @@ abstract class Model { protected static function getMc(): Memcached { if (self::$mc === MUST_MODIFY) { $config = parse_ini_file('../../settings.ini'); - $host = must_have_idx($config, 'MC_HOST'); + $cluster = must_have_idx($config, 'MC_HOST'); $port = must_have_idx($config, 'MC_PORT'); + $host = $cluster[array_rand($cluster)]; self::$mc = new Memcached(); self::$mc->addServer($host, $port); } return self::$mc; } + public static function getMemcachedStats(): mixed { + $stats = array(); + $mc = self::getMcWrite(); + foreach ($mc->getServerList() as $node) { + $mc_node = new Memcached(); + $mc_node->addServer($node['host'], $node['port']); + $stats[$node['host']] = $mc_node->getStats(); + } + return $stats; + } + + /** + * @codeCoverageIgnore + */ + protected static function getMcWrite(): Memcached { + if (self::$mc_write === MUST_MODIFY) { + $config = parse_ini_file('../../settings.ini'); + $cluster = must_have_idx($config, 'MC_HOST'); + $port = must_have_idx($config, 'MC_PORT'); + self::$mc_write = new Memcached(); + foreach ($cluster as $node) { + self::$mc_write->addServer($node, $port); + } + } + return self::$mc_write; + } + protected static function setMCRecords(string $key, mixed $records): void { - $mc = self::getMc(); - $mc->set( - static::$MC_KEY.static::$MC_KEYS->get($key), - $records, - static::$MC_EXPIRE, - ); + self::getCacheClassObject(); + $cache_key = static::$MC_KEY.static::$MC_KEYS->get($key); + + self::writeMCCluster($cache_key, $records); + self::$CACHE->setCache($cache_key, $records); } protected static function getMCRecords(string $key): mixed { - $mc = self::getMc(); - $mc_result = $mc->get(static::$MC_KEY.static::$MC_KEYS->get($key)); - return $mc_result; + self::getCacheClassObject(); + $cache_key = static::$MC_KEY.static::$MC_KEYS->get($key); + + $local_cache_result = self::$CACHE->getCache($cache_key); + if ($local_cache_result !== false) { + return $local_cache_result; + } else { + $mc = self::getMc(); + $mc_result = $mc->get($cache_key); + if ($mc_result !== false) { + self::$CACHE->setCache($cache_key, $mc_result); + } + + return $mc_result; + } } public static function invalidateMCRecords(?string $key = null): void { - $mc = self::getMc(); + self::getCacheClassObject(); + if ($key === null) { foreach (static::$MC_KEYS as $key_name => $mc_key) { - $mc->delete(static::$MC_KEY.static::$MC_KEYS->get($key_name)); + $cache_key = static::$MC_KEY.static::$MC_KEYS->get($key_name); + self::invalidateMCCluster($cache_key); + self::$CACHE->deleteCache($cache_key); + } + } else { + $cache_key = static::$MC_KEY.static::$MC_KEYS->get($key); + self::invalidateMCCluster($cache_key); + self::$CACHE->deleteCache($cache_key); + } + } + + public static function flushMCCluster(): bool { + $mc = self::getMcWrite(); + $status = false; + foreach ($mc->getServerList() as $node) { + $mc_node = new Memcached(); + $mc_node->addServer($node['host'], $node['port']); + $flush_status = $mc_node->flush(0); + if ($flush_status === true) { + $status = true; + } + } + return $status; + } + + protected static function writeMCCluster( + string $cache_key, + mixed $records, + ): void { + $mc = self::getMcWrite(); + foreach ($mc->getServerList() as $node) { + $mc_node = new Memcached(); + $mc_node->addServer($node['host'], $node['port']); + $mc_node->set($cache_key, $records, static::$MC_EXPIRE); + } + } + + protected static function invalidateMCCluster(string $cache_key): void { + $mc = self::getMcWrite(); + foreach ($mc->getServerList() as $node) { + $mc_node = new Memcached(); + $mc_node->addServer($node['host'], $node['port']); + $mc_node->delete($cache_key); + } + } + + public static function getCacheClassObject(): Cache { + if (self::$CACHE === MUST_MODIFY) { + self::$CACHE = new Cache(); + } + invariant( + self::$CACHE instanceof Cache, + 'Model::$CACHE should of type Map and not null', + ); + return self::$CACHE; + } + + public static function deleteLocalCache(?string $key = null): void { + self::getCacheClassObject(); + + if (get_called_class() === 'Model') { + self::$CACHE->flushCache(); + } else if ($key === null) { + foreach (static::$MC_KEYS as $key_name => $mc_key) { + $cache_key = static::$MC_KEY.static::$MC_KEYS->get($key_name); + self::$CACHE->deleteCache($cache_key); } } else { - $mc->delete(static::$MC_KEY.static::$MC_KEYS->get($key)); + $cache_key = static::$MC_KEY.static::$MC_KEYS->get($key); + self::$CACHE->deleteCache($cache_key); } } } diff --git a/src/models/MultiTeam.php b/src/models/MultiTeam.php index 7c9c8929..862b5b5b 100644 --- a/src/models/MultiTeam.php +++ b/src/models/MultiTeam.php @@ -8,18 +8,25 @@ class MultiTeam extends Team { $MC_KEYS = Map { 'ALL_TEAMS' => 'all_teams', 'LEADERBOARD' => 'leaderboard_teams', + 'LEADERBOARD_LIMIT' => 'leaderboard_limit', 'POINTS_BY_TYPE' => 'points_by_type', 'ALL_ACTIVE_TEAMS' => 'active_teams', 'ALL_VISIBLE_TEAMS' => 'visible_teams', 'TEAMS_BY_LOGO' => 'logo_teams', 'TEAMS_BY_LEVEL' => 'level_teams', + 'TEAMS_NAMES_BY_LEVEL' => 'level_teams_names', 'TEAMS_FIRST_CAP' => 'capture_teams', }; private static async function genTeamArrayFromDB( string $query, + int $limit = 0, ): Awaitable>> { $db = await self::genDb(); + if ($limit !== 0) { + $query .= ' LIMIT '.intval($limit); + } + $result = await $db->query($query); return $result->mapRows(); @@ -66,24 +73,57 @@ class MultiTeam extends Team { ): Awaitable { $all_teams = await self::genAllTeamsCache($refresh); $team = $all_teams->get($team_id); - invariant( - $team instanceof Team, - 'all_teams should of type Team and not null', - ); + invariant($team instanceof Team, 'team should of type Team and not null'); return $team; } + public static async function genLeaderboardLimit(): Awaitable { + $limit = false; + $limit = self::getMCRecords('LEADERBOARD_LIMIT'); + if (!$limit) { + $limit = await self::setLeaderboardLimit(); + } + return intval($limit); + } + + public static async function setLeaderboardLimit(): Awaitable { + $leaderboard_limit = await Configuration::gen('leaderboard_limit'); + self::setMCRecords('LEADERBOARD_LIMIT', $leaderboard_limit->getValue()); + return intval($leaderboard_limit->getValue()); + } + // Leaderboard order. public static async function genLeaderboard( + bool $limit = true, bool $refresh = false, ): Awaitable> { + list($leaderboard_limit, $leaderboard_limit_cache, $visible_teams) = + await \HH\Asio\va( + Configuration::gen('leaderboard_limit'), + self::genLeaderboardLimit(), + self::genAllVisibleTeams(), + ); + + $teams_count = count($visible_teams); $mc_result = self::getMCRecords('LEADERBOARD'); - if (!$mc_result || count($mc_result) === 0 || $refresh) { + if (!$mc_result || + count($mc_result) === 0 || + $leaderboard_limit_cache !== intval($leaderboard_limit->getValue()) || + ($limit === false && (count($mc_result) !== $teams_count)) || + $refresh) { + if ($limit === true) { + $teams = + await self::genTeamArrayFromDB( + 'SELECT * FROM teams WHERE active = 1 AND visible = 1 ORDER BY points DESC, last_score ASC', + intval($leaderboard_limit->getValue()), + ); + } else { + $teams = + await self::genTeamArrayFromDB( + 'SELECT * FROM teams WHERE active = 1 AND visible = 1 ORDER BY points DESC, last_score ASC', + ); + } $team_leaderboard = array(); - $teams = - await self::genTeamArrayFromDB( - 'SELECT * FROM teams WHERE active = 1 AND visible = 1 ORDER BY points DESC, last_score ASC', - ); foreach ($teams->items() as $team) { $team_leaderboard[] = Team::teamFromRow($team); } @@ -274,13 +314,25 @@ class MultiTeam extends Team { $mc_result = self::getMCRecords('TEAMS_BY_LEVEL'); if (!$mc_result || count($mc_result) === 0 || $refresh) { $teams_by_completed_level = array(); - $teams = + $scores = await self::genTeamArrayFromDB( - 'SELECT scores_log.level_id, teams.* FROM teams LEFT JOIN scores_log ON teams.id = scores_log.team_id WHERE teams.visible = 1 AND teams.active = 1 AND level_id IS NOT NULL ORDER BY scores_log.ts', + 'SELECT level_id, team_id FROM scores_log WHERE level_id IS NOT NULL ORDER BY ts', ); - foreach ($teams->items() as $team) { - $teams_by_completed_level[intval($team->get('level_id'))][] = - Team::teamFromRow($team); + $team_scores_awaitables = Map {}; + foreach ($scores->items() as $score) { + $team_scores_awaitables->add( + Pair { + $score->get('level_id'), + self::genTeam(intval($score->get('team_id'))), + }, + ); + } + $team_scores = await \HH\Asio\m($team_scores_awaitables); + + foreach ($team_scores as $level_id_key => $team) { + if ($team->getActive() === true && $team->getVisible() === true) { + $teams_by_completed_level[intval($level_id_key)][] = $team; + } } self::setMCRecords( 'TEAMS_BY_LEVEL', @@ -315,6 +367,127 @@ class MultiTeam extends Team { } } + public static async function genAllCompletedLevels( + bool $refresh = false, + ): Awaitable> { + $mc_result = self::getMCRecords('TEAMS_BY_LEVEL'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $teams_by_completed_level = array(); + $scores = + await self::genTeamArrayFromDB( + 'SELECT level_id, team_id FROM scores_log WHERE level_id IS NOT NULL ORDER BY ts', + ); + $team_scores_awaitables = Map {}; + foreach ($scores->items() as $score) { + if ($team_scores_awaitables->contains( + intval($score->get('level_id')), + )) { + $teams_vector = + $team_scores_awaitables->get(intval($score->get('level_id'))); + invariant( + $teams_vector instanceof Vector, + 'teams_map should of type Vector and not null', + ); + $teams_vector->add(self::genTeam(intval($score->get('team_id')))); + $team_scores_awaitables->set( + intval($score->get('level_id')), + $teams_vector, + ); + } else { + $teams_vector = Vector {}; + $teams_vector->add(self::genTeam(intval($score->get('team_id')))); + $team_scores_awaitables->add( + Pair {intval($score->get('level_id')), $teams_vector}, + ); + } + } + + $team_scores = await \HH\Asio\mmk( + $team_scores_awaitables, + async ($level_id_map_key, $teams_map) ==> { + $teams = await \HH\Asio\v($teams_map); + return $teams; + }, + ); + + foreach ($team_scores as $level_id_key => $teams_vector) { + foreach ($teams_vector as $team) { + if ($team->getActive() === true && $team->getVisible() === true) { + $teams_by_completed_level[intval($level_id_key)][] = $team; + } + } + } + self::setMCRecords( + 'TEAMS_BY_LEVEL', + new Map($teams_by_completed_level), + ); + $teams_by_completed_level = new Map($teams_by_completed_level); + invariant( + $teams_by_completed_level instanceof Map, + 'teams_by_completed_level should be a Map of Team', + ); + return $teams_by_completed_level; + } else { + invariant( + $mc_result instanceof Map, + 'cache return should of type Map and not null', + ); + return $mc_result; + } + } + + public static async function genCompletedLevelTeamNames( + int $level_id, + bool $refresh = false, + ): Awaitable> { + $mc_result = self::getMCRecords('TEAMS_NAMES_BY_LEVEL'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $team_names = array(); + $teams = await self::genAllCompletedLevels(); + invariant($teams instanceof Map, 'teams should be a Map of Team'); + foreach ($teams as $level => $completed_arr) { + invariant( + is_array($completed_arr), + 'completed_arr should be an array of Team', + ); + foreach ($completed_arr as $team_obj) { + invariant( + $team_obj instanceof Team, + 'team_obj should be of type Team', + ); + $team_names[$level][] = $team_obj->getName(); + } + } + self::setMCRecords('TEAMS_NAMES_BY_LEVEL', new Map($team_names)); + $team_names = new Map($team_names); + if ($team_names->contains($level_id)) { + $team_name = $team_names->get($level_id); + invariant( + is_array($team_name), + 'team_name should be an array of string and not null', + ); + return $team_name; + } else { + return array(); + } + } else { + invariant( + $mc_result instanceof Map, + 'cache return should of type string and not null', + ); + if ($mc_result->contains($level_id)) { + $team_name = $mc_result->get($level_id); + invariant( + is_array($team_name), + 'cache return should be an array of string and not null', + ); + return $team_name; + } else { + return array(); + } + } + } + public static async function genFirstCapture( int $level_id, bool $refresh = false, @@ -322,13 +495,23 @@ class MultiTeam extends Team { $mc_result = self::getMCRecords('TEAMS_FIRST_CAP'); if (!$mc_result || count($mc_result) === 0 || $refresh) { $first_team_captured_by_level = array(); - $teams = + $captures = await self::genTeamArrayFromDB( - 'SELECT * FROM teams LEFT JOIN scores_log ON teams.id = scores_log.team_id WHERE scores_log.ts IN (SELECT MIN(scores_log.ts) FROM scores_log WHERE scores_log.team_id IN (SELECT id FROM teams) GROUP BY scores_log.level_id)', + 'SELECT sl.level_id, sl.team_id FROM (SELECT level_id, MIN(ts) ts FROM scores_log LEFT JOIN teams ON team_id = teams.id WHERE teams.visible = 1 AND teams.active = 1 GROUP BY level_id) sl2 JOIN scores_log sl ON sl.level_id = sl2.level_id AND sl.ts = sl2.ts;', ); - foreach ($teams->items() as $team) { - $first_team_captured_by_level[intval($team->get('level_id'))] = - Team::teamFromRow($team); + $team_scores_awaitables = Map {}; + foreach ($captures->items() as $capture) { + $team_scores_awaitables->add( + Pair { + $capture->get('level_id'), + self::genTeam(intval($capture->get('team_id'))), + }, + ); + } + $team_scores = await \HH\Asio\m($team_scores_awaitables); + + foreach ($team_scores as $level_id_key => $team) { + $first_team_captured_by_level[intval($level_id_key)] = $team; } self::setMCRecords( 'TEAMS_FIRST_CAP', @@ -354,4 +537,26 @@ class MultiTeam extends Team { return $team; } } + + public static async function genMyTeamRank( + int $team_id, + ): Awaitable<(Team, int)> { + $team = false; + $rank = 1; + $leaderboard = await MultiTeam::genLeaderboard(); + foreach ($leaderboard as $team) { + if ($team_id === $team->getId()) { + return tuple($team, $rank); + } + $rank++; + } + + invariant( + $team instanceof Team, + 'team return should of type Team and not null', + ); + + return tuple($team, $rank); + } + } diff --git a/src/models/ScoreLog.php b/src/models/ScoreLog.php index 1c534847..d4813900 100644 --- a/src/models/ScoreLog.php +++ b/src/models/ScoreLog.php @@ -5,7 +5,12 @@ class ScoreLog extends Model { protected static string $MC_KEY = 'scorelog:'; protected static Map - $MC_KEYS = Map {'LEVEL_CAPTURES' => 'capture_teams'}; + $MC_KEYS = Map { + 'LEVEL_CAPTURES' => 'capture_teams', + 'ALL_SCORES' => 'all_scores', + 'SCORES_BY_TEAM' => 'scores_by_team', + 'ALL_LEVEL_CAPTURES' => 'all_capture_teams', + }; private function __construct( private int $id, @@ -54,14 +59,25 @@ private static function scorelogFromRow(Map $row): ScoreLog { // Get all scores. public static async function genAllScores(): Awaitable> { $db = await self::genDb(); - $result = await $db->queryf('SELECT * FROM scores_log ORDER BY ts DESC'); + $mc_result = self::getMCRecords('ALL_SCORES'); + if (!$mc_result || count($mc_result) === 0) { + $result = + await $db->queryf('SELECT * FROM scores_log ORDER BY ts DESC'); - $scores = array(); - foreach ($result->mapRows() as $row) { - $scores[] = self::scorelogFromRow($row); - } + $scores = array(); + foreach ($result->mapRows() as $row) { + $scores[] = self::scorelogFromRow($row); + } - return $scores; + self::setMCRecords('ALL_SCORES', $scores); + return $scores; + } else { + invariant( + is_array($mc_result), + 'cache return should be an array of type ScoreLog and not null', + ); + return $mc_result; + } } // Reset all scores. @@ -77,7 +93,7 @@ private static function scorelogFromRow(Map $row): ScoreLog { MultiTeam::invalidateMCRecords('TEAMS_FIRST_CAP'); // Invalidate Memcached MultiTeam data. } - // Check if there is a previous score. + // Check if there is a previous score. - honors team visibility public static async function genPreviousScore( int $level_id, int $team_id, @@ -88,7 +104,10 @@ private static function scorelogFromRow(Map $row): ScoreLog { if (!$mc_result || count($mc_result) === 0 || $refresh) { $db = await self::genDb(); $level_captures = Map {}; - $result = await $db->queryf('SELECT level_id, team_id FROM scores_log'); + $result = + await $db->queryf( + 'SELECT level_id, team_id FROM scores_log LEFT JOIN teams ON scores_log.team_id = teams.id WHERE teams.visible = 1', + ); foreach ($result->mapRows() as $row) { if ($level_captures->contains(intval($row->get('level_id')))) { $level_capture_teams = @@ -166,22 +185,137 @@ private static function scorelogFromRow(Map $row): ScoreLog { } } + // Check if there is a previous score. - ignores team visibility + public static async function genAllPreviousScore( + int $level_id, + int $team_id, + bool $any_team, + bool $refresh = false, + ): Awaitable { + $mc_result = self::getMCRecords('ALL_LEVEL_CAPTURES'); + if (!$mc_result || count($mc_result) === 0 || $refresh) { + $db = await self::genDb(); + $level_captures = Map {}; + $result = await $db->queryf('SELECT level_id, team_id FROM scores_log'); + foreach ($result->mapRows() as $row) { + if ($level_captures->contains(intval($row->get('level_id')))) { + $level_capture_teams = + $level_captures->get(intval($row->get('level_id'))); + invariant( + $level_capture_teams instanceof Vector, + 'level_capture_teams should of type Vector and not null', + ); + $level_capture_teams->add(intval($row->get('team_id'))); + $level_captures->set( + intval($row->get('level_id')), + $level_capture_teams, + ); + } else { + $level_capture_teams = Vector {}; + $level_capture_teams->add(intval($row->get('team_id'))); + $level_captures->add( + Pair {intval($row->get('level_id')), $level_capture_teams}, + ); + } + } + self::setMCRecords('ALL_LEVEL_CAPTURES', new Map($level_captures)); + if ($level_captures->contains($level_id)) { + if ($any_team) { + $level_capture_teams = $level_captures->get($level_id); + invariant( + $level_capture_teams instanceof Vector, + 'level_capture_teams should of type Vector and not null', + ); + $team_id_key = $level_capture_teams->linearSearch($team_id); + if ($team_id_key !== -1) { + $level_capture_teams->removeKey($team_id_key); + } + return intval(count($level_capture_teams)) > 0; + } else { + $level_capture_teams = $level_captures->get($level_id); + invariant( + $level_capture_teams instanceof Vector, + 'level_capture_teams should of type Vector and not null', + ); + $team_id_key = $level_capture_teams->linearSearch($team_id); + return $team_id_key !== -1; + } + } else { + return false; + } + } + invariant( + $mc_result instanceof Map, + 'cache return should of type Map and not null', + ); + if ($mc_result->contains($level_id)) { + if ($any_team) { + $level_capture_teams = $mc_result->get($level_id); + invariant( + $level_capture_teams instanceof Vector, + 'level_capture_teams should of type Vector and not null', + ); + $team_id_key = $level_capture_teams->linearSearch($team_id); + if ($team_id_key !== -1) { + $level_capture_teams->removeKey($team_id_key); + } + return intval(count($level_capture_teams)) > 0; + } else { + $level_capture_teams = $mc_result->get($level_id); + invariant( + $level_capture_teams instanceof Vector, + 'level_capture_teams should of type Vector and not null', + ); + $team_id_key = $level_capture_teams->linearSearch($team_id); + return $team_id_key !== -1; + } + } else { + return false; + } + } + // Get all scores by team. public static async function genAllScoresByTeam( int $team_id, + bool $refresh = false, ): Awaitable> { $db = await self::genDb(); - $result = await $db->queryf( - 'SELECT * FROM scores_log WHERE team_id = %d ORDER BY ts DESC', - $team_id, - ); - - $scores = array(); - foreach ($result->mapRows() as $row) { - $scores[] = self::scorelogFromRow($row); + $mc_result = self::getMCRecords('SCORES_BY_TEAM'); + if (!$mc_result || count($mc_result) === 0) { + $scores = array(); + $result = + await $db->queryf('SELECT * FROM scores_log ORDER BY ts DESC'); + foreach ($result->mapRows() as $row) { + $scores[$row->get('team_id')][] = self::scorelogFromRow($row); + } + self::setMCRecords('SCORES_BY_TEAM', new Map($scores)); + $team_scores = array(); + $scores = new Map($scores); + if ($scores->contains($team_id)) { + $team_scores = $scores->get($team_id); + invariant( + is_array($team_scores), + 'team_scores should an array and not null', + ); + return $team_scores; + } + return $team_scores; + } else { + invariant( + $mc_result instanceof Map, + 'cache return should be a Map of type ScoreLog and not null', + ); + $team_scores = array(); + if ($mc_result->contains($team_id)) { + $team_scores = $mc_result->get($team_id); + invariant( + is_array($team_scores), + 'team_scores should an array and not null', + ); + return $team_scores; + } + return $team_scores; } - - return $scores; } // Get all scores by type. @@ -226,23 +360,37 @@ private static function scorelogFromRow(Map $row): ScoreLog { int $team_id, int $points, string $type, - ): Awaitable { + ): Awaitable { $db = await self::genDb(); - await $db->queryf( - 'INSERT INTO scores_log (ts, level_id, team_id, points, type) VALUES (NOW(), %d, %d, %d, %s)', - $level_id, - $team_id, - $points, - $type, - ); - await ActivityLog::genCaptureLog($team_id, $level_id); - self::invalidateMCRecords(); // Invalidate Memcached ScoreLog data. - ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached ActivityLog data. - MultiTeam::invalidateMCRecords('ALL_TEAMS'); // Invalidate Memcached MultiTeam data. - MultiTeam::invalidateMCRecords('POINTS_BY_TYPE'); // Invalidate Memcached MultiTeam data. - MultiTeam::invalidateMCRecords('LEADERBOARD'); // Invalidate Memcached MultiTeam data. - MultiTeam::invalidateMCRecords('TEAMS_BY_LEVEL'); // Invalidate Memcached MultiTeam data. - MultiTeam::invalidateMCRecords('TEAMS_FIRST_CAP'); // Invalidate Memcached MultiTeam data. + //'INSERT INTO scores_log (ts, level_id, team_id, points, type) VALUES (NOW(), %d, %d, %d, %s)', + $result = + await $db->queryf( + 'INSERT INTO scores_log (ts, level_id, team_id, points, type) SELECT NOW(), %d, %d, %d, %s FROM DUAL WHERE NOT EXISTS (SELECT * FROM scores_log WHERE level_id = %d AND team_id = %d)', + $level_id, + $team_id, + $points, + $type, + $level_id, + $team_id, + ); + + $captured = $result->numRowsAffected() > 0 ? true : false; + + if ($captured === true) { + await ActivityLog::genCaptureLog($team_id, $level_id); + self::invalidateMCRecords(); // Invalidate Memcached ScoreLog data. + ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached ActivityLog data. + MultiTeam::invalidateMCRecords('ALL_TEAMS'); // Invalidate Memcached MultiTeam data. + MultiTeam::invalidateMCRecords('POINTS_BY_TYPE'); // Invalidate Memcached MultiTeam data. + MultiTeam::invalidateMCRecords('LEADERBOARD'); // Invalidate Memcached MultiTeam data. + $completed_level = await MultiTeam::genCompletedLevel($level_id); + if (count($completed_level) === 0) { + MultiTeam::invalidateMCRecords('TEAMS_FIRST_CAP'); // Invalidate Memcached MultiTeam data. + } + MultiTeam::invalidateMCRecords('TEAMS_BY_LEVEL'); // Invalidate Memcached MultiTeam data. + } + + return $captured; } public static async function genScoreLogUpdate( diff --git a/src/models/Session.php b/src/models/Session.php index 4ca6b62c..7f0b5745 100644 --- a/src/models/Session.php +++ b/src/models/Session.php @@ -173,7 +173,7 @@ private static function sessionFromRow(Map $row): Session { return true; } } - return self::getMCSession($cookie) != false; + return self::getMCSession($cookie) !== false; } public static async function genSessionDataIfExist( @@ -312,9 +312,11 @@ private static function sessionFromRow(Map $row): Session { return; } $db = await self::genDb(); - $expired_sessions = - await self::genExpiredSessionsForCleanup($maxlifetime); - $empty_sessions = await self::genEmptySessionsForCleanup(); + list($expired_sessions, $empty_sessions) = await \HH\Asio\va( + self::genExpiredSessionsForCleanup($maxlifetime), + self::genEmptySessionsForCleanup(), + ); + foreach ($expired_sessions as $session) { $cached_session = self::getMCSession($session->getCookie()); if ($cached_session === false) { @@ -355,17 +357,15 @@ private static function sessionFromRow(Map $row): Session { ); } } - // Clean up expired sessions - await $db->queryf( - 'DELETE FROM sessions WHERE UNIX_TIMESTAMP(last_access_ts) < %d', - time() - $maxlifetime, - ); - // Clean up empty sessions - await $db->queryf( - 'DELETE FROM sessions WHERE IFNULL(data, %s) = %s', - '', - '', - ); + // Clean up expired and empty sessions + $queries = Vector { + sprintf( + 'DELETE FROM sessions WHERE UNIX_TIMESTAMP(last_access_ts) < %d', + time() - $maxlifetime, + ), + 'DELETE FROM sessions WHERE data IS NULL', + }; + await $db->multiQuery($queries); } public static async function genUnprotectedSessions( @@ -447,8 +447,13 @@ private static function sessionFromRow(Map $row): Session { foreach ($all_sessions as $session_key) { $session_key = substr(strstr(substr(strstr($session_key, ':'), 1), ':'), 1); - $cached_sessions[] = $session_key; - $sessions[] = self::getMCSession($session_key); + $session = self::getMCSession($session_key); + if ($session !== false && + $session instanceof Session && + $session->getTeamId() !== 0) { + $cached_sessions[] = $session_key; + $sessions[] = $session; + } } $db = await self::genDb(); $result = await $db->queryf('SELECT * FROM sessions'); @@ -467,12 +472,10 @@ private static function sessionFromRow(Map $row): Session { } private static function setMCSession(string $key, mixed $records): void { - $mc = self::getMc(); $key = str_replace(' ', '', $key); - $mc->set( + self::writeMCCluster( self::$MC_KEY.self::$MC_KEYS->get('SESSIONS').$key, $records, - self::$MC_EXPIRE, ); } @@ -487,18 +490,20 @@ private static function getMCSession(string $key): mixed { public static function invalidateMCSessions(?string $key = null): void { $mc = self::getMc(); $key = str_replace(' ', '', $key); - /* HH_IGNORE_ERROR[4053]: HHVM doesn't beleive there is a getAllKeys() method, there is... */ - $mc_keys = $mc->getAllKeys(); if ($key === null) { + /* HH_IGNORE_ERROR[4053]: HHVM doesn't beleive there is a getAllKeys() method, there is... */ + $mc_keys = $mc->getAllKeys(); $all_sessions = preg_grep( '/'.self::$MC_KEY.self::$MC_KEYS->get('SESSIONS').'/', $mc_keys, ); foreach ($all_sessions as $session_key) { - $mc->delete($session_key); + self::invalidateMCCluster($session_key); } } else { - $mc->delete(self::$MC_KEY.self::$MC_KEYS->get('SESSIONS').$key); + self::invalidateMCCluster( + self::$MC_KEY.self::$MC_KEYS->get('SESSIONS').$key, + ); } } } diff --git a/src/models/Team.php b/src/models/Team.php index 2946a8ae..c9a64fb3 100644 --- a/src/models/Team.php +++ b/src/models/Team.php @@ -36,7 +36,7 @@ public function getVisible(): bool { } public function getName(): string { - return $this->name; + return mb_convert_encoding($this->name, 'UTF-8'); } public function getPasswordHash(): string { @@ -86,7 +86,7 @@ protected static function teamFromRow(Map $row): Team { ): Awaitable { foreach ($elements as $team) { $name = must_have_string($team, 'name'); - $exist = await self::genTeamExist($name); + $exist = await self::genTeamExist($name); // TODO: Combine Awaits if (!$exist) { $team_id = await self::genCreateAll( (bool) must_have_idx($team, 'active'), @@ -97,9 +97,9 @@ protected static function teamFromRow(Map $row): Team { (bool) must_have_idx($team, 'admin'), (bool) must_have_idx($team, 'protected'), (bool) must_have_idx($team, 'visible'), - ); + ); // TODO: Combine Awaits } - await Logo::genSetUsed(must_have_string($team, 'logo'), true); + await Logo::genSetUsed(must_have_string($team, 'logo'), true); // TODO: Combine Awaits } return true; } @@ -179,9 +179,12 @@ public static function regenerateHash(string $password_hash): bool { $ldap = await Configuration::gen('ldap'); if ($ldap->getValue() === '1' && !$team->getAdmin()) { // Get server information from configuration - $ldap_server = await Configuration::gen('ldap_server'); - $ldap_port = await Configuration::gen('ldap_port'); - $ldap_domain_suffix = await Configuration::gen('ldap_domain_suffix'); + list($ldap_server, $ldap_port, $ldap_domain_suffix) = + await \HH\Asio\va( + Configuration::gen('ldap_server'), + Configuration::gen('ldap_port'), + Configuration::gen('ldap_domain_suffix'), + ); $ldapconn = ldap_connect( $ldap_server->getValue(), intval($ldap_port->getValue()), @@ -241,15 +244,16 @@ public static function regenerateHash(string $password_hash): bool { $db = await self::genDb(); // Create team - await $db->queryf( - 'INSERT INTO teams (name, password_hash, logo, created_ts) VALUES (%s, %s, %s, NOW())', - $name, - $password_hash, - $logo, + await \HH\Asio\va( + $db->queryf( + 'INSERT INTO teams (name, password_hash, logo, created_ts) VALUES (%s, %s, %s, NOW())', + $name, + $password_hash, + $logo, + ), + Logo::genSetUsed($logo, true), ); - await Logo::genSetUsed($logo, true); - // Return newly created team_id $result = await $db->queryf( @@ -260,7 +264,12 @@ public static function regenerateHash(string $password_hash): bool { ); Logo::invalidateMCRecords(); - MultiTeam::invalidateMCRecords(); // Invalidate Memcached MultiTeam data. + // Delay rebuilding all cache for the new team, as they won't have any scoring data yet anyway. + MultiTeam::invalidateMCRecords('ALL_TEAMS'); + MultiTeam::invalidateMCRecords('ALL_ACTIVE_TEAMS'); + MultiTeam::invalidateMCRecords('ALL_VISIBLE_TEAMS'); + MultiTeam::invalidateMCRecords('TEAMS_BY_LOGO'); + invariant($result->numRows() === 1, 'Expected exactly one result'); return intval($result->mapRows()[0]['id']); } @@ -279,18 +288,20 @@ public static function regenerateHash(string $password_hash): bool { $db = await self::genDb(); // Create team - await $db->queryf( - 'INSERT INTO teams (name, password_hash, points, logo, active, admin, protected, visible, created_ts) VALUES (%s, %s, %d, %s, %d, %d, %d, %d, NOW())', - $name, - $password_hash, - $points, - $logo, - $active ? 1 : 0, - $admin ? 1 : 0, - $protected ? 1 : 0, - $visible ? 1 : 0, + await \HH\Asio\va( + $db->queryf( + 'INSERT INTO teams (name, password_hash, points, logo, active, admin, protected, visible, created_ts) VALUES (%s, %s, %d, %s, %d, %d, %d, %d, NOW())', + $name, + $password_hash, + $points, + $logo, + $active ? 1 : 0, + $admin ? 1 : 0, + $protected ? 1 : 0, + $visible ? 1 : 0, + ), + Logo::genSetUsed($logo, true), ); - await Logo::genSetUsed($logo, true); // Return newly created team_id $result = @@ -320,7 +331,10 @@ public static function regenerateHash(string $password_hash): bool { $email, $team_id, ); - MultiTeam::invalidateMCRecords(); // Invalidate Memcached MultiTeam data. + MultiTeam::invalidateMCRecords('ALL_TEAMS'); // Invalidate Memcached MultiTeam data. + MultiTeam::invalidateMCRecords('ALL_ACTIVE_TEAMS'); + MultiTeam::invalidateMCRecords('ALL_VISIBLE_TEAMS'); + MultiTeam::invalidateMCRecords('TEAMS_BY_LOGO'); } // Get a team data. @@ -349,17 +363,18 @@ public static function regenerateHash(string $password_hash): bool { await $db->queryf('SELECT logo FROM teams WHERE id = %d', $team_id); invariant($result->numRows() === 1, 'Expected exactly one result'); $logo_old = strval($result->mapRows()[0]['logo']); - await Logo::genSetUsed($logo_old, false); - await $db->queryf( - 'UPDATE teams SET name = %s, logo = %s , points = %d WHERE id = %d LIMIT 1', - $name, - $logo, - $points, - $team_id, + await \HH\Asio\va( + $db->queryf( + 'UPDATE teams SET name = %s, logo = %s , points = %d WHERE id = %d LIMIT 1', + $name, + $logo, + $points, + $team_id, + ), + Logo::genSetUsed($logo_old, false), + Logo::genSetUsed($logo, true), ); - await Logo::genSetUsed($logo, true); - Logo::invalidateMCRecords(); MultiTeam::invalidateMCRecords(); // Invalidate Memcached MultiTeam data. ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached ActivityLog data. @@ -376,7 +391,10 @@ public static function regenerateHash(string $password_hash): bool { $password_hash, $team_id, ); - MultiTeam::invalidateMCRecords(); // Invalidate Memcached MultiTeam data. + MultiTeam::invalidateMCRecords('ALL_TEAMS'); // Invalidate Memcached MultiTeam data. + MultiTeam::invalidateMCRecords('ALL_ACTIVE_TEAMS'); + MultiTeam::invalidateMCRecords('ALL_VISIBLE_TEAMS'); + MultiTeam::invalidateMCRecords('TEAMS_BY_LOGO'); await Session::genDeleteByTeam($team_id); } @@ -387,27 +405,70 @@ public static function regenerateHash(string $password_hash): bool { await $db->queryf('SELECT logo FROM teams WHERE id = %d', $team_id); invariant($result->numRows() === 1, 'Expected exactly one result'); $logo = strval($result->mapRows()[0]['logo']); + await Logo::genSetUsed($logo, false); - await $db->queryf( - 'DELETE FROM teams WHERE id = %d AND protected = 0 LIMIT 1', - $team_id, - ); - await $db->queryf( - 'DELETE FROM registration_tokens WHERE team_id = %d', - $team_id, - ); - await $db->queryf('DELETE FROM scores_log WHERE team_id = %d', $team_id); - await $db->queryf('DELETE FROM hints_log WHERE team_id = %d', $team_id); - await $db->queryf( - 'DELETE FROM failures_log WHERE team_id = %d', - $team_id, - ); + $queries = Vector { + sprintf( + 'DELETE FROM teams WHERE id = %d AND protected = 0 LIMIT 1', + $team_id, + ), + sprintf( + 'DELETE FROM registration_tokens WHERE team_id = %d', + $team_id, + ), + sprintf('DELETE FROM teams_oauth WHERE team_id = %d', $team_id), + sprintf('DELETE FROM scores_log WHERE team_id = %d', $team_id), + sprintf('DELETE FROM hints_log WHERE team_id = %d', $team_id), + sprintf('DELETE FROM failures_log WHERE team_id = %d', $team_id), + sprintf( + 'DELETE FROM activity_log WHERE subject = "Team:%d"', + $team_id, + ), + }; + await $db->multiQuery($queries); MultiTeam::invalidateMCRecords(); // Invalidate Memcached MultiTeam data. ActivityLog::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached ActivityLog data. + ScoreLog::invalidateMCRecords(); // Invalidate Memcached ScoreLog data. + HintLog::invalidateMCRecords(); // Invalidate Memcached ScoreLog data. await Session::genDeleteByTeam($team_id); } + public static async function genSetTeamName( + int $team_id, + string $team_name, + ): Awaitable { + $db = await self::genDb(); + + $team_name = trim($team_name); + + if ($team_name === '') { + return false; + } + + $shortname = substr($team_name, 0, 20); + + $team_exists = await Team::genTeamExist($shortname); + if ($team_exists === true) { + return false; + } else { + $team = await self::genTeam($team_id); + await self::genUpdate( + $shortname, + $team->getLogo(), + $team->getPoints(), + $team_id, + ); + MultiTeam::invalidateMCRecords('ALL_TEAMS'); // Invalidate Memcached MultiTeam data. + MultiTeam::invalidateMCRecords('ALL_ACTIVE_TEAMS'); + MultiTeam::invalidateMCRecords('ALL_VISIBLE_TEAMS'); + MultiTeam::invalidateMCRecords('TEAMS_BY_LOGO'); + ScoreLog::invalidateMCRecords(); // Invalidate Memcached ScoreLog data. + ActivityLog::invalidateMCRecords(); // Invalidate Memcached ActivityLog data. + return true; + } + } + // Enable or disable teams by passing 1 or 0. public static async function genSetStatus( int $team_id, @@ -734,29 +795,149 @@ public static function regenerateHash(string $password_hash): bool { Control::invalidateMCRecords('ALL_ACTIVITY'); // Invalidate Memcached Control data. } - public static async function genGetLiveSyncKey( - int $team_id, + public static async function genAuthTokenExists( string $type, - ): Awaitable { + string $token, + ): Awaitable { $db = await self::genDb(); - $result = await $db->queryf( - 'SELECT * FROM livesync WHERE team_id = %d AND type = %s', + + $team_id_result = await $db->queryf( + 'SELECT team_id FROM teams_oauth WHERE type = %s AND token = %s', + $type, + $token, + ); + + if ($team_id_result->numRows() === 1) { + return true; + } else { + return false; + } + } + + public static async function genTeamOAuthTokenExists( + string $type, + int $team_id, + ): Awaitable { + $db = await self::genDb(); + + $team_id_result = await $db->queryf( + 'SELECT id FROM teams_oauth WHERE type = %s AND team_id = %d', + $type, $team_id, + ); + + if ($team_id_result->numRows() === 1) { + return true; + } else { + return false; + } + } + + public static async function genTeamFromOAuthToken( + string $type, + string $token, + ): Awaitable { + $db = await self::genDb(); + + $team_id_result = await $db->queryf( + 'SELECT team_id FROM teams_oauth WHERE type = %s AND token = %s', $type, + $token, ); - invariant($result->numRows() === 1, 'Expected exactly one result'); - $username = strval(must_have_idx($result->mapRows()[0], 'username')); - $key_from_db = strval(must_have_idx($result->mapRows()[0], 'sync_key')); + $team_id = + intval(must_have_idx($team_id_result->mapRows()[0], 'team_id')); + $team = await self::genTeam($team_id); + return $team; + } - switch ($type) { - case 'fbctf': - $key = self::generateHash($key_from_db); - break; - // FALLTHROUGH - default: - $key = $key_from_db; - break; + public static async function genSetOAuthToken( + int $team_id, + string $type, + string $token, + ): Awaitable { + $db = await self::genDb(); + + $queries = Vector { + sprintf( + 'SELECT id FROM teams_oauth WHERE type = "%s" AND token = "%s"', + $db->escapeString($type), + $db->escapeString($token), + ), + sprintf( + 'SELECT id FROM teams_oauth WHERE team_id = %d AND type = "%s"', + $team_id, + $db->escapeString($type), + ), + }; + list($oauth_exists_result, $current_id_result) = + await $db->multiQuery($queries); + + if ($oauth_exists_result->numRows() > 0) { + return false; + } + + if ($current_id_result->numRows() === 1) { + $result = await $db->queryf( + 'UPDATE teams_oauth SET token = %s WHERE id = %d', + $token, + intval(must_have_idx($current_id_result->mapRows()[0], 'id')), + ); + if ($result) { + return true; + } + } else { + $result = await $db->queryf( + 'INSERT INTO teams_oauth (type, team_id, token) VALUES (%s, %d, %s)', + $type, + $team_id, + $token, + ); + if ($result) { + return true; + } + } + return false; + + } + + public static async function genGetLiveSyncKey( + int $team_id, + string $type, + ): Awaitable { + $db = await self::genDb(); + if ($type === 'general') { + $team = await self::genTeam($team_id); + $username = $team->getName(); + $key = ''; + } else { + $result = await $db->queryf( + 'SELECT * FROM livesync WHERE team_id = %d AND type = %s', + $team_id, + $type, + ); + invariant($result->numRows() === 1, 'Expected exactly one result'); + + $username = strval(must_have_idx($result->mapRows()[0], 'username')); + $key_from_db = strval(must_have_idx($result->mapRows()[0], 'sync_key')); + + switch ($type) { + case 'fbctf': + $key = self::generateHash($key_from_db); + break; + case 'facebook_oauth': + $key = $key_from_db; + $username = ''; + break; + case 'google_oauth': + $key = $key_from_db; + $username = ''; + break; + // FALLTHROUGH + default: + $key = $key_from_db; + break; + } } return strval($type.":".$username.":".$key); @@ -864,6 +1045,9 @@ public static function regenerateHash(string $password_hash): bool { $type, ); break; + case 'facebook_oauth': + return await Integration::genFacebookThirdPartyExists($key); + break; case 'google_oauth': $result = await $db->queryf( 'SELECT * FROM livesync WHERE sync_key = %s AND type = %s', @@ -909,6 +1093,7 @@ public static function regenerateHash(string $password_hash): bool { string $key, ): Awaitable { $db = await self::genDb(); + $email = ''; invariant(strpos($key, ':'), "Invalid live sync key"); list($type, $username, $key) = explode(':', $key); @@ -922,6 +1107,18 @@ public static function regenerateHash(string $password_hash): bool { ); invariant($result->numRows() > 0, 'Expected at least one result'); break; + case 'facebook_oauth': + $email = await Integration::genFacebookThirdPartyEmail($key); + invariant( + $email !== '', + 'Expected an email from genFacebookThirdPartyEmail, non returned.', + ); + $result = await $db->queryf( + 'SELECT * FROM livesync WHERE username = %s AND type = %s', + $email, + $type, + ); + break; case 'google_oauth': $result = await $db->queryf( 'SELECT * FROM livesync WHERE sync_key = %s AND type = %s', @@ -953,6 +1150,13 @@ public static function regenerateHash(string $password_hash): bool { return $team; } break; + case 'facebook_oauth': + if (strval($email) === strval($username)) { + $team_id = intval(must_have_idx($row, 'team_id')); + $team = await self::genTeam($team_id); + return $team; + } + break; // FALLTHROUGH default: if (strval($key) === strval($key_from_db)) { diff --git a/src/models/Token.php b/src/models/Token.php index f4e65d5f..a6817aeb 100644 --- a/src/models/Token.php +++ b/src/models/Token.php @@ -43,10 +43,7 @@ private static function tokenFromRow(Map $row): Token { private static function generate(): string { $token_len = 15; - $crypto_strong = True; - return md5( - base64_encode(openssl_random_pseudo_bytes($token_len, $crypto_strong)), - ); + return md5(base64_encode(random_bytes($token_len))); } // Create token. diff --git a/src/scripts/autorun.php b/src/scripts/autorun.php index 9f45bffc..19c07cf0 100644 --- a/src/scripts/autorun.php +++ b/src/scripts/autorun.php @@ -8,6 +8,7 @@ require_once (__DIR__.'/../Db.php'); require_once (__DIR__.'/../Utils.php'); require_once (__DIR__.'/../models/Model.php'); +require_once (__DIR__.'/../models/Cache.php'); require_once (__DIR__.'/../models/Importable.php'); require_once (__DIR__.'/../models/Exportable.php'); require_once (__DIR__.'/../models/Level.php'); @@ -19,6 +20,8 @@ require_once (__DIR__.'/../models/ScoreLog.php'); require_once (__DIR__.'/../models/HintLog.php'); require_once (__DIR__.'/../models/FailureLog.php'); +require_once (__DIR__.'/../models/Announcement.php'); +require_once (__DIR__.'/../models/ActivityLog.php'); while (1) { \HH\Asio\join(Control::genAutoRun()); diff --git a/src/scripts/bases.php b/src/scripts/bases.php index 8e4fb0f3..0e0746e2 100644 --- a/src/scripts/bases.php +++ b/src/scripts/bases.php @@ -8,6 +8,7 @@ require_once (__DIR__.'/../Db.php'); require_once (__DIR__.'/../Utils.php'); require_once (__DIR__.'/../models/Model.php'); +require_once (__DIR__.'/../models/Cache.php'); require_once (__DIR__.'/../models/Importable.php'); require_once (__DIR__.'/../models/Exportable.php'); require_once (__DIR__.'/../models/Level.php'); @@ -17,6 +18,8 @@ require_once (__DIR__.'/../models/ScoreLog.php'); require_once (__DIR__.'/../models/Control.php'); require_once (__DIR__.'/../models/MultiTeam.php'); +require_once (__DIR__.'/../models/Announcement.php'); +require_once (__DIR__.'/../models/ActivityLog.php'); $conf_game = \HH\Asio\join(Configuration::gen('game')); while ($conf_game->getValue() === '1') { @@ -35,14 +38,14 @@ if ($response['response']) { $code = 0; $json_r = json_decode($response['response'])[0]; - $teamname = $json_r->team; + $team_name = $json_r->team; // Give points to the team if exists - if (\HH\Asio\join(Team::genTeamExist($teamname))) { - $team = \HH\Asio\join(Team::genTeamByName($teamname)); + if (\HH\Asio\join(Team::genTeamExist($team_name))) { + $team = \HH\Asio\join(Team::genTeamByName($team_name)); \HH\Asio\join(Level::genScoreBase($response['id'], $team->getId())); //echo "Points\n"; } - //echo "Base(".strval($response['id']).") taken by ".$teamname."\n"; + //echo "Base(".strval($response['id']).") taken by ".$team_name."\n"; } else { $code = -1; //echo "Base(".strval($response['id']).") is DOWN\n"; @@ -58,4 +61,10 @@ // Wait until next iteration $bases_cycle = \HH\Asio\join(Configuration::gen('bases_cycle')); sleep(intval($bases_cycle->getValue())); + + // Flush the local cache before the next cycle to ensure the game is still running and the configuration of the bases hasn't changed (the script runs continuously). + Model::deleteLocalCache(); + + // Get current game status + $conf_game = \HH\Asio\join(Configuration::gen('game')); } diff --git a/src/scripts/liveimport.php b/src/scripts/liveimport.php index c5ea2c50..9b138d05 100644 --- a/src/scripts/liveimport.php +++ b/src/scripts/liveimport.php @@ -8,6 +8,7 @@ require_once (__DIR__.'/../Db.php'); require_once (__DIR__.'/../Utils.php'); require_once (__DIR__.'/../models/Model.php'); +require_once (__DIR__.'/../models/Cache.php'); require_once (__DIR__.'/../models/Importable.php'); require_once (__DIR__.'/../models/Exportable.php'); require_once (__DIR__.'/../models/Level.php'); @@ -19,7 +20,11 @@ require_once (__DIR__.'/../models/Country.php'); require_once (__DIR__.'/../models/Control.php'); require_once (__DIR__.'/../models/MultiTeam.php'); +require_once (__DIR__.'/../models/Integration.php'); require_once (__DIR__.'/../models/Model.php'); +require_once + (__DIR__.'/../../vendor/facebook/graph-sdk/src/Facebook/autoload.php') +; $long_opts = array('url:', 'sleep:', 'disable-ssl-verification', 'debug'); $options = getopt('', $long_opts); @@ -65,22 +70,22 @@ class LiveSyncImport { continue; } foreach ($data as $level) { - $mandatories_set = await self::genMandatoriesSet($level); + $mandatories_set = await self::genMandatoriesSet($level); // TODO: Combine Awaits if ($mandatories_set === false) { self::debug(true, $url, '!!!', 'Mandatory Values Not Set'); continue; } - $level = await self::genDefaults($level); - $level_id = await self::genLevel($url, $level, $debug); + $level = await self::genDefaults($level); // TODO: Combine Awaits + $level_id = await self::genLevel($url, $level, $debug); // TODO: Combine Awaits $teams = - await self::genTeamCaptures($url, $level, $level_id, $debug); + await self::genTeamCaptures($url, $level, $level_id, $debug); // TODO: Combine Awaits await self::genRecalculateScores( $url, $level, $level_id, $teams, $debug, - ); + ); // TODO: Combine Awaits } } else { self::debug( @@ -183,8 +188,10 @@ class LiveSyncImport { } $level_exists = await self::genLevelExists($level); if ($level_exists === false) { - $category_id = await self::genCategory($url, $level, $debug); - $country = await Country::genCountry(strval($level->entity_iso_code)); + list($category_id, $country) = await \HH\Asio\va( + self::genCategory($url, $level, $debug), + Country::genCountry(strval($level->entity_iso_code)), + ); $country_id = $country->getId(); $level_id = await Level::genCreate( strval($level->type), @@ -362,6 +369,10 @@ function($a, $b) { $teams_array = array(); $teams = await self::genTeamDefaults($teams); foreach ($teams as $team_livesync_key => $team_data) { + list($type, $username, $key) = explode(':', $team_livesync_key); + if ($type === 'general') { + continue; + } $team_exists = await Team::genLiveSyncKeyExists(strval($team_livesync_key)); if ($team_exists === true) { @@ -431,7 +442,7 @@ function($a, $b) { int $hint, bool $debug, ): Awaitable { - $team = await Team::genTeam($team_id); + $team = await MultiTeam::genTeam($team_id); $team_name = $team->getName(); $hint_used = false; if ($hint === 1) { @@ -475,9 +486,12 @@ function($a, $b) { bool $debug, ): Awaitable { if ($capture === 1) { - $team = await Team::genTeam($team_id); + list($team, $level_capture) = await \HH\Asio\va( + MultiTeam::genTeam($team_id), + Level::genScoreLevel($level_id, $team_id), + ); $team_name = $team->getName(); - $level_capture = await Level::genScoreLevel($level_id, $team_id); + if ($level_capture === true) { $scorelog = await ScoreLog::genLevelScoreByTeam($team_id, $level_id); await ScoreLog::genScoreLogUpdate( @@ -518,7 +532,7 @@ function($a, $b) { bool $debug, ): Awaitable { if (($hint === 1) && ($hint_used === true)) { - $team = await Team::genTeam($team_id); + $team = await MultiTeam::genTeam($team_id); $team_name = $team->getName(); await Team::genUpdate( strval($team_name), @@ -542,7 +556,7 @@ function($a, $b) { if (intval($team_data['capture']) === 0) { continue; } - $team = await Team::genTeam($team_id); + $team = await MultiTeam::genTeam($team_id); $team_name = $team->getName(); $current_bonus = $level->bonus - (intval($level->bonus_dec) * $level_captured); @@ -554,8 +568,10 @@ function($a, $b) { $existing_points = $scorelog->getPoints(); $total_points = $team->getPoints(); $total_points += $points - $existing_points; - await Team::genTeamUpdatePoints($team_id, $total_points); - await ScoreLog::genUpdateScoreLogBonus($level_id, $team_id, $points); + await \HH\Asio\va( + Team::genTeamUpdatePoints($team_id, $total_points), + ScoreLog::genUpdateScoreLogBonus($level_id, $team_id, $points), + ); $level_captured++; } if ($level_captured > 0) { @@ -592,4 +608,7 @@ public static function debug( LiveSyncImport::genProcess($urls, $check_certificates, $debug), ); sleep($sleep); + + // Flush the local cache before the next import cycle to ensure we get up-to-date team and level data (the script runs continuously). + Model::deleteLocalCache(); } diff --git a/src/scripts/progressive.php b/src/scripts/progressive.php index fde2fdef..0d9e75c1 100644 --- a/src/scripts/progressive.php +++ b/src/scripts/progressive.php @@ -7,6 +7,7 @@ require_once (__DIR__.'/../Db.php'); require_once (__DIR__.'/../Utils.php'); require_once (__DIR__.'/../models/Model.php'); +require_once (__DIR__.'/../models/Cache.php'); require_once (__DIR__.'/../models/Configuration.php'); require_once (__DIR__.'/../models/Progressive.php'); diff --git a/src/static/css/scss/_admin.scss b/src/static/css/scss/_admin.scss index e887c034..c69109b5 100644 --- a/src/static/css/scss/_admin.scss +++ b/src/static/css/scss/_admin.scss @@ -60,11 +60,20 @@ Admin Nav } } +.admin-nav-controls { + text-align: center; + padding-top: 20px; + + a { + margin: 20px auto 20px; + border-top: 1px solid $blue-border; + } +} + .admin-nav--footer { @include purista-bold; text-align: center; - padding-top: 20px; .branding-el { display: block; diff --git a/src/static/css/scss/_modals.scss b/src/static/css/scss/_modals.scss index 8ea7aea8..a5e69092 100644 --- a/src/static/css/scss/_modals.scss +++ b/src/static/css/scss/_modals.scss @@ -53,6 +53,11 @@ .fb-form { padding: 40px; } + + .fb-form-no-padding { + padding: 0px !important; + } + } .modal-title { diff --git a/src/static/js/admin.js b/src/static/js/admin.js index 69973c52..46c32abb 100644 --- a/src/static/js/admin.js +++ b/src/static/js/admin.js @@ -574,6 +574,14 @@ function flushMemcached() { sendAdminRequest(flush_memcached, true); } +//Reset Game Schedule +function resetGameSchedule() { + var reset_game_schedule = { + action: 'reset_game_schedule' + }; + sendAdminRequest(reset_game_schedule, true); +} + // Create tokens function createTokens() { var create_data = { @@ -1099,6 +1107,8 @@ module.exports = { exportCurrentCategories(); } else if (action === 'flush-memcached') { flushMemcached(); + } else if (action === 'reset-game-schedule') { + resetGameSchedule(); } else if (action === 'create-tokens') { createTokens(); } else if (action === 'export-tokens') { @@ -1212,7 +1222,7 @@ module.exports = { var start_day = $('input[type="number"][name="fb--schedule--start_day"]')[0].value; var start_hour = $('input[type="number"][name="fb--schedule--start_hour"]')[0].value; var start_min = $('input[type="number"][name="fb--schedule--start_min"]')[0].value; - var start_ts = new Date(start_month + "/" + start_day + "/" + start_year + " " + start_hour + ":" + start_min).getTime() / 1000; + var start_ts = Date.UTC(start_year, start_month - 1, start_day, start_hour, start_min) / 1000; if ($.isNumeric(start_ts)) { changeConfiguration("start_ts", start_ts); changeConfiguration("next_game", start_ts); @@ -1222,7 +1232,7 @@ module.exports = { var end_day = $('input[type="number"][name="fb--schedule--end_day"]')[0].value; var end_hour = $('input[type="number"][name="fb--schedule--end_hour"]')[0].value; var end_min = $('input[type="number"][name="fb--schedule--end_min"]')[0].value; - var end_ts = new Date(end_month + "/" + end_day + "/" + end_year + " " + end_hour + ":" + end_min).getTime() / 1000; + var end_ts = Date.UTC(end_year, end_month - 1, end_day, end_hour, end_min) / 1000; if ($.isNumeric(end_ts)) { changeConfiguration("end_ts", end_ts); } diff --git a/src/static/js/app.js b/src/static/js/app.js index 62a912e1..54beffe0 100644 --- a/src/static/js/app.js +++ b/src/static/js/app.js @@ -50,8 +50,15 @@ function enableAdminActiveState() { } $(document).ready(function() { - if (window.innerWidth < 960) { - window.location = '/index.php?page=mobile'; + var page_location = window.location.pathname + window.location.search; + if (window.innerWidth < 960 && page_location != '/index.php?page=mobile') { + window.location = '/index.php?page=mobile'; + } else if (window.innerWidth < 960 && page_location == '/index.php?page=mobile') { + setTimeout(function() { + window.location = '/index.php'; + }, 2000); + } else if (window.innerWidth >= 960 && page_location === '/index.php?page=mobile') { + window.location = '/index.php'; } FB_CTF.init(); diff --git a/src/static/js/fb-ctf.js b/src/static/js/fb-ctf.js index e1b22598..a59058cc 100644 --- a/src/static/js/fb-ctf.js +++ b/src/static/js/fb-ctf.js @@ -275,8 +275,18 @@ function setupInputListeners() { $mapSvg, $map, $countryHover, - reload = true, - reload_team = true; + refresh_active_config = false, + refresh_active_country = false, + refresh_active_map = false, + refresh_active_captures = false, + refresh_active_announcment = false, + refresh_active_activity = false, + refresh_active_team_data = false, + refresh_active_team_module = false, + refresh_active_leaderboard = false, + refresh_active_clear_map = false, + refresh_active_filter = false, + refresh_active_session = false; /** @@ -511,89 +521,67 @@ function setupInputListeners() { // Load initial activity loadActivityModule(); + + //Get current team captures + getCaptureData(); - // Configuration reloader + // Configuration/Session reloader setInterval(function() { loadConfData(); + checkActiveSession(); }, FB_CTF.data.CONF.refreshConf); // Countries and other modules - var count = 0; setInterval(function() { - if (reload == true || count > 1){ - reload = false; - if (FB_CTF.data.CONF.gameboard === '1') { - // Map - getCountryData(); - refreshMapData(); - // Announcements - if (Widget.getWidgetStatus('Announcements') === 'open') { - loadAnnouncementsModule(); - } - // Filter - if (Widget.getWidgetStatus('Filter') === 'open') { - loadSavedFilterModule(); - } - // Activity - if (Widget.getWidgetStatus('Activity') === 'open') { - loadActivityModule(); - } - } else { - clearMapData(); - clearAnnouncements(); - clearActivity(); + if (FB_CTF.data.CONF.gameboard === '1') { + // Map + getCountryData(); + refreshMapData(); + getCaptureData(); + // Announcements + if (Widget.getWidgetStatus('Announcements') === 'open') { + loadAnnouncementsModule(); } - } - - if (reload == false){ - count += 1; - } - - // reset counter - if (count > 1){ - count = 0; - reload = true; + // Filter + if (Widget.getWidgetStatus('Filter') === 'open') { + loadSavedFilterModule(); + } + // Activity + if (Widget.getWidgetStatus('Activity') === 'open') { + loadActivityModule(); + } + } else { + clearMapData(); + clearAnnouncements(); + clearActivity(); } }, FB_CTF.data.CONF.refreshMap); // Teams - var teams_count = 0; setInterval(function() { - if (reload_team == true || teams_count > 1){ - reload_team = false; - if (FB_CTF.data.CONF.gameboard === '1') { - // Teams - loadTeamData(); - if (Widget.getWidgetStatus('Teams') === 'open') { - loadTeamsModule(); - } - if (Widget.getWidgetStatus('Leaderboard') === 'open') { - loadLeaderboardModule(); - } - } else { - clearTeams(); - clearLeaderboard(); + if (FB_CTF.data.CONF.gameboard === '1') { + // Teams + loadTeamData(); + if (Widget.getWidgetStatus('Teams') === 'open') { + loadTeamsModule(); } - } - - if (reload_team == false){ - teams_count += 1; - } - - // reset team counter - if (teams_count > 1){ - teams_count = 0; - reload_team = true; + if (Widget.getWidgetStatus('Leaderboard') === 'open') { + loadLeaderboardModule(); + } + } else { + clearTeams(); + clearLeaderboard(); } }, FB_CTF.data.CONF.refreshMap); // Forcefully refreshing all modules every minute setInterval(function() { - loadAnnouncementsModule(); - loadSavedFilterModule(); - loadActivityModule(); - loadTeamsModule(); - loadLeaderboardModule(); + checkActiveSession(true); + loadAnnouncementsModule(true); + loadSavedFilterModule(true); + loadActivityModule(true); + loadTeamsModule(true); + loadLeaderboardModule(true); loadClockModule(); }, 60000); @@ -927,7 +915,7 @@ function setupInputListeners() { owner = data ? data.owner : '', attachments = data ? data.attachments : '', links = data ? data.links : ''; - + $('.country-name', $container).text(country); $('.country-title', $container).text(title); $('input[name=level_id]', $container).attr('value', level_id); @@ -975,6 +963,12 @@ function setupInputListeners() { $('.answer_no_bases').addClass('completely-hidden'); } + // Hide flag submission for captured levels + if ($.inArray(level_id, FB_CTF.data.CAPTURES) != -1) { + $('.answer_no_bases').addClass('completely-hidden'); + $('.answer_captured').removeClass('completely-hidden'); + } + // // event listeners // @@ -1047,9 +1041,19 @@ function setupInputListeners() { var responseData = JSON.parse(data); if (responseData.result === 'OK') { console.log('OK'); - $('input[name=answer]', $container).css("background-color", "#1f7a1f"); + $($container).on('keypress', function(e) { + if (e.keyCode == 13) { + e.preventDefault(); + } + }); $('.js-trigger-score', $container).text('YES!'); + $('input[name=answer]', $container).css("background-color", "#1f7a1f"); + $('.answer_no_bases > .fb-cta.cta--yellow.js-trigger-score').removeClass('js-trigger-score'); + refreshMapData(); // Refresh map so capture shows up right away + getCaptureData(); // Refresh captured levels so we can't reload the modal and see a submit button setTimeout(function() { + $('.answer_no_bases').addClass('completely-hidden'); + $('.answer_captured').removeClass('completely-hidden'); $('.js-close-modal', $container).click(); }, 2000); } else { @@ -1328,10 +1332,16 @@ function setupInputListeners() { var get = $.get(modulePath, function(data) { $self.html(data); - }).error(function() { + }).error(function(jqxhr, status, error) { console.error("There was a problem retrieving the module."); console.log(modulePath); + console.log(status); + console.log(error); console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } }); deferredArray.push(get); @@ -1361,9 +1371,15 @@ function setupInputListeners() { $mapSvg = $('#fb-gameboard-map'); $countryHover = $('[class~="country-hover"]', $mapSvg); enableClickAndDrag.init(); - }, 'html').error(function() { + }, 'html').error(function(jqxhr, status, error) { console.error("There was a problem loading the svg map"); + console.log(status); + console.log(error); console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } }); } @@ -1381,9 +1397,15 @@ function setupInputListeners() { $mapSvg = $('#fb-gameboard-map'); $countryHover = $('[class~="country-hover"]', $mapSvg); enableClickAndDrag.init(); - }, 'html').error(function() { + }, 'html').error(function(jqxhr, status, error) { console.error("There was a problem loading the svg map"); + console.log(status); + console.log(error); console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } }); } @@ -1397,9 +1419,15 @@ function setupInputListeners() { $listview = $('.fb-listview'); $listview.html(data); listviewEventListeners($listview); - }, 'html').error(function() { + }, 'html').error(function(jqxhr, status, error) { console.error("There was a problem loading the List View"); + console.log(status); + console.log(error); console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } }); } @@ -1415,31 +1443,48 @@ function setupInputListeners() { success_callback(); } }) - .error(function() { + .error(function(jqxhr, status, error) { console.error("There was a problem retrieving the module."); console.log(loadPath); + console.log(status); + console.log(error); console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } }); } /** * load the teams module */ - function loadTeamsModule() { - var teamsModulePath = 'inc/gameboard/modules/teams.php'; - var teamsTargetSelector = 'aside[data-module="teams"]'; - - return loadModuleGeneric(teamsModulePath, teamsTargetSelector); + function loadTeamsModule(force = false) { + if (refresh_active_team_module === false || force === true) { + refresh_active_team_module = true; + var teamsModulePath = 'inc/gameboard/modules/teams.php'; + var teamsTargetSelector = 'aside[data-module="teams"]'; + + return loadModuleGeneric(teamsModulePath, teamsTargetSelector, function() { + refresh_active_team_module = false; + }); + } } /** * load the leaderboard module */ - function loadLeaderboardModule() { - var leaderboardModulePath = 'inc/gameboard/modules/leaderboard.php'; - var leaderboardSelector = 'aside[data-module="leaderboard"]'; + function loadLeaderboardModule(force = false) { + if (refresh_active_leaderboard === false || force === true) { + refresh_active_leaderboard = true; + + var leaderboardModulePath = 'inc/gameboard/modules/leaderboard.php'; + var leaderboardSelector = 'aside[data-module="leaderboard"]'; - return loadModuleGeneric(leaderboardModulePath, leaderboardSelector); + return loadModuleGeneric(leaderboardModulePath, leaderboardSelector, function() { + refresh_active_leaderboard = false; + }); + } } /** @@ -1459,163 +1504,269 @@ function setupInputListeners() { /** * load the team data */ - function loadTeamData() { - var loadPath = 'data/teams.php'; - - return $.get(loadPath, function(data) { - FB_CTF.data.TEAMS = data; - var df = $.Deferred(); - reload_team = true; - return df.resolve(FB_CTF.data.TEAMS); - }, 'json').error(function(jqhxr, status, error) { - console.error("There was a problem retrieving the team data."); - console.log(loadPath); - console.log(status); - console.log(error); - console.error("/error"); - console.error("Team data request failed"); - reload_team = false; - }); + function loadTeamData(force = false) { + if (refresh_active_team_data === false || force === true) { + refresh_active_team_data = true; + var loadPath = 'data/teams.php'; + + return $.get(loadPath, function(data) { + FB_CTF.data.TEAMS = data; + var df = $.Deferred(); + return df.resolve(FB_CTF.data.TEAMS); + }, 'json').error(function(jqxhr, status, error) { + console.error("There was a problem retrieving the team data."); + console.log(loadPath); + console.log(status); + console.log(error); + console.error("/error"); + console.error("Team data request failed"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } + }).done(function() { + refresh_active_team_data = false; + }); + } + } + + /** + * load the team data + */ + function getCaptureData(force = false) { + if (refresh_active_captures === false || force === true) { + refresh_active_captures = true; + var loadPath = 'data/captures.php'; + + return $.get(loadPath, function(data) { + FB_CTF.data.CAPTURES = data; + var df = $.Deferred(); + return df.resolve(FB_CTF.data.CAPTURES); + }, 'json').error(function(jqxhr, status, error) { + console.error("There was a problem retrieving the captures data."); + console.log(loadPath); + console.log(status); + console.log(error); + console.error("/error"); + console.error("Captures data request failed"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } + }).done(function() { + refresh_active_captures = false; + }); + } } /** * load the announcements module */ - function loadAnnouncementsModule() { - var announcementsModulePath = 'inc/gameboard/modules/announcements.php'; - var announcementsTargetSelector = 'aside[data-module="announcements"]'; - - return loadModuleGeneric(announcementsModulePath, announcementsTargetSelector); + function loadAnnouncementsModule(force = false) { + if (refresh_active_announcment === false || force === true) { + refresh_active_announcment = true; + var announcementsModulePath = 'inc/gameboard/modules/announcements.php'; + var announcementsTargetSelector = 'aside[data-module="announcements"]'; + + return loadModuleGeneric(announcementsModulePath, announcementsTargetSelector, function() { + refresh_active_announcment = false; + }); + } } /** * load the filter module */ - function loadFilterModule() { - var filterModulePath = 'inc/gameboard/modules/filter.php'; - var filterTargetSelector = 'aside[data-module="filter"]'; - - return loadModuleGeneric( - filterModulePath, - filterTargetSelector, - function() { - Filter.rememberFilters(filterList); - } - ); + function loadFilterModule(force = false) { + if (refresh_active_filter === false || force === true) { + refresh_active_filter = true; + var filterModulePath = 'inc/gameboard/modules/filter.php'; + var filterTargetSelector = 'aside[data-module="filter"]'; + + return loadModuleGeneric( + filterModulePath, + filterTargetSelector, + function() { + refresh_active_filter = false; + Filter.rememberFilters(filterList); + } + ); + } } /** * wrapper to load and save/remember the filter module */ - function loadSavedFilterModule() { + function loadSavedFilterModule(force = false) { // Update variable for all filters to remember them filterList = Filter.detectFilters(); // Load filter module - return loadFilterModule(); + return loadFilterModule(force); } /** * load the activity module */ - function loadActivityModule() { - var activityModulePath = 'inc/gameboard/modules/activity.php'; - var activityTargetSelector = 'aside[data-module="activity"]'; - - return loadModuleGeneric(activityModulePath, activityTargetSelector); + function loadActivityModule(force = false) { + if (refresh_active_activity === false || force === true) { + refresh_active_activity = true; + var activityModulePath = 'inc/gameboard/modules/activity.php'; + var activityTargetSelector = 'aside[data-module="activity"]'; + + return loadModuleGeneric(activityModulePath, activityTargetSelector, function() { + refresh_active_activity = false; + }); + } } /** * load the configuration data */ - function loadConfData() { - var loadPath = 'data/configuration.php'; - - return $.get(loadPath, function(data) { - FB_CTF.data.CONF = data; - var df = $.Deferred(); - reload = true; - return df.resolve(FB_CTF.data.CONF); - }, 'json').error(function(jqhxr, status, error) { - console.error("There was a problem retrieving the conf data."); - console.log(loadPath); - console.log(status); - console.log(error); - console.error("/error"); - reload = false; - }); + function loadConfData(force = false) { + if (refresh_active_config === false || force === true) { + refresh_active_config = true; + var loadPath = 'data/configuration.php'; + + return $.get(loadPath, function(data) { + FB_CTF.data.CONF = data; + var df = $.Deferred(); + return df.resolve(FB_CTF.data.CONF); + }, 'json').error(function(jqxhr, status, error) { + console.error("There was a problem retrieving the conf data."); + console.log(loadPath); + console.log(status); + console.log(error); + console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } + }).done(function() { + refresh_active_config = false; + }); + } + } + + /** + * verify and active session, or redirect to login + */ + function checkActiveSession(force = false) { + if (refresh_active_session === false || force === true) { + refresh_active_session = true; + var loadPath = 'data/session.php'; + + return $.get(loadPath, function(data, response, xhr) { + if (xhr.getResponseHeader('Login-Page') === "true") { + console.log('Session is not active'); + console.log("Redirecting to '/index.php?page=login'"); + window.location.replace('/index.php?page=login'); + } + }).error(function(jqxhr, status, error) { + console.error("There was a problem retrieving the session data."); + console.log(loadPath); + console.log(status); + console.log(error); + console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } + }).done(function() { + refresh_active_session = false; + }); + } } /** * refresh the map data */ - function refreshMapData() { - var loadPath = 'data/map-data.php'; - - return $.get(loadPath, function(data) { - $.each(data, function(key, value) { - // First we clear all - $('#' + key)[0].classList.remove('active'); - $('#' + key)[0].parentNode.removeAttribute('data-captured'); - $('#' + key)[0].parentNode.children[1].classList.remove("captured--you"); - $('#' + key)[0].parentNode.children[1].classList.remove("captured--opponent"); - - // Active country - if (value.status === 'active') { - if (!$('#' + key).hasClass('active')) { - $('#' + key)[0].classList.add('active'); + function refreshMapData(force = false) { + if (refresh_active_map === false || force === true) { + refresh_active_map = true; + + var loadPath = 'data/map-data.php'; + + return $.get(loadPath, function(data) { + FB_CTF.data.MAP = data; + + $.each(data, function(key, value) { + + // First we clear all + $('#' + key)[0].classList.remove('active'); + $('#' + key)[0].parentNode.removeAttribute('data-captured'); + $('#' + key)[0].parentNode.children[1].classList.remove("captured--you"); + $('#' + key)[0].parentNode.children[1].classList.remove("captured--opponent"); + + // Active country + if (value.status === 'active') { + if (!$('#' + key).hasClass('active')) { + $('#' + key)[0].classList.add('active'); + } } + /*else { // Inactive country + $('#' + key)[0].classList.remove('active'); + $('#' + key)[0].parentNode.removeAttribute('data-captured'); + $('#' + key)[0].parentNode.children[1].classList.remove("captured--you"); + $('#' + key)[0].parentNode.children[1].classList.remove("captured--opponent"); + }*/ + if (value.captured == 'you') { + //$('#' + key)[0].parentNode.children[1].classList.remove("captured--opponent"); + $('#' + key)[0].parentNode.children[1].classList.add("captured--you"); + //$('#' + key)[0].parentNode.removeAttribute('data-captured'); + $('#' + key)[0].parentNode.setAttribute('data-captured', value.datacaptured); + } else if (value.captured == 'opponent') { + //$('#' + key)[0].parentNode.children[1].classList.remove("captured--you"); + $('#' + key)[0].parentNode.children[1].classList.add("captured--opponent"); + //$('#' + key)[0].parentNode.removeAttribute('data-captured'); + $('#' + key)[0].parentNode.setAttribute('data-captured', value.datacaptured); + } + }); + }, 'json').error(function(jqxhr, status, error) { + console.error("There was a problem retrieving the map data."); + console.log(loadPath); + console.log(status); + console.log(error); + console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); } - /*else { // Inactive country - $('#' + key)[0].classList.remove('active'); - $('#' + key)[0].parentNode.removeAttribute('data-captured'); - $('#' + key)[0].parentNode.children[1].classList.remove("captured--you"); - $('#' + key)[0].parentNode.children[1].classList.remove("captured--opponent"); - }*/ - if (value.captured == 'you') { - //$('#' + key)[0].parentNode.children[1].classList.remove("captured--opponent"); - $('#' + key)[0].parentNode.children[1].classList.add("captured--you"); - //$('#' + key)[0].parentNode.removeAttribute('data-captured'); - $('#' + key)[0].parentNode.setAttribute('data-captured', value.datacaptured); - } else if (value.captured == 'opponent') { - //$('#' + key)[0].parentNode.children[1].classList.remove("captured--you"); - $('#' + key)[0].parentNode.children[1].classList.add("captured--opponent"); - //$('#' + key)[0].parentNode.removeAttribute('data-captured'); - $('#' + key)[0].parentNode.setAttribute('data-captured', value.datacaptured); - } + }).done(function() { + refresh_active_map = false; }); - reload = true; - }, 'json').error(function(jqhxr, status, error) { - console.error("There was a problem retrieving the map data."); - console.log(loadPath); - console.log(status); - console.log(error); - console.error("/error"); - reload = false; - }); + } } /** * clear the map data */ - function clearMapData() { - var loadPath = 'data/map-data.php'; - - return $.get(loadPath, function(data) { - $.each(data, function(key) { - $('#' + key)[0].classList.remove('active'); - $('#' + key)[0].parentNode.removeAttribute('data-captured'); - $('#' + key)[0].parentNode.children[1].classList.remove("captured--you"); - $('#' + key)[0].parentNode.children[1].classList.remove("captured--opponent"); + function clearMapData(force = false) { + if (refresh_active_clear_map === false || force === true) { + refresh_active_clear_map = true; + + var loadPath = 'data/map-data.php'; + + return $.get(loadPath, function(data) { + $.each(data, function(key) { + $('#' + key)[0].classList.remove('active'); + $('#' + key)[0].parentNode.removeAttribute('data-captured'); + $('#' + key)[0].parentNode.children[1].classList.remove("captured--you"); + $('#' + key)[0].parentNode.children[1].classList.remove("captured--opponent"); + }); + }, 'json').error(function(jqxhr, status, error) { + console.error("There was a problem retrieving the map data."); + console.log(loadPath); + console.log(status); + console.log(error); + console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } + }).done(function() { + refresh_active_clear_map = false; }); - reload = true; - }, 'json').error(function(jqhxr, status, error) { - console.error("There was a problem retrieving the map data."); - console.log(loadPath); - console.log(status); - console.log(error); - console.error("/error"); - reload = false; - }); + } } @@ -1626,22 +1777,29 @@ function setupInputListeners() { * @return Deferred * - indicate that this jqxhr request is all done */ - function getCountryData() { - var loadPath = 'data/country-data.php'; - - return $.get(loadPath, function(data) { - FB_CTF.data.COUNTRIES = data; - var df = $.Deferred(); - reload = true; - return df.resolve(FB_CTF.data.COUNTRIES); - }, 'json').error(function(jqxhr, status, error) { - console.error("There was a problem retrieving the game data."); - console.log(loadPath); - console.log(status); - console.log(error); - console.error("/error"); - reload = false; - }); + function getCountryData(force = false) { + if (refresh_active_country === false || force === true) { + refresh_active_country = true; + var loadPath = 'data/country-data.php'; + + return $.get(loadPath, function(data) { + FB_CTF.data.COUNTRIES = data; + var df = $.Deferred(); + return df.resolve(FB_CTF.data.COUNTRIES); + }, 'json').error(function(jqxhr, status, error) { + console.error("There was a problem retrieving the game data."); + console.log(loadPath); + console.log(status); + console.log(error); + console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } + }).done(function() { + refresh_active_country = false; + }); + } } /** @@ -1899,25 +2057,35 @@ function setupInputListeners() { modalId = 'command-line', modalParams = 'p=command-line&modal=command-line', $cmdPromptList, - $cmdResultsList; + $cmdResultsList, + refresh_active_command = false; /** * load the commands data */ function loadCommandsData() { - var loadPath = 'data/command-line.php'; - - return $.get(loadPath, function(data) { - FB_CTF.data.COMMAND = data; - var df = $.Deferred(); - return df.resolve(FB_CTF.data.COMMAND); - }, 'json').error(function(jqhxr, status, error) { - console.error("There was a problem retrieving the commands data."); - console.log(loadPath); - console.log(status); - console.log(error); - console.error("/error"); - }); + if (refresh_active_command === false) { + refresh_active_command = true; + var loadPath = 'data/command-line.php'; + + return $.get(loadPath, function(data) { + FB_CTF.data.COMMAND = data; + var df = $.Deferred(); + return df.resolve(FB_CTF.data.COMMAND); + }, 'json').error(function(jqxhr, status, error) { + console.error("There was a problem retrieving the commands data."); + console.log(loadPath); + console.log(status); + console.log(error); + console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } + }).done(function() { + refresh_active_command = false; + }); + } } /** @@ -2381,9 +2549,15 @@ function setupInputListeners() { }); eventListeners(); } - }, 'json').error(function() { + }, 'json').error(function(jqxhr, status, error) { console.error("There was a problem retrieving the commands."); + console.log(status); + console.log(error); console.error("/error"); + if (jqxhr.status === 500 && jqxhr.getResponseHeader('Error-Redirect') === "true") { + console.log("Redirecting to '/index.php?page=error'"); + window.location.replace('/index.php?page=error'); + } }); }); } @@ -2501,7 +2675,46 @@ function setupInputListeners() { Modal.loadPopup('p=action&modal=account', 'action-account'); }); - // submit account modal + // submit account team name modal + $body.on('click', '.js-trigger-account-team-name-save', function(event) { + event.preventDefault(); + + var team_name = $('.team-name-form input[name=team_name]')[0].value; + var csrf_token = $('.team-name-form input[name=csrf_token]')[0].value; + var team_name_data = { + action: 'set_team_name', + team_name: team_name, + csrf_token: csrf_token + }; + + $.post( + 'index.php?p=game&ajax=true', + team_name_data + ).fail(function() { + // TODO: Make this a modal + console.log('ERROR'); + }).done(function(data) { + var responseData = JSON.parse(data); + if (responseData.result === 'OK') { + console.log('OK'); + $('.team-name-form input[name=team_name]').css("background-color", "#1f7a1f"); + $('.team-name-form span').text('Team Name updated.'); + } else { + console.log('Failed'); + $('.team-name-form input[name=team_name]').css("background-color", "#800000"); + $('.team-name-form span').text('Failed! Please try a different name.'); + } + }); + }); + + $body.on('keypress', '.team-name-form', function(e) { + if (e.keyCode == 13) { + e.preventDefault(); + $('.js-trigger-account-team-name-save').click(); + } + }); + + // submit account livesync modal $body.on('click', '.js-trigger-account-save', function(event) { event.preventDefault(); @@ -2538,22 +2751,33 @@ function setupInputListeners() { }); $body.on('keypress', '.account-link-form', function(e) { - if (e.keyCode == 13) { - e.preventDefault(); - $('.js-trigger-account-save').click(); - } - }); + if (e.keyCode == 13) { + e.preventDefault(); + $('.js-trigger-account-save').click(); + } + }); + + // open Facebook OAuth popup + $body.on('click', '.js-trigger-facebook-oauth', function(event) { + event.preventDefault(); + + var popup = window.open('/data/integration_oauth.php?type=facebook', 'Facebook OAuth', 'height=800,width=800,toolbar=no,scrollbars=1,status=no,location=no,directories=no'); + if (window.focus) { + popup.focus(); + } + return false; + }); // open Google OAuth popup $body.on('click', '.js-trigger-google-oauth', function(event) { - event.preventDefault(); + event.preventDefault(); - var popup = window.open('/data/google_oauth.php', 'Google OAuth', 'height=800,width=800,toolbar=no,scrollbars=1,status=no,location=no,directories=no'); - if (window.focus) { - popup.focus(); - } - return false; - }); + var popup = window.open('/data/integration_oauth.php?type=google', 'Google OAuth', 'height=800,width=800,toolbar=no,scrollbars=1,status=no,location=no,directories=no'); + if (window.focus) { + popup.focus(); + } + return false; + }); // click events $body.on('click', '.click-effect', function() { diff --git a/src/static/js/index.js b/src/static/js/index.js index 142894d1..ea1b5312 100644 --- a/src/static/js/index.js +++ b/src/static/js/index.js @@ -9,8 +9,10 @@ function teamNameFormError() { function teamLoginFormError() { $('.el--text')[0].classList.add('form-error'); + $('.el--text')[1].classList.add('form-error'); $('.fb-form input').on('change', function() { $('.el--text')[0].classList.remove('form-error'); + $('.el--text')[1].classList.remove('form-error'); }); } @@ -149,7 +151,7 @@ module.exports = { if (name && password && !logoInfo.error) { var register_data = { action: 'register_team', - teamname: name, + team_name: name, password: password, logo: logoInfo.logo, isCustomLogo: logoInfo.isCustom, @@ -182,7 +184,7 @@ module.exports = { if (name && password && !logoInfo.error) { var register_data = { action: 'register_names', - teamname: name, + team_name: name, password: password, logo: logoInfo.logo, isCustomLogo: logoInfo.isCustom, @@ -204,7 +206,7 @@ module.exports = { teamParam = 'team_id'; } else { team = $('.fb-form input[name="team_name"]')[0].value; - teamParam = 'teamname'; + teamParam = 'team_name'; } password = verifyTeamPassword(); diff --git a/src/static/svg/map/world.php b/src/static/svg/map/world.php index 6c6a6fb1..226aef86 100644 --- a/src/static/svg/map/world.php +++ b/src/static/svg/map/world.php @@ -2,12 +2,13 @@ require_once ($_SERVER['DOCUMENT_ROOT'].'/../vendor/autoload.php'); -/* HH_IGNORE_ERROR[1002] */ -SessionUtils::sessionStart(); -SessionUtils::enforceLogin(); - -class WorldMapController { +class WorldMapController extends ModuleController { public async function genRender(): Awaitable<:xhp> { + + /* HH_IGNORE_ERROR[1002] */ + SessionUtils::sessionStart(); + SessionUtils::enforceLogin(); + $worldMap = await $this->genRenderWorldMap(); return getId(), SessionUtils::sessionTeam(), false, @@ -106,5 +107,6 @@ class={$path_class} } } +/* HH_IGNORE_ERROR[1002] */ $map = new WorldMapController(); -echo \HH\Asio\join($map->genRender()); +$map->sendRender(); diff --git a/src/xhp/Custombranding.php b/src/xhp/Custombranding.php index e693700e..1b39a799 100644 --- a/src/xhp/Custombranding.php +++ b/src/xhp/Custombranding.php @@ -2,17 +2,15 @@ class :custombranding extends :x:element { category %flow; - attribute - string brandingText, - string brandingLogo; + attribute string brandingText, string brandingLogo; protected string $tagName = 'custombranding'; protected function render(): XHPRoot { return - :brandingLogo}/> -
              + :brandingLogo} /> +
              {$this->:brandingText}
              ; } diff --git a/src/xhp/Fbbranding.php b/src/xhp/Fbbranding.php index 28f6020b..ac57cfa1 100644 --- a/src/xhp/Fbbranding.php +++ b/src/xhp/Fbbranding.php @@ -2,8 +2,7 @@ class :fbbranding extends :x:element { category %flow; - attribute - string brandingText; + attribute string brandingText; protected string $tagName = 'fbbranding'; diff --git a/tests/_files/seed.xml b/tests/_files/seed.xml index b3d08f6a..451f5652 100644 --- a/tests/_files/seed.xml +++ b/tests/_files/seed.xml @@ -153,6 +153,12 @@ 0 description + + 6 + leaderboard_limit + 50 + description +
              id diff --git a/tests/models/ConfigurationTest.php b/tests/models/ConfigurationTest.php index 9df8c735..db337904 100644 --- a/tests/models/ConfigurationTest.php +++ b/tests/models/ConfigurationTest.php @@ -4,7 +4,7 @@ class ConfigurationTest extends FBCTFTest { public function testAllConfiguration(): void { $all = HH\Asio\join(Configuration::genAllConfiguration()); - $this->assertEquals(5, count($all)); + $this->assertEquals(6, count($all)); $c = $all[0]; $this->assertEquals(1, $c->getId()); diff --git a/tests/models/ScoreLogTest.php b/tests/models/ScoreLogTest.php index 793e92dc..b50d7fec 100644 --- a/tests/models/ScoreLogTest.php +++ b/tests/models/ScoreLogTest.php @@ -49,7 +49,7 @@ public function testPreviousScore(): void { $this->assertFalse($previous); } - public function testLogValidScore(): void { + public function testDuplicateLogValidScore(): void { HH\Asio\join(ScoreLog::genLogValidScore( 2, // level 1, // team @@ -57,6 +57,20 @@ public function testLogValidScore(): void { 'base', // any_team )); + $all = HH\Asio\join(ScoreLog::genAllScores()); + $this->assertEquals(2, count($all)); + $s = $all[0]; + $this->assertEquals(10, $s->getPoints()); + } + + public function testLogValidScore(): void { + HH\Asio\join(ScoreLog::genLogValidScore( + 1, // level + 1, // team + 5, // points + 'base', // any_team + )); + $all = HH\Asio\join(ScoreLog::genAllScores()); $this->assertEquals(3, count($all)); $s = $all[0]; diff --git a/tests/models/SessionTest.php b/tests/models/SessionTest.php index 6f969059..e79f947a 100644 --- a/tests/models/SessionTest.php +++ b/tests/models/SessionTest.php @@ -18,7 +18,7 @@ public function testCreate(): void { $all = HH\Asio\join(Session::genAllSessions()); $this->assertEquals(2, count($all)); - $a = $all[0]; + $a = $all[1]; $this->assertEquals(2, $a->getId()); $this->assertEquals('cookie2', $a->getCookie()); $this->assertEquals('data2', $a->getData());