diff --git a/app/Config/Schema/db_acl.php b/app/Config/Schema/db_acl.php index 495055b..3c2c49d 100644 --- a/app/Config/Schema/db_acl.php +++ b/app/Config/Schema/db_acl.php @@ -26,44 +26,46 @@ */ class DbAclSchema extends CakeSchema { - public function before($event = array()) { - return true; - } + public function before($event = []) { + return true; + } - public function after($event = array()) { - } + public function after($event = []) { + } - public $acos = array( - 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'), - 'parent_id' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 10), - 'model' => array('type' => 'string', 'null' => true), - 'foreign_key' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 10), - 'alias' => array('type' => 'string', 'null' => true), - 'lft' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 10), - 'rght' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 10), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) - ); + public $acos = [ + 'id' => ['type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'], + 'parent_id' => ['type' => 'integer', 'null' => true, 'default' => null, 'length' => 10], + 'model' => ['type' => 'string', 'null' => true], + 'foreign_key' => ['type' => 'integer', 'null' => true, 'default' => null, 'length' => 10], + 'alias' => ['type' => 'string', 'null' => true], + 'lft' => ['type' => 'integer', 'null' => true, 'default' => null, 'length' => 10], + 'rght' => ['type' => 'integer', 'null' => true, 'default' => null, 'length' => 10], + 'indexes' => ['PRIMARY' => ['column' => 'id', 'unique' => 1]] + ]; - public $aros = array( - 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'), - 'parent_id' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 10), - 'model' => array('type' => 'string', 'null' => true), - 'foreign_key' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 10), - 'alias' => array('type' => 'string', 'null' => true), - 'lft' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 10), - 'rght' => array('type' => 'integer', 'null' => true, 'default' => null, 'length' => 10), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) - ); - - public $aros_acos = array( - 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'), - 'aro_id' => array('type' => 'integer', 'null' => false, 'length' => 10, 'key' => 'index'), - 'aco_id' => array('type' => 'integer', 'null' => false, 'length' => 10), - '_create' => array('type' => 'string', 'null' => false, 'default' => '0', 'length' => 2), - '_read' => array('type' => 'string', 'null' => false, 'default' => '0', 'length' => 2), - '_update' => array('type' => 'string', 'null' => false, 'default' => '0', 'length' => 2), - '_delete' => array('type' => 'string', 'null' => false, 'default' => '0', 'length' => 2), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'ARO_ACO_KEY' => array('column' => array('aro_id', 'aco_id'), 'unique' => 1)) - ); + public $aros = [ + 'id' => ['type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'], + 'parent_id' => ['type' => 'integer', 'null' => true, 'default' => null, 'length' => 10], + 'model' => ['type' => 'string', 'null' => true], + 'foreign_key' => ['type' => 'integer', 'null' => true, 'default' => null, 'length' => 10], + 'alias' => ['type' => 'string', 'null' => true], + 'lft' => ['type' => 'integer', 'null' => true, 'default' => null, 'length' => 10], + 'rght' => ['type' => 'integer', 'null' => true, 'default' => null, 'length' => 10], + 'indexes' => ['PRIMARY' => ['column' => 'id', 'unique' => 1]] + ]; + public $aros_acos = [ + 'id' => ['type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'], + 'aro_id' => ['type' => 'integer', 'null' => false, 'length' => 10, 'key' => 'index'], + 'aco_id' => ['type' => 'integer', 'null' => false, 'length' => 10], + '_create' => ['type' => 'string', 'null' => false, 'default' => '0', 'length' => 2], + '_read' => ['type' => 'string', 'null' => false, 'default' => '0', 'length' => 2], + '_update' => ['type' => 'string', 'null' => false, 'default' => '0', 'length' => 2], + '_delete' => ['type' => 'string', 'null' => false, 'default' => '0', 'length' => 2], + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unique' => 1], + 'ARO_ACO_KEY' => ['column' => ['aro_id', 'aco_id'], 'unique' => 1] + ] + ]; } diff --git a/app/Config/Schema/i18n.php b/app/Config/Schema/i18n.php index febb2db..73a7d23 100644 --- a/app/Config/Schema/i18n.php +++ b/app/Config/Schema/i18n.php @@ -28,23 +28,28 @@ */ class I18nSchema extends CakeSchema { - public $name = 'i18n'; + public $name = 'i18n'; - public function before($event = array()) { - return true; - } + public function before($event = []) { + return true; + } - public function after($event = array()) { - } - - public $i18n = array( - 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'), - 'locale' => array('type' => 'string', 'null' => false, 'length' => 6, 'key' => 'index'), - 'model' => array('type' => 'string', 'null' => false, 'key' => 'index'), - 'foreign_key' => array('type' => 'integer', 'null' => false, 'length' => 10, 'key' => 'index'), - 'field' => array('type' => 'string', 'null' => false, 'key' => 'index'), - 'content' => array('type' => 'text', 'null' => true, 'default' => null), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1), 'locale' => array('column' => 'locale', 'unique' => 0), 'model' => array('column' => 'model', 'unique' => 0), 'row_id' => array('column' => 'foreign_key', 'unique' => 0), 'field' => array('column' => 'field', 'unique' => 0)) - ); + public function after($event = []) { + } + public $i18n = [ + 'id' => ['type' => 'integer', 'null' => false, 'default' => null, 'length' => 10, 'key' => 'primary'], + 'locale' => ['type' => 'string', 'null' => false, 'length' => 6, 'key' => 'index'], + 'model' => ['type' => 'string', 'null' => false, 'key' => 'index'], + 'foreign_key' => ['type' => 'integer', 'null' => false, 'length' => 10, 'key' => 'index'], + 'field' => ['type' => 'string', 'null' => false, 'key' => 'index'], + 'content' => ['type' => 'text', 'null' => true, 'default' => null], + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unique' => 1], + 'locale' => ['column' => 'locale', 'unique' => 0], + 'model' => ['column' => 'model', 'unique' => 0], + 'row_id' => ['column' => 'foreign_key', 'unique' => 0], + 'field' => ['column' => 'field', 'unique' => 0] + ] + ]; } diff --git a/app/Config/Schema/schema.php b/app/Config/Schema/schema.php index f6f8857..28f5772 100644 --- a/app/Config/Schema/schema.php +++ b/app/Config/Schema/schema.php @@ -2,554 +2,555 @@ App::uses('ClassRegistry', 'Utility'); class AppSchema extends CakeSchema { - public $config = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'key' => [ - 'type' => 'string', - 'null' => false, - ], - 'value' => [ - 'type' => 'text', - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $users = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'username' => [ - 'type' => 'string', - 'null' => false, - ], - 'password' => [ - 'type' => 'string', - 'null' => false, - ], - 'group_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'active' => [ - 'type' => 'boolean', - 'null' => false, - 'default' => false, - ], - 'expiration' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unique' => true], - ], - ]; - - public $groups = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'name' => [ - 'type' => 'string', - 'null' => false, - ], - 'team_number' => [ - 'type' => 'integer', - 'null' => true, - 'default' => null, - ], - 'parent_id' => [ - 'type' => 'integer', - 'null' => true, - 'default' => null, - ], - 'lft' => [ - 'type' => 'integer', - 'null' => true, - 'default' => null, - ], - 'rght' => [ - 'type' => 'integer', - 'null' => true, - 'default' => null, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $injects = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'sequence' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - 'title' => [ - 'type' => 'string', - 'null' => false, - ], - 'content' => [ - 'type' => 'text', - 'null' => false, - ], - 'from_name' => [ - 'type' => 'string', - 'null' => false, - ], - 'from_email' => [ - 'type' => 'string', - 'null' => false, - ], - 'grading_guide' => [ - 'type' => 'text', - 'null' => false, - 'default' => '', - ], - 'max_points' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - 'max_submissions' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 1, - ], - 'type' => [ - 'type' => 'string', - 'null' => false, - 'default' => 'noop', - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $schedules = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'inject_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'group_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'dependency_id' => [ - 'type' => 'integer', - 'null' => true, - 'default' => null, - ], - 'start' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - 'end' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - 'fuzzy' => [ - 'type' => 'boolean', - 'null' => false, - 'default' => false, - ], - 'active' => [ - 'type' => 'boolean', - 'null' => false, - 'default' => false, - ], - 'order' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $submissions = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'inject_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'user_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'group_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'created' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - 'data' => [ - 'type' => 'binary', - 'null' => false, - ], - 'deleted' => [ - 'type' => 'boolean', - 'null' => false, - 'default' => false, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $grades = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'submission_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'grader_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'created' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - 'grade' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - 'comments' => [ - 'type' => 'text', - 'null' => false, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $hints = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'inject_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'parent_id' => [ - 'type' => 'integer', - 'null' => true, - 'default' => null, - ], - 'title' => [ - 'type' => 'string', - 'null' => false, - ], - 'content' => [ - 'type' => 'text', - 'null' => false, - ], - 'time_wait' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - 'cost' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $used_hints = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'hint_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'user_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'group_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'time' => [ - 'type' => 'integer', - 'null' => false, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $announcements = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'content' => [ - 'type' => 'text', - 'null' => false, - ], - 'active' => [ - 'type' => 'boolean', - 'null' => false, - 'default' => false, - ], - 'expiration' => [ - 'type' => 'integer', - 'null' => false, - 'default' => 0, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $logs = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'time' => [ - 'type' => 'integer', - 'null' => false, - ], - 'type' => [ - 'type' => 'string', - 'null' => false, - ], - 'user_id' => [ - 'type' => 'integer', - ], - 'related_id' => [ - 'type' => 'integer', - ], - 'data' => [ - 'type' => 'text', - ], - 'ip' => [ - 'type' => 'string', - 'length' => 15, - ], - 'message' => [ - 'type' => 'string', - 'null' => false, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - public $attachments = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'inject_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'name' => [ - 'type' => 'string', - 'null' => false, - ], - 'data' => [ - 'type' => 'binary', - 'null' => false, - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - // ================================ - - public function before($event = array()) { - ConnectionManager::getDataSource('default')->cacheSources = false; - - return true; - } - - public function after($event = array()) { - switch ( true ) { - case (isset($event['create'])): - $this->_handleCreate($event); - break; - - case (isset($event['update'])): - $this->_handleUpdate($event); - break; - } - } - - private function _handleCreate($event) { - switch ( $event['create'] ) { - case 'config': - $this->_create('Config', [ - 'key' => 'homepage.title', - 'value' => 'Hello, World', - ]); - $this->_create('Config', [ - 'key' => 'homepage.body', - 'value' => '

It works!

', - ]); - - $this->_create('Config', [ - 'key' => 'competition.start', - 'value' => time(), - ]); - - $this->_create('Config', [ - 'key' => 'competition.sponsors', - 'value' => '[]', - ]); - - $this->_create('Config', [ - 'key' => 'engine.install_date', - 'value' => time(), - ]); - - // Default inject types built in. - // THIS MUST MAP TO A FILE INSIDE: - // app/Lib/InjectTypes/.php - $this->_create('Config', [ - 'key' => 'engine.inject_types', - 'value' => json_encode( - [ - 'FileSubmission', 'TextSubmission', 'ManualCheck', - 'FlagSubmission', 'NoOpSubmission', - ] - ), - ]); - break; - - case 'injects': - $this->_create('Inject', [ - 'title' => 'Learn about the InjectEngine', - 'content' => '

Maybe check the wiki?

', - 'from_name' => 'James Droste', - 'from_email' => 'ubnetdef@buffalo.edu', - 'grading_guide' => '

You\'ll know when you got this.

', - 'max_points' => 100, - 'type' => 'noop', - ]); - break; - - case 'schedules': - $this->_create('Schedule', [ - 'inject_id' => 1, - 'group_id' => env('GROUP_STAFF'), - 'active' => true, - ]); - break; - - case 'announcements': - $this->_create('Announcement', array( - 'content' => 'ie2 was just installed. Go configure it!', - 'active' => true, - 'expiration' => 0, - )); - break; - - case 'submissions': - // We have to change BLOB -> LONGBLOB - ClassRegistry::init('submissions')->query('ALTER TABLE submissions MODIFY data LONGBLOB'); - break; - - case 'attachments': - // We have to change BLOB -> LONGBLOB - ClassRegistry::init('attachments')->query('ALTER TABLE attachments MODIFY data LONGBLOB'); - break; - - case 'logs': - $this->_create('Log', [ - 'time' => time(), - 'type' => 'general', - 'user_id' => 1, - 'data' => json_encode([]), - 'ip' => '127.0.0.1', - 'message' => 'InjectEngine was just installed.', - ]); - break; - } - } - - private function _handleUpdate($event) { - switch ( $event['update'] ) { - case 'submissions': - // We have to change BLOB -> LONGBLOB - ClassRegistry::init('submissions')->query('ALTER TABLE submissions MODIFY data LONGBLOB'); - break; - - case 'attachments': - // We have to change BLOB -> LONGBLOB - ClassRegistry::init('attachments')->query('ALTER TABLE attachments MODIFY data LONGBLOB'); - break; - } - } - - private function _create($tbl, $data) { - $table = ClassRegistry::init($tbl); - - $table->create(); - $table->save(array($tbl => $data)); - } -} \ No newline at end of file + + public $config = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'key' => [ + 'type' => 'string', + 'null' => false, + ], + 'value' => [ + 'type' => 'text', + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $users = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'username' => [ + 'type' => 'string', + 'null' => false, + ], + 'password' => [ + 'type' => 'string', + 'null' => false, + ], + 'group_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'active' => [ + 'type' => 'boolean', + 'null' => false, + 'default' => false, + ], + 'expiration' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unique' => true], + ], + ]; + + public $groups = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'name' => [ + 'type' => 'string', + 'null' => false, + ], + 'team_number' => [ + 'type' => 'integer', + 'null' => true, + 'default' => null, + ], + 'parent_id' => [ + 'type' => 'integer', + 'null' => true, + 'default' => null, + ], + 'lft' => [ + 'type' => 'integer', + 'null' => true, + 'default' => null, + ], + 'rght' => [ + 'type' => 'integer', + 'null' => true, + 'default' => null, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $injects = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'sequence' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + 'title' => [ + 'type' => 'string', + 'null' => false, + ], + 'content' => [ + 'type' => 'text', + 'null' => false, + ], + 'from_name' => [ + 'type' => 'string', + 'null' => false, + ], + 'from_email' => [ + 'type' => 'string', + 'null' => false, + ], + 'grading_guide' => [ + 'type' => 'text', + 'null' => false, + 'default' => '', + ], + 'max_points' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + 'max_submissions' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 1, + ], + 'type' => [ + 'type' => 'string', + 'null' => false, + 'default' => 'noop', + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $schedules = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'inject_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'group_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'dependency_id' => [ + 'type' => 'integer', + 'null' => true, + 'default' => null, + ], + 'start' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + 'end' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + 'fuzzy' => [ + 'type' => 'boolean', + 'null' => false, + 'default' => false, + ], + 'active' => [ + 'type' => 'boolean', + 'null' => false, + 'default' => false, + ], + 'order' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $submissions = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'inject_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'user_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'group_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'created' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + 'data' => [ + 'type' => 'binary', + 'null' => false, + ], + 'deleted' => [ + 'type' => 'boolean', + 'null' => false, + 'default' => false, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $grades = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'submission_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'grader_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'created' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + 'grade' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + 'comments' => [ + 'type' => 'text', + 'null' => false, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $hints = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'inject_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'parent_id' => [ + 'type' => 'integer', + 'null' => true, + 'default' => null, + ], + 'title' => [ + 'type' => 'string', + 'null' => false, + ], + 'content' => [ + 'type' => 'text', + 'null' => false, + ], + 'time_wait' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + 'cost' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $used_hints = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'hint_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'user_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'group_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'time' => [ + 'type' => 'integer', + 'null' => false, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $announcements = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'content' => [ + 'type' => 'text', + 'null' => false, + ], + 'active' => [ + 'type' => 'boolean', + 'null' => false, + 'default' => false, + ], + 'expiration' => [ + 'type' => 'integer', + 'null' => false, + 'default' => 0, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $logs = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'time' => [ + 'type' => 'integer', + 'null' => false, + ], + 'type' => [ + 'type' => 'string', + 'null' => false, + ], + 'user_id' => [ + 'type' => 'integer', + ], + 'related_id' => [ + 'type' => 'integer', + ], + 'data' => [ + 'type' => 'text', + ], + 'ip' => [ + 'type' => 'string', + 'length' => 15, + ], + 'message' => [ + 'type' => 'string', + 'null' => false, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + public $attachments = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'inject_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'name' => [ + 'type' => 'string', + 'null' => false, + ], + 'data' => [ + 'type' => 'binary', + 'null' => false, + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + // ================================ + + public function before($event = []) { + ConnectionManager::getDataSource('default')->cacheSources = false; + + return true; + } + + public function after($event = []) { + switch (true) { + case (isset($event['create'])): + $this->handleCreate($event); + break; + + case (isset($event['update'])): + $this->handleUpdate($event); + break; + } + } + + private function handleCreate($event) { + switch ($event['create']) { + case 'config': + $this->create('Config', [ + 'key' => 'homepage.title', + 'value' => 'Hello, World', + ]); + $this->create('Config', [ + 'key' => 'homepage.body', + 'value' => '

It works!

', + ]); + + $this->create('Config', [ + 'key' => 'competition.start', + 'value' => time(), + ]); + + $this->create('Config', [ + 'key' => 'competition.sponsors', + 'value' => '[]', + ]); + + $this->create('Config', [ + 'key' => 'engine.install_date', + 'value' => time(), + ]); + + // Default inject types built in. + // THIS MUST MAP TO A FILE INSIDE: + // app/Lib/InjectTypes/.php + $this->create('Config', [ + 'key' => 'engine.inject_types', + 'value' => json_encode( + [ + 'FileSubmission', 'TextSubmission', 'ManualCheck', + 'FlagSubmission', 'NoOpSubmission', + ] + ), + ]); + break; + + case 'injects': + $this->create('Inject', [ + 'title' => 'Learn about the InjectEngine', + 'content' => '

Maybe check the wiki?

', + 'from_name' => 'James Droste', + 'from_email' => 'ubnetdef@buffalo.edu', + 'grading_guide' => '

You\'ll know when you got this.

', + 'max_points' => 100, + 'type' => 'noop', + ]); + break; + + case 'schedules': + $this->create('Schedule', [ + 'inject_id' => 1, + 'group_id' => env('GROUP_STAFF'), + 'active' => true, + ]); + break; + + case 'announcements': + $this->create('Announcement', [ + 'content' => 'ie2 was just installed. Go configure it!', + 'active' => true, + 'expiration' => 0, + ]); + break; + + case 'submissions': + // We have to change BLOB -> LONGBLOB + ClassRegistry::init('submissions')->query('ALTER TABLE submissions MODIFY data LONGBLOB'); + break; + + case 'attachments': + // We have to change BLOB -> LONGBLOB + ClassRegistry::init('attachments')->query('ALTER TABLE attachments MODIFY data LONGBLOB'); + break; + + case 'logs': + $this->create('Log', [ + 'time' => time(), + 'type' => 'general', + 'user_id' => 1, + 'data' => json_encode([]), + 'ip' => '127.0.0.1', + 'message' => 'InjectEngine was just installed.', + ]); + break; + } + } + + private function handleUpdate($event) { + switch ($event['update']) { + case 'submissions': + // We have to change BLOB -> LONGBLOB + ClassRegistry::init('submissions')->query('ALTER TABLE submissions MODIFY data LONGBLOB'); + break; + + case 'attachments': + // We have to change BLOB -> LONGBLOB + ClassRegistry::init('attachments')->query('ALTER TABLE attachments MODIFY data LONGBLOB'); + break; + } + } + + private function create($tbl, $data) { + $table = ClassRegistry::init($tbl); + + $table->create(); + $table->save([$tbl => $data]); + } +} diff --git a/app/Config/Schema/sessions.php b/app/Config/Schema/sessions.php index 14ff2c6..983aa39 100644 --- a/app/Config/Schema/sessions.php +++ b/app/Config/Schema/sessions.php @@ -26,20 +26,19 @@ */ class SessionsSchema extends CakeSchema { - public $name = 'Sessions'; + public $name = 'Sessions'; - public function before($event = array()) { - return true; - } + public function before($event = []) { + return true; + } - public function after($event = array()) { - } - - public $cake_sessions = array( - 'id' => array('type' => 'string', 'null' => false, 'key' => 'primary'), - 'data' => array('type' => 'text', 'null' => true, 'default' => null), - 'expires' => array('type' => 'integer', 'null' => true, 'default' => null), - 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) - ); + public function after($event = []) { + } + public $cake_sessions = [ + 'id' => ['type' => 'string', 'null' => false, 'key' => 'primary'], + 'data' => ['type' => 'text', 'null' => true, 'default' => null], + 'expires' => ['type' => 'integer', 'null' => true, 'default' => null], + 'indexes' => ['PRIMARY' => ['column' => 'id', 'unique' => 1]] + ]; } diff --git a/app/Config/bootstrap.php b/app/Config/bootstrap.php index 54b2502..6bfcb59 100644 --- a/app/Config/bootstrap.php +++ b/app/Config/bootstrap.php @@ -25,11 +25,11 @@ */ App::build( - [ - 'Plugin' => [ROOT . '/Plugin/', ROOT . '/app/Plugin/'], - 'Vendor' => [ROOT . '/vendor/', ROOT . '/app/Vendor/'] - ], - App::RESET + [ + 'Plugin' => [ROOT . '/Plugin/', ROOT . '/app/Plugin/'], + 'Vendor' => [ROOT . '/vendor/', ROOT . '/app/Vendor/'] + ], + App::RESET ); /** @@ -59,8 +59,8 @@ */ /** - * Custom Inflector rules, can be set to correctly pluralize or singularize table, model, controller names or whatever other - * string is passed to the inflection functions + * Custom Inflector rules, can be set to correctly pluralize or singularize table, model, + * controller names or whatever other string is passed to the inflection functions * * Inflector::rules('singular', array('rules' => array(), 'irregular' => array(), 'uninflected' => array())); * Inflector::rules('plural', array('rules' => array(), 'irregular' => array(), 'uninflected' => array())); @@ -79,48 +79,53 @@ App::uses('CakeEventManager', 'Event'); if (php_sapi_name() !== 'cli' && Configure::read('debug') > 0 && in_array('DebugKit', App::objects('plugin'))) { - CakePlugin::load('DebugKit'); + CakePlugin::load('DebugKit'); - // Auto attack DebugKit's toolbar on every controller - CakeEventManager::instance()->attach(function ($event) { - $controller = $event->subject(); + // Auto attack DebugKit's toolbar on every controller + CakeEventManager::instance()->attach(function ($event) { + $controller = $event->subject(); - $controller->Toolbar = $controller->Components->load( - 'DebugKit.Toolbar' - ); - }, 'Controller.initialize'); + $controller->Toolbar = $controller->Components->load( + 'DebugKit.Toolbar' + ); + }, 'Controller.initialize'); } // Dynamically load ScoreEngine plugin -if ( Configure::read('ie.feature.scoreengine') ) { - CakePlugin::load('ScoreEngine', ['routes' => true]); +if (Configure::read('ie.feature.scoreengine')) { + CakePlugin::load('ScoreEngine', ['routes' => true]); } // Dynamically load BankWeb plugin -if ( Configure::read('ie.feature.bankweb') ) { - CakePlugin::load('BankWeb', ['routes' => true]); +if (Configure::read('ie.feature.bankweb')) { + CakePlugin::load('BankWeb', ['routes' => true]); } // Admin Plugin CakePlugin::load('Admin'); /** - * You can attach event listeners to the request lifecycle as Dispatcher Filter . By Default CakePHP bundles two filters: + * You can attach event listeners to the request lifecycle as Dispatcher Filter . By Default + * CakePHP bundles two filters: * - * - AssetDispatcher filter will serve your asset files (css, images, js, etc) from your themes and plugins - * - CacheDispatcher filter will read the Cache.check configure variable and try to serve cached content generated from controllers + * - AssetDispatcher filter will serve your asset files (css, images, js, etc) from your + * themes and plugins + * - CacheDispatcher filter will read the Cache.check configure variable and try to serve cached + * content generated from controllers * * Feel free to remove or add filters as you see fit for your application. A few examples: * * Configure::write('Dispatcher.filters', array( - * 'MyCacheFilter', // will use MyCacheFilter class from the Routing/Filter package in your app. - * 'MyPlugin.MyFilter', // will use MyFilter class from the Routing/Filter package in MyPlugin plugin. - * array('callable' => $aFunction, 'on' => 'before', 'priority' => 9), // A valid PHP callback type to be called on beforeDispatch - * array('callable' => $anotherMethod, 'on' => 'after'), // A valid PHP callback type to be called on afterDispatch + * 'MyCacheFilter', // will use MyCacheFilter class from the Routing/Filter package in your app. + * 'MyPlugin.MyFilter', // will use MyFilter class from the Routing/Filter package in MyPlugin plugin. + * + * // A valid PHP callback type to be called on beforeDispatch + * array('callable' => $aFunction, 'on' => 'before', 'priority' => 9), + * array('callable' => $anotherMethod, 'on' => 'after'), // A valid PHP callback type to be called on afterDispatch * * )); */ Configure::write('Dispatcher.filters', [ - 'AssetDispatcher', - 'CacheDispatcher' + 'AssetDispatcher', + 'CacheDispatcher' ]); diff --git a/app/Config/core.php b/app/Config/core.php index 5e8cb25..1dea8fd 100644 --- a/app/Config/core.php +++ b/app/Config/core.php @@ -5,10 +5,10 @@ // Include the Composer autoloader // This does not use `App::import()` because `App::build()` // is called *after* `core.php` has been loaded -include (ROOT . DS . 'vendor' . DS . 'autoload.php'); +include(ROOT . DS . 'vendor' . DS . 'autoload.php'); // Include global functions -include (ROOT . DS . APP_DIR . DS . 'Config' . DS . 'functions.php'); +include(ROOT . DS . APP_DIR . DS . 'Config' . DS . 'functions.php'); // Remove and re-prepend CakePHP's autoloader as composer thinks it is the most important. // See https://github.com/composer/composer/commit/c80cb76b9b5082ecc3e5b53b1050f76bb27b127b @@ -17,15 +17,15 @@ // Specify the APP_NAME environment variable to skip .env file loading if (!env('APP_NAME')) { - // If there's a problem loading the .env file - load .env.default - // That means the code can assume appropriate env config always exists - // Don't trap this incase there's some other fundamental error - josegonzalez\Dotenv\Loader::load([ - 'filepath' => ROOT . DS . '.env', - 'toServer' => false, - 'skipExisting' => ['toServer'], - 'raiseExceptions' => true - ]); + // If there's a problem loading the .env file - load .env.default + // That means the code can assume appropriate env config always exists + // Don't trap this incase there's some other fundamental error + josegonzalez\Dotenv\Loader::load([ + 'filepath' => ROOT . DS . '.env', + 'toServer' => false, + 'skipExisting' => ['toServer'], + 'raiseExceptions' => true + ]); } /** @@ -34,23 +34,23 @@ * This is only really used in `bootstrap.php`, as that file does not * have the `env` function loaded at that time. */ - Configure::write('ie.feature.scoreengine', (bool)env('FEATURE_SCOREENGINE')); - Configure::write('ie.feature.bankweb', (bool)env('FEATURE_BANKWEB')); + Configure::write('ie.feature.scoreengine', (bool)env('FEATURE_SCOREENGINE')); + Configure::write('ie.feature.bankweb', (bool)env('FEATURE_BANKWEB')); /** * CakePHP Debug Level: * * Production Mode: - * 0: No error messages, errors, or warnings shown. Flash messages redirect. + * 0: No error messages, errors, or warnings shown. Flash messages redirect. * * Development Mode: - * 1: Errors and warnings shown, model caches refreshed, flash messages halted. - * 2: As in 1, but also with full debug messages and SQL output. + * 1: Errors and warnings shown, model caches refreshed, flash messages halted. + * 2: As in 1, but also with full debug messages and SQL output. * * In production mode, flash messages redirect after a time interval. * In development mode, you need to click the flash message to continue. */ - Configure::write('debug', (int)env('DEBUG')); + Configure::write('debug', (int)env('DEBUG')); /** * Configure the Error handler used to handle errors for your application. By default @@ -67,11 +67,11 @@ * * @see ErrorHandler for more information on error handling and configuration. */ - Configure::write('Error', [ - 'handler' => 'ErrorHandler::handleError', - 'level' => E_ALL & ~E_DEPRECATED, - 'trace' => true - ]); + Configure::write('Error', [ + 'handler' => 'ErrorHandler::handleError', + 'level' => E_ALL & ~E_DEPRECATED, + 'trace' => true + ]); /** * Configure the Exception handler used for uncaught exceptions. By default, @@ -90,16 +90,16 @@ * * @see ErrorHandler for more information on exception handling and configuration. */ - Configure::write('Exception', [ - 'handler' => 'ErrorHandler::handleException', - 'renderer' => 'ExceptionRenderer', - 'log' => true - ]); + Configure::write('Exception', [ + 'handler' => 'ErrorHandler::handleException', + 'renderer' => 'ExceptionRenderer', + 'log' => true + ]); /** * Application wide charset encoding */ - Configure::write('App.encoding', 'UTF-8'); + Configure::write('App.encoding', 'UTF-8'); /** * To configure CakePHP *not* to use mod_rewrite and to @@ -120,7 +120,7 @@ * included primarily as a development convenience - and * thus not recommended for production applications. */ - //Configure::write('App.baseUrl', env('SCRIPT_NAME')); + //Configure::write('App.baseUrl', env('SCRIPT_NAME')); /** * Uncomment the define below to use CakePHP prefix routes. @@ -131,20 +131,20 @@ * Set to an array of prefixes you want to use in your application. Use for * admin or other prefixed routes. * - * Routing.prefixes = array('admin', 'manager'); + * Routing.prefixes = array('admin', 'manager'); * * Enables: - * `admin_index()` and `/admin/controller/index` - * `manager_index()` and `/manager/controller/index` + * `admin_index()` and `/admin/controller/index` + * `manager_index()` and `/manager/controller/index` * */ - //Configure::write('Routing.prefixes', ['admin', 'staff']); + //Configure::write('Routing.prefixes', ['admin', 'staff']); /** * Turn off all caching application-wide. * */ - //Configure::write('Cache.disable', true); + //Configure::write('Cache.disable', true); /** * Enable cache checking. @@ -155,7 +155,7 @@ * or in each action using $this->cacheAction = true. * */ - //Configure::write('Cache.check', true); + //Configure::write('Cache.check', true); /** * Enable cache view prefixes. @@ -165,7 +165,7 @@ * for instance. Each version can then have its own view cache namespace. * Note: The final cache file name will then be `prefix_cachefilename`. */ - //Configure::write('Cache.viewPrefix', 'prefix'); + //Configure::write('Cache.viewPrefix', 'prefix'); /** * Session configuration. @@ -204,22 +204,22 @@ * the cake shell command: cake schema create Sessions * */ - Configure::write('Session', [ - 'defaults' => 'php', - 'cookie' => env('APP_NAME'), - 'cookieTimeout' => env('SESSION_COOKIE_TIMEOUT'), - 'autoRegenerate' => true, - ]); + Configure::write('Session', [ + 'defaults' => 'php', + 'cookie' => env('APP_NAME'), + 'cookieTimeout' => env('SESSION_COOKIE_TIMEOUT'), + 'autoRegenerate' => true, + ]); /** * A random string used in security hashing methods. */ - Configure::write('Security.salt', env('SECURITY_SALT')); + Configure::write('Security.salt', env('SECURITY_SALT')); /** * A random numeric string (digits only) used to encrypt/decrypt strings. */ - Configure::write('Security.cipherSeed', env('SECURITY_CIPHER_SEED')); + Configure::write('Security.cipherSeed', env('SECURITY_CIPHER_SEED')); /** * Apply timestamps with the last modified time to static assets (js, css, images). @@ -229,7 +229,7 @@ * Set to `true` to apply timestamps when debug > 0. Set to 'force' to always enable * timestamping regardless of debug value. */ - //Configure::write('Asset.timestamp', true); + //Configure::write('Asset.timestamp', true); /** * Compress CSS output by removing comments, whitespace, repeating tags, etc. @@ -238,7 +238,7 @@ * * To use, prefix the CSS link URL with '/ccss/' instead of '/css/' or use HtmlHelper::css(). */ - //Configure::write('Asset.filter.css', 'css.php'); + //Configure::write('Asset.filter.css', 'css.php'); /** * Plug in your own custom JavaScript compressor by dropping a script in your webroot to handle the @@ -246,32 +246,32 @@ * * To use, prefix your JavaScript link URLs with '/cjs/' instead of '/js/' or use JavaScriptHelper::link(). */ - //Configure::write('Asset.filter.js', 'custom_javascript_output_filter.php'); + //Configure::write('Asset.filter.js', 'custom_javascript_output_filter.php'); /** * The class name and database used in CakePHP's * access control lists. */ - Configure::write('Acl.classname', 'DbAcl'); - Configure::write('Acl.database', 'default'); + Configure::write('Acl.classname', 'DbAcl'); + Configure::write('Acl.database', 'default'); /** * Uncomment this line and correct your server timezone to fix * any date & time related errors. */ - date_default_timezone_set('UTC'); + date_default_timezone_set('UTC'); /** * Configure Cache from environment variables */ - Cache::config('default', CacheDsn::parse(env('CACHE_URL'))); - Cache::config('debug_kit', CacheDsn::parse(env('CACHE_DEBUG_KIT_URL'))); - Cache::config('_cake_core_', CacheDsn::parse(env('CACHE_CAKE_CORE_URL'))); - Cache::config('_cake_model_', CacheDsn::parse(env('CACHE_CAKE_MODEL_URL'))); + Cache::config('default', CacheDsn::parse(env('CACHE_URL'))); + Cache::config('debug_kit', CacheDsn::parse(env('CACHE_DEBUG_KIT_URL'))); + Cache::config('_cake_core_', CacheDsn::parse(env('CACHE_CAKE_CORE_URL'))); + Cache::config('_cake_model_', CacheDsn::parse(env('CACHE_CAKE_MODEL_URL'))); /** * Configure logs from environment variables */ - App::uses('CakeLog', 'Log'); - CakeLog::config('default', LogDsn::parse(env('LOG_URL'))); - CakeLog::config('error', LogDsn::parse(env('LOG_ERROR_URL'))); + App::uses('CakeLog', 'Log'); + CakeLog::config('default', LogDsn::parse(env('LOG_URL'))); + CakeLog::config('error', LogDsn::parse(env('LOG_ERROR_URL'))); diff --git a/app/Config/database.php b/app/Config/database.php index c82add0..276591f 100644 --- a/app/Config/database.php +++ b/app/Config/database.php @@ -2,9 +2,9 @@ use AD7six\Dsn\Wrapper\CakePHP\V2\DbDsn; class DATABASE_CONFIG { - public function __construct() { - $this->default = DbDsn::parse(env('INJECTENGINE_DB')); - $this->scoreengine = DbDsn::parse(env('SCOREENGINE_DB')); - $this->test = DbDsn::parse(env('DATABASE_TEST_URL')); - } + public function __construct() { + $this->default = DbDsn::parse(env('INJECTENGINE_DB')); + $this->scoreengine = DbDsn::parse(env('SCOREENGINE_DB')); + $this->test = DbDsn::parse(env('DATABASE_TEST_URL')); + } } diff --git a/app/Config/email.php b/app/Config/email.php index b144d4b..81aefa9 100644 --- a/app/Config/email.php +++ b/app/Config/email.php @@ -2,9 +2,9 @@ use AD7six\Dsn\Wrapper\CakePHP\V2\EmailDsn; class EmailConfig { - public function __construct() { - $this->default = EmailDsn::parse(env('EMAIL_URL')); - $this->smtp = EmailDsn::parse(env('EMAIL_SMTP_URL')); - $this->fast = EmailDsn::parse(env('EMAIL_FAST_URL')); - } + public function __construct() { + $this->default = EmailDsn::parse(env('EMAIL_URL')); + $this->smtp = EmailDsn::parse(env('EMAIL_SMTP_URL')); + $this->fast = EmailDsn::parse(env('EMAIL_FAST_URL')); + } } diff --git a/app/Config/functions.php b/app/Config/functions.php index 996fb1c..2030fd9 100644 --- a/app/Config/functions.php +++ b/app/Config/functions.php @@ -7,7 +7,7 @@ * @return str The encoded string */ function encode_quotes($str) { - return str_replace(['"', "'", '&'], ['"', ''', '&'], $str); + return str_replace(['"', "'", '&'], ['"', ''', '&'], $str); } /** @@ -19,8 +19,8 @@ function encode_quotes($str) { * @return string The formatted time from the timestamp */ function tz_date($format, $ts) { - $date = new DateTime('@'.$ts); - $date->setTimezone(new DateTimeZone(env('TIMEZONE_USER'))); + $date = new DateTime('@'.$ts); + $date->setTimezone(new DateTimeZone(env('TIMEZONE_USER'))); - return $date->format($format); -} \ No newline at end of file + return $date->format($format); +} diff --git a/app/Config/routes.php b/app/Config/routes.php index 6091907..f3db0df 100644 --- a/app/Config/routes.php +++ b/app/Config/routes.php @@ -26,16 +26,16 @@ * its action called 'display', and we pass a param to select the view file * to use (in this case, /app/View/Pages/home.ctp)... */ - Router::connect('/', ['controller' => 'pages', 'action' => 'index']); + Router::connect('/', ['controller' => 'pages', 'action' => 'index']); /** * Load all plugin routes. See the CakePlugin documentation on * how to customize the loading of plugin routes. */ - CakePlugin::routes(); + CakePlugin::routes(); /** * Load the CakePHP default routes. Only remove this if you do not want to use * the built-in default routes. */ - require CAKE . 'Config' . DS . 'routes.php'; + require CAKE . 'Config' . DS . 'routes.php'; diff --git a/app/Console/Command/EngineShell.php b/app/Console/Command/EngineShell.php index b533548..9321abc 100644 --- a/app/Console/Command/EngineShell.php +++ b/app/Console/Command/EngineShell.php @@ -8,286 +8,297 @@ * for the InjectEngine. Here be (some) dragons. */ class EngineShell extends AppShell { - public $uses = ['Group', 'User']; - /** - * Default groups to create on - * a fresh install - */ - private $groups = [ - [ - 'parent' => 'root', - 'name' => 'Staff', - ], - [ - 'parent' => 'root', - 'name' => 'Blue Teams', - ], - [ - 'parent' => 'Staff', - 'name' => 'Administrative Team', - ], - [ - 'parent' => 'Staff', - 'name' => 'White Team', - ], - ]; - - /** - * Default users to create on - * a fresh install - */ - private $users = [ - [ - 'userpass' => 'admin', - 'group' => 'Administrative Team', - ], - [ - 'userpass' => 'team0', - 'group' => 'Team 0', - ] - ]; - - /** - * Engine Command: Install - * - * Wipe's the current InjectEngine install, and re-initializes - * the database, as well as users. - */ - public function install() { - $this->out('Installing ie2'); - $this->hr(); - - $this->out('Initializing the database.......', 0); - - // Initialize our database - $this->dispatchShell('schema', 'create', '--yes', '--quiet'); - - // Create the basic groups - foreach ( $this->groups AS $g ) { - $this->dispatchShell('engine', 'create_group', $g['parent'], $g['name'], '--quiet', '--yes'); - } - $this->dispatchShell('engine', 'create_group', 'Blue Teams', 'Team 0', '--quiet', '--yes', '-t', '0'); - - // DB initialized - $this->out('DONE!'); - - if ( (bool)env('FEATURE_BANKWEB') ) { - $this->out('Initializing BankWeb database...', 0); - - $this->dispatchShell('engine', 'install_bankweb', '--quiet'); - - $this->out('DONE!'); - } - - // Create some users - $this->out('Creating the initial users......', 0); - foreach ( $this->users AS $u ) { - $this->dispatchShell('engine', 'create_user', $u['userpass'], $u['group'], '--quiet', '--yes', '--password', $u['userpass']); - } - $this->out('DONE!'); - - // Done! - $this->out('Installation completed!'); - $this->hr(); - - $this->out('
User Credentials
'); - foreach ( $this->users AS $u ) { - $this->out('Username: '.$u['userpass']); - $this->out('Password: '.$u['userpass']); - $this->out(); - } - } - - /** - * Engine Command: Install BankWeb - * - * Basically runs schema create for the BankWeb - * plugin. Pretty wrapper. - */ - public function install_bankweb() { - $this->out('Installing BankWeb'); - $this->hr(); - - $this->out('Initializing the database...', 0); - - // Initialize our database - $this->dispatchShell('schema', 'create', '--plugin', 'BankWeb', '--yes', '--quiet'); - - // DB initialized - $this->out('DONE!'); - - // Done! - $this->out('Installation completed!'); - } - - /** - * Engine Command: Create User - * - * Create's a user. Nothing more, nothing less. - */ - public function create_user() { - list($user, $group) = $this->args; - $yes = (isset($this->params['yes']) && !empty($this->params['yes'])); - - if ( !isset($this->params['password']) ) { - $pass = $this->in('Password: '); - $this->hr(); - } else { - $pass = $this->params['password']; - } - - // Check if the username exists - if ( !empty($this->User->findByUsername($user)) ) { - $this->error(sprintf('A user with the same username (%s) already exists!', $user)); - } - - $group_data = $this->Group->findByName($group); - if ( empty($group_data) ) { - $this->error(sprintf('Group "%s" not found', $group)); - } - - $this->out(sprintf('Creating user "%s" with group "%s"', $user, $group_data['Group']['name'])); - - if ( $yes || $this->in('Are you sure you want to create the user?', ['y', 'n'], 'y') == 'y' ) { - $this->hr(); - - // Create the user - $this->out('Creating the user...'); - $this->User->create(); - $this->User->save([ - 'username' => $user, - 'password' => $pass, - 'group_id' => $group_data['Group']['id'], - 'active' => true, - ]); - - $this->out(sprintf('User (%d) created!', $this->User->id)); - } - } - - /** - * Engine Command: Create Group - * - * Create's a group. Nothing more, nothing less. - */ - public function create_group() { - list($parent, $group) = $this->args; - $yes = (isset($this->params['yes']) && !empty($this->params['yes'])); - - // Check if the group exists - if ( !empty($this->Group->findByName($group)) ) { - $this->error(sprintf('Group "%s" already exists', $group)); - } - - // Check if our parent exists, if it's not root - $parent_id = null; - - if ( $parent != 'root' ) { - $data = $this->Group->findByName($parent); - - if ( empty($data) ) { - $this->error(sprintf('I was unable to find the parent group "%s"', $parent)); - } - - $parent = $data['Group']['name']; - $parent_id = $data['Group']['id']; - } - - $this->out(sprintf('Creating group "%s" under parent "%s"', $group, $parent)); - - if ( $yes || $this->in('Are you sure you want to create the group?', ['y', 'n'], 'y') == 'y' ) { - $this->hr(); - - // Create the group - $this->out('Creating the group...'); - $this->Group->create(); - $this->Group->save([ - 'name' => $group, - 'team_number' => (isset($this->params['team_number']) ? $this->params['team_number'] : null), - 'parent_id' => $parent_id, - ]); - - $this->out(sprintf('Group (%s) created!', $group)); - } - } - - /* + public $uses = ['Group', 'User']; + + /** + * Default groups to create on + * a fresh install + */ + private $groups = [ + [ + 'parent' => 'root', + 'name' => 'Staff', + ], + [ + 'parent' => 'root', + 'name' => 'Blue Teams', + ], + [ + 'parent' => 'Staff', + 'name' => 'Administrative Team', + ], + [ + 'parent' => 'Staff', + 'name' => 'White Team', + ], + ]; + + /** + * Default users to create on + * a fresh install + */ + private $users = [ + [ + 'userpass' => 'admin', + 'group' => 'Administrative Team', + ], + [ + 'userpass' => 'team0', + 'group' => 'Team 0', + ] + ]; + + /** + * Engine Command: Install + * + * Wipe's the current InjectEngine install, and re-initializes + * the database, as well as users. + */ + public function install() { + $this->out('Installing ie2'); + $this->hr(); + + $this->out('Initializing the database.......', 0); + + // Initialize our database + $this->dispatchShell('schema', 'create', '--yes', '--quiet'); + + // Create the basic groups + foreach ($this->groups as $g) { + $this->dispatchShell('engine', 'create_group', $g['parent'], $g['name'], '--quiet', '--yes'); + } + $this->dispatchShell('engine', 'create_group', 'Blue Teams', 'Team 0', '--quiet', '--yes', '-t', '0'); + + // DB initialized + $this->out('DONE!'); + + if ((bool)env('FEATURE_BANKWEB')) { + $this->out('Initializing BankWeb database...', 0); + + $this->dispatchShell('engine', 'install_bankweb', '--quiet'); + + $this->out('DONE!'); + } + + // Create some users + $this->out('Creating the initial users......', 0); + foreach ($this->users as $u) { + $this->dispatchShell( + 'engine', + 'create_user', + $u['userpass'], + $u['group'], + '--quiet', + '--yes', + '--password', + $u['userpass'] + ); + } + $this->out('DONE!'); + + // Done! + $this->out('Installation completed!'); + $this->hr(); + + $this->out('
User Credentials
'); + foreach ($this->users as $u) { + $this->out('Username: '.$u['userpass']); + $this->out('Password: '.$u['userpass']); + $this->out(); + } + } + + /** + * Engine Command: Install BankWeb + * + * Basically runs schema create for the BankWeb + * plugin. Pretty wrapper. + */ + public function install_bankweb() { + $this->out('Installing BankWeb'); + $this->hr(); + + $this->out('Initializing the database...', 0); + + // Initialize our database + $this->dispatchShell('schema', 'create', '--plugin', 'BankWeb', '--yes', '--quiet'); + + // DB initialized + $this->out('DONE!'); + + // Done! + $this->out('Installation completed!'); + } + + /** + * Engine Command: Create User + * + * Create's a user. Nothing more, nothing less. + */ + public function create_user() { + list($user, $group) = $this->args; + $yes = (isset($this->params['yes']) && !empty($this->params['yes'])); + + if (!isset($this->params['password'])) { + $pass = $this->in('Password: '); + $this->hr(); + } else { + $pass = $this->params['password']; + } + + // Check if the username exists + if (!empty($this->User->findByUsername($user))) { + $this->error(sprintf('A user with the same username (%s) already exists!', $user)); + } + + $group_data = $this->Group->findByName($group); + if (empty($group_data)) { + $this->error(sprintf('Group "%s" not found', $group)); + } + + $this->out(sprintf('Creating user "%s" with group "%s"', $user, $group_data['Group']['name'])); + + if ($yes || $this->in('Are you sure you want to create the user?', ['y', 'n'], 'y') == 'y') { + $this->hr(); + + // Create the user + $this->out('Creating the user...'); + $this->User->create(); + $this->User->save([ + 'username' => $user, + 'password' => $pass, + 'group_id' => $group_data['Group']['id'], + 'active' => true, + ]); + + $this->out(sprintf('User (%d) created!', $this->User->id)); + } + } + + /** + * Engine Command: Create Group + * + * Create's a group. Nothing more, nothing less. + */ + public function create_group() { + list($parent, $group) = $this->args; + $yes = (isset($this->params['yes']) && !empty($this->params['yes'])); + + // Check if the group exists + if (!empty($this->Group->findByName($group))) { + $this->error(sprintf('Group "%s" already exists', $group)); + } + + // Check if our parent exists, if it's not root + $parent_id = null; + + if ($parent != 'root') { + $data = $this->Group->findByName($parent); + + if (empty($data)) { + $this->error(sprintf('I was unable to find the parent group "%s"', $parent)); + } + + $parent = $data['Group']['name']; + $parent_id = $data['Group']['id']; + } + + $this->out(sprintf('Creating group "%s" under parent "%s"', $group, $parent)); + + if ($yes || $this->in('Are you sure you want to create the group?', ['y', 'n'], 'y') == 'y') { + $this->hr(); + + // Create the group + $this->out('Creating the group...'); + $this->Group->create(); + $this->Group->save([ + 'name' => $group, + 'team_number' => (isset($this->params['team_number']) ? $this->params['team_number'] : null), + 'parent_id' => $parent_id, + ]); + + $this->out(sprintf('Group (%s) created!', $group)); + } + } + + /* * ======================================== * The following are required for CakePHP, * but are undocumented. Sorry. * ======================================== */ - public function startup() { - parent::startup(); - $this->stdout->styles('header', array('underline' => true)); - } - - public function getOptionParser() { - $parser = parent::getOptionParser(); - - $parser - ->description('A console tool to manage ie2') - ->addSubCommand('install', [ - 'help' => 'Installs ie2', - ]) - ->addSubCommand('install_bankweb', [ - 'help' => 'Installs the BankWeb plugin', - ]) - ->addSubCommand('create_user', [ - 'help' => 'Create a user for ie2', - 'parser' => [ - 'arguments' => [ - 'username' => [ - 'help' => 'The username of the user you wish to create', - 'required' => true, - ], - 'group' => [ - 'help' => 'The group the new user will be apart of', - 'required' => true, - ], - ], - 'options' => [ - 'yes' => [ - 'short' => 'y', - 'boolean' => true, - 'help' => 'Do not prompt for confirmation. Be careful!', - ], - 'password' => [ - 'short' => 'p', - 'help' => 'The password of the user you wish to create', - ], - ], - ], - ]) - ->addSubCommand('create_group', [ - 'help' => 'Create a group for ie2', - 'parser' => [ - 'arguments' => [ - 'parent' => [ - 'help' => 'The parent of the group you wish to create. If you wish to create a root group, use "root" here.', - 'required' => true, - ], - 'name' => [ - 'help' => 'The name of the group you wish to create', - 'required' => true, - ], - ], - 'options' => [ - 'yes' => [ - 'short' => 'y', - 'boolean' => true, - 'help' => 'Do not prompt for confirmation. Be careful!', - ], - 'team_number' => [ - 'short' => 't', - 'help' => 'Associate a team number with this group.', - ], - ], - ], - ]); - - return $parser; - } + public function startup() { + parent::startup(); + $this->stdout->styles('header', ['underline' => true]); + } + + public function getOptionParser() { + $parser = parent::getOptionParser(); + + $parser + ->description('A console tool to manage ie2') + ->addSubCommand('install', [ + 'help' => 'Installs ie2', + ]) + ->addSubCommand('install_bankweb', [ + 'help' => 'Installs the BankWeb plugin', + ]) + ->addSubCommand('create_user', [ + 'help' => 'Create a user for ie2', + 'parser' => [ + 'arguments' => [ + 'username' => [ + 'help' => 'The username of the user you wish to create', + 'required' => true, + ], + 'group' => [ + 'help' => 'The group the new user will be apart of', + 'required' => true, + ], + ], + 'options' => [ + 'yes' => [ + 'short' => 'y', + 'boolean' => true, + 'help' => 'Do not prompt for confirmation. Be careful!', + ], + 'password' => [ + 'short' => 'p', + 'help' => 'The password of the user you wish to create', + ], + ], + ], + ]) + ->addSubCommand('create_group', [ + 'help' => 'Create a group for ie2', + 'parser' => [ + 'arguments' => [ + 'parent' => [ + 'help' => 'The parent of the group you wish to create. '. + 'If you wish to create a root group, use "root" here.', + 'required' => true, + ], + 'name' => [ + 'help' => 'The name of the group you wish to create', + 'required' => true, + ], + ], + 'options' => [ + 'yes' => [ + 'short' => 'y', + 'boolean' => true, + 'help' => 'Do not prompt for confirmation. Be careful!', + ], + 'team_number' => [ + 'short' => 't', + 'help' => 'Associate a team number with this group.', + ], + ], + ], + ]); + + return $parser; + } } diff --git a/app/Console/cake.php b/app/Console/cake.php index 30e6df9..69038f8 100644 --- a/app/Console/cake.php +++ b/app/Console/cake.php @@ -22,25 +22,25 @@ $dispatcher = 'Cake' . $ds . 'Console' . $ds . 'ShellDispatcher.php'; if (function_exists('ini_set')) { - $root = dirname(dirname(dirname(__FILE__))) . $ds . 'vendor' . $ds . 'cakephp'; + $root = dirname(dirname(dirname(__FILE__))) . $ds . 'vendor' . $ds . 'cakephp'; - // the following line differs from its sibling - // /lib/Cake/Console/Templates/skel/Console/cake.php - ini_set('include_path', $root . $ds . 'cakephp' . $ds . 'lib' . PATH_SEPARATOR . ini_get('include_path')); + // the following line differs from its sibling + // /lib/Cake/Console/Templates/skel/Console/cake.php + ini_set('include_path', $root . $ds . 'cakephp' . $ds . 'lib' . PATH_SEPARATOR . ini_get('include_path')); } define('TMP', dirname(dirname(dirname(__FILE__))) . $ds . 'tmp' . $ds); define('WWW_ROOT', dirname(dirname(dirname(__FILE__))) . $ds . 'webroot' . $ds); if (!include $dispatcher) { - trigger_error('Could not locate CakePHP core files.', E_USER_ERROR); + trigger_error('Could not locate CakePHP core files.', E_USER_ERROR); } unset($dispatcher, $root, $ds); // Make passing "--working ." to the shell obsolete, when invoking Console/cake.php directly if (!in_array('--working', $argv)) { - $argv[] = '--working'; - $argv[] = dirname(dirname(__FILE__)); + $argv[] = '--working'; + $argv[] = dirname(dirname(__FILE__)); } return ShellDispatcher::run($argv); diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php index 7975ce0..fe3af9a 100644 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -3,222 +3,225 @@ use Respect\Validation\Exceptions\NestedValidationException; class AppController extends Controller { - public $components = [ - 'Auth', - 'Flash' => [ - 'className' => 'BootstrapFlash', - ], - 'RequestHandler', - 'Session', - 'Paginator' => [ - 'settings' => [ - 'limit' => 20, - 'paramType' => 'querystring', - ] - ], - 'Preflight', - ]; - - public $uses = ['Announcement', 'Config', 'Log']; - public $helpers = ['Auth', 'Misc', 'Session']; - - protected $validators = []; - - /** - * Before Filter Hook - * - * Hook ran before ANY request. Currently - * sets some template variables depending on user state. - */ - public function beforeFilter() { - parent::beforeFilter(); - - // Setup the read announcements - if ( !$this->Session->check('read_announcements') ) { - $this->Session->write('read_announcements', []); - } - - // Setup a constant for the competition start - if ( !defined('COMPETITION_START') ) { - define('COMPETITION_START', $this->Config->getKey('competition.start')); - } - - // Git version (because it looks cool) - if ( Cache::read('version') === false ) { - exec('git status', $unused, $status); - - if ( $status === 0 ) { - exec('git rev-parse --short HEAD', $version_short); - exec('git rev-parse HEAD', $version_long); - - $version_short = trim($version_short[0]); - $version_long = trim($version_long[0]); - } else { - $version_short = 'DEV'; - $version_long = 'development'; - } - - Cache::write('version', [$version_short, $version_long]); - } else { - list($version_short, $version_long) = Cache::read('version'); - } - - $this->set('version', $version_short); - $this->set('version_long', $version_long); - - // Other template stuff - $this->set('announcements', $this->Announcement->getAll()); - $this->set('emulating', ($this->Auth->item('emulating') == true)); - } - - /** - * Before Render Hook - * - * Basically sets up the AuthHelper (which is a proxy) - */ - public function beforeRender() { - parent::beforeRender(); - - $this->helpers['Auth'] = [ - 'auth' => $this->Auth, - ]; - } - - /** - * After Filter Hook - * - * Compress all the html! - */ - public function afterFilter() { - parent::afterFilter(); - - if ( env('DEBUG') == 0 ) { - $parser = \WyriHaximus\HtmlCompress\Factory::construct(); - $compressedHtml = $parser->compress($this->response->body()); - - $this->response->body($compressedHtml); - } - } - - /** - * Ajax Response - * - * @param $data The output - * @param $status The HTTP status code - * @return CakeResponse - */ - protected function ajaxResponse($data, $status=200) { - $this->layout = 'ajax'; - - return new CakeResponse([ - 'type' => 'json', - 'body' => (is_array($data) ? json_encode($data) : $data), - 'status' => $status, - ]); - } - - /** - * Log Message - * - * @param $type The log type - * @param $message The message - * @param $data [Optional] Extra data to log - * @param $related [Optional] Related ID of this message - * @param $who [Optional] Who did this action - * @return void - */ - public function logMessage($type, $message, $data=[], $related=0, $who=false) { - if ( $who === false ) { - $who = ($this->Auth->loggedIn() ? $this->Auth->user('id') : NULL); - } - - $safe = !((bool)env('X_FORWARDED_ENABLED') == true); - - $this->Log->create(); - $this->Log->save([ - 'time' => time(), - 'type' => $type, - 'user_id' => $who, - 'related_id' => ($related > 0 ? $related : NULL), - 'data' => json_encode($data), - 'ip' => $this->request->clientIp($safe), - 'message' => $message, - ]); - } - - /** - * Validate request data against - * $validators - * - * @return array [errors,validated_data] - */ - protected function _validate() { - // Validate the input - $errors = []; - $modify = []; - - foreach ( $this->validators AS $key => $validator ) { - // If we're missing something, stop it's bad. - if ( !isset($this->request->data[$key]) ) { - $errors[] = sprintf('Missing input "%s"', $key); - continue; - } - - try { - $validator->assert($this->request->data[$key]); - - $data[$key] = $this->request->data[$key]; - } catch ( NestedValidationException $e ) { - $errors[] = sprintf( - 'Input %s must have pass the following rules:
-%s', - $key, - implode('
-', $e->getMessages()) - ); - } - } - - return [ - 'errors' => $errors, - 'data' => $data, - ]; - } - - /** - * Generates a flash message for a failed - * validation - * - * @param $errors Array of the errors - * @return void - */ - protected function _errorFlash($errors) { - $this->Flash->danger('The following errors have occured:
'.implode('
', $errors)); - } - - /** - * Sends a slack message - * - */ - protected function _sendSlack($msg, $extra=[]) { - if ( !env('SLACK_ENDPOINT') ) return; - - // Sprintf it - $msg = sprintf($msg, $this->Auth->user('username'), $this->Auth->group('name')); - - // Build the payload - $payload = 'payload='.json_encode([ - 'text' => $msg, - 'link_names' => 1, - ] + $extra); - - $ch = curl_init(env('SLACK_ENDPOINT')); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); - curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - $result = curl_exec($ch); - - curl_close($ch); - - return $result; - } + + public $components = [ + 'Auth', + 'Flash' => [ + 'className' => 'BootstrapFlash', + ], + 'RequestHandler', + 'Session', + 'Paginator' => [ + 'settings' => [ + 'limit' => 20, + 'paramType' => 'querystring', + ] + ], + 'Preflight', + ]; + + public $uses = ['Announcement', 'Config', 'Log']; + + public $helpers = ['Auth', 'Misc', 'Session']; + + protected $validators = []; + + /** + * Before Filter Hook + * + * Hook ran before ANY request. Currently + * sets some template variables depending on user state. + */ + public function beforeFilter() { + parent::beforeFilter(); + + // Setup the read announcements + if (!$this->Session->check('read_announcements')) { + $this->Session->write('read_announcements', []); + } + + // Setup a constant for the competition start + if (!defined('COMPETITION_START')) { + define('COMPETITION_START', $this->Config->getKey('competition.start')); + } + + // Git version (because it looks cool) + if (Cache::read('version') === false) { + exec('git status', $unused, $status); + + if ($status === 0) { + exec('git rev-parse --short HEAD', $version_short); + exec('git rev-parse HEAD', $version_long); + + $version_short = trim($version_short[0]); + $version_long = trim($version_long[0]); + } else { + $version_short = 'DEV'; + $version_long = 'development'; + } + + Cache::write('version', [$version_short, $version_long]); + } else { + list($version_short, $version_long) = Cache::read('version'); + } + + $this->set('version', $version_short); + $this->set('version_long', $version_long); + + // Other template stuff + $this->set('announcements', $this->Announcement->getAll()); + $this->set('emulating', ($this->Auth->item('emulating') == true)); + } + + /** + * Before Render Hook + * + * Basically sets up the AuthHelper (which is a proxy) + */ + public function beforeRender() { + parent::beforeRender(); + + $this->helpers['Auth'] = [ + 'auth' => $this->Auth, + ]; + } + + /** + * After Filter Hook + * + * Compress all the html! + */ + public function afterFilter() { + parent::afterFilter(); + + if (env('DEBUG') == 0) { + $parser = \WyriHaximus\HtmlCompress\Factory::construct(); + $compressedHtml = $parser->compress($this->response->body()); + + $this->response->body($compressedHtml); + } + } + + /** + * Ajax Response + * + * @param $data The output + * @param $status The HTTP status code + * @return CakeResponse + */ + protected function ajaxResponse($data, $status = 200) { + $this->layout = 'ajax'; + + return new CakeResponse([ + 'type' => 'json', + 'body' => (is_array($data) ? json_encode($data) : $data), + 'status' => $status, + ]); + } + + /** + * Log Message + * + * @param $type The log type + * @param $message The message + * @param $data [Optional] Extra data to log + * @param $related [Optional] Related ID of this message + * @param $who [Optional] Who did this action + * @return void + */ + public function logMessage($type, $message, $data = [], $related = 0, $who = false) { + if ($who === false) { + $who = ($this->Auth->loggedIn() ? $this->Auth->user('id') : null); + } + + $safe = !((bool)env('X_FORWARDED_ENABLED') == true); + + $this->Log->create(); + $this->Log->save([ + 'time' => time(), + 'type' => $type, + 'user_id' => $who, + 'related_id' => ($related > 0 ? $related : null), + 'data' => json_encode($data), + 'ip' => $this->request->clientIp($safe), + 'message' => $message, + ]); + } + + /** + * Validate request data against + * $validators + * + * @return array [errors,validated_data] + */ + protected function validate() { + // Validate the input + $errors = []; + $modify = []; + + foreach ($this->validators as $key => $validator) { + // If we're missing something, stop it's bad. + if (!isset($this->request->data[$key])) { + $errors[] = sprintf('Missing input "%s"', $key); + continue; + } + + try { + $validator->assert($this->request->data[$key]); + + $data[$key] = $this->request->data[$key]; + } catch (NestedValidationException $e) { + $errors[] = sprintf( + 'Input %s must have pass the following rules:
-%s', + $key, + implode('
-', $e->getMessages()) + ); + } + } + + return [ + 'errors' => $errors, + 'data' => $data, + ]; + } + + /** + * Generates a flash message for a failed + * validation + * + * @param $errors Array of the errors + * @return void + */ + protected function errorFlash($errors) { + $this->Flash->danger('The following errors have occured:
'.implode('
', $errors)); + } + + /** + * Sends a slack message + * + */ + protected function sendSlack($msg, $extra = []) { + if (!env('SLACK_ENDPOINT')) { return; + } + + // Sprintf it + $msg = sprintf($msg, $this->Auth->user('username'), $this->Auth->group('name')); + + // Build the payload + $payload = 'payload='.json_encode([ + 'text' => $msg, + 'link_names' => 1, + ] + $extra); + + $ch = curl_init(env('SLACK_ENDPOINT')); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $result = curl_exec($ch); + + curl_close($ch); + + return $result; + } } diff --git a/app/Controller/AttachmentController.php b/app/Controller/AttachmentController.php index ddef6d6..3725fb6 100644 --- a/app/Controller/AttachmentController.php +++ b/app/Controller/AttachmentController.php @@ -2,52 +2,53 @@ App::uses('AppController', 'Controller'); class AttachmentController extends AppController { - public $uses = ['Attachment', 'Schedule']; - - /** - * Dynamic Index Page - * - * @url /attachment - * @url /attachment/index - */ - public function index() { - return $this->redirect(['controller' => 'pages', 'action' => 'index']); - } - - /** - * Attachment View - * - * @url /attachment/view/// - */ - public function view($aid=false, $access_key=false) { - $attachment = $this->Attachment->findById($aid); - if ( empty($attachment) ) { - throw new NotFoundException('Unknown attachment'); - } - - $data = json_decode($attachment['Attachment']['data'], true); - $download = (isset($this->params['url']['download']) && $this->params['url']['download'] == true); - - // Verify the "access_key" - if ( $access_key != md5($aid.env('SECURITY_CIPHER_SEED')) ) { - throw new ForbiddenException('Invalid access key'); - } - - // Let's verify our data is correct - if ( md5(base64_decode($data['data'])) !== $data['hash'] ) { - throw new InternalErrorException('Data storage failure'); - } - - // Create the new response for the data - $response = new CakeResponse(); - $response->type($data['extension']); - $response->body(base64_decode($data['data'])); - $response->disableCache(); - - $type = ($download ? 'attachment' : 'inline'); - $filename = $attachment['Attachment']['name']; - $response->header('Content-Disposition', $type.'; filename="'.$filename.'"'); - - return $response; - } + + public $uses = ['Attachment', 'Schedule']; + + /** + * Dynamic Index Page + * + * @url /attachment + * @url /attachment/index + */ + public function index() { + return $this->redirect(['controller' => 'pages', 'action' => 'index']); + } + + /** + * Attachment View + * + * @url /attachment/view/// + */ + public function view($aid = false, $access_key = false) { + $attachment = $this->Attachment->findById($aid); + if (empty($attachment)) { + throw new NotFoundException('Unknown attachment'); + } + + $data = json_decode($attachment['Attachment']['data'], true); + $download = (isset($this->params['url']['download']) && $this->params['url']['download'] == true); + + // Verify the "access_key" + if ($access_key != md5($aid.env('SECURITY_CIPHER_SEED'))) { + throw new ForbiddenException('Invalid access key'); + } + + // Let's verify our data is correct + if (md5(base64_decode($data['data'])) !== $data['hash']) { + throw new InternalErrorException('Data storage failure'); + } + + // Create the new response for the data + $response = new CakeResponse(); + $response->type($data['extension']); + $response->body(base64_decode($data['data'])); + $response->disableCache(); + + $type = ($download ? 'attachment' : 'inline'); + $filename = $attachment['Attachment']['name']; + $response->header('Content-Disposition', $type.'; filename="'.$filename.'"'); + + return $response; + } } diff --git a/app/Controller/Component/AuthComponent.php b/app/Controller/Component/AuthComponent.php index 5549aab..bb9f472 100644 --- a/app/Controller/Component/AuthComponent.php +++ b/app/Controller/Component/AuthComponent.php @@ -3,273 +3,279 @@ App::uses('Security', 'Utility'); class AuthComponent extends Component { - public $components = ['Cookie', 'Session']; - public $uses = ['User']; - - protected $controller; - - /** - * String to prefix all our - * session keys with. - */ - const SESSION_PREFIX = 'auth'; - - /** - * String to store our - * redirect URL. - */ - const SESSION_REDIRECT = 'auth_redirect'; - - /** - * AuthComponent Initialize Hook - * - * Auto logs out the user if their account has - * expired. Also updates their last activity session time. - */ - public function initialize(Controller $controller) { - // If we're logged in, make sure we haven't expired - if ( $this->loggedIn() && $this->_isExpired($this->user('expiration')) ) { - $this->logout(); - } - - if ( $this->loggedIn() ) { - $this->Session->write(self::SESSION_PREFIX.'.last_activity', time()); - } - - $this->controller = $controller; - } - - /** - * Checks if a timestamp has passed ('expired') - * - */ - private function _isExpired($expiration) { - return ($expiration > 0) ? (time() > $expiration) : false; - } - - /** - * Login - * - * @param $username The username of the account - * @param $password The password of the account - * @return boolean If the login was successful - */ - public function login($username, $password) { - $UserModel = ClassRegistry::init('User'); - $GroupModel = ClassRegistry::init('Group'); - $user = $UserModel->findByUsername($username); - - // Does the user exist? - if ( empty($user) ) return false; - - // Do we have the right password? - $actual_password = $user['User']['password']; - if ( Security::hash($password, 'blowfish', $actual_password) !== $actual_password ) return false; - - // Is the user active? - if ( $user['User']['active'] == 0 ) return false; - - // Is the user expired? - if ( $this->_isExpired($user['User']['expiration']) ) return false; - - // Save the data - unset($user['User']['password']); - $this->Session->write(self::SESSION_PREFIX, $user); - $this->Session->write(self::SESSION_PREFIX.'.last_activity', time()); - $this->Session->write(self::SESSION_PREFIX.'.groups', $GroupModel->getGroups($user['Group']['id'])); - - return true; - } - - /** - * Logout - * - * @return void - */ - public function logout() { - $this->Session->destroy(); - } - - /** - * Protects a page - * - * If the user is not logged in, they will be - * redirected to /user/login. - * - * In addition, we can protect a page by - * requiring a certain group id - * - * @param $required_group The group ID the current - * user must be apart of, in order to view the page - * @return mixed - */ - public function protect($required_group=false) { - if ( !$this->loggedIn() ) { - $this->Session->write(self::SESSION_REDIRECT, $this->controller->request->here); - - return $this->controller->redirect('/user/login'); - } - - if ( $required_group !== false ) { - if ( !in_array($required_group, $this->item('groups')) ) { - throw new ForbiddenException('You are unauthorized to view this page'); - } - } - - return true; - } - - /** - * Redirect URL - * - * Get's the redirect url after a user has logged - * in. - * - * @return string - */ - public function redirectURL() { - $url = $this->Session->consume(self::SESSION_REDIRECT); - - return (empty($url) ? '/' : $url); - } - - /** - * Emulates a user account - * - * Emulation will change the current session's user to the emulated user - * account. The current session will be saved, however. - * - * @param $uidOrUsername The username or id of the account you are emulating - */ - public function emulate($uidOrUsername) { - $UserModel = ClassRegistry::init('User'); - $GroupModel = ClassRegistry::init('Group'); - $user = $UserModel->find('first', [ - 'conditions' => [ - 'OR' => [ - 'User.username' => $uidOrUsername, - 'User.id' => $uidOrUsername, - ], - ] - ]); - - if ( empty($user) ) { - throw new InternalErrorException('Unknown username!'); - } - - $oldSession = $this->Session->consume(self::SESSION_PREFIX); - $this->Session->write(self::SESSION_PREFIX, $user); - $this->Session->write([ - self::SESSION_PREFIX.'.last_activity' => time(), - self::SESSION_PREFIX.'.oldSession' => $oldSession, - self::SESSION_PREFIX.'.emulating' => true, - self::SESSION_PREFIX.'.groups' => $GroupModel->getGroups($user['Group']['id']), - ]); - } - - /** - * Ends an emulation session - * - * Kills the current session, and restores the previously saved session. - */ - public function emulateExit() { - if ( $this->item('emulating') != true ) { - throw new InternalErrorException('A valid emulation session is not present!'); - } - - $newSession = $this->Session->consume(self::SESSION_PREFIX.'.oldSession'); - $this->Session->destroy(); - - $this->Session->write(self::SESSION_PREFIX, $newSession); - $this->Session->write(self::SESSION_PREFIX.'.last_activity', time()); - } - - /** - * Logged In - * - * @return boolean If the user is logged in - */ - public function loggedIn() { - return ($this->Session->check(self::SESSION_PREFIX)); - } - - /** - * Is Staff - * - * @return boolean If the user is a staff member - */ - public function isStaff() { - return $this->is(env('GROUP_STAFF')); - } - - /** - * Is Administrator - * - * @return boolean If the user is an administrator - */ - public function isAdmin() { - return $this->is(env('GROUP_ADMINS')); - } - - /** - * Is White Team - * - * @return boolean If the user is a White Team member - */ - public function isWhiteTeam() { - return $this->is(env('GROUP_WHITE')); - } - - /** - * Is Blue Team - * - * @return boolean If the user is a Blue Team Member - */ - public function isBlueTeam() { - return $this->is(env('GROUP_BLUE')); - } - - /** - * Is Helper - * - * @param $group The group id - * @return boolean If the user is in the group - */ - public function is($group) { - return ($this->loggedIn() ? in_array($group, $this->item('groups')) : false); - } - - /** - * User Information Accessor - * - * @param string The key of the item you wish to access (optional) - * @return mixed The value of the item you are accessing - */ - public function user($item='') { - $key = 'User'.(empty($item) ? '' : '.'.$item); - return $this->item($key); - } - - /** - * Group Information Accessor - * - * @param string The key of the item you wish to access (optional) - * @return mixed The value of the item you are accessing - */ - public function group($item='') { - $key = 'Group'.(empty($item) ? '' : '.'.$item); - return $this->item($key); - } - - /** - * Item Accessor - * - * @param string The key of the item you wish to access (optional) - * @return mixed The value of the item you are accessing - */ - public function item($item=false) { - $key = ($item === false ? self::SESSION_PREFIX : implode('.', [self::SESSION_PREFIX, $item])); - - return ($this->loggedIn() ? $this->Session->read($key) : ''); - } -} \ No newline at end of file + + public $components = ['Cookie', 'Session']; + + public $uses = ['User']; + + protected $controller; + + /** + * String to prefix all our + * session keys with. + */ + const SESSION_PREFIX = 'auth'; + + /** + * String to store our + * redirect URL. + */ + const SESSION_REDIRECT = 'auth_redirect'; + + /** + * AuthComponent Initialize Hook + * + * Auto logs out the user if their account has + * expired. Also updates their last activity session time. + */ + public function initialize(Controller $controller) { + // If we're logged in, make sure we haven't expired + if ($this->loggedIn() && $this->isExpired($this->user('expiration'))) { + $this->logout(); + } + + if ($this->loggedIn()) { + $this->Session->write(self::SESSION_PREFIX.'.last_activity', time()); + } + + $this->controller = $controller; + } + + /** + * Checks if a timestamp has passed ('expired') + * + */ + private function isExpired($expiration) { + return ($expiration > 0) ? (time() > $expiration) : false; + } + + /** + * Login + * + * @param $username The username of the account + * @param $password The password of the account + * @return boolean If the login was successful + */ + public function login($username, $password) { + $UserModel = ClassRegistry::init('User'); + $GroupModel = ClassRegistry::init('Group'); + $user = $UserModel->findByUsername($username); + + // Does the user exist? + if (empty($user)) { return false; + } + + // Do we have the right password? + $actual_password = $user['User']['password']; + if (Security::hash($password, 'blowfish', $actual_password) !== $actual_password) { return false; + } + + // Is the user active? + if ($user['User']['active'] == 0) { return false; + } + + // Is the user expired? + if ($this->isExpired($user['User']['expiration'])) { return false; + } + + // Save the data + unset($user['User']['password']); + $this->Session->write(self::SESSION_PREFIX, $user); + $this->Session->write(self::SESSION_PREFIX.'.last_activity', time()); + $this->Session->write(self::SESSION_PREFIX.'.groups', $GroupModel->getGroups($user['Group']['id'])); + + return true; + } + + /** + * Logout + * + * @return void + */ + public function logout() { + $this->Session->destroy(); + } + + /** + * Protects a page + * + * If the user is not logged in, they will be + * redirected to /user/login. + * + * In addition, we can protect a page by + * requiring a certain group id + * + * @param $required_group The group ID the current + * user must be apart of, in order to view the page + * @return mixed + */ + public function protect($required_group = false) { + if (!$this->loggedIn()) { + $this->Session->write(self::SESSION_REDIRECT, $this->controller->request->here); + + return $this->controller->redirect('/user/login'); + } + + if ($required_group !== false) { + if (!in_array($required_group, $this->item('groups'))) { + throw new ForbiddenException('You are unauthorized to view this page'); + } + } + + return true; + } + + /** + * Redirect URL + * + * Get's the redirect url after a user has logged + * in. + * + * @return string + */ + public function redirectURL() { + $url = $this->Session->consume(self::SESSION_REDIRECT); + + return (empty($url) ? '/' : $url); + } + + /** + * Emulates a user account + * + * Emulation will change the current session's user to the emulated user + * account. The current session will be saved, however. + * + * @param $uidOrUsername The username or id of the account you are emulating + */ + public function emulate($uidOrUsername) { + $UserModel = ClassRegistry::init('User'); + $GroupModel = ClassRegistry::init('Group'); + $user = $UserModel->find('first', [ + 'conditions' => [ + 'OR' => [ + 'User.username' => $uidOrUsername, + 'User.id' => $uidOrUsername, + ], + ] + ]); + + if (empty($user)) { + throw new InternalErrorException('Unknown username!'); + } + + $oldSession = $this->Session->consume(self::SESSION_PREFIX); + $this->Session->write(self::SESSION_PREFIX, $user); + $this->Session->write([ + self::SESSION_PREFIX.'.last_activity' => time(), + self::SESSION_PREFIX.'.oldSession' => $oldSession, + self::SESSION_PREFIX.'.emulating' => true, + self::SESSION_PREFIX.'.groups' => $GroupModel->getGroups($user['Group']['id']), + ]); + } + + /** + * Ends an emulation session + * + * Kills the current session, and restores the previously saved session. + */ + public function emulateExit() { + if ($this->item('emulating') != true) { + throw new InternalErrorException('A valid emulation session is not present!'); + } + + $newSession = $this->Session->consume(self::SESSION_PREFIX.'.oldSession'); + $this->Session->destroy(); + + $this->Session->write(self::SESSION_PREFIX, $newSession); + $this->Session->write(self::SESSION_PREFIX.'.last_activity', time()); + } + + /** + * Logged In + * + * @return boolean If the user is logged in + */ + public function loggedIn() { + return ($this->Session->check(self::SESSION_PREFIX)); + } + + /** + * Is Staff + * + * @return boolean If the user is a staff member + */ + public function isStaff() { + return $this->is(env('GROUP_STAFF')); + } + + /** + * Is Administrator + * + * @return boolean If the user is an administrator + */ + public function isAdmin() { + return $this->is(env('GROUP_ADMINS')); + } + + /** + * Is White Team + * + * @return boolean If the user is a White Team member + */ + public function isWhiteTeam() { + return $this->is(env('GROUP_WHITE')); + } + + /** + * Is Blue Team + * + * @return boolean If the user is a Blue Team Member + */ + public function isBlueTeam() { + return $this->is(env('GROUP_BLUE')); + } + + /** + * Is Helper + * + * @param $group The group id + * @return boolean If the user is in the group + */ + public function is($group) { + return ($this->loggedIn() ? in_array($group, $this->item('groups')) : false); + } + + /** + * User Information Accessor + * + * @param string The key of the item you wish to access (optional) + * @return mixed The value of the item you are accessing + */ + public function user($item = '') { + $key = 'User'.(empty($item) ? '' : '.'.$item); + return $this->item($key); + } + + /** + * Group Information Accessor + * + * @param string The key of the item you wish to access (optional) + * @return mixed The value of the item you are accessing + */ + public function group($item = '') { + $key = 'Group'.(empty($item) ? '' : '.'.$item); + return $this->item($key); + } + + /** + * Item Accessor + * + * @param string The key of the item you wish to access (optional) + * @return mixed The value of the item you are accessing + */ + public function item($item = false) { + $key = ($item === false ? self::SESSION_PREFIX : implode('.', [self::SESSION_PREFIX, $item])); + + return ($this->loggedIn() ? $this->Session->read($key) : ''); + } +} diff --git a/app/Controller/Component/BootstrapFlashComponent.php b/app/Controller/Component/BootstrapFlashComponent.php index 9eed00d..808ee9e 100644 --- a/app/Controller/Component/BootstrapFlashComponent.php +++ b/app/Controller/Component/BootstrapFlashComponent.php @@ -2,30 +2,31 @@ App::uses('FlashComponent', 'Controller/Component'); class BootstrapFlashComponent extends FlashComponent { - private $_bootstrapFlashClasses = array('success', 'info', 'warning', 'danger'); - public function __call($name, $args) { - if ( in_array($name, $this->_bootstrapFlashClasses) ) { - // Duplicating most of the parent::__call functionality - $options = array( - 'element' => 'bootstrap_default', - 'params' => array( - 'type' => $name, - ), - ); + private $bootstrapFlashClasses = ['success', 'info', 'warning', 'danger']; - if ( count($args) < 1 ) { - throw new InternalErrorException('Flash message missing.'); - } + public function __call($name, $args) { + if (in_array($name, $this->bootstrapFlashClasses)) { + // Duplicating most of the parent::__call functionality + $options = [ + 'element' => 'bootstrap_default', + 'params' => [ + 'type' => $name, + ], + ]; + if (count($args) < 1) { + throw new InternalErrorException('Flash message missing.'); + } - if (!empty($args[1])) { - $options += (array)$args[1]; - } - $this->set($args[0], $options); - } else { - return parent::__call($name, $args); - } - } -} \ No newline at end of file + if (!empty($args[1])) { + $options += (array)$args[1]; + } + + $this->set($args[0], $options); + } else { + return parent::__call($name, $args); + } + } +} diff --git a/app/Controller/Component/PreflightComponent.php b/app/Controller/Component/PreflightComponent.php index 378f30a..14abc3e 100644 --- a/app/Controller/Component/PreflightComponent.php +++ b/app/Controller/Component/PreflightComponent.php @@ -4,260 +4,273 @@ App::uses('Security', 'Utility'); class PreflightComponent extends Component { - /** - * Array of all the checks that should - * be ran. - */ - protected $checks = [ - 'verifySecurityKeys', 'checkDatabaseConnection', - 'checkGroupMappings', 'checkInjectTypes', - ]; - - /** - * PreflightComponent Initialize Hook - * - * This will only be ran once per hour. - * It will run through the application, ensuring - * it has been configured properly. - */ - public function initialize(Controller $controller) { - // Disable preflight on DEBUG - if ( env('DEBUG') == 2 || !file_exists(ROOT.'/.env') ) { - return; - } - - // Calculate the hash of the config file. Don't run Preflight if it - // hasn't changed - $hash = md5(file_get_contents(ROOT.'/.env')); - if ( Cache::read('preflight_check') == $hash ) { - return; - } - - // Disable preflight on the Admin plugin (otherwise, how would we fix issues) - if ( $controller->request->params['plugin'] == 'admin' ) { - return; - } - - // Additional checks for ScoreEngine - if ( (bool)env('FEATURE_SCOREENGINE') ) { - $this->checks[] = 'checkScoringDB'; - $this->checks[] = 'checkScoreEngine'; - } - - // Additional checks for BankWeb - if ( (bool)env('FEATURE_BANKWEB') ) { - $this->checks[] = 'checkBankWeb'; - $this->checks[] = 'checkBankWebTable'; - - if ( (bool)env('BANKWEB_SLACK_ENABLED') ) { - $this->checks[] = 'checkBankWebSlack'; - } - } - - foreach ( $this->checks AS $check ) { - $passedOrErrorMessage = $this->$check(); - - if ( $passedOrErrorMessage !== true ) { - throw new InternalErrorException('Preflight Error: '.$passedOrErrorMessage); - } - } - - // If we got here, we can save and cache the result - Cache::write('preflight_check', $hash); - } - - /** - * Verify Security Keys - * - * Ensures 'Security.salt' and 'Security.cipherSeed' are both non-empty and - * non-default values. - * @return boolean If the check passed - */ - public function verifySecurityKeys() { - // Pls no defaults - if (empty(Configure::read('Security.salt')) OR Configure::read('Security.salt') === 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi') { - return 'Please update the Security.salt config value'; - } - if (empty(Configure::read('Security.cipherSeed')) OR Configure::read('Security.cipherSeed') === '76859309657453542496749683645') { - return 'Please update the Security.cipherSeed config value.'; - } - - return true; - } - - /** - * Check Database Connection - * - * Ensures the database connection to the InjectEngine - * is working - * @return boolean If the check passed - */ - public function checkDatabaseConnection() { - App::uses('ConnectionManager', 'Model'); - - // Check database connection for the InjectEngine - $conn = ConnectionManager::getDataSource('default'); - if ( !$conn->isConnected() ) { - return 'Unable to connect to the InjectEngine Database'; - } - - return true; - } - - /** - * Check Group Mappings - * - * Ensures 'GROUP_STAFF', 'GROUP_BLUE', 'GROUP_ADMINS', and 'GROUP_WHITE' - * have valid group mappings. - * @return boolean If the check passed - */ - public function checkGroupMappings() { - $GroupModel = ClassRegistry::init('Group'); - - // Check the group mappings - foreach ( ['GROUP_STAFF', 'GROUP_BLUE', 'GROUP_ADMINS', 'GROUP_WHITE'] AS $group ) { - $gid = env($group); - - if ( empty($gid) ) { - return 'Please setup a group mapping for '.$group; - } - - if ( empty($GroupModel->findById($gid)) ) { - return sprintf('Invalid GID mapping for %s (GID: %d)', $group, $gid); - } - } - - return true; - } - - /** - * Check Inject Types - * - * Verifies that all the inject types listed in 'engine.inject_types' - * exist and are callable. - */ - public function checkInjectTypes() { - $ConfigModel = ClassRegistry::init('Config'); - $injectTypes = json_decode($ConfigModel->getKey('engine.inject_types')); - - if ( json_last_error() != JSON_ERROR_NONE ) { - return sprintf('JSON Error decoding "engine.inject_types": %s', json_last_error_msg()); - } - - // Should this be a warning? - if ( empty($injectTypes) ) { - return 'No inject types are configured (See config key: engine.inject_types)'; - } - - foreach ( $injectTypes AS $type ) { - $className = sprintf('InjectTypes\\%s', $type); - - if ( !class_exists($className) ) { - return sprintf('Unknown inject type "%s" - does this file exist in "app/Vendor/InjectTypes"?', $type); - } - } - - return true; - } - - /** - * Check ScoreEngine DB - * - * Verifies the ScoreEngine DB is setup correctly - */ - public function checkScoringDB() { - App::uses('ConnectionManager', 'Model'); - - // Check database connection for the InjectEngine - $conn = ConnectionManager::getDataSource('scoreengine'); - if ( !$conn->isConnected() ) { - return 'Unable to connect to the ScoreEngine Database'; - } - - return true; - } - - /** - * Check ScoreEngine - * - * Verifies the ScoreEngine is setup correctly - */ - public function checkScoreEngine() { - $tables = ['Check', 'Round', 'Service', 'Team', 'TeamService']; - $missing_tables = []; - - foreach ( $tables AS $table ) { - $tbl = ClassRegistry::init('BankWeb.'.$table); - - try { - $tbl->find('first'); - } catch ( Exception $e ) { - $missing_tables[] = $table; - } - } - - return !empty($missing_tables) ? 'ScoreEngine is not setup. Missing DB table(s): '.implode(', ', $missing_tables) : true; - } - - /** - * Check BankWeb - * - * Verifies some env variables are set, and that 'BANKWEB_PRODUCTS' exists - */ - public function checkBankWeb() { - foreach ( ['BANKAPI_SERVER', 'BANKAPI_TIMEOUT', 'BANKWEB_PRODUCTS', 'BANKWEB_WHITETEAM_ACCOUNT', 'BANKWEB_PUBLIC_APIINFO'] AS $key ) { - if ( env($key) === null ) { - return sprintf('Please setup the variable "%s" to use the BankWeb Feature.', $key); - } - } - - $products = ROOT . DS . env('BANKWEB_PRODUCTS'); - if ( !file_exists($products) ) { - return sprintf('Please make sure the file "%s" exists (as set in "BANKWEB_PRODUCTS")', $products); - } - - $contents = json_decode(file_get_contents($products)); - if ( json_last_error() != JSON_ERROR_NONE ) { - return sprintf('JSON Error with "BANKWEB_PRODUCTS" - %s', json_last_error_msg()); - } - - return true; - } - - /** - * Check BankWeb Table - * - * That the BankWeb Table (account_mappings) exists - */ - public function checkBankWebTable() { - $table = ClassRegistry::init('BankWeb.AccountMapping'); - - try { - $table->find('first'); - } catch ( Exception $e ) { - return 'BankWeb plugin is not setup - please run `./cake engine install_bankweb`'; - } - - return true; - } - - /** - * Check BankWeb Slack Configuration - * - * Verify that BankWeb's slack configuration is correct - */ - public function checkBankWebSlack() { - $url = env('SLACK_ENDPOINT'); - - $ch = curl_init(env('SLACK_ENDPOINT')); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_exec($ch); - $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if ( $http_code != 400 ) { - return 'Invalid slack endpoint setup. Please verify that your Slack URL is correct.'; - } - return true; - } -} \ No newline at end of file + + /** + * Array of all the checks that should + * be ran. + */ + protected $checks = [ + 'verifySecurityKeys', 'checkDatabaseConnection', + 'checkGroupMappings', 'checkInjectTypes', + ]; + + /** + * PreflightComponent Initialize Hook + * + * This will only be ran once per hour. + * It will run through the application, ensuring + * it has been configured properly. + */ + public function initialize(Controller $controller) { + // Disable preflight on DEBUG + if (env('DEBUG') == 2 || !file_exists(ROOT.'/.env')) { + return; + } + + // Calculate the hash of the config file. Don't run Preflight if it + // hasn't changed + $hash = md5(file_get_contents(ROOT.'/.env')); + if (Cache::read('preflight_check') == $hash) { + return; + } + + // Disable preflight on the Admin plugin (otherwise, how would we fix issues) + if ($controller->request->params['plugin'] == 'admin') { + return; + } + + // Additional checks for ScoreEngine + if ((bool)env('FEATURE_SCOREENGINE')) { + $this->checks[] = 'checkScoringDB'; + $this->checks[] = 'checkScoreEngine'; + } + + // Additional checks for BankWeb + if ((bool)env('FEATURE_BANKWEB')) { + $this->checks[] = 'checkBankWeb'; + $this->checks[] = 'checkBankWebTable'; + + if ((bool)env('BANKWEB_SLACK_ENABLED')) { + $this->checks[] = 'checkBankWebSlack'; + } + } + + foreach ($this->checks as $check) { + $passedOrErrorMessage = $this->$check(); + + if ($passedOrErrorMessage !== true) { + throw new InternalErrorException('Preflight Error: '.$passedOrErrorMessage); + } + } + + // If we got here, we can save and cache the result + Cache::write('preflight_check', $hash); + } + + /** + * Verify Security Keys + * + * Ensures 'Security.salt' and 'Security.cipherSeed' are both non-empty and + * non-default values. + * @return boolean If the check passed + */ + public function verifySecurityKeys() { + // Pls no defaults + if (empty(Configure::read('Security.salt')) + || Configure::read('Security.salt') === 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi' + ) { + return 'Please update the Security.salt config value'; + } + + if (empty(Configure::read('Security.cipherSeed')) + || Configure::read('Security.cipherSeed') === '76859309657453542496749683645' + ) { + return 'Please update the Security.cipherSeed config value.'; + } + + return true; + } + + /** + * Check Database Connection + * + * Ensures the database connection to the InjectEngine + * is working + * @return boolean If the check passed + */ + public function checkDatabaseConnection() { + App::uses('ConnectionManager', 'Model'); + + // Check database connection for the InjectEngine + $conn = ConnectionManager::getDataSource('default'); + if (!$conn->isConnected()) { + return 'Unable to connect to the InjectEngine Database'; + } + + return true; + } + + /** + * Check Group Mappings + * + * Ensures 'GROUP_STAFF', 'GROUP_BLUE', 'GROUP_ADMINS', and 'GROUP_WHITE' + * have valid group mappings. + * @return boolean If the check passed + */ + public function checkGroupMappings() { + $GroupModel = ClassRegistry::init('Group'); + + // Check the group mappings + foreach (['GROUP_STAFF', 'GROUP_BLUE', 'GROUP_ADMINS', 'GROUP_WHITE'] as $group) { + $gid = env($group); + + if (empty($gid)) { + return 'Please setup a group mapping for '.$group; + } + + if (empty($GroupModel->findById($gid))) { + return sprintf('Invalid GID mapping for %s (GID: %d)', $group, $gid); + } + } + + return true; + } + + /** + * Check Inject Types + * + * Verifies that all the inject types listed in 'engine.inject_types' + * exist and are callable. + */ + public function checkInjectTypes() { + $ConfigModel = ClassRegistry::init('Config'); + $injectTypes = json_decode($ConfigModel->getKey('engine.inject_types')); + + if (json_last_error() != JSON_ERROR_NONE) { + return sprintf('JSON Error decoding "engine.inject_types": %s', json_last_error_msg()); + } + + // Should this be a warning? + if (empty($injectTypes)) { + return 'No inject types are configured (See config key: engine.inject_types)'; + } + + foreach ($injectTypes as $type) { + $className = sprintf('InjectTypes\\%s', $type); + + if (!class_exists($className)) { + return sprintf('Unknown inject type "%s" - does this file exist in "app/Vendor/InjectTypes"?', $type); + } + } + + return true; + } + + /** + * Check ScoreEngine DB + * + * Verifies the ScoreEngine DB is setup correctly + */ + public function checkScoringDB() { + App::uses('ConnectionManager', 'Model'); + + // Check database connection for the InjectEngine + $conn = ConnectionManager::getDataSource('scoreengine'); + if (!$conn->isConnected()) { + return 'Unable to connect to the ScoreEngine Database'; + } + + return true; + } + + /** + * Check ScoreEngine + * + * Verifies the ScoreEngine is setup correctly + */ + public function checkScoreEngine() { + $tables = ['Check', 'Round', 'Service', 'Team', 'TeamService']; + $missing_tables = []; + + foreach ($tables as $table) { + $tbl = ClassRegistry::init('BankWeb.'.$table); + + try { + $tbl->find('first'); + } catch (Exception $e) { + $missing_tables[] = $table; + } + } + + $missing = implode(', ', $missing_tables); + return !empty($missing_tables) ? 'ScoreEngine is not setup. Missing DB table(s): '.$missing : true; + } + + /** + * Check BankWeb + * + * Verifies some env variables are set, and that 'BANKWEB_PRODUCTS' exists + */ + public function checkBankWeb() { + foreach ([ + 'BANKAPI_SERVER', + 'BANKAPI_TIMEOUT', + 'BANKWEB_PRODUCTS', + 'BANKWEB_WHITETEAM_ACCOUNT', + 'BANKWEB_PUBLIC_APIINFO' + ] as $key) { + if (env($key) === null) { + return sprintf('Please setup the variable "%s" to use the BankWeb Feature.', $key); + } + } + + $products = ROOT . DS . env('BANKWEB_PRODUCTS'); + if (!file_exists($products)) { + return sprintf('Please make sure the file "%s" exists (as set in "BANKWEB_PRODUCTS")', $products); + } + + $contents = json_decode(file_get_contents($products)); + if (json_last_error() != JSON_ERROR_NONE) { + return sprintf('JSON Error with "BANKWEB_PRODUCTS" - %s', json_last_error_msg()); + } + + return true; + } + + /** + * Check BankWeb Table + * + * That the BankWeb Table (account_mappings) exists + */ + public function checkBankWebTable() { + $table = ClassRegistry::init('BankWeb.AccountMapping'); + + try { + $table->find('first'); + } catch (Exception $e) { + return 'BankWeb plugin is not setup - please run `./cake engine install_bankweb`'; + } + + return true; + } + + /** + * Check BankWeb Slack Configuration + * + * Verify that BankWeb's slack configuration is correct + */ + public function checkBankWebSlack() { + $url = env('SLACK_ENDPOINT'); + + $ch = curl_init(env('SLACK_ENDPOINT')); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($http_code != 400) { + return 'Invalid slack endpoint setup. Please verify that your Slack URL is correct.'; + } + return true; + } +} diff --git a/app/Controller/InjectsController.php b/app/Controller/InjectsController.php index 2ddab4f..4381bb3 100644 --- a/app/Controller/InjectsController.php +++ b/app/Controller/InjectsController.php @@ -2,258 +2,267 @@ App::uses('AppController', 'Controller'); class InjectsController extends AppController { - public $uses = ['Config', 'Hint', 'UsedHint', 'Schedule', 'Submission']; - - private $groups = []; - - public function beforeFilter() { - parent::beforeFilter(); - - // Enforce logins - $this->Auth->protect(); - - // Mark the tab as active - $this->set('at_injects', true); - - // Administrator blue team override - $this->groups = $this->Auth->item('groups'); - if ( $this->Auth->isStaff() ) { - $this->groups[] = (int) env('GROUP_BLUE'); - } - - // Load + setup the InjectStyler helper - $this->helpers[] = 'InjectStyler'; - $this->helpers['InjectStyler'] = [ - 'types' => $this->Config->getInjectTypes(), - 'inject' => new stdClass(), // Nothing...for now - ]; - } - - /** - * Inject Inbox Page - * - * @url /injects - * @url /injects/index - */ - public function index() { - if ( (bool)env('INJECT_INBOX_STREAM_VIEW') ) { - $this->set('injects', $this->Schedule->getInjects($this->groups)); - return $this->render('index_stream'); - } else { - return $this->render('index_list'); - } - } - - /** - * API Endpoint for Injects - * - * The Inject Inbox Page will call - * this endpoint every xx seconds. - * - * @url /injects/api - */ - public function api() { - return $this->ajaxResponse([ - 'injects' => $this->Schedule->getInjects($this->groups), - ]); - } - - /** - * Inject View Page - * - * @url /injects/view/ - */ - public function view($sid=false) { - $inject = $this->Schedule->getInject($sid, $this->groups); - if ( empty($inject) ) { - throw new NotFoundException('Unknown inject'); - } - - $submissions = $this->Submission->getSubmissions($inject->getInjectId(), $this->Auth->group('id')); - - // Setup the InjectStyler helper with the latest inject - $this->helpers['InjectStyler']['inject'] = $inject; - - $this->set('hints', $this->Hint->find('count', ['conditions' => ['inject_id' => $inject->getInjectId()]])); - $this->set('inject', $inject); - $this->set('submissions', $submissions); - } - - /** - * Inject Submission Endpoint - * - * @url /injects/submit - */ - public function submit() { - if ( !$this->request->is('post') || !isset($this->request->data['id'])) { - throw new MethodNotAllowedException('Unauthorized'); - } - - $inject = $this->Schedule->getInject($this->request->data['id'], $this->groups); - if ( empty($inject) ) { - throw new NotFoundException('Unknown inject'); - } - if ( !$inject->isAcceptingSubmissions() ) { - throw new UnauthorizedException('Inject is no longer accepting submissions'); - } - - // Create a new InjectType Manager - $typeManager = new InjectTypes\Manager($this->Config->getInjectTypes()); - $injectType = $typeManager->get($inject->getType()); - - if ( !$injectType->validateSubmission($inject, $this->request->data) ) { - throw new BadRequestException('Non-valid submission'); - } - - // We're good! Save it! - $this->Submission->create(); - $this->Submission->save([ - 'inject_id' => $inject->getInjectId(), - 'user_id' => $this->Auth->user('id'), - 'group_id' => $this->Auth->group('id'), - 'created' => time(), - 'data' => $injectType->handleSubmission($inject, $this->request->data), - ]); - - $this->logMessage( - 'submission', - sprintf('Created submission for Inject #%d', $inject->getSequence()), - [], - $this->Submission->id - ); - $this->Flash->success('Successfully submitted!'); - return $this->redirect('/injects/view/'.$inject->getScheduleId()); - } - - /** - * Delete Inject Submission - * - * @url /injects/delete/ - */ - public function delete($sid=false) { - $submission = $this->Submission->findById($sid); - if ( empty($submission) || !in_array($submission['Group']['id'], $this->groups) ) { - throw new NotFoundException('Unknown submission'); - } - - $this->Submission->id = $submission['Submission']['id']; - $this->Submission->save([ - 'deleted' => true, - ]); - - $this->logMessage( - 'submission', - sprintf('Deleted submission #%d on Inject #%d', $sid, $submission['Inject']['sequence']), - [ - 'user' => $this->Auth->user('username'), - ], - $sid - ); - $this->Flash->success('Successfully deleted the submission!'); - $this->redirect($this->referer()); - } - - /** - * View (download) Submission - * - * @url /injects/submission/ - */ - public function submission($sid=false) { - $submission = $this->Submission->getSubmission($sid, $this->Auth->group('id'), true); - if ( empty($submission) ) { - throw new NotFoundException('Unknown submission'); - } - - $data = json_decode($submission['Submission']['data'], true); - $download = (isset($this->params['url']['download']) && $this->params['url']['download'] == true); - - // Let's verify our data is correct - if ( md5(base64_decode($data['data'])) !== $data['hash'] ) { - throw new InternalErrorException('Data storage failure'); - } - - // Create the new response for the data - $response = new CakeResponse(); - $response->type($data['extension']); - $response->body(base64_decode($data['data'])); - $response->disableCache(); - - $type = ($download ? 'attachment' : 'inline'); - $filename = $data['filename']; - $response->header('Content-Disposition', $type.'; filename="'.$filename.'"'); - - return $response; - } - - /** - * API Endpoint for Hints - * - * Gets all the unlocked hints for an inject - * - * @url /injects/hints/ - */ - public function hints($id=false) { - $inject = $this->Schedule->getInject($id, $this->groups); - if ( empty($inject) ) { - throw new NotFoundException('Unknown inject'); - } - - $hints = $this->Hint->getHints($inject->getInjectId(), $this->Auth->group('id')); - if ( empty($hints) ) { - throw new NotFoundException('Unknown inject'); - } - - $this->layout = 'ajax'; - $this->set('inject', $inject); - $this->set('hints', $hints); - } - - /** - * API Endpoint to Unlock a Hint - * - * Unlocks a hint - * - * @url /injects/unlock_hint/ - */ - public function unlock_hint($sid=false, $id=false) { - $inject = $this->Schedule->getInject($sid, $this->groups); - if ( empty($inject) ) { - return $this->ajaxResponse(false); - } - - $hints = $this->Hint->getHints($inject->getInjectId(), $this->Auth->group('id')); - if ( empty($hints) ) { - return $this->ajaxResponse(false); - } - - // Find said hint, make sure we can unlock it - $hint_title = '-error-'; - foreach ( $hints AS $h ) { - if ( $h['Hint']['id'] != $id ) continue; - - if ( $h['Hint']['unlocked'] ) return $this->ajaxResponse(true); - if ( !$h['Hint']['dependency_met'] ) return $this->ajaxResponse(false); - if ( $h['Hint']['time_wait'] > 0 && $inject->getStart()+$h['Hint']['time_wait'] > time() ) return $this->ajaxResponse(false); - - $hint_title = $h['Hint']['title']; - } - - // If we got here, we're good - $this->UsedHint->create(); - $this->UsedHint->save([ - 'hint_id' => $id, - 'user_id' => $this->Auth->user('id'), - 'group_id' => $this->Auth->group('id'), - 'time' => time(), - ]); - - $this->logMessage( - 'hint', - sprintf('Unlocked Hint "%s" for Inject #%d', $hint_title, $inject->getSequence()), - [], - $id - ); - - return $this->ajaxResponse(true); - } + + public $uses = ['Config', 'Hint', 'UsedHint', 'Schedule', 'Submission']; + + private $groups = []; + + public function beforeFilter() { + parent::beforeFilter(); + + // Enforce logins + $this->Auth->protect(); + + // Mark the tab as active + $this->set('at_injects', true); + + // Administrator blue team override + $this->groups = $this->Auth->item('groups'); + if ($this->Auth->isStaff()) { + $this->groups[] = (int)env('GROUP_BLUE'); + } + + // Load + setup the InjectStyler helper + $this->helpers[] = 'InjectStyler'; + $this->helpers['InjectStyler'] = [ + 'types' => $this->Config->getInjectTypes(), + 'inject' => new stdClass(), // Nothing...for now + ]; + } + + /** + * Inject Inbox Page + * + * @url /injects + * @url /injects/index + */ + public function index() { + if ((bool)env('INJECT_INBOX_STREAM_VIEW')) { + $this->set('injects', $this->Schedule->getInjects($this->groups)); + return $this->render('index_stream'); + } else { + return $this->render('index_list'); + } + } + + /** + * API Endpoint for Injects + * + * The Inject Inbox Page will call + * this endpoint every xx seconds. + * + * @url /injects/api + */ + public function api() { + return $this->ajaxResponse([ + 'injects' => $this->Schedule->getInjects($this->groups), + ]); + } + + /** + * Inject View Page + * + * @url /injects/view/ + */ + public function view($sid = false) { + $inject = $this->Schedule->getInject($sid, $this->groups); + if (empty($inject)) { + throw new NotFoundException('Unknown inject'); + } + + $submissions = $this->Submission->getSubmissions($inject->getInjectId(), $this->Auth->group('id')); + + // Setup the InjectStyler helper with the latest inject + $this->helpers['InjectStyler']['inject'] = $inject; + + $this->set('hints', $this->Hint->find('count', ['conditions' => ['inject_id' => $inject->getInjectId()]])); + $this->set('inject', $inject); + $this->set('submissions', $submissions); + } + + /** + * Inject Submission Endpoint + * + * @url /injects/submit + */ + public function submit() { + if (!$this->request->is('post') || !isset($this->request->data['id'])) { + throw new MethodNotAllowedException('Unauthorized'); + } + + $inject = $this->Schedule->getInject($this->request->data['id'], $this->groups); + if (empty($inject)) { + throw new NotFoundException('Unknown inject'); + } + if (!$inject->isAcceptingSubmissions()) { + throw new UnauthorizedException('Inject is no longer accepting submissions'); + } + + // Create a new InjectType Manager + $typeManager = new InjectTypes\Manager($this->Config->getInjectTypes()); + $injectType = $typeManager->get($inject->getType()); + + if (!$injectType->validateSubmission($inject, $this->request->data)) { + throw new BadRequestException('Non-valid submission'); + } + + // We're good! Save it! + $this->Submission->create(); + $this->Submission->save([ + 'inject_id' => $inject->getInjectId(), + 'user_id' => $this->Auth->user('id'), + 'group_id' => $this->Auth->group('id'), + 'created' => time(), + 'data' => $injectType->handleSubmission($inject, $this->request->data), + ]); + + $this->logMessage( + 'submission', + sprintf('Created submission for Inject #%d', $inject->getSequence()), + [], + $this->Submission->id + ); + $this->Flash->success('Successfully submitted!'); + return $this->redirect('/injects/view/'.$inject->getScheduleId()); + } + + /** + * Delete Inject Submission + * + * @url /injects/delete/ + */ + public function delete($sid = false) { + $submission = $this->Submission->findById($sid); + if (empty($submission) || !in_array($submission['Group']['id'], $this->groups)) { + throw new NotFoundException('Unknown submission'); + } + + $this->Submission->id = $submission['Submission']['id']; + $this->Submission->save([ + 'deleted' => true, + ]); + + $this->logMessage( + 'submission', + sprintf('Deleted submission #%d on Inject #%d', $sid, $submission['Inject']['sequence']), + [ + 'user' => $this->Auth->user('username'), + ], + $sid + ); + $this->Flash->success('Successfully deleted the submission!'); + $this->redirect($this->referer()); + } + + /** + * View (download) Submission + * + * @url /injects/submission/ + */ + public function submission($sid = false) { + $submission = $this->Submission->getSubmission($sid, $this->Auth->group('id'), true); + if (empty($submission)) { + throw new NotFoundException('Unknown submission'); + } + + $data = json_decode($submission['Submission']['data'], true); + $download = (isset($this->params['url']['download']) && $this->params['url']['download'] == true); + + // Let's verify our data is correct + if (md5(base64_decode($data['data'])) !== $data['hash']) { + throw new InternalErrorException('Data storage failure'); + } + + // Create the new response for the data + $response = new CakeResponse(); + $response->type($data['extension']); + $response->body(base64_decode($data['data'])); + $response->disableCache(); + + $type = ($download ? 'attachment' : 'inline'); + $filename = $data['filename']; + $response->header('Content-Disposition', $type.'; filename="'.$filename.'"'); + + return $response; + } + + /** + * API Endpoint for Hints + * + * Gets all the unlocked hints for an inject + * + * @url /injects/hints/ + */ + public function hints($id = false) { + $inject = $this->Schedule->getInject($id, $this->groups); + if (empty($inject)) { + throw new NotFoundException('Unknown inject'); + } + + $hints = $this->Hint->getHints($inject->getInjectId(), $this->Auth->group('id')); + if (empty($hints)) { + throw new NotFoundException('Unknown inject'); + } + + $this->layout = 'ajax'; + $this->set('inject', $inject); + $this->set('hints', $hints); + } + + /** + * API Endpoint to Unlock a Hint + * + * Unlocks a hint + * + * @url /injects/unlock_hint/ + */ + public function unlock_hint($sid = false, $id = false) { + $inject = $this->Schedule->getInject($sid, $this->groups); + if (empty($inject)) { + return $this->ajaxResponse(false); + } + + $hints = $this->Hint->getHints($inject->getInjectId(), $this->Auth->group('id')); + if (empty($hints)) { + return $this->ajaxResponse(false); + } + + // Find said hint, make sure we can unlock it + $hint_title = '-error-'; + foreach ($hints as $h) { + if ($h['Hint']['id'] != $id) { + continue; + } + + if ($h['Hint']['unlocked']) { + return $this->ajaxResponse(true); + } + if (!$h['Hint']['dependency_met']) { + return $this->ajaxResponse(false); + } + if ($h['Hint']['time_wait'] > 0 && $inject->getStart() + $h['Hint']['time_wait'] > time()) { + return $this->ajaxResponse(false); + } + + $hint_title = $h['Hint']['title']; + } + + // If we got here, we're good + $this->UsedHint->create(); + $this->UsedHint->save([ + 'hint_id' => $id, + 'user_id' => $this->Auth->user('id'), + 'group_id' => $this->Auth->group('id'), + 'time' => time(), + ]); + + $this->logMessage( + 'hint', + sprintf('Unlocked Hint "%s" for Inject #%d', $hint_title, $inject->getSequence()), + [], + $id + ); + + return $this->ajaxResponse(true); + } } diff --git a/app/Controller/PagesController.php b/app/Controller/PagesController.php index 758fd40..e4caad5 100644 --- a/app/Controller/PagesController.php +++ b/app/Controller/PagesController.php @@ -2,33 +2,35 @@ App::uses('AppController', 'Controller'); class PagesController extends AppController { - public $uses = ['Config']; - /** - * Dynamic Index Page - * - * @url / - * @url /pages/index - */ - public function index() { - $this->set('at_home', true); - - $this->set('title', $this->Config->getKey('homepage.title')); - $this->set('body', $this->Config->getKey('homepage.body')); - } + public $uses = ['Config']; - /** - * Announcement Read Endpoint - * - * @url /pages/announcement_read - */ - public function announcement_read($aid=false) { - if ( $aid == false || !is_numeric($aid) ) return $this->ajaxResponse(null); + /** + * Dynamic Index Page + * + * @url / + * @url /pages/index + */ + public function index() { + $this->set('at_home', true); - $read = $this->Session->consume('read_announcements'); - $read[] = $aid; - $this->Session->write('read_announcements', $read); - - return $this->ajaxResponse(null); - } + $this->set('title', $this->Config->getKey('homepage.title')); + $this->set('body', $this->Config->getKey('homepage.body')); + } + + /** + * Announcement Read Endpoint + * + * @url /pages/announcement_read + */ + public function announcement_read($aid = false) { + if ($aid == false || !is_numeric($aid)) { return $this->ajaxResponse(null); + } + + $read = $this->Session->consume('read_announcements'); + $read[] = $aid; + $this->Session->write('read_announcements', $read); + + return $this->ajaxResponse(null); + } } diff --git a/app/Controller/StaffController.php b/app/Controller/StaffController.php index 7d45940..470064f 100644 --- a/app/Controller/StaffController.php +++ b/app/Controller/StaffController.php @@ -3,320 +3,333 @@ App::uses('InjectAbstraction', 'Lib'); class StaffController extends AppController { - public $uses = [ - 'Config', 'Inject', 'UsedHint', 'Hint', 'Log', - 'Grade', 'Group', 'Schedule', 'Submission', - ]; - - /** - * Pagination Settings - */ - public $paginate = [ - 'OnlyGraded' => [ - 'fields' => [ - 'Submission.id', 'Submission.created', 'Submission.deleted', - 'Inject.id', 'Inject.title', 'Inject.sequence', 'Inject.type', - 'User.username', 'Group.name', 'Group.team_number', - 'Grade.created', 'Grade.grade', 'Grade.comments', - 'Grader.username', - ], - 'joins' => [ - [ - 'table' => 'users', - 'alias' => 'Grader', - 'type' => 'LEFT', - 'conditions' => [ - 'Grader.id = Grade.grader_id', - ], - ] - ], - 'conditions' => [ - 'OR' => [ - 'Grade.created IS NOT NULL', - 'Submission.deleted' => true, - ], - ], - 'order' => [ - 'Grade.created' => 'DESC', - 'Submission.created' => 'DESC', - ], - ], - ]; - - public function beforeFilter() { - parent::beforeFilter(); - - // Enforce staff only - $this->Auth->protect(env('GROUP_STAFF')); - - // Load + setup the InjectStyler helper - $this->helpers[] = 'InjectStyler'; - $this->helpers['InjectStyler'] = [ - 'types' => $this->Config->getInjectTypes(), - 'inject' => new stdClass(), // Nothing...for now - ]; - - // Load+Setup ScoreEngine EngineOutputter, if ScoreEngine is enabled - if ( (bool)env('FEATURE_SCOREENGINE') ) { - $this->helpers[] = 'ScoreEngine.EngineOutputter'; - $this->uses = array_merge($this->uses, ['ScoreEngine.Check', 'ScoreEngine.Team', 'ScoreEngine.Service']); - - $this->helpers['ScoreEngine.EngineOutputter']['data'] = $this->Check->getChecksTable( - $this->Team->findAllByEnabled(true), - $this->Service->findAllByEnabled(true) - ); - } - - // We're at the staff page - $this->set('at_staff', true); - } - - /** - * Competition Overview Page - * - * @url /staff - * @url /staff/index - */ - public function index() { - // Static page - } - - /** - * Competition Overview API - * - * @url /staff/api - */ - public function api() { - $this->layout = 'ajax'; - - if ( (bool)env('FEATURE_SCOREENGINE') ) { - $this->uses[] = 'ScoreEngine.Round'; - $this->set('round', $this->Round->getLastRound()); - } - - $active_injects = $this->Schedule->getInjects(env('GROUP_BLUE')); - foreach ( $active_injects AS $i => $inject ) { - if ( $inject->isExpired() ) unset($active_injects[$i]); - } - - $this->set('active_injects', $active_injects); - $this->set('recent_expired', $this->Schedule->getRecentExpired(env('GROUP_BLUE'))); - $this->set('recent_logs', $this->Log->find('all', [ - 'fields' => [ - 'Log.id', 'Log.time', 'Log.type', 'Log.data', - 'Log.ip', 'Log.message', 'User.username', 'User.group_id', - ], - 'contain' => [ - 'User' => [ - 'Group.name', - ] - ], - 'limit' => 20, - 'order' => [ - 'Log.id' => 'DESC' - ], - ])); - } - - /** - * Inject View Page - * - * @url /staff/inject/ - */ - public function inject($id=false) { - $inject = $this->Inject->findById($id); - if ( empty($inject) ) { - throw new NotFoundException('Unknown Inject'); - } - - $inject = new InjectAbstraction($inject, 0); - - // Setup the InjectStyler helper with the latest inject - $this->helpers['InjectStyler']['inject'] = $inject; - - $this->set('hints', $this->Hint->find('count', ['conditions' => ['inject_id' => $inject->getInjectId()]])); - $this->set('inject', $inject); - } - - /** - * Grader Island Page - * - * @url /staff/graders - */ - public function graders() { - $this->set('ungraded', $this->Submission->getAllUngradedSubmissions()); - - $this->Paginator->settings += $this->paginate['OnlyGraded']; - $this->set('graded', $this->Paginator->paginate('Submission')); - } - - /** - * Submission Grade Page - * - * @url /staff/grade/ - */ - public function grade($sid=false) { - $submission = $this->Submission->getSubmission($sid); - if ( empty($submission) ) { - throw new NotFoundException('Unknown submission'); - } - - if ( $this->request->is('post') ) { - if ( - !isset($this->request->data['grade']) || - (empty($this->request->data['grade']) && $this->request->data['grade'] != 0) || - $this->request->data['grade'] > $submission['Inject']['max_points'] - ) { - $this->Flash->danger('Incomplete data. Please try again.'); - return $this->redirect('/staff/grade/'.$sid); - } - - $data = [ - 'grade' => $this->request->data['grade'], - 'comments' => isset($this->request->data['comments']) ? $this->request->data['comments'] : 'N/A', - ]; - $grade = $this->Grade->findBySubmissionId($sid); - - if ( empty($grade) ) { - $this->Grade->create(); - - $data['submission_id'] = $sid; - $data['grader_id'] = $this->Auth->user('id'); - $data['created'] = time(); - - $logMessage = sprintf('Graded submission #%d for %s', $sid, $submission['Group']['name']); - } else { - $this->Grade->id = $grade['Grade']['id']; - - $logMessage = sprintf('Edited submission #%d for %s', $sid, $submission['Group']['name']); - } - - // Save + log - $this->Grade->save($data); - $this->logMessage( - 'grading', - $logMessage, - [ - 'previous_grade' => (empty($grade) ? null : $grade['Grade']['grade']), - 'previous_comments' => (empty($grade) ? null : $grade['Grade']['comments']), - 'new_grade' => $data['grade'], - 'new_comments' => $data['comments'], - ], - $this->Grade->id - ); - - // Return home, ponyboy - $this->Flash->success('Saved!'); - return $this->redirect('/staff/graders'); - } - - $this->set('submission', $submission); - } - - /** - * View (download) Submission - * - * @url /staff/submission/ - */ - public function submission($sid=false) { - $submission = $this->Submission->getSubmission($sid); - if ( empty($submission) ) { - throw new NotFoundException('Unknown submission'); - } - - $data = json_decode($submission['Submission']['data'], true); - $download = (isset($this->params['url']['download']) && $this->params['url']['download'] == true); - - // Let's verify our data is correct - if ( md5(base64_decode($data['data'])) !== $data['hash'] ) { - throw new InternalErrorException('Data storage failure.'); - } - - // Create the new response for the data - $response = new CakeResponse(); - $response->type($data['extension']); - $response->body(base64_decode($data['data'])); - $response->disableCache(); - - $type = ($download ? 'attachment' : 'inline'); - $filename = $data['filename']; - $response->header('Content-Disposition', $type.'; filename="'.$filename.'"'); - - return $response; - } - - /** - * Export Grades - * - * @url /staff/export - */ - public function export() { - $blueTeams = array_merge($this->Group->getChildren(env('GROUP_BLUE')), [env('GROUP_BLUE')]); - $submissions = $this->Submission->getAllSubmissions($blueTeams, true); - $injects = $this->Schedule->getInjects($blueTeams, false); - $used_hints = $this->UsedHint->find('all'); - $out = []; - - // Lookup for hint deductions - $hintDeduction = function($team, $inject) use($used_hints) { - $deduction = 0; - - foreach ( $used_hints AS $h ) { - if ( $h['UsedHint']['group_id'] != $team ) continue; - if ( $h['Hint']['inject_id'] != $inject ) continue; - - $deduction += $h['Hint']['cost']; - } - - return $deduction; - }; - - // Grab all the injects - $seenInjects = []; - foreach ( $injects AS $i ) { - // We ignore injects with a sequence number of zero - if ( $i->getSequence() == 0 ) continue; - - // One sequence number pls - if ( in_array($i->getSequence(), $seenInjects) ) continue; - - $seenInjects[] = $i->getSequence(); - } - sort($seenInjects); - - // Build the header - $header = ['team_number']; - foreach ( $seenInjects AS $i ) { - $header[] = sprintf('inject_%d_grade', $i); - } - $out[] = implode(',', $header); - - // Parse the (fun) data - $scores = []; - foreach ( $submissions AS $s ) { - $tn = $s['Group']['team_number']; - $inject = $s['Inject']['sequence']; - - if ( !isset($scores[$tn]) ) { - $scores[$tn] = []; - } - - $grade = $s['Grade']['grade'] - $hintDeduction($tn, $s['Inject']['id']); - $scores[$tn][$inject] = $grade; - } - - // Output the grades - foreach ( $scores AS $team => $data ) { - $line = [$team]; - - foreach ( $seenInjects AS $i ) { - // Default the grade to 0 if it's not submitted - $line[] = isset($data[$i]) ? $data[$i] : 0; - } - - $out[] = implode(',', $line); - } - - return $this->ajaxResponse(implode(PHP_EOL, $out)); - } + + public $uses = [ + 'Config', 'Inject', 'UsedHint', 'Hint', 'Log', + 'Grade', 'Group', 'Schedule', 'Submission', + ]; + + /** + * Pagination Settings + */ + public $paginate = [ + 'OnlyGraded' => [ + 'fields' => [ + 'Submission.id', 'Submission.created', 'Submission.deleted', + 'Inject.id', 'Inject.title', 'Inject.sequence', 'Inject.type', + 'User.username', 'Group.name', 'Group.team_number', + 'Grade.created', 'Grade.grade', 'Grade.comments', + 'Grader.username', + ], + 'joins' => [ + [ + 'table' => 'users', + 'alias' => 'Grader', + 'type' => 'LEFT', + 'conditions' => [ + 'Grader.id = Grade.grader_id', + ], + ] + ], + 'conditions' => [ + 'OR' => [ + 'Grade.created IS NOT NULL', + 'Submission.deleted' => true, + ], + ], + 'order' => [ + 'Grade.created' => 'DESC', + 'Submission.created' => 'DESC', + ], + ], + ]; + + public function beforeFilter() { + parent::beforeFilter(); + + // Enforce staff only + $this->Auth->protect(env('GROUP_STAFF')); + + // Load + setup the InjectStyler helper + $this->helpers[] = 'InjectStyler'; + $this->helpers['InjectStyler'] = [ + 'types' => $this->Config->getInjectTypes(), + 'inject' => new stdClass(), // Nothing...for now + ]; + + // Load+Setup ScoreEngine EngineOutputter, if ScoreEngine is enabled + if ((bool)env('FEATURE_SCOREENGINE')) { + $this->helpers[] = 'ScoreEngine.EngineOutputter'; + $this->uses = array_merge($this->uses, ['ScoreEngine.Check', 'ScoreEngine.Team', 'ScoreEngine.Service']); + + $this->helpers['ScoreEngine.EngineOutputter']['data'] = $this->Check->getChecksTable( + $this->Team->findAllByEnabled(true), + $this->Service->findAllByEnabled(true) + ); + } + + // We're at the staff page + $this->set('at_staff', true); + } + + /** + * Competition Overview Page + * + * @url /staff + * @url /staff/index + */ + public function index() { + // Static page + } + + /** + * Competition Overview API + * + * @url /staff/api + */ + public function api() { + $this->layout = 'ajax'; + + if ((bool)env('FEATURE_SCOREENGINE')) { + $this->uses[] = 'ScoreEngine.Round'; + $this->set('round', $this->Round->getLastRound()); + } + + $active_injects = $this->Schedule->getInjects(env('GROUP_BLUE')); + foreach ($active_injects as $i => $inject) { + if ($inject->isExpired()) { unset($active_injects[$i]); + } + } + + $this->set('active_injects', $active_injects); + $this->set('recent_expired', $this->Schedule->getRecentExpired(env('GROUP_BLUE'))); + $this->set('recent_logs', $this->Log->find('all', [ + 'fields' => [ + 'Log.id', 'Log.time', 'Log.type', 'Log.data', + 'Log.ip', 'Log.message', 'User.username', 'User.group_id', + ], + 'contain' => [ + 'User' => [ + 'Group.name', + ] + ], + 'limit' => 20, + 'order' => [ + 'Log.id' => 'DESC' + ], + ])); + } + + /** + * Inject View Page + * + * @url /staff/inject/ + */ + public function inject($id = false) { + $inject = $this->Inject->findById($id); + if (empty($inject)) { + throw new NotFoundException('Unknown Inject'); + } + + $inject = new InjectAbstraction($inject, 0); + + // Setup the InjectStyler helper with the latest inject + $this->helpers['InjectStyler']['inject'] = $inject; + + $this->set('hints', $this->Hint->find('count', ['conditions' => ['inject_id' => $inject->getInjectId()]])); + $this->set('inject', $inject); + } + + /** + * Grader Island Page + * + * @url /staff/graders + */ + public function graders() { + $this->set('ungraded', $this->Submission->getAllUngradedSubmissions()); + + $this->Paginator->settings += $this->paginate['OnlyGraded']; + $this->set('graded', $this->Paginator->paginate('Submission')); + } + + /** + * Submission Grade Page + * + * @url /staff/grade/ + */ + public function grade($sid = false) { + $submission = $this->Submission->getSubmission($sid); + if (empty($submission)) { + throw new NotFoundException('Unknown submission'); + } + + if ($this->request->is('post')) { + if (!isset($this->request->data['grade']) + || (empty($this->request->data['grade']) && $this->request->data['grade'] != 0) + || $this->request->data['grade'] > $submission['Inject']['max_points'] + ) { + $this->Flash->danger('Incomplete data. Please try again.'); + return $this->redirect('/staff/grade/'.$sid); + } + + $data = [ + 'grade' => $this->request->data['grade'], + 'comments' => isset($this->request->data['comments']) ? $this->request->data['comments'] : 'N/A', + ]; + $grade = $this->Grade->findBySubmissionId($sid); + + if (empty($grade)) { + $this->Grade->create(); + + $data['submission_id'] = $sid; + $data['grader_id'] = $this->Auth->user('id'); + $data['created'] = time(); + + $logMessage = sprintf('Graded submission #%d for %s', $sid, $submission['Group']['name']); + } else { + $this->Grade->id = $grade['Grade']['id']; + + $logMessage = sprintf('Edited submission #%d for %s', $sid, $submission['Group']['name']); + } + + // Save + log + $this->Grade->save($data); + $this->logMessage( + 'grading', + $logMessage, + [ + 'previous_grade' => (empty($grade) ? null : $grade['Grade']['grade']), + 'previous_comments' => (empty($grade) ? null : $grade['Grade']['comments']), + 'new_grade' => $data['grade'], + 'new_comments' => $data['comments'], + ], + $this->Grade->id + ); + + // Return home, ponyboy + $this->Flash->success('Saved!'); + return $this->redirect('/staff/graders'); + } + + $this->set('submission', $submission); + } + + /** + * View (download) Submission + * + * @url /staff/submission/ + */ + public function submission($sid = false) { + $submission = $this->Submission->getSubmission($sid); + if (empty($submission)) { + throw new NotFoundException('Unknown submission'); + } + + $data = json_decode($submission['Submission']['data'], true); + $download = (isset($this->params['url']['download']) && $this->params['url']['download'] == true); + + // Let's verify our data is correct + if (md5(base64_decode($data['data'])) !== $data['hash']) { + throw new InternalErrorException('Data storage failure.'); + } + + // Create the new response for the data + $response = new CakeResponse(); + $response->type($data['extension']); + $response->body(base64_decode($data['data'])); + $response->disableCache(); + + $type = ($download ? 'attachment' : 'inline'); + $filename = $data['filename']; + $response->header('Content-Disposition', $type.'; filename="'.$filename.'"'); + + return $response; + } + + /** + * Export Grades + * + * @url /staff/export + */ + public function export() { + $blueTeams = array_merge($this->Group->getChildren(env('GROUP_BLUE')), [env('GROUP_BLUE')]); + $submissions = $this->Submission->getAllSubmissions($blueTeams, true); + $injects = $this->Schedule->getInjects($blueTeams, false); + $used_hints = $this->UsedHint->find('all'); + $out = []; + + // Lookup for hint deductions + $hintDeductions = []; + foreach ($used_hints as $h) { + $team_id = $h['UsedHint']['group_id']; + $inject_id = $h['Hint']['inject_id']; + + if (!isset($hintDeductions[$team_id])) { + $hintDeductions[$team_id] = []; + } + if (!isset($hintDeductions[$team_id][$inject_id])) { + $hintDeductions[$team_id][$inject_id] = 0; + } + + $hintDeductions[$team_id][$inject_id] += $h['Hint']['cost']; + } + + // Grab all the injects + $seenInjects = []; + foreach ($injects as $i) { + // We ignore injects with a sequence number of zero + if ($i->getSequence() == 0) { + continue; + } + + // One sequence number pls + if (in_array($i->getSequence(), $seenInjects)) { + continue; + } + + $seenInjects[] = $i->getSequence(); + } + sort($seenInjects); + + // Build the header + $header = ['team_number']; + foreach ($seenInjects as $i) { + $header[] = sprintf('inject_%d_grade', $i); + } + $out[] = implode(',', $header); + + // Parse the (fun) data + $scores = []; + foreach ($submissions as $s) { + $tn = $s['Group']['team_number']; + $in = $s['Inject']['id']; + $inject = $s['Inject']['sequence']; + + if (!isset($scores[$tn])) { + $scores[$tn] = []; + } + + $deduction = 0; + if (isset($hintDeductions[$tn]) && isset($hintDeductions[$tn][$in])) { + $deduction = $hintDeductions[$tn][$in]; + } + + $grade = $s['Grade']['grade'] - $deduction; + $scores[$tn][$inject] = $grade; + } + + // Output the grades + foreach ($scores as $team => $data) { + $line = [$team]; + + foreach ($seenInjects as $i) { + // Default the grade to 0 if it's not submitted + $line[] = isset($data[$i]) ? $data[$i] : 0; + } + + $out[] = implode(',', $line); + } + + return $this->ajaxResponse(implode(PHP_EOL, $out)); + } } diff --git a/app/Controller/UserController.php b/app/Controller/UserController.php index b759ce5..fcf42aa 100644 --- a/app/Controller/UserController.php +++ b/app/Controller/UserController.php @@ -2,113 +2,115 @@ App::uses('AppController', 'Controller'); class UserController extends AppController { - public $uses = ['Config', 'Group', 'User']; - - /** - * User Login Page - * - * @url /user/login - */ - public function login() { - if ( $this->Auth->loggedIn() ) return $this->redirect('/'); - - $username = ''; - - if ( $this->request->is('post') ) { - $username = $this->request->data['username']; - $password = $this->request->data['password']; - - if ( $this->Auth->login($username, $password) ) { - $this->logMessage('users', 'User has logged in'); - - return $this->redirect($this->Auth->redirectURL()); - } - - $this->logMessage('users', sprintf('Failed login for "%s"', htmlentities($username))); - $this->Flash->danger('Unknown username or password!'); - } - - $this->set('at_login', true); - $this->set('username', $username); - } - - /** - * User Logout Page - * - * @url /user/logout - */ - public function logout() { - $this->Auth->protect(); - $this->Auth->logout(); - - return $this->redirect('/'); - } - - /** - * User Profile Page - * - * @url /user/profile - */ - public function profile() { - $this->Auth->protect(); - - $canChangePassword = ($this->Auth->isBlueTeam() ? (bool)env('FEATURE_BLUE_PASSWORD_CHANGES') : true); - - if ( $this->request->is('post') && $canChangePassword ) { - // Update Password - $old_password = $this->request->data['old_password']; - $new_password = $this->request->data['new_password']; - $new_password2 = $this->request->data['new_password2']; - - if ( $new_password != $new_password2 ) { - $this->Flash->danger('Your new password does not match.'); - } else { - // Fetch the current password - $user = $this->User->findById($this->Auth->user('id')); - $cur_password = $user['User']['password']; - - if ( Security::hash($old_password, 'blowfish', $cur_password) === $cur_password ) { - // Update password - $this->User->id = $this->Auth->user('id'); - $this->User->save([ - 'password' => $new_password, - ]); - - // Log it - $this->logMessage('users', 'Updated his/her password'); - - // Message it - $this->Flash->success('Profile updated!'); - } else { - $this->Flash->danger('You entered the wrong current password.'); - } - } - } - - $this->set('at_profile', true); - $this->set('password_change_enabled', $canChangePassword); - $this->set('group_path', $this->Group->getGroupPath($this->Auth->group('id'))); - } - - /** - * User Emulation Clear Page - * - * @url /user/emulate_clear - */ - public function emulate_clear() { - $this->Auth->protect(); - - if ( $this->Auth->item('emulating') == true ) { - $oldUID = $this->Auth->user('id'); - $oldUser = $this->Auth->user('username'); - - $this->Auth->emulateExit(); - - $msg = sprintf('Finished emulating user %s', $oldUser); - $this->logMessage('emulate', $msg, [], $oldUID); - $this->Flash->success($msg.'!'); - } - - return $this->redirect('/'); - } + + public $uses = ['Config', 'Group', 'User']; + + /** + * User Login Page + * + * @url /user/login + */ + public function login() { + if ($this->Auth->loggedIn()) { return $this->redirect('/'); + } + + $username = ''; + + if ($this->request->is('post')) { + $username = $this->request->data['username']; + $password = $this->request->data['password']; + + if ($this->Auth->login($username, $password)) { + $this->logMessage('users', 'User has logged in'); + + return $this->redirect($this->Auth->redirectURL()); + } + + $this->logMessage('users', sprintf('Failed login for "%s"', htmlentities($username))); + $this->Flash->danger('Unknown username or password!'); + } + + $this->set('at_login', true); + $this->set('username', $username); + } + + /** + * User Logout Page + * + * @url /user/logout + */ + public function logout() { + $this->Auth->protect(); + $this->Auth->logout(); + + return $this->redirect('/'); + } + + /** + * User Profile Page + * + * @url /user/profile + */ + public function profile() { + $this->Auth->protect(); + + $canChangePassword = ($this->Auth->isBlueTeam() ? (bool)env('FEATURE_BLUE_PASSWORD_CHANGES') : true); + + if ($this->request->is('post') && $canChangePassword) { + // Update Password + $old_password = $this->request->data['old_password']; + $new_password = $this->request->data['new_password']; + $new_password2 = $this->request->data['new_password2']; + + if ($new_password != $new_password2) { + $this->Flash->danger('Your new password does not match.'); + } else { + // Fetch the current password + $user = $this->User->findById($this->Auth->user('id')); + $cur_password = $user['User']['password']; + + if (Security::hash($old_password, 'blowfish', $cur_password) === $cur_password) { + // Update password + $this->User->id = $this->Auth->user('id'); + $this->User->save([ + 'password' => $new_password, + ]); + + // Log it + $this->logMessage('users', 'Updated his/her password'); + + // Message it + $this->Flash->success('Profile updated!'); + } else { + $this->Flash->danger('You entered the wrong current password.'); + } + } + } + + $this->set('at_profile', true); + $this->set('password_change_enabled', $canChangePassword); + $this->set('group_path', $this->Group->getGroupPath($this->Auth->group('id'))); + } + + /** + * User Emulation Clear Page + * + * @url /user/emulate_clear + */ + public function emulate_clear() { + $this->Auth->protect(); + + if ($this->Auth->item('emulating') == true) { + $oldUID = $this->Auth->user('id'); + $oldUser = $this->Auth->user('username'); + + $this->Auth->emulateExit(); + + $msg = sprintf('Finished emulating user %s', $oldUser); + $this->logMessage('emulate', $msg, [], $oldUID); + $this->Flash->success($msg.'!'); + } + + return $this->redirect('/'); + } } diff --git a/app/Lib/InjectAbstraction.php b/app/Lib/InjectAbstraction.php index c5ac7cd..e3658f5 100644 --- a/app/Lib/InjectAbstraction.php +++ b/app/Lib/InjectAbstraction.php @@ -7,293 +7,298 @@ * This represents a specific inject */ class InjectAbstraction implements JsonSerializable { - /** - * Copy of the data returned from the - * Schedule model - */ - private $data; - - /** - * Default date string output - */ - const DATE_FORMAT = 'F j, Y \a\t g:iA'; - - /** - * The time for an active inject - * to be considered 'recent' - */ - const ACTIVE_RECENT = 5 * 60; - - /** - * The time for an expired inject - * to be considered 'recent' - */ - const EXPIRED_RECENT = 30 * 60; - - /** - * String used when an inject - * starts immediately - */ - const STR_IMMEDIATELY = 'Immediately'; - - /** - * String used when an inject - * never ends - */ - const STR_NEVER = 'Never'; - - /** - * Inject Constructor - * - * @param $data Data returned from the model - * @param $submissionCount Count of how many submissions - */ - public function __construct($data, $submissionCount) { - $this->data = $data; - $this->data['Schedule']['submission_count'] = $submissionCount; - } - - /** - * Is Recent Accessor - * - * @return bool If the inject is 'recent' - */ - public function isRecent() { - $time = ($this->isExpired() ? $this->getEnd() : $this->getStart()); - $recent = ($this->isExpired() ? self::EXPIRED_RECENT : self::ACTIVE_RECENT); - $howLongAgo = (time() - $time); - - return ($time > 0 && $recent >= $howLongAgo); - } - - /** - * Is Expired Accessor - * - * @return bool If the inject has expired - */ - public function isExpired() { - return ($this->getEnd() > 0 ? $this->getEnd() <= time() : false); - } - - /** - * Is Fuzzy Accessor - * - * @return bool If the inject is fuzzy scheduled - */ - public function isFuzzy() { - return $this->getScheduleFuzzy(); - } - - /** - * Is Accepting Submissions - * - * @return bool If the inject can be submitted - */ - public function isAcceptingSubmissions() { - return ($this->isExpired() == false && $this->getRemainingSubmissions() > 0); - } - - /** - * Remaining Submissions Accessor - * - * @return int The number of remaining submissions - */ - public function getRemainingSubmissions() { - return $this->getMaxSubmissions() - $this->getSubmissionCount(); - } - - /** - * Inject Start Accessor - * - * @return int Unix timestamp of the start - * of this inject. - */ - public function getStart() { - $start = $this->getScheduleStart(); - - if ( $this->isFuzzy() && $start > 0 ) { - $start += COMPETITION_START; - } - - return $start; - } - public function getStartString() { - return ($this->getStart() > 0 - ? tz_date(self::DATE_FORMAT, $this->getStart()) : self::STR_IMMEDIATELY); - } - public function getManagerStartString() { - if ( !$this->isFuzzy() || $this->getStart() == 0 ) return $this->getStartString(); - - return $this->_fuzzyDuration('+', $this->getStart()); - } - - /** - * Inject End Accessor - * - * @return int Unix timestamp of the end - * of this inject. - */ - public function getEnd() { - $end = $this->getScheduleEnd(); - - if ( $this->isFuzzy() && $end > 0 ) { - $end += COMPETITION_START; - } - - return $end; - } - public function getEndString() { - return ($this->getEnd() > 0 - ? tz_date(self::DATE_FORMAT, $this->getEnd()) : self::STR_NEVER); - } - public function getManagerEndString() { - if ( !$this->isFuzzy() || $this->getEnd() == 0 ) return $this->getEndString(); - - return $this->_fuzzyDuration('+', $this->getEnd()); - } - - /** - * Inject Duration Accessor - * - * @return int The inject duration in the - * form of minutes - */ - public function getDuration() { - if ( $this->getEnd() == 0 ) return 0; - if ( $this->getStart() == 0 ) return -1; - - $duration = ($this->getEnd() - $this->getStart()); - return round($duration / 60); - } - public function getDurationString() { - $duration = $this->getDuration(); - - if ( $duration < 0 ) { - return 'N/A'; - } else if ( $duration == 0 ) { - return '∞'; - } else { - return $this->_fuzzyDuration('', $this->getEnd(), $this->getStart()); - } - } - - /** - * Has Attachments - * - * @return bool If the inject has attachments - */ - public function hasAttachments() { - return isset($this->data['Inject']['Attachment']) && count($this->data['Inject']['Attachment']) > 0; - } - - /** - * Get Attachments - * - * @return array The inject attachments - */ - public function getAttachments() { - if ( !$this->hasAttachments() ) { - return []; - } - - $attachments = []; - foreach ( $this->data['Inject']['Attachment'] AS $a ) { - $attachments[] = [ - 'id' => $a['id'], - 'name' => $a['name'], - ]; - } - - return $attachments; - } - - /** - * Generic accessor method - * - * This method will capture all "getSOMETHING" - * method calls - * - * @return mixed The data you're looking for - */ - public function __call($name, $args) { - if ( count($args) > 0 ) return; - if ( substr($name, 0, 3) != 'get' ) return; - - $key = Inflector::underscore(substr($name, 3)); - - // Deal with a possible call that includes the - // group (ex: getGroupName -> ['Group']['name']) - if ( ($pos = strpos($key, '_')) !== false ) { - $grp = ucfirst(substr($key, 0, $pos)); - $subkey = substr($key, $pos+1); - - if ( isset($this->data[$grp][$subkey]) ) { - return $this->data[$grp][$subkey]; - } - } - - // Fallback to matching everything - foreach ( $this->data AS $m => $data ) { - if ( isset($data[$key]) ) { - return $data[$key]; - } - } - - return null; - } - - /** - * JSON Serialize Method - * - * This method gets called when we - * json_encode this object - * - * @return array Data to be serialized - */ - public function jsonSerialize() { - return [ - 'id' => $this->getScheduleId(), - 'title' => $this->getTitle(), - 'start' => $this->getStartString(), - 'end' => $this->getEndString(), - 'expired' => $this->isExpired(), - 'submitted' => ($this->getSubmissionCount() > 0), - ]; - } - - /** - * Fuzzy Duration Generator - * - * @source http://stackoverflow.com/a/18602474 - * @param $time The time you're checking - * @return string The fuzzy time - */ - private function _fuzzyDuration($prepend, $time, $start=COMPETITION_START) { - $start = new DateTime('@'.$start); - $end = new DateTime('@'.$time); - $diff = $end->diff($start); - - $diff->w = floor($diff->d / 7); - $diff->d -= $diff->w * 7; - - $string = [ - 'y' => 'year', - 'm' => 'month', - 'w' => 'week', - 'd' => 'day', - 'h' => 'hour', - 'i' => 'minute', - 's' => 'second', - ]; - - foreach ($string as $k => &$v) { - if ( $diff->$k ) { - $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : ''); - } else { - unset($string[$k]); - } - } - - return $prepend.implode(', ', $string); - } -} \ No newline at end of file + + /** + * Copy of the data returned from the + * Schedule model + */ + private $data; + + /** + * Default date string output + */ + const DATE_FORMAT = 'F j, Y \a\t g:iA'; + + /** + * The time for an active inject + * to be considered 'recent' + */ + const ACTIVE_RECENT = 5 * 60; + + /** + * The time for an expired inject + * to be considered 'recent' + */ + const EXPIRED_RECENT = 30 * 60; + + /** + * String used when an inject + * starts immediately + */ + const STR_IMMEDIATELY = 'Immediately'; + + /** + * String used when an inject + * never ends + */ + const STR_NEVER = 'Never'; + + /** + * Inject Constructor + * + * @param $data Data returned from the model + * @param $submissionCount Count of how many submissions + */ + public function __construct($data, $submissionCount) { + $this->data = $data; + $this->data['Schedule']['submission_count'] = $submissionCount; + } + + /** + * Is Recent Accessor + * + * @return bool If the inject is 'recent' + */ + public function isRecent() { + $time = ($this->isExpired() ? $this->getEnd() : $this->getStart()); + $recent = ($this->isExpired() ? self::EXPIRED_RECENT : self::ACTIVE_RECENT); + $howLongAgo = (time() - $time); + + return ($time > 0 && $recent >= $howLongAgo); + } + + /** + * Is Expired Accessor + * + * @return bool If the inject has expired + */ + public function isExpired() { + return ($this->getEnd() > 0 ? $this->getEnd() <= time() : false); + } + + /** + * Is Fuzzy Accessor + * + * @return bool If the inject is fuzzy scheduled + */ + public function isFuzzy() { + return $this->getScheduleFuzzy(); + } + + /** + * Is Accepting Submissions + * + * @return bool If the inject can be submitted + */ + public function isAcceptingSubmissions() { + return ($this->isExpired() == false && $this->getRemainingSubmissions() > 0); + } + + /** + * Remaining Submissions Accessor + * + * @return int The number of remaining submissions + */ + public function getRemainingSubmissions() { + return $this->getMaxSubmissions() - $this->getSubmissionCount(); + } + + /** + * Inject Start Accessor + * + * @return int Unix timestamp of the start + * of this inject. + */ + public function getStart() { + $start = $this->getScheduleStart(); + + if ($this->isFuzzy() && $start > 0) { + $start += COMPETITION_START; + } + + return $start; + } + public function getStartString() { + return ($this->getStart() > 0 ? tz_date(self::DATE_FORMAT, $this->getStart()) : self::STR_IMMEDIATELY); + } + public function getManagerStartString() { + if (!$this->isFuzzy() || $this->getStart() == 0) { return $this->getStartString(); + } + + return $this->fuzzyDuration('+', $this->getStart()); + } + + /** + * Inject End Accessor + * + * @return int Unix timestamp of the end + * of this inject. + */ + public function getEnd() { + $end = $this->getScheduleEnd(); + + if ($this->isFuzzy() && $end > 0) { + $end += COMPETITION_START; + } + + return $end; + } + public function getEndString() { + return ($this->getEnd() > 0 ? tz_date(self::DATE_FORMAT, $this->getEnd()) : self::STR_NEVER); + } + public function getManagerEndString() { + if (!$this->isFuzzy() || $this->getEnd() == 0) { return $this->getEndString(); + } + + return $this->fuzzyDuration('+', $this->getEnd()); + } + + /** + * Inject Duration Accessor + * + * @return int The inject duration in the + * form of minutes + */ + public function getDuration() { + if ($this->getEnd() == 0) { return 0; + } + if ($this->getStart() == 0) { return -1; + } + + $duration = ($this->getEnd() - $this->getStart()); + return round($duration / 60); + } + public function getDurationString() { + $duration = $this->getDuration(); + + if ($duration < 0) { + return 'N/A'; + } elseif ($duration == 0) { + return '∞'; + } else { + return $this->fuzzyDuration('', $this->getEnd(), $this->getStart()); + } + } + + /** + * Has Attachments + * + * @return bool If the inject has attachments + */ + public function hasAttachments() { + return isset($this->data['Inject']['Attachment']) && count($this->data['Inject']['Attachment']) > 0; + } + + /** + * Get Attachments + * + * @return array The inject attachments + */ + public function getAttachments() { + if (!$this->hasAttachments()) { + return []; + } + + $attachments = []; + foreach ($this->data['Inject']['Attachment'] as $a) { + $attachments[] = [ + 'id' => $a['id'], + 'name' => $a['name'], + ]; + } + + return $attachments; + } + + /** + * Generic accessor method + * + * This method will capture all "getSOMETHING" + * method calls + * + * @return mixed The data you're looking for + */ + public function __call($name, $args) { + if (count($args) > 0) { return; + } + if (substr($name, 0, 3) != 'get') { return; + } + + $key = Inflector::underscore(substr($name, 3)); + + // Deal with a possible call that includes the + // group (ex: getGroupName -> ['Group']['name']) + if (($pos = strpos($key, '_')) !== false) { + $grp = ucfirst(substr($key, 0, $pos)); + $subkey = substr($key, $pos + 1); + + if (isset($this->data[$grp][$subkey])) { + return $this->data[$grp][$subkey]; + } + } + + // Fallback to matching everything + foreach ($this->data as $m => $data) { + if (isset($data[$key])) { + return $data[$key]; + } + } + + return null; + } + + /** + * JSON Serialize Method + * + * This method gets called when we + * json_encode this object + * + * @return array Data to be serialized + */ + public function jsonSerialize() { + return [ + 'id' => $this->getScheduleId(), + 'title' => $this->getTitle(), + 'start' => $this->getStartString(), + 'end' => $this->getEndString(), + 'expired' => $this->isExpired(), + 'submitted' => ($this->getSubmissionCount() > 0), + ]; + } + + /** + * Fuzzy Duration Generator + * + * @source http://stackoverflow.com/a/18602474 + * @param $time The time you're checking + * @return string The fuzzy time + */ + private function fuzzyDuration($prepend, $time, $start = COMPETITION_START) { + $start = new DateTime('@'.$start); + $end = new DateTime('@'.$time); + $diff = $end->diff($start); + + $diff->w = floor($diff->d / 7); + $diff->d -= $diff->w * 7; + + $string = [ + 'y' => 'year', + 'm' => 'month', + 'w' => 'week', + 'd' => 'day', + 'h' => 'hour', + 'i' => 'minute', + 's' => 'second', + ]; + + foreach ($string as $k => &$v) { + if ($diff->$k) { + $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : ''); + } else { + unset($string[$k]); + } + } + + return $prepend.implode(', ', $string); + } +} diff --git a/app/Lib/InjectTypes/FileSubmission.php b/app/Lib/InjectTypes/FileSubmission.php index 2634197..b66be14 100644 --- a/app/Lib/InjectTypes/FileSubmission.php +++ b/app/Lib/InjectTypes/FileSubmission.php @@ -2,18 +2,19 @@ namespace InjectTypes; class FileSubmission extends InjectSubmissionBase { - private $acceptedExtensions = ['pdf', 'doc', 'docx']; - public function getID() { - return 'file'; - } + private $acceptedExtensions = ['pdf', 'doc', 'docx']; - public function getName() { - return 'File Submission'; - } + public function getID() { + return 'file'; + } - public function getTemplate() { - return <<<'TEMPLATE' + public function getName() { + return 'File Submission'; + } + + public function getTemplate() { + return <<<'TEMPLATE'

@@ -22,68 +23,71 @@ public function getTemplate() {
TEMPLATE; - } - - public function getSubmittedTemplate($submissions) { - $tpl = '
    '; - - foreach ( $submissions AS $s ) { - $urlDelete = $this->_url('/injects/delete/'.$s['Submission']['id']); - $urlDownload = $this->_url('/injects/submission/'.$s['Submission']['id']); - $d = json_decode($s['Submission']['data'], true); - - $tpl .= '
  • '. - '

    '. - 'Submission on '.$this->_date($s['Submission']['created']). - 'Delete

    '. - '

    File: '.$d['filename'].'

    '. - '
  • '; - } - - if ( empty($submissions) ) { - $tpl .= '
  • '. - '

    No submissions.

    '. - '
  • '; - } - - $tpl .= '
'; - return $tpl; - } - - public function getGraderTemplate($s) { - $data = json_decode($s['Submission']['data'], true); - $url = $this->_url('/staff/submission/'.$s['Submission']['id']); - - $rtn = 'Submission Download ('.$data['filename'].')'; - if ( $data['extension'] != 'pdf' ) { - return $rtn; - } - - $rtn .= '
'. - ''. - '
'; - - return $rtn; - } - - public function validateSubmission($inject, $submission) { - return ( - isset($submission['content']) && - !empty($submission['content']) && - is_uploaded_file($submission['content']['tmp_name']) && - in_array(pathinfo($submission['content']['name'], PATHINFO_EXTENSION), $this->acceptedExtensions) - ); - } - - public function handleSubmission($inject, $submission) { - $uploadedFile = $submission['content']; - $contents = file_get_contents($uploadedFile['tmp_name']); - - return json_encode([ - 'filename' => htmlentities($uploadedFile['name']), - 'extension' => pathinfo($uploadedFile['name'], PATHINFO_EXTENSION), - 'hash' => md5($contents), - 'data' => base64_encode($contents), - ]); - } -} \ No newline at end of file + } + + public function getSubmittedTemplate($submissions) { + $tpl = '
    '; + + foreach ($submissions as $s) { + $urlDelete = $this->url('/injects/delete/'.$s['Submission']['id']); + $urlDownload = $this->url('/injects/submission/'.$s['Submission']['id']); + $d = json_decode($s['Submission']['data'], true); + + $tpl .= '
  • '. + '

    '. + 'Submission on '.$this->date($s['Submission']['created']). + 'Delete

    '. + '

    '. + 'File: '.$d['filename'].''. + '

    '. + '
  • '; + } + + if (empty($submissions)) { + $tpl .= '
  • '. + '

    No submissions.

    '. + '
  • '; + } + + $tpl .= '
'; + return $tpl; + } + + public function getGraderTemplate($s) { + $data = json_decode($s['Submission']['data'], true); + $url = $this->url('/staff/submission/'.$s['Submission']['id']); + + $rtn = ''. + 'Submission Download ('.$data['filename'].')'; + if ($data['extension'] != 'pdf') { + return $rtn; + } + + $rtn .= '
'. + ''. + '
'; + + return $rtn; + } + + public function validateSubmission($inject, $submission) { + return ( + isset($submission['content']) && + !empty($submission['content']) && + is_uploaded_file($submission['content']['tmp_name']) && + in_array(pathinfo($submission['content']['name'], PATHINFO_EXTENSION), $this->acceptedExtensions) + ); + } + + public function handleSubmission($inject, $submission) { + $uploadedFile = $submission['content']; + $contents = file_get_contents($uploadedFile['tmp_name']); + + return json_encode([ + 'filename' => htmlentities($uploadedFile['name']), + 'extension' => pathinfo($uploadedFile['name'], PATHINFO_EXTENSION), + 'hash' => md5($contents), + 'data' => base64_encode($contents), + ]); + } +} diff --git a/app/Lib/InjectTypes/FlagSubmission.php b/app/Lib/InjectTypes/FlagSubmission.php index 72d5be2..43a827f 100644 --- a/app/Lib/InjectTypes/FlagSubmission.php +++ b/app/Lib/InjectTypes/FlagSubmission.php @@ -2,32 +2,32 @@ namespace InjectTypes; class FlagSubmission extends InjectSubmissionBase { - - public function getID() { - return 'flag'; - } - - public function getName() { - return 'Flag Submission (TODO)'; - } - - public function getTemplate() { - return 'TODO.'; - } - - public function getSubmittedTemplate($submissions) { - return 'TODO'; - } - - public function getGraderTemplate($submissions) { - return 'TODO'; - } - - public function validateSubmission($inject, $submission) { - return false; - } - - public function handleSubmission($inject, $submission) { - throw new BadMethodCallException('Not implemented'); - } -} \ No newline at end of file + + public function getID() { + return 'flag'; + } + + public function getName() { + return 'Flag Submission (TODO)'; + } + + public function getTemplate() { + return 'TODO.'; + } + + public function getSubmittedTemplate($submissions) { + return 'TODO'; + } + + public function getGraderTemplate($submissions) { + return 'TODO'; + } + + public function validateSubmission($inject, $submission) { + return false; + } + + public function handleSubmission($inject, $submission) { + throw new BadMethodCallException('Not implemented'); + } +} diff --git a/app/Lib/InjectTypes/InjectSubmissionBase.php b/app/Lib/InjectTypes/InjectSubmissionBase.php index 06b5c96..af1c8f9 100644 --- a/app/Lib/InjectTypes/InjectSubmissionBase.php +++ b/app/Lib/InjectTypes/InjectSubmissionBase.php @@ -1,113 +1,114 @@ types[$type->getID()] = $type; - } - } + public function __construct($types) { + foreach ($types as $type_name) { + $name = sprintf('InjectTypes\\%s', $type_name); + $type = new $name(); - public function get($id) { - if ( !isset($this->types[$id]) ) { - throw new BadMethodCallException('Unknown type!'); - } + $this->types[$type->getID()] = $type; + } + } - return $this->types[$id]; - } + public function get($id) { + if (!isset($this->types[$id])) { + throw new BadMethodCallException('Unknown type!'); + } - public function getAll() { - return $this->types; - } -} \ No newline at end of file + return $this->types[$id]; + } + + public function getAll() { + return $this->types; + } +} diff --git a/app/Lib/InjectTypes/ManualCheck.php b/app/Lib/InjectTypes/ManualCheck.php index 1398daf..775932c 100644 --- a/app/Lib/InjectTypes/ManualCheck.php +++ b/app/Lib/InjectTypes/ManualCheck.php @@ -2,61 +2,61 @@ namespace InjectTypes; class ManualCheck extends InjectSubmissionBase { - - public function getID() { - return 'manual'; - } - public function getName() { - return 'Manual Submission (TODO)'; - } + public function getID() { + return 'manual'; + } - public function getTemplate() { - return <<<'TEMPLATE' + public function getName() { + return 'Manual Submission (TODO)'; + } + + public function getTemplate() { + return <<<'TEMPLATE'
TEMPLATE; - } - - public function getSubmittedTemplate($submissions) { - $tpl = '
    '; - - foreach ( $submissions AS $s ) { - $urlDelete = $this->_url('/injects/delete/'.$s['Submission']['id']); - $d = json_decode($s['Submission']['data'], true); - - $tpl .= '
  • '. - '

    '. - 'Manual Check Requested on '.$this->_date($s['Submission']['created']). - 'Delete

    '. - '
  • '; - } - - if ( empty($submissions) ) { - $tpl .= '
  • '. - '

    No manual checks requested.

    '. - '
  • '; - } - - $tpl .= '
'; - return $tpl; - } - - public function getGraderTemplate($submissions) { - return 'TODO'; - } - - public function validateSubmission($inject, $submission) { - return true; - } - - public function handleSubmission($inject, $submission) { - return json_encode([ - 'completed' => false, - 'requested' => time(), - ]); - } -} \ No newline at end of file + } + + public function getSubmittedTemplate($submissions) { + $tpl = '
    '; + + foreach ($submissions as $s) { + $urlDelete = $this->url('/injects/delete/'.$s['Submission']['id']); + $d = json_decode($s['Submission']['data'], true); + + $tpl .= '
  • '. + '

    '. + 'Manual Check Requested on '.$this->date($s['Submission']['created']). + 'Delete

    '. + '
  • '; + } + + if (empty($submissions)) { + $tpl .= '
  • '. + '

    No manual checks requested.

    '. + '
  • '; + } + + $tpl .= '
'; + return $tpl; + } + + public function getGraderTemplate($submissions) { + return 'TODO'; + } + + public function validateSubmission($inject, $submission) { + return true; + } + + public function handleSubmission($inject, $submission) { + return json_encode([ + 'completed' => false, + 'requested' => time(), + ]); + } +} diff --git a/app/Lib/InjectTypes/NoOpSubmission.php b/app/Lib/InjectTypes/NoOpSubmission.php index ac8add7..b976988 100644 --- a/app/Lib/InjectTypes/NoOpSubmission.php +++ b/app/Lib/InjectTypes/NoOpSubmission.php @@ -2,34 +2,34 @@ namespace InjectTypes; class NoOpSubmission extends InjectSubmissionBase { - const TPL = '
  • '. - '

    %s

'; + const TPL = '
  • '. + '

    %s

'; - public function getID() { - return 'noop'; - } + public function getID() { + return 'noop'; + } - public function getName() { - return 'NoOp Submission'; - } + public function getName() { + return 'NoOp Submission'; + } - public function getTemplate() { - return sprintf(self::TPL, 'No submission actions available for this inject.'); - } + public function getTemplate() { + return sprintf(self::TPL, 'No submission actions available for this inject.'); + } - public function getSubmittedTemplate($submissions) { - return sprintf(self::TPL, 'No submissions.'); - } + public function getSubmittedTemplate($submissions) { + return sprintf(self::TPL, 'No submissions.'); + } - public function getGraderTemplate($submissions) { - return 'This shouldn\'t happen.'; - } + public function getGraderTemplate($submissions) { + return 'This shouldn\'t happen.'; + } - public function validateSubmission($inject, $submission) { - return false; - } + public function validateSubmission($inject, $submission) { + return false; + } - public function handleSubmission($inject, $submission) { - throw new BadMethodCallException('No-Op submissions cannot be submitted'); - } -} \ No newline at end of file + public function handleSubmission($inject, $submission) { + throw new BadMethodCallException('No-Op submissions cannot be submitted'); + } +} diff --git a/app/Lib/InjectTypes/TextSubmission.php b/app/Lib/InjectTypes/TextSubmission.php index c8bdb1e..a735990 100644 --- a/app/Lib/InjectTypes/TextSubmission.php +++ b/app/Lib/InjectTypes/TextSubmission.php @@ -3,16 +3,16 @@ class TextSubmission extends InjectSubmissionBase { - public function getID() { - return 'text'; - } + public function getID() { + return 'text'; + } - public function getName() { - return 'Text Submission'; - } + public function getName() { + return 'Text Submission'; + } - public function getTemplate() { - return <<<'TEMPLATE' + public function getTemplate() { + return <<<'TEMPLATE'

@@ -23,43 +23,43 @@ public function getTemplate() { TEMPLATE; - } + } - public function getSubmittedTemplate($submissions) { - $tpl = '
    '; + public function getSubmittedTemplate($submissions) { + $tpl = '
      '; - foreach ( $submissions AS $s ) { - $url = $this->_url('/injects/delete/'.$s['Submission']['id']); + foreach ($submissions as $s) { + $url = $this->_url('/injects/delete/'.$s['Submission']['id']); - $tpl .= '
    • '. - '

      '. - 'Submission on '.$this->_date($s['Submission']['created']). - 'Delete

      '. - '

      '.nl2br($s['Submission']['data']).'

      '. - '
    • '; - } + $tpl .= '
    • '. + '

      '. + 'Submission on '.$this->date($s['Submission']['created']). + 'Delete

      '. + '

      '.nl2br($s['Submission']['data']).'

      '. + '
    • '; + } - if ( empty($submissions) ) { - $tpl .= '
    • '. - '

      No submissions.

      '. - '
    • '; - } + if (empty($submissions)) { + $tpl .= '
    • '. + '

      No submissions.

      '. + '
    • '; + } - $tpl .= '
    '; - return $tpl; - } + $tpl .= '
'; + return $tpl; + } - public function getGraderTemplate($s) { - return nl2br($s['Submission']['data']); - } + public function getGraderTemplate($s) { + return nl2br($s['Submission']['data']); + } - public function validateSubmission($inject, $submission) { - return (isset($submission['content']) && !empty($submission['content'])); - } + public function validateSubmission($inject, $submission) { + return (isset($submission['content']) && !empty($submission['content'])); + } - public function handleSubmission($inject, $submission) { - $clean_content = htmlspecialchars($submission['content']); + public function handleSubmission($inject, $submission) { + $clean_content = htmlspecialchars($submission['content']); - return $clean_content; - } -} \ No newline at end of file + return $clean_content; + } +} diff --git a/app/Model/Announcement.php b/app/Model/Announcement.php index f2e2a54..ac3f2aa 100644 --- a/app/Model/Announcement.php +++ b/app/Model/Announcement.php @@ -6,18 +6,18 @@ * */ class Announcement extends AppModel { - - /** - * Get's all active announcements - * - * @return array The active announcements - */ - public function getAll() { - return $this->find('all', [ - 'conditions' => [ - 'Announcement.active' => true, - 'Announcement.expiration <=' => time(), - ], - ]); - } + + /** + * Get's all active announcements + * + * @return array The active announcements + */ + public function getAll() { + return $this->find('all', [ + 'conditions' => [ + 'Announcement.active' => true, + 'Announcement.expiration <=' => time(), + ], + ]); + } } diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index b0caff5..ca3e00e 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -38,13 +38,12 @@ class AppModel extends Model { * * @var int */ - public $recursive = -1; + public $recursive = -1; /** * behaviors used by model * * @var array */ - public $actsAs = ['Containable']; - + public $actsAs = ['Containable']; } diff --git a/app/Model/Attachment.php b/app/Model/Attachment.php index 31fdf82..930a0ae 100644 --- a/app/Model/Attachment.php +++ b/app/Model/Attachment.php @@ -6,5 +6,5 @@ * */ class Attachment extends AppModel { - + } diff --git a/app/Model/Config.php b/app/Model/Config.php index 74a1a3e..1bb156b 100644 --- a/app/Model/Config.php +++ b/app/Model/Config.php @@ -6,64 +6,65 @@ * */ class Config extends AppModel { - /** - * Override the table for this model. - * Otherwise, CakePHP would use "configs" - */ - public $useTable = 'config'; - /** - * Cache key template - */ - const CACHE_KEY = 'Config.%s'; + /** + * Override the table for this model. + * Otherwise, CakePHP would use "configs" + */ + public $useTable = 'config'; - /** - * After Save Model Hook - * - * This will clear the cache of the updated - * item when a save is completed. - */ - public function afterSave($created, $options=[]) { - if ( !$created ) { - Cache::delete(sprintf(self::CACHE_KEY, $this->data['Config']['key'])); - } - } + /** + * Cache key template + */ + const CACHE_KEY = 'Config.%s'; - /** - * Get Config Key - * - * Will attempt to use the cache first, then - * query the database. - * - * @param $key The config key to get - * @return mixed The value - */ - public function getKey($key) { - $data = Cache::read(sprintf(self::CACHE_KEY, $key)); + /** + * After Save Model Hook + * + * This will clear the cache of the updated + * item when a save is completed. + */ + public function afterSave($created, $options = []) { + if (!$created) { + Cache::delete(sprintf(self::CACHE_KEY, $this->data['Config']['key'])); + } + } - if ( $data === false ) { - $data = $this->find('first', [ - 'conditions' => [ - 'key' => $key, - ], - ]); + /** + * Get Config Key + * + * Will attempt to use the cache first, then + * query the database. + * + * @param $key The config key to get + * @return mixed The value + */ + public function getKey($key) { + $data = Cache::read(sprintf(self::CACHE_KEY, $key)); - Cache::write(sprintf(self::CACHE_KEY, $key), $data); - } + if ($data === false) { + $data = $this->find('first', [ + 'conditions' => [ + 'key' => $key, + ], + ]); - return empty($data) ? '' : $data['Config']['value']; - } + Cache::write(sprintf(self::CACHE_KEY, $key), $data); + } - /** - * Get Inject Types - * - * This is basically a proxy function to get the key - * 'engine.inject_types'. It wraps json_decode around it - * too, so the data is actually useful. - * - * @return array The configured inject types - */ - public function getInjectTypes() { - return json_decode($this->getKey('engine.inject_types')); - } + return empty($data) ? '' : $data['Config']['value']; + } + + /** + * Get Inject Types + * + * This is basically a proxy function to get the key + * 'engine.inject_types'. It wraps json_decode around it + * too, so the data is actually useful. + * + * @return array The configured inject types + */ + public function getInjectTypes() { + return json_decode($this->getKey('engine.inject_types')); + } } diff --git a/app/Model/Group.php b/app/Model/Group.php index 2548af7..39246ca 100644 --- a/app/Model/Group.php +++ b/app/Model/Group.php @@ -6,53 +6,55 @@ * */ class Group extends AppModel { - public $actsAs = ['Tree']; - public $hasMany = ['User']; - - /** - * Gets an array of the groups - * - * @param $id The ID of the group you are getting groups for - * @return array An array containing the group, and all higher groups - */ - public function getGroups($id) { - $groups = []; - foreach ( $this->getPath($id) AS $p ) { - $groups[] = $p['Group']['id']; - } - - return $groups; - } - - /** - * Gets the children of a group - * - * @param $id The ID of the group you are getting children for - * @return array An array containing the children - */ - public function getChildren($id) { - $children = []; - foreach ( $this->children($id) AS $c ) { - $children[] = $c['Group']['id']; - } - - return $children; - } - - /** - * Gets the 'pretty' version of the group - * Example: Staff/White Team - * - * @param $id The ID of the group you are getting the path for - * @param $separator The separator you wish to use. Defaults to "/" - * @return string The pretty path - */ - public function getGroupPath($id, $separator='/') { - $path = []; - foreach ( $this->getPath($id) AS $p ) { - $path[] = $p['Group']['name']; - } - - return implode($separator, $path); - } + + public $actsAs = ['Tree']; + + public $hasMany = ['User']; + + /** + * Gets an array of the groups + * + * @param $id The ID of the group you are getting groups for + * @return array An array containing the group, and all higher groups + */ + public function getGroups($id) { + $groups = []; + foreach ($this->getPath($id) as $p) { + $groups[] = $p['Group']['id']; + } + + return $groups; + } + + /** + * Gets the children of a group + * + * @param $id The ID of the group you are getting children for + * @return array An array containing the children + */ + public function getChildren($id) { + $children = []; + foreach ($this->children($id) as $c) { + $children[] = $c['Group']['id']; + } + + return $children; + } + + /** + * Gets the 'pretty' version of the group + * Example: Staff/White Team + * + * @param $id The ID of the group you are getting the path for + * @param $separator The separator you wish to use. Defaults to "/" + * @return string The pretty path + */ + public function getGroupPath($id, $separator = '/') { + $path = []; + foreach ($this->getPath($id) as $p) { + $path[] = $p['Group']['name']; + } + + return implode($separator, $path); + } } diff --git a/app/Model/Hint.php b/app/Model/Hint.php index 0d7a2e6..58b9554 100644 --- a/app/Model/Hint.php +++ b/app/Model/Hint.php @@ -6,51 +6,55 @@ * */ class Hint extends AppModel { - public $belongsTo = ['Inject']; - public $recursive = 1; - - /** - * All the hints (and which are unlocked) - * - * @param $inject_id Inject ID you are getting hints for - * @param $group_id The Group ID you are getting hints for - * @return array An array containing the hints - */ - public function getHints($inject_id, $group_id) { - $data = $this->find('all', [ - 'fields' => [ - 'Hint.id', 'Hint.title', 'Hint.content', 'Hint.parent_id', - 'Hint.time_wait', 'Hint.cost', 'UsedHint.time' - ], - 'joins' => [ - [ - 'table' => 'used_hints', - 'alias' => 'UsedHint', - 'type' => 'left', - 'conditions' => [ - 'UsedHint.hint_id = Hint.id', - 'UsedHint.group_id' => $group_id - ] - ] - ], - 'conditions' => [ - 'Hint.inject_id' => $inject_id, - ] - ]); - - // Unlocked hints lookup table - $unlockedHints = []; - foreach ( $data AS $d ) { - $unlockedHints[$d['Hint']['id']] = (!empty($d['UsedHint']['time'])); - } - - // Add some helper data like if it's unlocked, - // or a dependency is met - foreach ( $data AS &$d ) { - $d['Hint']['unlocked'] = $unlockedHints[$d['Hint']['id']]; - $d['Hint']['dependency_met'] = ($d['Hint']['parent_id'] != NULL) ? $unlockedHints[$d['Hint']['parent_id']] : true; - } - - return $data; - } + + public $belongsTo = ['Inject']; + + public $recursive = 1; + + /** + * All the hints (and which are unlocked) + * + * @param $inject_id Inject ID you are getting hints for + * @param $group_id The Group ID you are getting hints for + * @return array An array containing the hints + */ + public function getHints($inject_id, $group_id) { + $data = $this->find('all', [ + 'fields' => [ + 'Hint.id', 'Hint.title', 'Hint.content', 'Hint.parent_id', + 'Hint.time_wait', 'Hint.cost', 'UsedHint.time' + ], + 'joins' => [ + [ + 'table' => 'used_hints', + 'alias' => 'UsedHint', + 'type' => 'left', + 'conditions' => [ + 'UsedHint.hint_id = Hint.id', + 'UsedHint.group_id' => $group_id + ] + ] + ], + 'conditions' => [ + 'Hint.inject_id' => $inject_id, + ] + ]); + + // Unlocked hints lookup table + $unlockedHints = []; + foreach ($data as $d) { + $unlockedHints[$d['Hint']['id']] = (!empty($d['UsedHint']['time'])); + } + + // Add some helper data like if it's unlocked, + // or a dependency is met + foreach ($data as &$d) { + $dependency_met = ($d['Hint']['parent_id'] != null) ? $unlockedHints[$d['Hint']['parent_id']] : true; + + $d['Hint']['unlocked'] = $unlockedHints[$d['Hint']['id']]; + $d['Hint']['dependency_met'] = $dependency_met; + } + + return $data; + } } diff --git a/app/Model/Inject.php b/app/Model/Inject.php index 1372ef1..56b62da 100644 --- a/app/Model/Inject.php +++ b/app/Model/Inject.php @@ -6,6 +6,8 @@ * */ class Inject extends AppModel { - public $hasMany = ['Attachment']; - public $recursive = 1; + + public $hasMany = ['Attachment']; + + public $recursive = 1; } diff --git a/app/Model/Log.php b/app/Model/Log.php index 48f1bf2..7ead536 100644 --- a/app/Model/Log.php +++ b/app/Model/Log.php @@ -6,6 +6,8 @@ * */ class Log extends AppModel { - public $belongsTo = ['User']; - public $recursive = 2; + + public $belongsTo = ['User']; + + public $recursive = 2; } diff --git a/app/Model/Schedule.php b/app/Model/Schedule.php index 963e178..c053017 100644 --- a/app/Model/Schedule.php +++ b/app/Model/Schedule.php @@ -7,303 +7,306 @@ * */ class Schedule extends AppModel { - public $belongsTo = ['Inject']; - public $recursive = 2; - - /** - * Get Active Injects (RAW) - * - * Okay, this is the monster in the room. - * I'm sorry. It'll grab injects based on - * if they're active, AND their start time - * has passed - * - * @param $groups The groups to check for injects in - * @param $onlyActive Only include injects that are active - * @return array All the active injects - */ - public function getInjectsRaw($groups, $onlyActive=true) { - $now = time(); - $conditions = ['Schedule.group_id' => $groups]; - - if ( $onlyActive ) { - $conditions += [ - 'Schedule.active' => true, - 'OR' => [ - [ - 'Schedule.fuzzy' => false, - 'Schedule.start <=' => $now, - ], - [ - 'Schedule.fuzzy' => true, - 'Schedule.start <=' => ($now - COMPETITION_START) - ], - [ - 'Schedule.start' => 0, - ], - ], - ]; - } - - $injects = $this->find('all', [ - 'conditions' => $conditions, - - // Ordering is hard. Sorry. - // We'll do base ordering on the order. - // Following that, sequence number, - // and then end times that aren't - // forever. - 'order' => [ - 'Schedule.order ASC', - 'Inject.sequence ASC', - '(Schedule.end > 0) DESC', - 'Schedule.end ASC', - ], - ]); - - // Now remove any duplicates - $cache = []; - foreach ( $injects AS $k => $v ) { - $injectID = $v['Inject']['id']; - $newEnd = $v['Schedule']['end']; - - if ( !isset($cache[$injectID]) ) { - $cache[$injectID] = [ - 'end' => $newEnd, - 'key' => $k, - ]; - - continue; - } - - $oldEnd = $cache[$injectID]['end']; - $oldKey = $cache[$injectID]['key']; - - // So we're going to prefer an inject - // with the latest end time - if ( ($newEnd == 0 && $oldEnd > 0) || ($oldEnd > 0 && $newEnd > $oldEnd) ) { - unset($injects[$oldKey]); - - $cache[$injectID] = [ - 'end' => $newEnd, - 'key' => $k, - ]; - } else { - unset($injects[$k]); - } - } - - return $injects; - } - - /** - * Get (an) Inject (RAW) - * - * A little nicer than getInjects...but still we have dragons :( - * - * @param $id The schedule ID of the inject - * @param $groups The groups the current user is in - * @param $show_expired [Optional] Still return the inject, even - * if it's expired - * @return array The inject (if it's active/exists) - */ - public function getInjectRaw($id, $groups, $show_expired=false) { - $conditions = [ - 'Schedule.id' => $id, - 'Schedule.group_id' => $groups, - 'Schedule.active' => true, - ]; - - if ( !$show_expired ) { - $now = time(); - - $conditions['OR'] = [ - [ - 'Schedule.fuzzy' => false, - 'Schedule.start <=' => $now, - ], - [ - 'Schedule.fuzzy' => true, - 'Schedule.start <=' => ($now - COMPETITION_START) - ], - [ - 'Schedule.start' => 0, - ] - ]; - } - - return $this->find('first', [ - 'conditions' => $conditions, - ]); - } - - /** - * Get Injects (and wrap them) - * - * This function uses the raw data from - * `getInjectsRaw` and wraps every inject - * inside an InjectAbstraction class - * - * @param $groups The groups to check for injects in - * @param $onlyActive Only include injects that are active - * @return array All the active injects - */ - public function getInjects($groups, $onlyActive=true) { - $rtn = []; - - foreach ( $this->getInjectsRaw($groups, $onlyActive) AS $inject ) { - $submissionCount = ClassRegistry::init('Submission')->getCount($inject['Inject']['id'], $groups); - $rtn[] = new InjectAbstraction($inject, $submissionCount); - } - - return $rtn; - } - - /** - * Get (an) Inject (and wrap it) - * - * This function uses the raw data from - * `getInjectRaw` and wraps the inject - * inside an InjectAbstraction class - * - * @param $id The schedule ID of the inject - * @param $groups The groups the current user is in - * @param $show_expired [Optional] Still return the inject, even - * if it's expired - * @return array The inject (if it's active/exists) - */ - public function getInject($id, $groups, $show_expired=false) { - $inject = $this->getInjectRaw($id, $groups, $show_expired); - - if ( !empty($inject) ) { - $submissionCount = ClassRegistry::init('Submission')->getCount($inject['Inject']['id'], $groups); - $inject = new InjectAbstraction($inject, $submissionCount); - } - - return $inject; - } - - /** - * Get recently expired injects - * - * This function uses the raw data from - * `getInjectsRaw` and wraps every inject - * inside an InjectAbstraction class - * - * @param $groups The groups to check for injects in - * @param $howRecent How recent the injects have expired - * @return array All the active injects - */ - public function getRecentExpired($groups, $howRecent=(90 * 60)) { - $now = time(); - $nowCS = ($now - COMPETITION_START); - - $data = $this->find('all', [ - 'conditions' => [ - 'Schedule.active' => true, - 'Schedule.group_id' => $groups, - 'Schedule.end !=' => 0, - 'OR' => [ - [ - 'Schedule.fuzzy' => true, - 'Schedule.end <' => $nowCS, - 'Schedule.end >=' => ($nowCS - $howRecent), - ], - [ - 'Schedule.fuzzy' => false, - 'Schedule.end <' => $now, - 'Schedule.end >=' => ($now - $howRecent), - ] - ], - ], - ]); - - $rtn = []; - foreach ( $data AS $d ) { - $rtn[] = new InjectAbstraction($d, 0); - } - - return $rtn; - } - - /** - * Get _ALL_ Schedules - * - * @param $activeOnly [Optional] Only show active - * @return array All the [active] schedules - */ - public function getAllSchedules($activeOnly=true) { - $this->bindModel([ - 'belongsTo' => ['Group'], - ]); - - $data = $this->find('all', [ - 'conditions' => [ - 'Schedule.active' => ($activeOnly ? true : [true,false]), - ], - ]); - - $rtn = []; - foreach ( $data AS $d ) { - $rtn[] = new InjectAbstraction($d, 0); - } - - return $rtn; - } - - /** - * Get Schedule Bounds - * - * Returns an array containing the min and - * max times for the schedule list - * - * @param $round Should we round the times - * @return array The min/max times - */ - public function getScheduleBounds($round=true) { - $this->virtualFields['min'] = 'IF(Schedule.fuzzy = 1, Schedule.start + '.COMPETITION_START.', Schedule.start)'; - $this->virtualFields['max'] = 'IF(Schedule.fuzzy = 1 AND Schedule.end > 0, Schedule.end + '.COMPETITION_START.', Schedule.end)'; - - $min = $this->find('first', [ - 'fields' => [ - 'Schedule.min' - ], - 'conditions' => [ - 'Schedule.active' => true, - ], - 'order' => [ - 'Schedule.min ASC', - ], - ]); - - $max = $this->find('first', [ - 'fields' => [ - 'Schedule.max' - ], - 'conditions' => [ - 'Schedule.active' => true, - ], - 'order' => [ - 'Schedule.max DESC', - ], - ]); - - $bounds = [ - 'min' => $min['Schedule']['min'], - 'max' => $max['Schedule']['max'], - ]; - - // Now round them - if ( $round ) { - $min = DateTime::createFromFormat('Y-m-d H:00:00', date('Y-m-d H:00:00', $bounds['min'])); - $max = DateTime::createFromFormat('Y-m-d H:00:00', date('Y-m-d H:00:00', $bounds['max'])); - $min->modify('-1 hour'); - $max->modify('+1 hour'); - - $bounds['min'] = $min->getTimestamp(); - $bounds['max'] = $max->getTimestamp(); - } - - return $bounds; - } + + public $belongsTo = ['Inject']; + + public $recursive = 2; + + /** + * Get Active Injects (RAW) + * + * Okay, this is the monster in the room. + * I'm sorry. It'll grab injects based on + * if they're active, AND their start time + * has passed + * + * @param $groups The groups to check for injects in + * @param $onlyActive Only include injects that are active + * @return array All the active injects + */ + public function getInjectsRaw($groups, $onlyActive = true) { + $now = time(); + $conditions = ['Schedule.group_id' => $groups]; + + if ($onlyActive) { + $conditions += [ + 'Schedule.active' => true, + 'OR' => [ + [ + 'Schedule.fuzzy' => false, + 'Schedule.start <=' => $now, + ], + [ + 'Schedule.fuzzy' => true, + 'Schedule.start <=' => ($now - COMPETITION_START) + ], + [ + 'Schedule.start' => 0, + ], + ], + ]; + } + + $injects = $this->find('all', [ + 'conditions' => $conditions, + + // Ordering is hard. Sorry. + // We'll do base ordering on the order. + // Following that, sequence number, + // and then end times that aren't + // forever. + 'order' => [ + 'Schedule.order ASC', + 'Inject.sequence ASC', + '(Schedule.end > 0) DESC', + 'Schedule.end ASC', + ], + ]); + + // Now remove any duplicates + $cache = []; + foreach ($injects as $k => $v) { + $injectID = $v['Inject']['id']; + $newEnd = $v['Schedule']['end']; + + if (!isset($cache[$injectID])) { + $cache[$injectID] = [ + 'end' => $newEnd, + 'key' => $k, + ]; + + continue; + } + + $oldEnd = $cache[$injectID]['end']; + $oldKey = $cache[$injectID]['key']; + + // So we're going to prefer an inject + // with the latest end time + if (($newEnd == 0 && $oldEnd > 0) || ($oldEnd > 0 && $newEnd > $oldEnd)) { + unset($injects[$oldKey]); + + $cache[$injectID] = [ + 'end' => $newEnd, + 'key' => $k, + ]; + } else { + unset($injects[$k]); + } + } + + return $injects; + } + + /** + * Get (an) Inject (RAW) + * + * A little nicer than getInjects...but still we have dragons :( + * + * @param $id The schedule ID of the inject + * @param $groups The groups the current user is in + * @param $show_expired [Optional] Still return the inject, even + * if it's expired + * @return array The inject (if it's active/exists) + */ + public function getInjectRaw($id, $groups, $show_expired = false) { + $conditions = [ + 'Schedule.id' => $id, + 'Schedule.group_id' => $groups, + 'Schedule.active' => true, + ]; + + if (!$show_expired) { + $now = time(); + + $conditions['OR'] = [ + [ + 'Schedule.fuzzy' => false, + 'Schedule.start <=' => $now, + ], + [ + 'Schedule.fuzzy' => true, + 'Schedule.start <=' => ($now - COMPETITION_START) + ], + [ + 'Schedule.start' => 0, + ] + ]; + } + + return $this->find('first', [ + 'conditions' => $conditions, + ]); + } + + /** + * Get Injects (and wrap them) + * + * This function uses the raw data from + * `getInjectsRaw` and wraps every inject + * inside an InjectAbstraction class + * + * @param $groups The groups to check for injects in + * @param $onlyActive Only include injects that are active + * @return array All the active injects + */ + public function getInjects($groups, $onlyActive = true) { + $rtn = []; + + foreach ($this->getInjectsRaw($groups, $onlyActive) as $inject) { + $submissionCount = ClassRegistry::init('Submission')->getCount($inject['Inject']['id'], $groups); + $rtn[] = new InjectAbstraction($inject, $submissionCount); + } + + return $rtn; + } + + /** + * Get (an) Inject (and wrap it) + * + * This function uses the raw data from + * `getInjectRaw` and wraps the inject + * inside an InjectAbstraction class + * + * @param $id The schedule ID of the inject + * @param $groups The groups the current user is in + * @param $show_expired [Optional] Still return the inject, even + * if it's expired + * @return array The inject (if it's active/exists) + */ + public function getInject($id, $groups, $show_expired = false) { + $inject = $this->getInjectRaw($id, $groups, $show_expired); + + if (!empty($inject)) { + $submissionCount = ClassRegistry::init('Submission')->getCount($inject['Inject']['id'], $groups); + $inject = new InjectAbstraction($inject, $submissionCount); + } + + return $inject; + } + + /** + * Get recently expired injects + * + * This function uses the raw data from + * `getInjectsRaw` and wraps every inject + * inside an InjectAbstraction class + * + * @param $groups The groups to check for injects in + * @param $howRecent How recent the injects have expired + * @return array All the active injects + */ + public function getRecentExpired($groups, $howRecent = (90 * 60)) { + $now = time(); + $nowCS = ($now - COMPETITION_START); + + $data = $this->find('all', [ + 'conditions' => [ + 'Schedule.active' => true, + 'Schedule.group_id' => $groups, + 'Schedule.end !=' => 0, + 'OR' => [ + [ + 'Schedule.fuzzy' => true, + 'Schedule.end <' => $nowCS, + 'Schedule.end >=' => ($nowCS - $howRecent), + ], + [ + 'Schedule.fuzzy' => false, + 'Schedule.end <' => $now, + 'Schedule.end >=' => ($now - $howRecent), + ] + ], + ], + ]); + + $rtn = []; + foreach ($data as $d) { + $rtn[] = new InjectAbstraction($d, 0); + } + + return $rtn; + } + + /** + * Get _ALL_ Schedules + * + * @param $activeOnly [Optional] Only show active + * @return array All the [active] schedules + */ + public function getAllSchedules($activeOnly = true) { + $this->bindModel([ + 'belongsTo' => ['Group'], + ]); + + $data = $this->find('all', [ + 'conditions' => [ + 'Schedule.active' => ($activeOnly ? true : [true,false]), + ], + ]); + + $rtn = []; + foreach ($data as $d) { + $rtn[] = new InjectAbstraction($d, 0); + } + + return $rtn; + } + + /** + * Get Schedule Bounds + * + * Returns an array containing the min and + * max times for the schedule list + * + * @param $round Should we round the times + * @return array The min/max times + */ + public function getScheduleBounds($round = true) { + $this->virtualFields['min'] = 'IF(Schedule.fuzzy = 1, Schedule.start + '.COMPETITION_START.', Schedule.start)'; + $this->virtualFields['max'] = 'IF(Schedule.fuzzy = 1 AND Schedule.end > 0, '. + 'Schedule.end + '.COMPETITION_START.', Schedule.end)'; + + $min = $this->find('first', [ + 'fields' => [ + 'Schedule.min' + ], + 'conditions' => [ + 'Schedule.active' => true, + ], + 'order' => [ + 'Schedule.min ASC', + ], + ]); + + $max = $this->find('first', [ + 'fields' => [ + 'Schedule.max' + ], + 'conditions' => [ + 'Schedule.active' => true, + ], + 'order' => [ + 'Schedule.max DESC', + ], + ]); + + $bounds = [ + 'min' => $min['Schedule']['min'], + 'max' => $max['Schedule']['max'], + ]; + + // Now round them + if ($round) { + $min = DateTime::createFromFormat('Y-m-d H:00:00', date('Y-m-d H:00:00', $bounds['min'])); + $max = DateTime::createFromFormat('Y-m-d H:00:00', date('Y-m-d H:00:00', $bounds['max'])); + $min->modify('-1 hour'); + $max->modify('+1 hour'); + + $bounds['min'] = $min->getTimestamp(); + $bounds['max'] = $max->getTimestamp(); + } + + return $bounds; + } } diff --git a/app/Model/Submission.php b/app/Model/Submission.php index 99987fe..0aac564 100644 --- a/app/Model/Submission.php +++ b/app/Model/Submission.php @@ -6,207 +6,210 @@ * */ class Submission extends AppModel { - public $belongsTo = ['Inject', 'User', 'Group']; - public $hasOne = ['Grade']; - public $recursive = 1; - - /** - * Get All Ungraded-Submissions - * - * This retrieves the submissions done by - * a group that is ungraded. - * - * @return array The submissions that are ungraded. - */ - public function getAllUngradedSubmissions() { - return $this->find('all', [ - 'fields' => [ - 'Submission.id', 'Submission.created', 'Submission.deleted', - 'Inject.id', 'Inject.title', 'Inject.sequence', 'Inject.type', - 'User.username', 'Group.name', 'Group.team_number', - 'Grade.created', 'Grade.grade', 'Grade.comments', - 'Grader.username', - ], - - 'joins' => [ - [ - 'table' => 'users', - 'alias' => 'Grader', - 'type' => 'LEFT', - 'conditions' => [ - 'Grader.id = Grade.grader_id', - ], - ] - ], - - 'conditions' => [ - 'Grade.created IS NULL', - 'Submission.deleted' => false, - ], - - 'order' => [ - 'Grade.created DESC', - 'Submission.created DESC', - ], - ]); - } - - /** - * Get All Submissions - * - * This retrieves the submissions done by - * a group. - * - * @param $group The group ID - * @param $noDeleted Discard deleted submissions - * @return array The submissions done by this group - */ - public function getAllSubmissions($group=false, $noDeleted=false) { - $conditions = []; - - if ( $group !== false ) { - $conditions['Group.id'] = $group; - } - if ( $noDeleted ) { - $conditions['Submission.deleted'] = false; - } - - return $this->find('all', [ - 'fields' => [ - 'Submission.id', 'Submission.created', 'Submission.deleted', - 'Inject.id', 'Inject.title', 'Inject.sequence', 'Inject.type', - 'User.username', 'Group.id', 'Group.name', 'Group.team_number', - 'Grade.created', 'Grade.grade', 'Grade.comments', - 'Grader.username', - ], - - 'joins' => [ - [ - 'table' => 'users', - 'alias' => 'Grader', - 'type' => 'LEFT', - 'conditions' => [ - 'Grader.id = Grade.grader_id', - ], - ] - ], - - 'conditions' => $conditions, - - 'order' => [ - 'Grade.created DESC', - 'Submission.created DESC', - ], - ]); - } - - /** - * Get Submission - * - * This retrieves the submission done by - * a group. - * - * @param $sid The submission ID - * @return array The submissions done by this group - */ - public function getSubmission($sid, $group=false, $noDeleted=false) { - $conditions = [ - 'Submission.id' => $sid, - ]; - - if ( $group !== false ) { - $conditions['Group.id'] = $group; - } - if ( $noDeleted ) { - $conditions['Submission.deleted'] = false; - } - - return $this->find('first', [ - 'fields' => [ - 'Submission.*', 'Inject.*', 'User.*', - 'Group.*', 'Grade.*', 'Grader.*', - ], - - 'joins' => [ - [ - 'table' => 'users', - 'alias' => 'Grader', - 'type' => 'LEFT', - 'conditions' => [ - 'Grader.id = Grade.grader_id', - ], - ] - ], - - 'conditions' => $conditions, - ]); - } - - /** - * Get Submissions - * - * This retrieves the the submissions for - * a specific inject done by a group. This - * will filter out deleted submissions, too. - * - * @param $id The inject ID - * @param $group The group ID - * @return array The submissions done by this group - * for this inject - */ - public function getSubmissions($id, $group) { - return $this->find('all', [ - 'conditions' => [ - 'Inject.id' => $id, - 'Group.id' => $group, - 'Submission.deleted' => false, - ], - ]); - } - - /** - * Get Submissions Count - * - * Basically `getSubmissions` - * - * @param $id The inject ID - * @param $group The group ID - * @return int The number of the submissions done by - * this group for this inject - */ - public function getCount($id, $group) { - return $this->find('count', [ - 'conditions' => [ - 'Inject.id' => $id, - 'Group.id' => $group, - 'Submission.deleted' => false, - ], - ]); - } - - /** - * Get Grade Totals - * - * @param $groups The groups you wish to get grades for - * @return array The grades for all the groups - */ - public function getGrades($groups) { - $this->virtualFields['total_grade'] = 'SUM(Grade.grade)'; - - return $this->find('all', [ - 'fields' => [ - 'Submission.total_grade', 'Group.name', 'Group.team_number', - ], - 'conditions' => [ - 'Group.id' => $groups, - 'Submission.deleted' => false, - ], - 'group' => [ - 'Group.id' - ], - 'order' => [ - 'Submission.total_grade DESC', - ], - ]); - } + + public $belongsTo = ['Inject', 'User', 'Group']; + + public $hasOne = ['Grade']; + + public $recursive = 1; + + /** + * Get All Ungraded-Submissions + * + * This retrieves the submissions done by + * a group that is ungraded. + * + * @return array The submissions that are ungraded. + */ + public function getAllUngradedSubmissions() { + return $this->find('all', [ + 'fields' => [ + 'Submission.id', 'Submission.created', 'Submission.deleted', + 'Inject.id', 'Inject.title', 'Inject.sequence', 'Inject.type', + 'User.username', 'Group.name', 'Group.team_number', + 'Grade.created', 'Grade.grade', 'Grade.comments', + 'Grader.username', + ], + + 'joins' => [ + [ + 'table' => 'users', + 'alias' => 'Grader', + 'type' => 'LEFT', + 'conditions' => [ + 'Grader.id = Grade.grader_id', + ], + ] + ], + + 'conditions' => [ + 'Grade.created IS NULL', + 'Submission.deleted' => false, + ], + + 'order' => [ + 'Grade.created DESC', + 'Submission.created DESC', + ], + ]); + } + + /** + * Get All Submissions + * + * This retrieves the submissions done by + * a group. + * + * @param $group The group ID + * @param $noDeleted Discard deleted submissions + * @return array The submissions done by this group + */ + public function getAllSubmissions($group = false, $noDeleted = false) { + $conditions = []; + + if ($group !== false) { + $conditions['Group.id'] = $group; + } + if ($noDeleted) { + $conditions['Submission.deleted'] = false; + } + + return $this->find('all', [ + 'fields' => [ + 'Submission.id', 'Submission.created', 'Submission.deleted', + 'Inject.id', 'Inject.title', 'Inject.sequence', 'Inject.type', + 'User.username', 'Group.id', 'Group.name', 'Group.team_number', + 'Grade.created', 'Grade.grade', 'Grade.comments', + 'Grader.username', + ], + + 'joins' => [ + [ + 'table' => 'users', + 'alias' => 'Grader', + 'type' => 'LEFT', + 'conditions' => [ + 'Grader.id = Grade.grader_id', + ], + ] + ], + + 'conditions' => $conditions, + + 'order' => [ + 'Grade.created DESC', + 'Submission.created DESC', + ], + ]); + } + + /** + * Get Submission + * + * This retrieves the submission done by + * a group. + * + * @param $sid The submission ID + * @return array The submissions done by this group + */ + public function getSubmission($sid, $group = false, $noDeleted = false) { + $conditions = [ + 'Submission.id' => $sid, + ]; + + if ($group !== false) { + $conditions['Group.id'] = $group; + } + if ($noDeleted) { + $conditions['Submission.deleted'] = false; + } + + return $this->find('first', [ + 'fields' => [ + 'Submission.*', 'Inject.*', 'User.*', + 'Group.*', 'Grade.*', 'Grader.*', + ], + + 'joins' => [ + [ + 'table' => 'users', + 'alias' => 'Grader', + 'type' => 'LEFT', + 'conditions' => [ + 'Grader.id = Grade.grader_id', + ], + ] + ], + + 'conditions' => $conditions, + ]); + } + + /** + * Get Submissions + * + * This retrieves the the submissions for + * a specific inject done by a group. This + * will filter out deleted submissions, too. + * + * @param $id The inject ID + * @param $group The group ID + * @return array The submissions done by this group + * for this inject + */ + public function getSubmissions($id, $group) { + return $this->find('all', [ + 'conditions' => [ + 'Inject.id' => $id, + 'Group.id' => $group, + 'Submission.deleted' => false, + ], + ]); + } + + /** + * Get Submissions Count + * + * Basically `getSubmissions` + * + * @param $id The inject ID + * @param $group The group ID + * @return int The number of the submissions done by + * this group for this inject + */ + public function getCount($id, $group) { + return $this->find('count', [ + 'conditions' => [ + 'Inject.id' => $id, + 'Group.id' => $group, + 'Submission.deleted' => false, + ], + ]); + } + + /** + * Get Grade Totals + * + * @param $groups The groups you wish to get grades for + * @return array The grades for all the groups + */ + public function getGrades($groups) { + $this->virtualFields['total_grade'] = 'SUM(Grade.grade)'; + + return $this->find('all', [ + 'fields' => [ + 'Submission.total_grade', 'Group.name', 'Group.team_number', + ], + 'conditions' => [ + 'Group.id' => $groups, + 'Submission.deleted' => false, + ], + 'group' => [ + 'Group.id' + ], + 'order' => [ + 'Submission.total_grade DESC', + ], + ]); + } } diff --git a/app/Model/UsedHint.php b/app/Model/UsedHint.php index 144caa7..8ae3434 100644 --- a/app/Model/UsedHint.php +++ b/app/Model/UsedHint.php @@ -6,6 +6,8 @@ * */ class UsedHint extends AppModel { - public $belongsTo = ['Hint']; - public $recursive = 1; + + public $belongsTo = ['Hint']; + + public $recursive = 1; } diff --git a/app/Model/User.php b/app/Model/User.php index 13ae4d0..709510c 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -7,21 +7,23 @@ * */ class User extends AppModel { - public $belongsTo = ['Group']; - public $recursive = 1; - /** - * Before Save Hook - * - * Ensures if the "password" key is set, we hash it correctly (using bcrypt) - * @param $options Unknown - * @return boolean If the operation we're doing worked - */ - public function beforeSave($options = array()) { - if ( !empty($this->data['User']['password']) ) { - $this->data['User']['password'] = Security::hash($this->data['User']['password'], 'blowfish'); - } + public $belongsTo = ['Group']; - return true; - } + public $recursive = 1; + + /** + * Before Save Hook + * + * Ensures if the "password" key is set, we hash it correctly (using bcrypt) + * @param $options Unknown + * @return boolean If the operation we're doing worked + */ + public function beforeSave($options = []) { + if (!empty($this->data['User']['password'])) { + $this->data['User']['password'] = Security::hash($this->data['User']['password'], 'blowfish'); + } + + return true; + } } diff --git a/app/Plugin/Admin/Controller/AdminAppController.php b/app/Plugin/Admin/Controller/AdminAppController.php index 2f265a2..8f0b459 100644 --- a/app/Plugin/Admin/Controller/AdminAppController.php +++ b/app/Plugin/Admin/Controller/AdminAppController.php @@ -2,13 +2,13 @@ App::uses('AppController', 'Controller'); class AdminAppController extends AppController { - public function beforeFilter() { - parent::beforeFilter(); + public function beforeFilter() { + parent::beforeFilter(); - // We're doing a backend request, require backend access - $this->Auth->protect(env('GROUP_ADMINS')); + // We're doing a backend request, require backend access + $this->Auth->protect(env('GROUP_ADMINS')); - // Set the active menu item - $this->set('at_backend', true); - } -} \ No newline at end of file + // Set the active menu item + $this->set('at_backend', true); + } +} diff --git a/app/Plugin/Admin/Controller/GroupsController.php b/app/Plugin/Admin/Controller/GroupsController.php index e067623..576b1e5 100644 --- a/app/Plugin/Admin/Controller/GroupsController.php +++ b/app/Plugin/Admin/Controller/GroupsController.php @@ -3,143 +3,146 @@ use Respect\Validation\Rules; class GroupsController extends AdminAppController { - public $uses = ['Group']; - - public function beforeFilter() { - parent::beforeFilter(); - - $this->validators = [ - 'name' => new Rules\AllOf( - new Rules\Alnum('-_'), - new Rules\NotEmpty() - ), - 'team_number' => new Rules\Optional( - new Rules\Digit() - ), - 'parent_id' => new Rules\Optional( - new Rules\Digit() - ), - ]; - } - - /** - * Group List Page - * - * @url /admin/group - * @url /admin/group/index - */ - public function index() { - $mappings = []; - foreach ( $this->Group->find('all') AS $g ) { - if ( $g['Group']['team_number'] === NULL ) continue; - - $mappings[$g['Group']['id']] = $g['Group']['team_number']; - } - - $this->set('mappings', $mappings); - $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); - } - - /** - * Create Group - * - * @url /admin/group/create - */ - public function create() { - if ( $this->request->is('post') ) { - // Validate the input - $res = $this->_validate(); - - if ( empty($res['errors']) ) { - $this->Group->create(); - $this->Group->save($res['data']); - - $this->logMessage( - 'groups', - sprintf('Created group "%s"', $created['name']), - [], - $this->Group->id - ); - - $this->Flash->success('The group has been created!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'groups', 'action' => 'index']); - } else { - $this->_errorFlash($res['errors']); - } - } - - $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); - } - - /** - * Edit Group - * - * @url /admin/group/edit/ - */ - public function edit($gid=false) { - $group = $this->Group->findById($gid); - if ( empty($group) ) { - throw new NotFoundException('Unknown group'); - } - - if ( $this->request->is('post') ) { - // Validate the input - $res = $this->_validate(); - - if ( empty($res['errors']) ) { - $this->Group->id = $gid; - $this->Group->save($res['data']); - - $this->logMessage( - 'groups', - sprintf('Updated group "%s"', $group['Group']['name']), - [ - 'old_group' => $group['Group'], - 'new_group' => $res['data'], - ], - $uid - ); - - $this->Flash->success('The user has been updated!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'groups', 'action' => 'index']); - } else { - $this->_errorFlash($res['errors']); - } - } - - $this->set('group', $group); - $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); - } - - /** - * Delete group - * - * @url /admin/group/delete/ - */ - public function delete($gid=false) { - $group = $this->Group->findById($gid); - if ( empty($group) ) { - throw new NotFoundException('Unknown group'); - } - - if ( $this->request->is('post') ) { - $this->Group->delete($gid); - - $msg = sprintf('Deleted group "%s" (#%d)', $group['Group']['name'], $gid); - - $this->logMessage( - 'groups', - $msg, - [ - 'group' => $group['Group'], - ], - $gid - ); - - $this->Flash->success($msg); - return $this->redirect(['plugin' => 'admin', 'controller' => 'groups', 'action' => 'index']); - } - - $this->set('group', $group); - } + + public $uses = ['Group']; + + public function beforeFilter() { + parent::beforeFilter(); + + $this->validators = [ + 'name' => new Rules\AllOf( + new Rules\Alnum('-_'), + new Rules\NotEmpty() + ), + 'team_number' => new Rules\Optional( + new Rules\Digit() + ), + 'parent_id' => new Rules\Optional( + new Rules\Digit() + ), + ]; + } + + /** + * Group List Page + * + * @url /admin/group + * @url /admin/group/index + */ + public function index() { + $mappings = []; + foreach ($this->Group->find('all') as $g) { + if ($g['Group']['team_number'] === null) { + continue; + } + + $mappings[$g['Group']['id']] = $g['Group']['team_number']; + } + + $this->set('mappings', $mappings); + $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); + } + + /** + * Create Group + * + * @url /admin/group/create + */ + public function create() { + if ($this->request->is('post')) { + // Validate the input + $res = $this->validate(); + + if (empty($res['errors'])) { + $this->Group->create(); + $this->Group->save($res['data']); + + $this->logMessage( + 'groups', + sprintf('Created group "%s"', $created['name']), + [], + $this->Group->id + ); + + $this->Flash->success('The group has been created!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'groups', 'action' => 'index']); + } else { + $this->errorFlash($res['errors']); + } + } + + $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); + } + + /** + * Edit Group + * + * @url /admin/group/edit/ + */ + public function edit($gid = false) { + $group = $this->Group->findById($gid); + if (empty($group)) { + throw new NotFoundException('Unknown group'); + } + + if ($this->request->is('post')) { + // Validate the input + $res = $this->validate(); + + if (empty($res['errors'])) { + $this->Group->id = $gid; + $this->Group->save($res['data']); + + $this->logMessage( + 'groups', + sprintf('Updated group "%s"', $group['Group']['name']), + [ + 'old_group' => $group['Group'], + 'new_group' => $res['data'], + ], + $uid + ); + + $this->Flash->success('The user has been updated!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'groups', 'action' => 'index']); + } else { + $this->errorFlash($res['errors']); + } + } + + $this->set('group', $group); + $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); + } + + /** + * Delete group + * + * @url /admin/group/delete/ + */ + public function delete($gid = false) { + $group = $this->Group->findById($gid); + if (empty($group)) { + throw new NotFoundException('Unknown group'); + } + + if ($this->request->is('post')) { + $this->Group->delete($gid); + + $msg = sprintf('Deleted group "%s" (#%d)', $group['Group']['name'], $gid); + + $this->logMessage( + 'groups', + $msg, + [ + 'group' => $group['Group'], + ], + $gid + ); + + $this->Flash->success($msg); + return $this->redirect(['plugin' => 'admin', 'controller' => 'groups', 'action' => 'index']); + } + + $this->set('group', $group); + } } diff --git a/app/Plugin/Admin/Controller/HintsController.php b/app/Plugin/Admin/Controller/HintsController.php index 9700bd6..3863d1c 100644 --- a/app/Plugin/Admin/Controller/HintsController.php +++ b/app/Plugin/Admin/Controller/HintsController.php @@ -3,156 +3,157 @@ use Respect\Validation\Rules; class HintsController extends AdminAppController { - public $uses = ['Hint', 'Inject']; - - public function beforeFilter() { - parent::beforeFilter(); - - // Setup the validators - $this->validators = [ - 'inject_id' => new Rules\AllOf( - new Rules\Digit() - ), - 'parent_id' => new Rules\Optional( - new Rules\Digit() - ), - 'title' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'content' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'time_wait' => new Rules\AllOf( - new Rules\Digit() - ), - 'cost' => new Rules\AllOf( - new Rules\Digit() - ), - ]; - } - - /** - * Hint List Page - * - * @url /admin/hints - * @url /admin/hints/index - */ - public function index() { - $this->set('hints', $this->Hint->find('all')); - } - - /** - * Create Hint - * - * @url /admin/hints/create - */ - public function create() { - if ( $this->request->is('post') ) { - // Validate the input - $res = $this->_validate(); - - if ( empty($res['errors']) ) { - $this->Hint->create(); - $this->Hint->save($res['data']); - - $this->logMessage( - 'hints', - sprintf('Created hint "%s"', $res['data']['title']), - [], - $id - ); - - $this->Flash->success('The hint has been created!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'hints', 'action' => 'index']); - } else { - $this->_errorFlash($res['errors']); - } - } - - $this->set('hints', $this->Hint->find('all')); - $this->set('injects', $this->Inject->find('all')); - } - - /** - * Edit Hint - * - * @url /admin/hints/edit/ - */ - public function edit($id=false) { - $hint = $this->Hint->findById($id); - if ( empty($hint) ) { - throw new NotFoundException('Unknown hint'); - } - - if ( $this->request->is('post') ) { - // Validate the input - $res = $this->_validate(); - - if ( empty($res['errors']) ) { - // Fix parent_id - if ( $res['data']['parent_id'] == 0 ) { - $res['data']['parent_id'] = NULL; - } - - $this->Hint->id = $id; - $this->Hint->save($res['data']); - - $this->logMessage( - 'hints', - sprintf('Updated hint "%s"', $hint['Hint']['title']), - [ - 'old_hint' => $hint['Hint'], - 'new_hint' => $res['data'], - ], - $id - ); - - $this->Flash->success('The hint has been updated!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'hints', 'action' => 'index']); - } else { - $this->_errorFlash($res['errors']); - } - } - - $this->set('hints', $this->Hint->find('all')); - $this->set('injects', $this->Inject->find('all')); - $this->set('hint', $hint); - } - - /** - * Delete Hint - * - * @url /admin/hints/delete/ - */ - public function delete($id=false) { - $hint = $this->Hint->findById($id); - if ( empty($hint) ) { - throw new NotFoundException('Unknown hint'); - } - - if ( $this->request->is('post') ) { - $this->Hint->delete($id); - - $msg = sprintf('Deleted hint "%s"', $hint['Hint']['title']); - $this->logMessage('hints', $msg, ['hint' => $hint], $id); - $this->Flash->success($msg.'!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'hints', 'action' => 'index']); - } - - $this->set('hint', $hint); - } - - /** - * View Hint - * - * @url /admin/hints/view/ - */ - public function view($id=false) { - $log = $this->Log->findById($id); - if ( empty($log) ) { - throw new NotFoundException('Unknown Log ID'); - } - - $this->set('log', $log); - } + + public $uses = ['Hint', 'Inject']; + + public function beforeFilter() { + parent::beforeFilter(); + + // Setup the validators + $this->validators = [ + 'inject_id' => new Rules\AllOf( + new Rules\Digit() + ), + 'parent_id' => new Rules\Optional( + new Rules\Digit() + ), + 'title' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'content' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'time_wait' => new Rules\AllOf( + new Rules\Digit() + ), + 'cost' => new Rules\AllOf( + new Rules\Digit() + ), + ]; + } + + /** + * Hint List Page + * + * @url /admin/hints + * @url /admin/hints/index + */ + public function index() { + $this->set('hints', $this->Hint->find('all')); + } + + /** + * Create Hint + * + * @url /admin/hints/create + */ + public function create() { + if ($this->request->is('post')) { + // Validate the input + $res = $this->validate(); + + if (empty($res['errors'])) { + $this->Hint->create(); + $this->Hint->save($res['data']); + + $this->logMessage( + 'hints', + sprintf('Created hint "%s"', $res['data']['title']), + [], + $id + ); + + $this->Flash->success('The hint has been created!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'hints', 'action' => 'index']); + } else { + $this->errorFlash($res['errors']); + } + } + + $this->set('hints', $this->Hint->find('all')); + $this->set('injects', $this->Inject->find('all')); + } + + /** + * Edit Hint + * + * @url /admin/hints/edit/ + */ + public function edit($id = false) { + $hint = $this->Hint->findById($id); + if (empty($hint)) { + throw new NotFoundException('Unknown hint'); + } + + if ($this->request->is('post')) { + // Validate the input + $res = $this->validate(); + + if (empty($res['errors'])) { + // Fix parent_id + if ($res['data']['parent_id'] == 0) { + $res['data']['parent_id'] = null; + } + + $this->Hint->id = $id; + $this->Hint->save($res['data']); + + $this->logMessage( + 'hints', + sprintf('Updated hint "%s"', $hint['Hint']['title']), + [ + 'old_hint' => $hint['Hint'], + 'new_hint' => $res['data'], + ], + $id + ); + + $this->Flash->success('The hint has been updated!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'hints', 'action' => 'index']); + } else { + $this->errorFlash($res['errors']); + } + } + + $this->set('hints', $this->Hint->find('all')); + $this->set('injects', $this->Inject->find('all')); + $this->set('hint', $hint); + } + + /** + * Delete Hint + * + * @url /admin/hints/delete/ + */ + public function delete($id = false) { + $hint = $this->Hint->findById($id); + if (empty($hint)) { + throw new NotFoundException('Unknown hint'); + } + + if ($this->request->is('post')) { + $this->Hint->delete($id); + + $msg = sprintf('Deleted hint "%s"', $hint['Hint']['title']); + $this->logMessage('hints', $msg, ['hint' => $hint], $id); + $this->Flash->success($msg.'!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'hints', 'action' => 'index']); + } + + $this->set('hint', $hint); + } + + /** + * View Hint + * + * @url /admin/hints/view/ + */ + public function view($id = false) { + $log = $this->Log->findById($id); + if (empty($log)) { + throw new NotFoundException('Unknown Log ID'); + } + + $this->set('log', $log); + } } diff --git a/app/Plugin/Admin/Controller/InjectsController.php b/app/Plugin/Admin/Controller/InjectsController.php index ca19a7b..af72884 100644 --- a/app/Plugin/Admin/Controller/InjectsController.php +++ b/app/Plugin/Admin/Controller/InjectsController.php @@ -3,205 +3,206 @@ use Respect\Validation\Rules; class InjectsController extends AdminAppController { - public $uses = ['Attachment', 'Config', 'Inject', 'Schedule']; - - public function beforeFilter() { - parent::beforeFilter(); - - // Load + setup the InjectStyler helper - $this->helpers[] = 'InjectStyler'; - $this->helpers['InjectStyler'] = [ - 'types' => $this->Config->getInjectTypes(), - 'inject' => new stdClass(), // Nothing...for now - ]; - - // Setup the validators - $this->validators = [ - 'sequence' => new Rules\AllOf( - new Rules\Digit() - ), - 'title' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'content' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'from_name' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'from_email' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'grading_guide' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'max_points' => new Rules\AllOf( - new Rules\Digit(), - new Rules\NotEmpty() - ), - 'max_submissions' => new Rules\AllOf( - new Rules\Digit(), - new Rules\NotEmpty() - ), - 'type' => new Rules\AllOf( - new Rules\Alnum('-_'), - new Rules\NotEmpty() - ), - ]; - } - - /** - * Inject List Page - * - * @url /admin/injects - * @url /admin/injects/index - */ - public function index() { - $this->set('injects', $this->Inject->find('all')); - } - - /** - * Create Inject - * - * @url /admin/injects/create - */ - public function create() { - if ( $this->request->is('post') ) { - // Validate the input - $res = $this->_validate(); - - if ( empty($res['errors']) ) { - // Upload the new attachments - if ( isset($this->request->data['new_attachments'])) { - foreach ( $this->request->data['new_attachments'] AS $new ) { - $contents = file_get_contents($new['tmp_name']); - $data = json_encode([ - 'extension' => pathinfo($new['name'], PATHINFO_EXTENSION), - 'hash' => md5($contents), - 'data' => base64_encode($contents) - ]); - - $this->Attachment->create(); - $this->Attachment->save([ - 'inject_id' => $inject['Inject']['id'], - 'name' => $new['name'], - 'data' => $data, - ]); - } - } - - $this->Inject->create(); - $this->Inject->save($res['data']); - - $this->logMessage( - 'injects', - sprintf('Created inject "%s"', $res['data']['title']), - [], - $this->Inject->id - ); - - $this->Flash->success('The inject has been created!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'injects', 'action' => 'index']); - } else { - $this->_errorFlash($res['errors']); - } - } - } - - /** - * Edit Inject - * - * @url /admin/injects/edit/ - */ - public function edit($id=false) { - $inject = $this->Inject->findById($id); - if ( empty($inject) ) { - throw new NotFoundException('Unknown inject'); - } - - if ( $this->request->is('post') ) { - // Validate the input - $res = $this->_validate(); - - if ( empty($res['errors']) ) { - // Figure out if we deleted any attachments - foreach ( $inject['Attachment'] AS $i => $a ) { - if ( !isset($this->request->data['attachments'][$a['id']]) ) { - $this->Attachment->delete($a['id']); - - unset($inject['Attachment'][$i]); - } - } - - // Upload the new attachments - if ( isset($this->request->data['new_attachments'])) { - foreach ( $this->request->data['new_attachments'] AS $new ) { - $contents = file_get_contents($new['tmp_name']); - $data = json_encode([ - 'extension' => pathinfo($new['name'], PATHINFO_EXTENSION), - 'hash' => md5($contents), - 'data' => base64_encode($contents) - ]); - - $this->Attachment->create(); - $this->Attachment->save([ - 'inject_id' => $inject['Inject']['id'], - 'name' => $new['name'], - 'data' => $data, - ]); - } - } - - $this->Inject->id = $id; - $this->Inject->save($res['data']); - - $this->logMessage( - 'injects', - sprintf('Updated inject "%s"', $inject['Inject']['title']), - [ - 'old_inject' => $inject['Inject'], - 'new_inject' => $res['data'], - ], - $id - ); - - $this->Flash->success('The inject has been updated!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'injects', 'action' => 'index']); - } else { - $this->_errorFlash($res['errors']); - } - } - - $this->set('inject', $inject); - } - - /** - * Delete Inject - * - * @url /admin/injects/delete/ - */ - public function delete($id=false) { - $inject = $this->Inject->findById($id); - if ( empty($inject) ) { - throw new NotFoundException('Unknown inject'); - } - - if ( $this->request->is('post') ) { - $this->Inject->delete($id); - - // Delete all associated schedules - $schedules = []; - foreach ( $this->Schedule->findByInjectId($id) AS $s ) { - $schedules[] = $s['Schedule']['id']; - } - $this->Schedule->delete($schedules); - - $msg = sprintf('Deleted inject "%s"', $inject['Inject']['title']); - $this->logMessage('injects', $msg, ['inject' => $inject], $id); - $this->Flash->success($msg.'!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'injects', 'action' => 'index']); - } - - $this->set('inject', $inject); - } + + public $uses = ['Attachment', 'Config', 'Inject', 'Schedule']; + + public function beforeFilter() { + parent::beforeFilter(); + + // Load + setup the InjectStyler helper + $this->helpers[] = 'InjectStyler'; + $this->helpers['InjectStyler'] = [ + 'types' => $this->Config->getInjectTypes(), + 'inject' => new stdClass(), // Nothing...for now + ]; + + // Setup the validators + $this->validators = [ + 'sequence' => new Rules\AllOf( + new Rules\Digit() + ), + 'title' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'content' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'from_name' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'from_email' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'grading_guide' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'max_points' => new Rules\AllOf( + new Rules\Digit(), + new Rules\NotEmpty() + ), + 'max_submissions' => new Rules\AllOf( + new Rules\Digit(), + new Rules\NotEmpty() + ), + 'type' => new Rules\AllOf( + new Rules\Alnum('-_'), + new Rules\NotEmpty() + ), + ]; + } + + /** + * Inject List Page + * + * @url /admin/injects + * @url /admin/injects/index + */ + public function index() { + $this->set('injects', $this->Inject->find('all')); + } + + /** + * Create Inject + * + * @url /admin/injects/create + */ + public function create() { + if ($this->request->is('post')) { + // Validate the input + $res = $this->validate(); + + if (empty($res['errors'])) { + // Upload the new attachments + if (isset($this->request->data['new_attachments'])) { + foreach ($this->request->data['new_attachments'] as $new) { + $contents = file_get_contents($new['tmp_name']); + $data = json_encode([ + 'extension' => pathinfo($new['name'], PATHINFO_EXTENSION), + 'hash' => md5($contents), + 'data' => base64_encode($contents) + ]); + + $this->Attachment->create(); + $this->Attachment->save([ + 'inject_id' => $inject['Inject']['id'], + 'name' => $new['name'], + 'data' => $data, + ]); + } + } + + $this->Inject->create(); + $this->Inject->save($res['data']); + + $this->logMessage( + 'injects', + sprintf('Created inject "%s"', $res['data']['title']), + [], + $this->Inject->id + ); + + $this->Flash->success('The inject has been created!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'injects', 'action' => 'index']); + } else { + $this->errorFlash($res['errors']); + } + } + } + + /** + * Edit Inject + * + * @url /admin/injects/edit/ + */ + public function edit($id = false) { + $inject = $this->Inject->findById($id); + if (empty($inject)) { + throw new NotFoundException('Unknown inject'); + } + + if ($this->request->is('post')) { + // Validate the input + $res = $this->validate(); + + if (empty($res['errors'])) { + // Figure out if we deleted any attachments + foreach ($inject['Attachment'] as $i => $a) { + if (!isset($this->request->data['attachments'][$a['id']])) { + $this->Attachment->delete($a['id']); + + unset($inject['Attachment'][$i]); + } + } + + // Upload the new attachments + if (isset($this->request->data['new_attachments'])) { + foreach ($this->request->data['new_attachments'] as $new) { + $contents = file_get_contents($new['tmp_name']); + $data = json_encode([ + 'extension' => pathinfo($new['name'], PATHINFO_EXTENSION), + 'hash' => md5($contents), + 'data' => base64_encode($contents) + ]); + + $this->Attachment->create(); + $this->Attachment->save([ + 'inject_id' => $inject['Inject']['id'], + 'name' => $new['name'], + 'data' => $data, + ]); + } + } + + $this->Inject->id = $id; + $this->Inject->save($res['data']); + + $this->logMessage( + 'injects', + sprintf('Updated inject "%s"', $inject['Inject']['title']), + [ + 'old_inject' => $inject['Inject'], + 'new_inject' => $res['data'], + ], + $id + ); + + $this->Flash->success('The inject has been updated!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'injects', 'action' => 'index']); + } else { + $this->errorFlash($res['errors']); + } + } + + $this->set('inject', $inject); + } + + /** + * Delete Inject + * + * @url /admin/injects/delete/ + */ + public function delete($id = false) { + $inject = $this->Inject->findById($id); + if (empty($inject)) { + throw new NotFoundException('Unknown inject'); + } + + if ($this->request->is('post')) { + $this->Inject->delete($id); + + // Delete all associated schedules + $schedules = []; + foreach ($this->Schedule->findByInjectId($id) as $s) { + $schedules[] = $s['Schedule']['id']; + } + $this->Schedule->delete($schedules); + + $msg = sprintf('Deleted inject "%s"', $inject['Inject']['title']); + $this->logMessage('injects', $msg, ['inject' => $inject], $id); + $this->Flash->success($msg.'!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'injects', 'action' => 'index']); + } + + $this->set('inject', $inject); + } } diff --git a/app/Plugin/Admin/Controller/LogsController.php b/app/Plugin/Admin/Controller/LogsController.php index 5574e91..071e9b6 100644 --- a/app/Plugin/Admin/Controller/LogsController.php +++ b/app/Plugin/Admin/Controller/LogsController.php @@ -2,45 +2,46 @@ App::uses('AdminAppController', 'Admin.Controller'); class LogsController extends AdminAppController { - public $uses = ['Log']; - public $paginate = [ - 'fields' => [ - 'Log.id', 'Log.time', 'Log.type', 'Log.data', - 'Log.ip', 'Log.message', 'User.username', 'User.group_id', - ], - 'contain' => [ - 'User' => [ - 'Group.name', - ] - ], - 'order' => [ - 'Log.id' => 'DESC' - ], - ]; + public $uses = ['Log']; - /** - * Log List Page - * - * @url /admin/logs - * @url /admin/logs/index - */ - public function index() { - $this->Paginator->settings += $this->paginate; - $this->set('recent_logs', $this->Paginator->paginate('Log')); - } + public $paginate = [ + 'fields' => [ + 'Log.id', 'Log.time', 'Log.type', 'Log.data', + 'Log.ip', 'Log.message', 'User.username', 'User.group_id', + ], + 'contain' => [ + 'User' => [ + 'Group.name', + ] + ], + 'order' => [ + 'Log.id' => 'DESC' + ], + ]; - /** - * View Log - * - * @url /admin/logs/view/ - */ - public function view($id=false) { - $log = $this->Log->findById($id); - if ( empty($log) ) { - throw new NotFoundException('Unknown Log ID'); - } + /** + * Log List Page + * + * @url /admin/logs + * @url /admin/logs/index + */ + public function index() { + $this->Paginator->settings += $this->paginate; + $this->set('recent_logs', $this->Paginator->paginate('Log')); + } - $this->set('log', $log); - } + /** + * View Log + * + * @url /admin/logs/view/ + */ + public function view($id = false) { + $log = $this->Log->findById($id); + if (empty($log)) { + throw new NotFoundException('Unknown Log ID'); + } + + $this->set('log', $log); + } } diff --git a/app/Plugin/Admin/Controller/ScheduleController.php b/app/Plugin/Admin/Controller/ScheduleController.php index c231a46..6f7d1d1 100644 --- a/app/Plugin/Admin/Controller/ScheduleController.php +++ b/app/Plugin/Admin/Controller/ScheduleController.php @@ -3,300 +3,314 @@ App::uses('InjectAbstraction', 'Lib'); class ScheduleController extends AppController { - public $uses = ['Config', 'Inject', 'Group', 'Schedule']; - - /** - * Before Filter Hook - * - * Set's the active tab to be staff - */ - public function beforeFilter() { - parent::beforeFilter(); - - if ( in_array($this->request->action, ['index', 'api']) ) { - $this->Auth->protect(env('GROUP_STAFF')); - $this->set('at_staff', true); - } else { - $this->Auth->protect(env('GROUP_ADMINS')); - $this->set('at_backend', true); - } - } - - /** - * Overview Page - * - * @url /admin/schedule - * @url /admin/schedule/index - */ - public function index() { - $bounds = $this->Schedule->getScheduleBounds(); - - $this->set('start', $bounds['min']); - $this->set('end', $bounds['max']); - } - - /** - * Overview API Page - * - * @url /admin/schedule/api - */ - public function api() { - if ( - $this->request->is('post') && - isset($this->request->data['changes']) && - is_array($this->request->data['changes']) - ) { - foreach ( $this->request->data['changes'] AS $c ) { - $schedule = $this->Schedule->findById($c['id']); - if ( empty($schedule) ) continue; - - $start = ($schedule['Schedule']['fuzzy'] ? $c['start'] - COMPETITION_START : $c['start']); - $end = ($schedule['Schedule']['fuzzy'] ? $c['end'] - COMPETITION_START : $c['end']); - - // Bad time - we don't want negatives - if ( 0 > $start || 0 > $end ) continue; - - $this->Schedule->id = $c['id']; - $this->Schedule->save([ - 'start' => $start, - 'end' => $end, - ]); - } - - return $this->ajaxResponse(true); - } - $out = ['data' => []]; - - $schedules = $this->Schedule->getAllSchedules(); - $bounds = $this->Schedule->getScheduleBounds(); - - foreach ( $schedules AS $s ) { - $out['data'][] = [ - 'id' => $s->getScheduleId(), - 'inject_id' => $s->getInjectId(), - 'text' => $s->getTitle().' ('.$s->getGroupName().')', - 'group' => $s->getGroupName(), - 'start_date' => date('d-m-Y G:i:s', $s->getStart() > 0 ? $s->getStart() : $bounds['min']), - 'start_ts' => $s->getStart(), - 'end_date' => date('d-m-Y G:i:s', $s->getEnd() > 0 ? $s->getEnd() : $bounds['max']), - 'end_ts' => $s->getEnd(), - ]; - } - - return $this->ajaxResponse($out); - } - - /** - * Manager Page - * - * @url /admin/schedule/manager - */ - public function manager() { - $this->set('injects', $this->Schedule->getAllSchedules(false)); - } - - /** - * Create a schedule. - * - * @url /admin/schedule/create - * @url /admin/schedule/create/ - */ - public function create($sid=false) { - if ( $this->request->is('post') ) { - $create = []; - $missing = []; - foreach ( array_keys($this->Schedule->schema()) AS $key ) { - if ( in_array($key, ['id']) ) continue; - - if ( !isset($this->request->data[$key]) ) { - $missing[] = $key; - continue; - } - - // Fix dependency_id to be NULL if the ID is 0 - if ( $key == 'dependency_id' && $this->request->data['dependency_id'] == 0 ) { - $this->request->data['dependency_id'] = NULL; - } - - $create[$key] = $this->request->data[$key]; - } - - if ( empty($missing) ) { - $this->Schedule->create(); - $this->Schedule->save($create); - - $msg = sprintf('Created schedule #%d', $this->Schedule->id); - - $this->logMessage( - 'schedule', - $msg, - [ - 'schedule' => $create, - ], - $sid - ); - - $this->Flash->success($msg.'!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'manager']); - } else { - $this->Flash->danger(sprintf('You are missing %s!', implode(', ', $missing))); - return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'create']); - } - } - - $this->set('injects', $this->Inject->find('all')); - $this->set('groups', $this->Group->generateTreeList(null, null, null, '--')); - - if ( $sid !== false && is_numeric($sid) ) { - $schedule = $this->Schedule->findById($sid); - if ( empty($schedule) ) { - throw new NotFoundException('Unknown Schedule ID'); - } - - // Load + setup the InjectStyler helper - $this->helpers[] = 'InjectStyler'; - $this->helpers['InjectStyler'] = [ - 'types' => $this->Config->getInjectTypes(), - 'inject' => new stdClass(), // Nothing...for now - ]; - - $this->set('schedule', $schedule); - } - } - - /** - * Flip the status of a schedule - * - * @url /admin/schedule/flip/ - */ - public function flip($sid=false) { - $schedule = $this->Schedule->findById($sid); - - if ( !empty($schedule) ) { - $this->Schedule->id = $sid; - $this->Schedule->save([ - 'active' => !($schedule['Schedule']['active']), - ]); - - $msg = sprintf('%sctivated inject "%s"', ($schedule['Schedule']['active'] ? 'Dea' : 'A'), $schedule['Inject']['title']); - - $this->logMessage( - 'schedule', - $msg, - [ - 'old_status' => $schedule['Schedule']['active'], - 'new_status' => !$schedule['Schedule']['active'], - ], - $sid - ); - - $this->Flash->success($msg.'!'); - } else { - $this->Flash->danger('Unknown Schedule ID'); - } - - return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'manager']); - } - - /** - * Edit a schedule. - * - * @url /admin/schedule/edit/ - */ - public function edit($sid) { - $schedule = $this->Schedule->findById($sid); - if ( empty($schedule) ) { - throw new NotFoundException('Unknown Schedule ID'); - } - - if ( $this->request->is('post') ) { - $this->Schedule->id = $sid; - - // Fix dependency_id to be NULL if the ID is 0 - if ( isset($this->request->data['dependency_id']) && $this->request->data['dependency_id'] == 0 ) { - $this->request->data['dependency_id'] = NULL; - } - - $update = []; - foreach ( $schedule['Schedule'] AS $k => $v ) { - if ( !isset($this->request->data[$k]) ) continue; - if ( $this->request->data[$k] == $v ) continue; - - $update[$k] = $this->request->data[$k]; - } - - if ( !empty($update) ) { - $this->Schedule->save($update); - - $msg = sprintf('Edited schedule #%d', $sid); - - $this->logMessage( - 'schedule', - $msg, - [ - 'old_schedule' => $schedule['Schedule'], - 'new_schedule' => $this->request->data, - 'delta' => $update, - ], - $sid - ); - - $this->Flash->success($msg.'!'); - } else { - $this->Flash->danger('There are no changes to save'); - } - - return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'manager']); - } - - // Load + setup the InjectStyler helper - $this->helpers[] = 'InjectStyler'; - $this->helpers['InjectStyler'] = [ - 'types' => $this->Config->getInjectTypes(), - 'inject' => new stdClass(), // Nothing...for now - ]; - - $this->set('injects', $this->Inject->find('all')); - $this->set('groups', $this->Group->generateTreeList(null, null, null, '--')); - $this->set('schedule', $schedule); - } - - /** - * Delete a schedule. SPOOKY - * - * @url /admin/schedule/delete/ - */ - public function delete($sid) { - $schedule = $this->Schedule->findById($sid); - if ( empty($schedule) ) { - throw new NotFoundException('Unknown Schedule ID'); - } - - if ( $this->request->is('post') ) { - $this->Schedule->delete($sid); - - $msg = sprintf('Deleted schedule #%d', $sid); - - $this->logMessage( - 'schedule', - $msg, - [ - 'schedule' => $schedule['Schedule'], - ], - $sid - ); - - $this->Flash->success($msg); - return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'manager']); - } - - // Load + setup the InjectStyler helper - $this->helpers[] = 'InjectStyler'; - $this->helpers['InjectStyler'] = [ - 'types' => $this->Config->getInjectTypes(), - 'inject' => new stdClass(), // Nothing...for now - ]; - - $this->set('schedule', new InjectAbstraction($schedule, 0)); - } + + public $uses = ['Config', 'Inject', 'Group', 'Schedule']; + + /** + * Before Filter Hook + * + * Set's the active tab to be staff + */ + public function beforeFilter() { + parent::beforeFilter(); + + if (in_array($this->request->action, ['index', 'api'])) { + $this->Auth->protect(env('GROUP_STAFF')); + $this->set('at_staff', true); + } else { + $this->Auth->protect(env('GROUP_ADMINS')); + $this->set('at_backend', true); + } + } + + /** + * Overview Page + * + * @url /admin/schedule + * @url /admin/schedule/index + */ + public function index() { + $bounds = $this->Schedule->getScheduleBounds(); + + $this->set('start', $bounds['min']); + $this->set('end', $bounds['max']); + } + + /** + * Overview API Page + * + * @url /admin/schedule/api + */ + public function api() { + if ($this->request->is('post') + && isset($this->request->data['changes']) + && is_array($this->request->data['changes']) + ) { + foreach ($this->request->data['changes'] as $c) { + $schedule = $this->Schedule->findById($c['id']); + if (empty($schedule)) { + continue; + } + + $start = ($schedule['Schedule']['fuzzy'] ? $c['start'] - COMPETITION_START : $c['start']); + $end = ($schedule['Schedule']['fuzzy'] ? $c['end'] - COMPETITION_START : $c['end']); + + // Bad time - we don't want negatives + if (0 > $start || 0 > $end) { + continue; + } + + $this->Schedule->id = $c['id']; + $this->Schedule->save([ + 'start' => $start, + 'end' => $end, + ]); + } + + return $this->ajaxResponse(true); + } + $out = ['data' => []]; + + $schedules = $this->Schedule->getAllSchedules(); + $bounds = $this->Schedule->getScheduleBounds(); + + foreach ($schedules as $s) { + $out['data'][] = [ + 'id' => $s->getScheduleId(), + 'inject_id' => $s->getInjectId(), + 'text' => $s->getTitle().' ('.$s->getGroupName().')', + 'group' => $s->getGroupName(), + 'start_date' => date('d-m-Y G:i:s', $s->getStart() > 0 ? $s->getStart() : $bounds['min']), + 'start_ts' => $s->getStart(), + 'end_date' => date('d-m-Y G:i:s', $s->getEnd() > 0 ? $s->getEnd() : $bounds['max']), + 'end_ts' => $s->getEnd(), + ]; + } + + return $this->ajaxResponse($out); + } + + /** + * Manager Page + * + * @url /admin/schedule/manager + */ + public function manager() { + $this->set('injects', $this->Schedule->getAllSchedules(false)); + } + + /** + * Create a schedule. + * + * @url /admin/schedule/create + * @url /admin/schedule/create/ + */ + public function create($sid = false) { + if ($this->request->is('post')) { + $create = []; + $missing = []; + foreach (array_keys($this->Schedule->schema()) as $key) { + if (in_array($key, ['id'])) { + continue; + } + + if (!isset($this->request->data[$key])) { + $missing[] = $key; + continue; + } + + // Fix dependency_id to be NULL if the ID is 0 + if ($key == 'dependency_id' && $this->request->data['dependency_id'] == 0) { + $this->request->data['dependency_id'] = null; + } + + $create[$key] = $this->request->data[$key]; + } + + if (empty($missing)) { + $this->Schedule->create(); + $this->Schedule->save($create); + + $msg = sprintf('Created schedule #%d', $this->Schedule->id); + + $this->logMessage( + 'schedule', + $msg, + [ + 'schedule' => $create, + ], + $sid + ); + + $this->Flash->success($msg.'!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'manager']); + } else { + $this->Flash->danger(sprintf('You are missing %s!', implode(', ', $missing))); + return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'create']); + } + } + + $this->set('injects', $this->Inject->find('all')); + $this->set('groups', $this->Group->generateTreeList(null, null, null, '--')); + + if ($sid !== false && is_numeric($sid)) { + $schedule = $this->Schedule->findById($sid); + if (empty($schedule)) { + throw new NotFoundException('Unknown Schedule ID'); + } + + // Load + setup the InjectStyler helper + $this->helpers[] = 'InjectStyler'; + $this->helpers['InjectStyler'] = [ + 'types' => $this->Config->getInjectTypes(), + 'inject' => new stdClass(), // Nothing...for now + ]; + + $this->set('schedule', $schedule); + } + } + + /** + * Flip the status of a schedule + * + * @url /admin/schedule/flip/ + */ + public function flip($sid = false) { + $schedule = $this->Schedule->findById($sid); + + if (!empty($schedule)) { + $this->Schedule->id = $sid; + $this->Schedule->save([ + 'active' => !($schedule['Schedule']['active']), + ]); + + $msg = sprintf( + '%sctivated inject "%s"', + ($schedule['Schedule']['active'] ? 'Dea' : 'A'), + $schedule['Inject']['title'] + ); + + $this->logMessage( + 'schedule', + $msg, + [ + 'old_status' => $schedule['Schedule']['active'], + 'new_status' => !$schedule['Schedule']['active'], + ], + $sid + ); + + $this->Flash->success($msg.'!'); + } else { + $this->Flash->danger('Unknown Schedule ID'); + } + + return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'manager']); + } + + /** + * Edit a schedule. + * + * @url /admin/schedule/edit/ + */ + public function edit($sid) { + $schedule = $this->Schedule->findById($sid); + if (empty($schedule)) { + throw new NotFoundException('Unknown Schedule ID'); + } + + if ($this->request->is('post')) { + $this->Schedule->id = $sid; + + // Fix dependency_id to be NULL if the ID is 0 + if (isset($this->request->data['dependency_id']) && $this->request->data['dependency_id'] == 0) { + $this->request->data['dependency_id'] = null; + } + + $update = []; + foreach ($schedule['Schedule'] as $k => $v) { + if (!isset($this->request->data[$k])) { + continue; + } + if ($this->request->data[$k] == $v) { + continue; + } + + $update[$k] = $this->request->data[$k]; + } + + if (!empty($update)) { + $this->Schedule->save($update); + + $msg = sprintf('Edited schedule #%d', $sid); + + $this->logMessage( + 'schedule', + $msg, + [ + 'old_schedule' => $schedule['Schedule'], + 'new_schedule' => $this->request->data, + 'delta' => $update, + ], + $sid + ); + + $this->Flash->success($msg.'!'); + } else { + $this->Flash->danger('There are no changes to save'); + } + + return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'manager']); + } + + // Load + setup the InjectStyler helper + $this->helpers[] = 'InjectStyler'; + $this->helpers['InjectStyler'] = [ + 'types' => $this->Config->getInjectTypes(), + 'inject' => new stdClass(), // Nothing...for now + ]; + + $this->set('injects', $this->Inject->find('all')); + $this->set('groups', $this->Group->generateTreeList(null, null, null, '--')); + $this->set('schedule', $schedule); + } + + /** + * Delete a schedule. SPOOKY + * + * @url /admin/schedule/delete/ + */ + public function delete($sid) { + $schedule = $this->Schedule->findById($sid); + if (empty($schedule)) { + throw new NotFoundException('Unknown Schedule ID'); + } + + if ($this->request->is('post')) { + $this->Schedule->delete($sid); + + $msg = sprintf('Deleted schedule #%d', $sid); + + $this->logMessage( + 'schedule', + $msg, + [ + 'schedule' => $schedule['Schedule'], + ], + $sid + ); + + $this->Flash->success($msg); + return $this->redirect(['plugin' => 'admin', 'controller' => 'schedule', 'action' => 'manager']); + } + + // Load + setup the InjectStyler helper + $this->helpers[] = 'InjectStyler'; + $this->helpers['InjectStyler'] = [ + 'types' => $this->Config->getInjectTypes(), + 'inject' => new stdClass(), // Nothing...for now + ]; + + $this->set('schedule', new InjectAbstraction($schedule, 0)); + } } diff --git a/app/Plugin/Admin/Controller/SiteController.php b/app/Plugin/Admin/Controller/SiteController.php index 14c5e1d..b470ff0 100644 --- a/app/Plugin/Admin/Controller/SiteController.php +++ b/app/Plugin/Admin/Controller/SiteController.php @@ -3,198 +3,206 @@ use Respect\Validation\Rules; class SiteController extends AdminAppController { - public $uses = ['Announcement', 'Config']; - - /** - * Config Index Page - * - * @url /admin/site - * @url /admin/site/index - */ - public function index() { - $this->set('announce', $this->Announcement->find('all')); - $this->set('config', $this->Config->find('all')); - } - - /** - * Config API Page - * - * @url /admin/site/api// - */ - public function api($type='announcement', $id=false) { - switch ( $type ) { - case 'config': - $config = $this->Config->findById($id); - if ( empty($config) ) { - throw new NotFoundException('Unknown config'); - } - - $data = $config['Config']; - break; - - case 'announcement': - $announcement = $this->Announcement->findById($id); - if ( empty($announcement) ) { - throw new NotFoundException('Unknown announcement'); - } - - $data = $announcement['Announcement']; - break; - - default: - throw new MethodNotAllowedException(); - break; - } - - return $this->ajaxResponse($data); - } - - /** - * Config Edit/Create URL - * - * @url /admin/site/api - */ - public function config() { - if ( !$this->request->is('post') ) { - throw new MethodNotAllowedException(); - } - - // Validate the input - $this->validators = [ - 'id' => new Rules\AllOf( - new Rules\Digit() - ), - 'key' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'value' => new Rules\AllOf( - new Rules\NotEmpty() - ), - ]; - $res = $this->_validate(); - - if ( !empty($res['errors']) ) { - $this->_errorFlash($res['errors']); - - return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); - } - - if ( $res['data']['id'] > 0 ) { - $config = $this->Config->findById($res['data']['id']); - if ( empty($config) ) { - throw new NotFoundException('Unknown config'); - } - - $this->Config->id = $res['data']['id']; - $this->Config->save($res['data']); - - $msg = sprintf('Edited config value "%s"', $config['Config']['key']); - - $this->logMessage('config', $msg, ['old_config' => $config['Config'], 'new_config' => $res['data']], $config['Config']['id']); - $this->Flash->success($msg.'!'); - } else { - // Fix the data - unset($res['data']['id']); - - $this->Config->create(); - $this->Config->save($res['data']); - - $msg = sprintf('Created config value "%s"', $res['data']['key']); - $this->logMessage('config', $msg, [], $this->Config->id); - $this->Flash->success($msg.'!'); - } - - return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); - } - - /** - * Announcement Edit/Create URL - * - * @url /admin/site/announcement - */ - public function announcement() { - if ( !$this->request->is('post') ) { - throw new MethodNotAllowedException(); - } - - // Validate the input - $this->validators = [ - 'id' => new Rules\AllOf( - new Rules\Digit() - ), - 'content' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'active' => new Rules\AllOf( - new Rules\Digit() - ), - 'expiration' => new Rules\AllOf( - new Rules\Digit() - ), - ]; - $res = $this->_validate(); - - if ( !empty($res['errors']) ) { - $this->_errorFlash($res['errors']); - - return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); - } - - if ( $res['data']['id'] > 0 ) { - $announcement = $this->Announcement->findById($res['data']['id']); - if ( empty($announcement) ) { - throw new NotFoundException('Unknown announcement'); - } - - $this->Announcement->id = $res['data']['id']; - $this->Announcement->save($res['data']); - - $msg = sprintf('Edited announcement #%d', $announcement['Announcement']['id']); - - $this->logMessage('announcement', $msg, [ - 'old_announcement' => $announcement['Announcement'], - 'new_announcement' => $res['data'] - ], $announcement['Announcement']['id']); - $this->Flash->success($msg.'!'); - } else { - // Fix the data - unset($res['data']['id']); - - $this->Announcement->create(); - $this->Announcement->save($res['data']); - - $msg = sprintf('Created announcement #%d', $this->Announcement->id); - $this->logMessage('announcement', $msg, [], $this->Announcement->id); - $this->Flash->success($msg.'!'); - } - - return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); - } - - /** - * Config Delete - * - * @url /admin/site/delete// - */ - public function delete($type='announcement', $id=false) { - $modal = ($type == 'announcement' ? $this->Announcement : $this->Config); - $msgType = ($type == 'announcement' ? 'Announcement' : 'Config'); - $logType = ($type == 'announcement' ? 'announcement' : 'config'); - - $data = $modal->findById($id); - if ( empty($data) ) { - throw new NotFoundException('Unknown '.$msgType); - } - - if ( $this->request->is('post') ) { - $modal->delete($id); - - $msg = sprintf('Deleted %s #%d', $msgType, $data[$msgType]['id']); - $this->logMessage($logType, $msg, [$logType => $data], $id); - $this->Flash->success($msg.'!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); - } - - $this->set('data', $data); - } + + public $uses = ['Announcement', 'Config']; + + /** + * Config Index Page + * + * @url /admin/site + * @url /admin/site/index + */ + public function index() { + $this->set('announce', $this->Announcement->find('all')); + $this->set('config', $this->Config->find('all')); + } + + /** + * Config API Page + * + * @url /admin/site/api// + */ + public function api($type = 'announcement', $id = false) { + switch ($type) { + case 'config': + $config = $this->Config->findById($id); + if (empty($config)) { + throw new NotFoundException('Unknown config'); + } + + $data = $config['Config']; + break; + + case 'announcement': + $announcement = $this->Announcement->findById($id); + if (empty($announcement)) { + throw new NotFoundException('Unknown announcement'); + } + + $data = $announcement['Announcement']; + break; + + default: + throw new MethodNotAllowedException(); + } + + return $this->ajaxResponse($data); + } + + /** + * Config Edit/Create URL + * + * @url /admin/site/api + */ + public function config() { + if (!$this->request->is('post')) { + throw new MethodNotAllowedException(); + } + + // Validate the input + $this->validators = [ + 'id' => new Rules\AllOf( + new Rules\Digit() + ), + 'key' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'value' => new Rules\AllOf( + new Rules\NotEmpty() + ), + ]; + $res = $this->validate(); + + if (!empty($res['errors'])) { + $this->errorFlash($res['errors']); + + return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); + } + + if ($res['data']['id'] > 0) { + $config = $this->Config->findById($res['data']['id']); + if (empty($config)) { + throw new NotFoundException('Unknown config'); + } + + $this->Config->id = $res['data']['id']; + $this->Config->save($res['data']); + + $msg = sprintf('Edited config value "%s"', $config['Config']['key']); + + $this->logMessage( + 'config', + $msg, + [ + 'old_config' => $config['Config'], + 'new_config' => $res['data'] + ], + $config['Config']['id'] + ); + $this->Flash->success($msg.'!'); + } else { + // Fix the data + unset($res['data']['id']); + + $this->Config->create(); + $this->Config->save($res['data']); + + $msg = sprintf('Created config value "%s"', $res['data']['key']); + $this->logMessage('config', $msg, [], $this->Config->id); + $this->Flash->success($msg.'!'); + } + + return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); + } + + /** + * Announcement Edit/Create URL + * + * @url /admin/site/announcement + */ + public function announcement() { + if (!$this->request->is('post')) { + throw new MethodNotAllowedException(); + } + + // Validate the input + $this->validators = [ + 'id' => new Rules\AllOf( + new Rules\Digit() + ), + 'content' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'active' => new Rules\AllOf( + new Rules\Digit() + ), + 'expiration' => new Rules\AllOf( + new Rules\Digit() + ), + ]; + $res = $this->validate(); + + if (!empty($res['errors'])) { + $this->errorFlash($res['errors']); + + return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); + } + + if ($res['data']['id'] > 0) { + $announcement = $this->Announcement->findById($res['data']['id']); + if (empty($announcement)) { + throw new NotFoundException('Unknown announcement'); + } + + $this->Announcement->id = $res['data']['id']; + $this->Announcement->save($res['data']); + + $msg = sprintf('Edited announcement #%d', $announcement['Announcement']['id']); + + $this->logMessage('announcement', $msg, [ + 'old_announcement' => $announcement['Announcement'], + 'new_announcement' => $res['data'] + ], $announcement['Announcement']['id']); + $this->Flash->success($msg.'!'); + } else { + // Fix the data + unset($res['data']['id']); + + $this->Announcement->create(); + $this->Announcement->save($res['data']); + + $msg = sprintf('Created announcement #%d', $this->Announcement->id); + $this->logMessage('announcement', $msg, [], $this->Announcement->id); + $this->Flash->success($msg.'!'); + } + + return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); + } + + /** + * Config Delete + * + * @url /admin/site/delete// + */ + public function delete($type = 'announcement', $id = false) { + $modal = ($type == 'announcement' ? $this->Announcement : $this->Config); + $msgType = ($type == 'announcement' ? 'Announcement' : 'Config'); + $logType = ($type == 'announcement' ? 'announcement' : 'config'); + + $data = $modal->findById($id); + if (empty($data)) { + throw new NotFoundException('Unknown '.$msgType); + } + + if ($this->request->is('post')) { + $modal->delete($id); + + $msg = sprintf('Deleted %s #%d', $msgType, $data[$msgType]['id']); + $this->logMessage($logType, $msg, [$logType => $data], $id); + $this->Flash->success($msg.'!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'site', 'action' => 'index']); + } + + $this->set('data', $data); + } } diff --git a/app/Plugin/Admin/Controller/UsersController.php b/app/Plugin/Admin/Controller/UsersController.php index 8a963c3..1bc1753 100644 --- a/app/Plugin/Admin/Controller/UsersController.php +++ b/app/Plugin/Admin/Controller/UsersController.php @@ -3,210 +3,215 @@ use Respect\Validation\Rules; class UsersController extends AdminAppController { - public $uses = ['User', 'Group']; - - public function beforeFilter() { - parent::beforeFilter(); - - $this->validators = [ - 'username' => new Rules\AllOf( - new Rules\Alnum('-_'), - new Rules\NotEmpty(), - new Rules\NoWhitespace() - ), - 'password' => new Rules\AlwaysValid(), - 'group_id' => new Rules\AllOf( - new Rules\Digit(), - new Rules\NotEmpty() - ), - 'active' => new Rules\AllOf( - new Rules\BoolVal() - ), - 'expiration' => new Rules\AllOf( - new Rules\Length(1, 10, true) - ), - ]; - } - - /** - * User List Page - * - * @url /admin/user - * @url /admin/user/index - */ - public function index() { - $this->set('users', $this->User->find('all', [ - 'fields' => [ - 'User.id', 'User.username', 'User.expiration', 'User.active', 'Group.name' - ], - ])); - } - - /** - * Emulate User - * - * @url /admin/user/emulate/ - */ - public function emulate($uidOrUsername=false) { - try { - $curUID = $this->Auth->user('id'); - - $this->Auth->emulate($uidOrUsername); - - $msg = sprintf('Emulated user %s', $this->Auth->user('username')); - $this->logMessage('emulate', $msg, [], $this->Auth->user('id'), $curUID); - $this->Flash->success($msg.'!'); - - return $this->redirect('/'); - } catch ( InternalErrorException $e ) { - throw $e; - } - } - - /** - * Create User - * - * @url /admin/user/create - */ - public function create() { - if ( $this->request->is('post') ) { - // Validate the input - $res = $this->_validate(); - - if ( empty($res['errors']) ) { - $this->User->create(); - $this->User->save($res['data']); - - $this->logMessage( - 'users', - sprintf('Created user "%s"', $res['data']['username']), - [], - $this->User->id - ); - - $this->Flash->success('The user has been created!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'users', 'action' => 'index']); - } else { - $this->_errorFlash($res['errors']); - } - } - - $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); - } - - /** - * Edit User - * - * @url /adminuser/edit/ - * @url /admin/user/edit/ - */ - public function edit($uid=false) { - $user = $this->User->findById($uid); - if ( empty($user) ) { - throw new NotFoundException('Unknown user'); - } - - if ( $this->request->is('post') ) { - // Validate the input - $res = $this->_validate(); - - if ( empty($res['errors']) ) { - // Clear out the password, if it's empty - if ( empty($res['data']['password']) ) { - unset($res['data']['password']); - } - - $this->User->id = $uid; - $this->User->save($res['data']); - - // Redact the old password - $user['User']['password'] = '-redacted-'; - - // ...and the new one - if ( isset($res['data']['password']) ) { - $res['data']['password'] = '-redacted-'; - } - - $this->logMessage( - 'users', - sprintf('Updated user "%s"', $user['User']['username']), - [ - 'old_user' => $user['User'], - 'new_user' => $res['data'], - ], - $uid - ); - - $this->Flash->success('The user has been updated!'); - return $this->redirect(['plugin' => 'admin', 'controller' => 'users', 'action' => 'index']); - } else { - $this->_errorFlash($res['errors']); - } - } - - $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); - $this->set('user', $user); - } - - /** - * Delete User - * - * @url /admin/user/delete/ - */ - public function delete($uid=false) { - $user = $this->User->findById($uid); - if ( empty($user) ) { - throw new NotFoundException('Unknown user'); - } - - if ( $this->request->is('post') ) { - $this->User->delete($uid); - - $msg = sprintf('Deleted user "%s" (#%d)', $user['User']['username'], $uid); - - $this->logMessage( - 'users', - $msg, - [ - 'user' => $user['User'], - ], - $uid - ); - - $this->Flash->success($msg); - return $this->redirect(['plugin' => 'admin', 'controller' => 'users', 'action' => 'index']); - } - - $this->set('user', $user); - } - - /** - * Toggle User Status - * - * @url /admin/user/flip/ - */ - public function flip($uid=false) { - $user = $this->User->findById($uid); - if ( empty($user) ) { - throw new NotFoundException('Unknown user'); - } - - $this->User->id = $uid; - $this->User->save([ - 'active' => !$user['User']['active'], - ]); - - $this->logMessage( - 'users', - sprintf('Flipped the status user "%s" to %sactive', $user['User']['username'], $user['User']['active'] ? 'in' : ''), - [ - 'old_status' => $user['User']['active'], - 'new_status' => !$user['User']['active'], - ], - $uid - ); - - $this->Flash->success(sprintf('Toggled status for user %s!', $user['User']['username'])); - return $this->redirect(['plugin' => 'admin', 'controller' => 'users', 'action' => 'index']); - } + + public $uses = ['User', 'Group']; + + public function beforeFilter() { + parent::beforeFilter(); + + $this->validators = [ + 'username' => new Rules\AllOf( + new Rules\Alnum('-_'), + new Rules\NotEmpty(), + new Rules\NoWhitespace() + ), + 'password' => new Rules\AlwaysValid(), + 'group_id' => new Rules\AllOf( + new Rules\Digit(), + new Rules\NotEmpty() + ), + 'active' => new Rules\AllOf( + new Rules\BoolVal() + ), + 'expiration' => new Rules\AllOf( + new Rules\Length(1, 10, true) + ), + ]; + } + + /** + * User List Page + * + * @url /admin/user + * @url /admin/user/index + */ + public function index() { + $this->set('users', $this->User->find('all', [ + 'fields' => [ + 'User.id', 'User.username', 'User.expiration', 'User.active', 'Group.name' + ], + ])); + } + + /** + * Emulate User + * + * @url /admin/user/emulate/ + */ + public function emulate($uidOrUsername = false) { + try { + $curUID = $this->Auth->user('id'); + + $this->Auth->emulate($uidOrUsername); + + $msg = sprintf('Emulated user %s', $this->Auth->user('username')); + $this->logMessage('emulate', $msg, [], $this->Auth->user('id'), $curUID); + $this->Flash->success($msg.'!'); + + return $this->redirect('/'); + } catch (InternalErrorException $e) { + throw $e; + } + } + + /** + * Create User + * + * @url /admin/user/create + */ + public function create() { + if ($this->request->is('post')) { + // Validate the input + $res = $this->validate(); + + if (empty($res['errors'])) { + $this->User->create(); + $this->User->save($res['data']); + + $this->logMessage( + 'users', + sprintf('Created user "%s"', $res['data']['username']), + [], + $this->User->id + ); + + $this->Flash->success('The user has been created!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'users', 'action' => 'index']); + } else { + $this->errorFlash($res['errors']); + } + } + + $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); + } + + /** + * Edit User + * + * @url /adminuser/edit/ + * @url /admin/user/edit/ + */ + public function edit($uid = false) { + $user = $this->User->findById($uid); + if (empty($user)) { + throw new NotFoundException('Unknown user'); + } + + if ($this->request->is('post')) { + // Validate the input + $res = $this->validate(); + + if (empty($res['errors'])) { + // Clear out the password, if it's empty + if (empty($res['data']['password'])) { + unset($res['data']['password']); + } + + $this->User->id = $uid; + $this->User->save($res['data']); + + // Redact the old password + $user['User']['password'] = '-redacted-'; + + // ...and the new one + if (isset($res['data']['password'])) { + $res['data']['password'] = '-redacted-'; + } + + $this->logMessage( + 'users', + sprintf('Updated user "%s"', $user['User']['username']), + [ + 'old_user' => $user['User'], + 'new_user' => $res['data'], + ], + $uid + ); + + $this->Flash->success('The user has been updated!'); + return $this->redirect(['plugin' => 'admin', 'controller' => 'users', 'action' => 'index']); + } else { + $this->errorFlash($res['errors']); + } + } + + $this->set('groups', $this->Group->generateTreeList(null, null, null, '-- ')); + $this->set('user', $user); + } + + /** + * Delete User + * + * @url /admin/user/delete/ + */ + public function delete($uid = false) { + $user = $this->User->findById($uid); + if (empty($user)) { + throw new NotFoundException('Unknown user'); + } + + if ($this->request->is('post')) { + $this->User->delete($uid); + + $msg = sprintf('Deleted user "%s" (#%d)', $user['User']['username'], $uid); + + $this->logMessage( + 'users', + $msg, + [ + 'user' => $user['User'], + ], + $uid + ); + + $this->Flash->success($msg); + return $this->redirect(['plugin' => 'admin', 'controller' => 'users', 'action' => 'index']); + } + + $this->set('user', $user); + } + + /** + * Toggle User Status + * + * @url /admin/user/flip/ + */ + public function flip($uid = false) { + $user = $this->User->findById($uid); + if (empty($user)) { + throw new NotFoundException('Unknown user'); + } + + $this->User->id = $uid; + $this->User->save([ + 'active' => !$user['User']['active'], + ]); + + $this->logMessage( + 'users', + sprintf( + 'Flipped the status user "%s" to %sactive', + $user['User']['username'], + $user['User']['active'] ? 'in' : '' + ), + [ + 'old_status' => $user['User']['active'], + 'new_status' => !$user['User']['active'], + ], + $uid + ); + + $this->Flash->success(sprintf('Toggled status for user %s!', $user['User']['username'])); + return $this->redirect(['plugin' => 'admin', 'controller' => 'users', 'action' => 'index']); + } } diff --git a/app/Plugin/BankWeb/Config/Schema/schema.php b/app/Plugin/BankWeb/Config/Schema/schema.php index 9896ba1..a973506 100644 --- a/app/Plugin/BankWeb/Config/Schema/schema.php +++ b/app/Plugin/BankWeb/Config/Schema/schema.php @@ -2,54 +2,56 @@ App::uses('ClassRegistry', 'Utility'); class BankWebSchema extends CakeSchema { - public $account_mappings = [ - 'id' => [ - 'type' => 'integer', - 'null' => false, - 'key' => 'primary', - ], - 'group_id' => [ - 'type' => 'integer', - 'null' => false, - ], - 'username' => [ - 'type' => 'text', - ], - 'password' => [ - 'type' => 'text', - ], - - 'indexes' => [ - 'PRIMARY' => ['column' => 'id', 'unqiue' => true], - ], - ]; - - // ================================ - - public function before($event = array()) { - ConnectionManager::getDataSource('default')->cacheSources = false; - - return true; - } - - public function after($event = array()) { - if ( !isset($event['create']) ) return; - - switch ( $event['create'] ) { - case 'account_mappings': - $this->_create('AccountMapping', [ - 'group_id' => env('GROUP_STAFF'), - 'username' => 'admin', - 'password' => 'admin', - ]); - break; - } - } - - private function _create($tbl, $data) { - $table = ClassRegistry::init($tbl); - - $table->create(); - $table->save(array($tbl => $data)); - } -} \ No newline at end of file + + public $account_mappings = [ + 'id' => [ + 'type' => 'integer', + 'null' => false, + 'key' => 'primary', + ], + 'group_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'username' => [ + 'type' => 'text', + ], + 'password' => [ + 'type' => 'text', + ], + + 'indexes' => [ + 'PRIMARY' => ['column' => 'id', 'unqiue' => true], + ], + ]; + + // ================================ + + public function before($event = []) { + ConnectionManager::getDataSource('default')->cacheSources = false; + + return true; + } + + public function after($event = []) { + if (!isset($event['create'])) { return; + } + + switch ($event['create']) { + case 'account_mappings': + $this->create('AccountMapping', [ + 'group_id' => env('GROUP_STAFF'), + 'username' => 'admin', + 'password' => 'admin', + ]); + break; + } + } + + private function create($tbl, $data) { + $table = ClassRegistry::init($tbl); + + $table->create(); + $table->save([$tbl => $data]); + } +} diff --git a/app/Plugin/BankWeb/Config/routes.php b/app/Plugin/BankWeb/Config/routes.php index 6091cfa..8a1f893 100644 --- a/app/Plugin/BankWeb/Config/routes.php +++ b/app/Plugin/BankWeb/Config/routes.php @@ -16,4 +16,3 @@ Router::connect('/bank/:controller', ['plugin' => 'BankWeb']); Router::connect('/bank/:controller/:action', ['plugin' => 'BankWeb']); Router::connect('/bank/:controller/:action/**', ['plugin' => 'BankWeb']); - diff --git a/app/Plugin/BankWeb/Controller/AccountController.php b/app/Plugin/BankWeb/Controller/AccountController.php index f64178b..856155c 100644 --- a/app/Plugin/BankWeb/Controller/AccountController.php +++ b/app/Plugin/BankWeb/Controller/AccountController.php @@ -3,133 +3,134 @@ class AccountController extends BankWebAppController { - public function beforeFilter() { - parent::beforeFilter(); - - // Set the active menu item - $this->set('at_team', true); - } - - /** - * Account List Page - * - * @url /bank/account - * @url /bank/account/index - */ - public function index() { - $this->set('accounts', $this->BankApi->accounts()); - } - - /** - * Account Create - * - * @url /bank/account/create - */ - public function create() { - if ( !$this->request->is('post') || !isset($this->request->data['pin']) || !is_numeric($this->request->data['pin']) ) { - $this->Flash->danger('Please ensure your PIN is numeric!'); - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); - } - - try { - $res = $this->BankApi->newAccount($this->request->data['pin']); - } catch ( Exception $e ) { - $this->Flash->danger($e->getMessage()); - } - - if ( $res === true ) { - $this->Flash->success('Created new account!'); - } - - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); - } - - /** - * Transfer Money - * - * @url /bank/account/transfer/ - */ - public function transfer($id=false) { - if ( $id === false || !is_numeric($id) ) { - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); - } - - if ( - $this->request->is('post') && - isset($this->request->data['srcAcc']) && - is_numeric($this->request->data['srcAcc']) && - isset($this->request->data['dstAcc']) && - is_numeric($this->request->data['dstAcc']) && - isset($this->request->data['amount']) && - is_numeric($this->request->data['amount']) && - isset($this->request->data['pin']) && - is_numeric($this->request->data['pin']) - ) { - try { - $res = $this->BankApi->transfer( - $this->request->data['srcAcc'], - $this->request->data['dstAcc'], - $this->request->data['amount'], - $this->request->data['pin'] - ); - } catch ( Exception $e ) { - $this->Flash->danger($e->getMessage()); - } - - if ( $res === true ) { - $this->Flash->success('Successfully transferred money!'); - } - - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); - } - - $this->set('acc', htmlentities($id)); - } - - /** - * Transactions List - * - * @url /bank/account/transactions/ - */ - public function transactions($id=false) { - if ( $id === false || !is_numeric($id) ) { - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); - } - - $this->set('account', htmlentities($id)); - $this->set('logs', $this->BankApi->transfers($id)); - } - - /** - * Account PIN Change - * - * @url /bank/account/pin/ - */ - public function pin($id=false) { - if ( $id === false || !is_numeric($id) ) { - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); - } - - if ( - $this->request->is('post') && - isset($this->request->data['pin']) && - is_numeric($this->request->data['pin']) && - isset($this->request->data['newpin']) && - is_numeric($this->request->data['newpin']) - ) { - try { - $res = $this->BankApi->changePin($id, $this->request->data['pin'], $this->request->data['newpin']); - } catch ( Exception $e ) { - $this->Flash->danger($e->getMessage()); - } - - if ( $res === true ) { - $this->Flash->success('Successfully updated pin!'); - } - - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); - } - - $this->set('account', htmlentities($id)); - } -} \ No newline at end of file + public function beforeFilter() { + parent::beforeFilter(); + + // Set the active menu item + $this->set('at_team', true); + } + + /** + * Account List Page + * + * @url /bank/account + * @url /bank/account/index + */ + public function index() { + $this->set('accounts', $this->BankApi->accounts()); + } + + /** + * Account Create + * + * @url /bank/account/create + */ + public function create() { + if (!$this->request->is('post') + || !isset($this->request->data['pin']) + || !is_numeric($this->request->data['pin']) + ) { + $this->Flash->danger('Please ensure your PIN is numeric!'); + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); + } + + try { + $res = $this->BankApi->newAccount($this->request->data['pin']); + } catch (Exception $e) { + $this->Flash->danger($e->getMessage()); + } + + if ($res === true) { + $this->Flash->success('Created new account!'); + } + + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); + } + + /** + * Transfer Money + * + * @url /bank/account/transfer/ + */ + public function transfer($id = false) { + if ($id === false || !is_numeric($id)) { + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); + } + + if ($this->request->is('post') + && isset($this->request->data['srcAcc']) + && is_numeric($this->request->data['srcAcc']) + && isset($this->request->data['dstAcc']) + && is_numeric($this->request->data['dstAcc']) + && isset($this->request->data['amount']) + && is_numeric($this->request->data['amount']) + && isset($this->request->data['pin']) + && is_numeric($this->request->data['pin']) + ) { + try { + $res = $this->BankApi->transfer( + $this->request->data['srcAcc'], + $this->request->data['dstAcc'], + $this->request->data['amount'], + $this->request->data['pin'] + ); + } catch (Exception $e) { + $this->Flash->danger($e->getMessage()); + } + + if ($res === true) { + $this->Flash->success('Successfully transferred money!'); + } + + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); + } + + $this->set('acc', htmlentities($id)); + } + + /** + * Transactions List + * + * @url /bank/account/transactions/ + */ + public function transactions($id = false) { + if ($id === false || !is_numeric($id)) { + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); + } + + $this->set('account', htmlentities($id)); + $this->set('logs', $this->BankApi->transfers($id)); + } + + /** + * Account PIN Change + * + * @url /bank/account/pin/ + */ + public function pin($id = false) { + if ($id === false || !is_numeric($id)) { + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); + } + + if ($this->request->is('post') + && isset($this->request->data['pin']) + && is_numeric($this->request->data['pin']) + && isset($this->request->data['newpin']) + && is_numeric($this->request->data['newpin']) + ) { + try { + $res = $this->BankApi->changePin($id, $this->request->data['pin'], $this->request->data['newpin']); + } catch (Exception $e) { + $this->Flash->danger($e->getMessage()); + } + + if ($res === true) { + $this->Flash->success('Successfully updated pin!'); + } + + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'account', 'action' => 'index']); + } + + $this->set('account', htmlentities($id)); + } +} diff --git a/app/Plugin/BankWeb/Controller/BankWebAppController.php b/app/Plugin/BankWeb/Controller/BankWebAppController.php index fffcd5c..ee45ebe 100644 --- a/app/Plugin/BankWeb/Controller/BankWebAppController.php +++ b/app/Plugin/BankWeb/Controller/BankWebAppController.php @@ -2,21 +2,23 @@ App::uses('AppController', 'Controller'); class BankWebAppController extends AppController { - public $components = ['BankWeb.BankApi']; - public $uses = ['BankWeb.AccountMapping']; - public function beforeFilter() { - parent::beforeFilter(); + public $components = ['BankWeb.BankApi']; - // Ensure logins - $this->Auth->protect(); + public $uses = ['BankWeb.AccountMapping']; - // Grab the account + set the credentials for the API - $account = $this->AccountMapping->getAccount($this->Auth->item('groups')); - - if ( empty($account) ) { - throw new BadRequestException('You do not have a bank account associated with your user/groups'); - } - $this->BankApi->setCredentials($account['AccountMapping']['username'], $account['AccountMapping']['password']); - } -} \ No newline at end of file + public function beforeFilter() { + parent::beforeFilter(); + + // Ensure logins + $this->Auth->protect(); + + // Grab the account + set the credentials for the API + $account = $this->AccountMapping->getAccount($this->Auth->item('groups')); + + if (empty($account)) { + throw new BadRequestException('You do not have a bank account associated with your user/groups'); + } + $this->BankApi->setCredentials($account['AccountMapping']['username'], $account['AccountMapping']['password']); + } +} diff --git a/app/Plugin/BankWeb/Controller/BankadminController.php b/app/Plugin/BankWeb/Controller/BankadminController.php index 1086f7f..071b6f5 100644 --- a/app/Plugin/BankWeb/Controller/BankadminController.php +++ b/app/Plugin/BankWeb/Controller/BankadminController.php @@ -3,127 +3,136 @@ use Respect\Validation\Rules; class BankadminController extends BankWebAppController { - public $uses = ['Group', 'BankWeb.AccountMapping']; - - public function beforeFilter() { - parent::beforeFilter(); - - // Set the active menu item - $this->set('at_backend', true); - - // Setup validators - $this->validators = [ - 'id' => new Rules\AllOf( - new Rules\Digit() - ), - 'group_id' => new Rules\AllOf( - new Rules\Digit() - ), - 'username' => new Rules\AllOf( - new Rules\NotEmpty() - ), - 'password' => new Rules\AllOf( - new Rules\NotEmpty() - ), - ]; - } - - /** - * BankWEB Admin Index Page - * - * @url /admin/bank - * @url /bank_web/admin - * @url /admin/bank/index - * @url /bank_web/admin/index - */ - public function index() { - $this->set('groups', $this->Group->generateTreeList(null, null, null, '--')); - $this->set('accounts', $this->AccountMapping->find('all')); - } - - /** - * BankWEB Admin API - * - * @url /admin/bank/api/ - * @url /bank_web/admin/api/ - */ - public function api($id=false) { - $data = $this->AccountMapping->findById($id); - if ( empty($data) ) { - throw new NotFoundException('Unknown account'); - } - - return $this->ajaxResponse($data['AccountMapping']); - } - - /** - * BankWEB Credentials Save - * - * @url /admin/bank/save - * @url /bank_web/admin/save - */ - public function save() { - if ( !$this->request->is('post') ) { - throw new MethodNotAllowedException(); - } - - // Validate the input - $res = $this->_validate(); - - if ( !empty($res['errors']) ) { - $this->_errorFlash($res['errors']); - - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'bankadmin', 'action' => 'index']); - } - - if ( $res['data']['id'] > 0 ) { - $data = $this->AccountMapping->findById($res['data']['id']); - if ( empty($data) ) { - throw new NotFoundException('Unknown account mapping'); - } - - $this->AccountMapping->id = $res['data']['id']; - $this->AccountMapping->save($res['data']); - - $msg = sprintf('Edited account mapping for user "%s"', $data['AccountMapping']['username']); - - $this->logMessage('bank', $msg, ['old_mapping' => $data['AccountMapping'], 'new_mapping' => $res['data']], $data['AccountMapping']['id']); - $this->Flash->success($msg.'!'); - } else { - // Fix the data - unset($res['data']['id']); - - $this->AccountMapping->create(); - $this->AccountMapping->save($res['data']); - - $msg = sprintf('Created account mapping on user "%s"', $res['data']['username']); - $this->logMessage('bank', $msg, [], $this->AccountMapping->id); - $this->Flash->success($msg.'!'); - } - - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'bankadmin', 'action' => 'index']); - } - - /** - * BankWEB Account Mapping Delete - * - * @url /admin/bank/delete// - */ - public function delete($id=false) { - $data = $this->AccountMapping->findById($id); - if ( empty($data) ) { - throw new NotFoundException('Unknown acount'); - } - - if ( $this->request->is('post') ) { - $this->AccountMapping->delete($id); - - $msg = sprintf('Deleted account mapping for user "%s"', $data['AccountMapping']['username']); - $this->logMessage('bank', $msg, ['mapping' => $data], $id); - $this->Flash->success($msg.'!'); - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'bankadmin', 'action' => 'index']); - } - - $this->set('data', $data); - } + + public $uses = ['Group', 'BankWeb.AccountMapping']; + + public function beforeFilter() { + parent::beforeFilter(); + + // Set the active menu item + $this->set('at_backend', true); + + // Setup validators + $this->validators = [ + 'id' => new Rules\AllOf( + new Rules\Digit() + ), + 'group_id' => new Rules\AllOf( + new Rules\Digit() + ), + 'username' => new Rules\AllOf( + new Rules\NotEmpty() + ), + 'password' => new Rules\AllOf( + new Rules\NotEmpty() + ), + ]; + } + + /** + * BankWEB Admin Index Page + * + * @url /admin/bank + * @url /bank_web/admin + * @url /admin/bank/index + * @url /bank_web/admin/index + */ + public function index() { + $this->set('groups', $this->Group->generateTreeList(null, null, null, '--')); + $this->set('accounts', $this->AccountMapping->find('all')); + } + + /** + * BankWEB Admin API + * + * @url /admin/bank/api/ + * @url /bank_web/admin/api/ + */ + public function api($id = false) { + $data = $this->AccountMapping->findById($id); + if (empty($data)) { + throw new NotFoundException('Unknown account'); + } + + return $this->ajaxResponse($data['AccountMapping']); + } + + /** + * BankWEB Credentials Save + * + * @url /admin/bank/save + * @url /bank_web/admin/save + */ + public function save() { + if (!$this->request->is('post')) { + throw new MethodNotAllowedException(); + } + + // Validate the input + $res = $this->validate(); + + if (!empty($res['errors'])) { + $this->errorFlash($res['errors']); + + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'bankadmin', 'action' => 'index']); + } + + if ($res['data']['id'] > 0) { + $data = $this->AccountMapping->findById($res['data']['id']); + if (empty($data)) { + throw new NotFoundException('Unknown account mapping'); + } + + $this->AccountMapping->id = $res['data']['id']; + $this->AccountMapping->save($res['data']); + + $msg = sprintf('Edited account mapping for user "%s"', $data['AccountMapping']['username']); + + $this->logMessage( + 'bank', + $msg, + [ + 'old_mapping' => $data['AccountMapping'], + 'new_mapping' => $res['data'] + ], + $data['AccountMapping']['id'] + ); + $this->Flash->success($msg.'!'); + } else { + // Fix the data + unset($res['data']['id']); + + $this->AccountMapping->create(); + $this->AccountMapping->save($res['data']); + + $msg = sprintf('Created account mapping on user "%s"', $res['data']['username']); + $this->logMessage('bank', $msg, [], $this->AccountMapping->id); + $this->Flash->success($msg.'!'); + } + + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'bankadmin', 'action' => 'index']); + } + + /** + * BankWEB Account Mapping Delete + * + * @url /admin/bank/delete// + */ + public function delete($id = false) { + $data = $this->AccountMapping->findById($id); + if (empty($data)) { + throw new NotFoundException('Unknown acount'); + } + + if ($this->request->is('post')) { + $this->AccountMapping->delete($id); + + $msg = sprintf('Deleted account mapping for user "%s"', $data['AccountMapping']['username']); + $this->logMessage('bank', $msg, ['mapping' => $data], $id); + $this->Flash->success($msg.'!'); + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'bankadmin', 'action' => 'index']); + } + + $this->set('data', $data); + } } diff --git a/app/Plugin/BankWeb/Controller/Component/BankApiComponent.php b/app/Plugin/BankWeb/Controller/Component/BankApiComponent.php index 7221d2c..5c071e8 100644 --- a/app/Plugin/BankWeb/Controller/Component/BankApiComponent.php +++ b/app/Plugin/BankWeb/Controller/Component/BankApiComponent.php @@ -2,221 +2,223 @@ App::uses('Component', 'Controller'); class BankApiComponent extends Component { - private $server; - private $timeout; - - private $username = null; - private $password = null; - private $session = null; - - /** - * BankAPI Initialize Hook - * - * Sets up a bunch of settings - */ - public function initialize(Controller $controller) { - $this->server = env('BANKAPI_SERVER'); - $this->timeout = env('BANKAPI_TIMEOUT'); - } - - /** - * Set Credentials - * - * Saves the user/pass for any future - * requests done by this component - * - * @param $user The username - * @param $pass The password - * @return void - */ - public function setCredentials($user, $pass) { - $this->username = $user; - $this->password = $pass; - } - - /** - * Login - * - * Attempts a login request - * - * @return string/bool True if it worked. A string with - * the message if it doesn't. - */ - public function login() { - $result = $this->request('login', ['username' => $this->username, 'password' => $this->password]); - - if ( $result['code'] != 200 ) { - throw new BankException('Bank Error: '.$result['message']); - } - - $this->session = $result['session']; - return true; - } - - /** - * Transfer Money - * - * @param $src The source account - * @param $dst The dest account - * @param $amt The amount to transfer - * @param $pin The source account pin - * @return bool/string True if it worked. A string - * with the message if it doesn't. - */ - public function transfer($src, $dst, $amt, $pin) { - if ( $this->session == null ) { - $this->login(); - } - - $result = $this->request('transfer', [ - 'session' => $this->session, - 'src' => $src, - 'dst' => $dst, - 'amount' => $amt, - 'pin' => $pin, - ]); - - if ( $result['code'] != 200 ) { - throw new BankException('Bank Error: '.$result['message']); - } - return true; - } - - /** - * Get All Transfers - * - * @param $account The account you wish to view - * logs for. - * @return array/string Array of logs if it worked. A string - * with the message if it doesn't. - */ - public function transfers($account) { - if ( $this->session == null ) { - $this->login(); - } - - $result = $this->request('transfers', [ - 'session' => $this->session, - 'account' => $account, - ]); - - if ( $result['code'] != 200 ) { - throw new BankException('Bank Error: '.$result['message']); - } - return $result['transactions']; - } - - /** - * Change PIN - * - * @param $account The account you wish to change the pin for - * @param $oldPin The old pin - * @param $newPin The new pin - * @return bool/string True if it worked. A string - * with the message if it doesn't. - */ - public function changePin($account, $oldPin, $newPin) { - if ( $this->session == null ) { - $this->login(); - } - - $result = $this->request('changePin', [ - 'session' => $this->session, - 'account' => $account, - 'pin' => $oldPin, - 'newpin' => $newPin, - ]); - - if ( $result['code'] != 200 ) { - throw new BankException('Bank Error: '.$result['message']); - } - return true; - } - - /** - * Account List - * - * @return array/string Array of accounts if it worked. A string - * with the message if it doesn't. - */ - public function accounts() { - if ( $this->session == null ) { - $this->login(); - } - - $result = $this->request('accounts', [ - 'session' => $this->session, - ]); - - if ( $result['code'] != 200 ) { - throw new BankException('Bank Error: '.$result['message']); - } - return $result['accounts']; - } - - /** - * Create Account - * - * @param $pin The pin number - * @return bool/string True if it worked. A string - * with the message if it doesn't. - */ - public function newAccount($pin) { - if ( $this->session == null ) { - $this->login(); - } - - $result = $this->request('newAccount', [ - 'session' => $this->session, - 'pin' => $pin, - ]); - - if ( $result['code'] != 200 ) { - throw new BankException('Bank Error: '.$result['message']); - } - return true; - } - - - /** - * API Request - * - * General function to make an API request - * to the BankAPI - * - * @param $endpoint The endpoint you wish to dall - * @param $data An array of post data - * @return An array with a data - */ - public function request($endpoint, $data) { - $postData = []; - foreach ( $data AS $k => $v ) { - $postData[] = sprintf('%s=%s', urlencode($k), urlencode($v)); - } - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, sprintf('%s/%s', $this->server, $endpoint)); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $postData)); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - // Set the UA to the app version - list($version_short, $version_long) = Cache::read('version'); - curl_setopt($ch,CURLOPT_USERAGENT, 'ie2/BankWeb @ '.$version_short); - - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); - curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); - - $result = curl_exec($ch); - - curl_close($ch); - - if ( $result === false ) { - throw new RuntimeException('Failed to contact the BankAPI Server. Please try again later.'); - } - - return json_decode($result, true); - } -} -class BankException extends InternalErrorException { } \ No newline at end of file + private $server; + + private $timeout; + + private $username = null; + + private $password = null; + + private $session = null; + + /** + * BankAPI Initialize Hook + * + * Sets up a bunch of settings + */ + public function initialize(Controller $controller) { + $this->server = env('BANKAPI_SERVER'); + $this->timeout = env('BANKAPI_TIMEOUT'); + } + + /** + * Set Credentials + * + * Saves the user/pass for any future + * requests done by this component + * + * @param $user The username + * @param $pass The password + * @return void + */ + public function setCredentials($user, $pass) { + $this->username = $user; + $this->password = $pass; + } + + /** + * Login + * + * Attempts a login request + * + * @return string/bool True if it worked. A string with + * the message if it doesn't. + */ + public function login() { + $result = $this->request('login', ['username' => $this->username, 'password' => $this->password]); + + if ($result['code'] != 200) { + throw new InternalErrorException('Bank Error: '.$result['message']); + } + + $this->session = $result['session']; + return true; + } + + /** + * Transfer Money + * + * @param $src The source account + * @param $dst The dest account + * @param $amt The amount to transfer + * @param $pin The source account pin + * @return bool/string True if it worked. A string + * with the message if it doesn't. + */ + public function transfer($src, $dst, $amt, $pin) { + if ($this->session == null) { + $this->login(); + } + + $result = $this->request('transfer', [ + 'session' => $this->session, + 'src' => $src, + 'dst' => $dst, + 'amount' => $amt, + 'pin' => $pin, + ]); + + if ($result['code'] != 200) { + throw new InternalErrorException('Bank Error: '.$result['message']); + } + return true; + } + + /** + * Get All Transfers + * + * @param $account The account you wish to view + * logs for. + * @return array/string Array of logs if it worked. A string + * with the message if it doesn't. + */ + public function transfers($account) { + if ($this->session == null) { + $this->login(); + } + + $result = $this->request('transfers', [ + 'session' => $this->session, + 'account' => $account, + ]); + + if ($result['code'] != 200) { + throw new InternalErrorException('Bank Error: '.$result['message']); + } + return $result['transactions']; + } + + /** + * Change PIN + * + * @param $account The account you wish to change the pin for + * @param $oldPin The old pin + * @param $newPin The new pin + * @return bool/string True if it worked. A string + * with the message if it doesn't. + */ + public function changePin($account, $oldPin, $newPin) { + if ($this->session == null) { + $this->login(); + } + + $result = $this->request('changePin', [ + 'session' => $this->session, + 'account' => $account, + 'pin' => $oldPin, + 'newpin' => $newPin, + ]); + + if ($result['code'] != 200) { + throw new InternalErrorException('Bank Error: '.$result['message']); + } + return true; + } + + /** + * Account List + * + * @return array/string Array of accounts if it worked. A string + * with the message if it doesn't. + */ + public function accounts() { + if ($this->session == null) { + $this->login(); + } + + $result = $this->request('accounts', [ + 'session' => $this->session, + ]); + + if ($result['code'] != 200) { + throw new InternalErrorException('Bank Error: '.$result['message']); + } + return $result['accounts']; + } + + /** + * Create Account + * + * @param $pin The pin number + * @return bool/string True if it worked. A string + * with the message if it doesn't. + */ + public function newAccount($pin) { + if ($this->session == null) { + $this->login(); + } + + $result = $this->request('newAccount', [ + 'session' => $this->session, + 'pin' => $pin, + ]); + + if ($result['code'] != 200) { + throw new InternalErrorException('Bank Error: '.$result['message']); + } + return true; + } + + + /** + * API Request + * + * General function to make an API request + * to the BankAPI + * + * @param $endpoint The endpoint you wish to dall + * @param $data An array of post data + * @return An array with a data + */ + public function request($endpoint, $data) { + $postData = []; + foreach ($data as $k => $v) { + $postData[] = sprintf('%s=%s', urlencode($k), urlencode($v)); + } + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, sprintf('%s/%s', $this->server, $endpoint)); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $postData)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + // Set the UA to the app version + list($version_short, $version_long) = Cache::read('version'); + curl_setopt($ch, CURLOPT_USERAGENT, 'ie2/BankWeb @ '.$version_short); + + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + + $result = curl_exec($ch); + + curl_close($ch); + + if ($result === false) { + throw new RuntimeException('Failed to contact the BankAPI Server. Please try again later.'); + } + + return json_decode($result, true); + } +} diff --git a/app/Plugin/BankWeb/Controller/InfoController.php b/app/Plugin/BankWeb/Controller/InfoController.php index d8930e0..52fc6fd 100644 --- a/app/Plugin/BankWeb/Controller/InfoController.php +++ b/app/Plugin/BankWeb/Controller/InfoController.php @@ -3,28 +3,28 @@ class InfoController extends BankWebAppController { - public function beforeFilter() { - parent::beforeFilter(); + public function beforeFilter() { + parent::beforeFilter(); - // Set the active menu item - $this->set('at_team', true); - } + // Set the active menu item + $this->set('at_team', true); + } - /** - * Account Information Page - * - * @url /bank/info - * @url /bank/info/index - */ - public function index() { - if ( (bool)env('BANKWEB_PUBLIC_APIINFO') == false && !$this->Auth->isAdmin() ) { - throw new ForbiddenException('This feature is disabled'); - } + /** + * Account Information Page + * + * @url /bank/info + * @url /bank/info/index + */ + public function index() { + if ((bool)env('BANKWEB_PUBLIC_APIINFO') == false && !$this->Auth->isAdmin()) { + throw new ForbiddenException('This feature is disabled'); + } - $account = $this->AccountMapping->getAccount($this->Auth->item('groups')); + $account = $this->AccountMapping->getAccount($this->Auth->item('groups')); - $this->set('api', parse_url(env('BANKAPI_SERVER'))); - $this->set('username', $account['AccountMapping']['username']); - $this->set('password', $account['AccountMapping']['password']); - } -} \ No newline at end of file + $this->set('api', parse_url(env('BANKAPI_SERVER'))); + $this->set('username', $account['AccountMapping']['username']); + $this->set('password', $account['AccountMapping']['password']); + } +} diff --git a/app/Plugin/BankWeb/Controller/ProductsController.php b/app/Plugin/BankWeb/Controller/ProductsController.php index 9285603..53f7c40 100644 --- a/app/Plugin/BankWeb/Controller/ProductsController.php +++ b/app/Plugin/BankWeb/Controller/ProductsController.php @@ -2,74 +2,79 @@ App::uses('BankWebAppController', 'BankWeb.Controller'); class ProductsController extends BankWebAppController { - /** - * Array of products loaded from the - * env variable 'BANKWEB_PRODUCTS' - */ - private $products = []; - public function beforeFilter() { - parent::beforeFilter(); + /** + * Array of products loaded from the + * env variable 'BANKWEB_PRODUCTS' + */ + private $products = []; - // Load the products - $filename = ROOT . DS . env('BANKWEB_PRODUCTS'); - $this->products = json_decode(file_get_contents($filename), true); + public function beforeFilter() { + parent::beforeFilter(); - // Set the active menu item - $this->set('at_bank', true); - } + // Load the products + $filename = ROOT . DS . env('BANKWEB_PRODUCTS'); + $this->products = json_decode(file_get_contents($filename), true); - /** - * Product List Page - * - * @url /bank - * @url /bank/products - * @url /bank/products/index - */ - public function index() { - $this->set('products', $this->products); - } + // Set the active menu item + $this->set('at_bank', true); + } - /** - * Product Purchase Confirmation - * - * @url /bank/products/confirm/ - */ - public function confirm($id=false) { - if ( $id === false || !isset($this->products[$id]) ) { - throw new NotFoundException('Unknown product'); - } + /** + * Product List Page + * + * @url /bank + * @url /bank/products + * @url /bank/products/index + */ + public function index() { + $this->set('products', $this->products); + } - $product = $this->products[$id]; - if ( !$product['enabled'] ) { - throw new NotFoundException('Unknown product'); - } + /** + * Product Purchase Confirmation + * + * @url /bank/products/confirm/ + */ + public function confirm($id = false) { + if ($id === false || !isset($this->products[$id])) { + throw new NotFoundException('Unknown product'); + } - if ( - $this->request->is('post') && - isset($this->request->data['srcAcc']) && - is_numeric($this->request->data['srcAcc']) && - isset($this->request->data['pin']) && - is_numeric($this->request->data['pin']) - ) { - try { - $res = $this->BankApi->transfer($_POST['srcAcc'], env('BANKWEB_WHITETEAM_ACCOUNT'), $product['cost'], $_POST['pin']); - } catch ( Exception $e ) { - $this->Flash->danger($e->getMessage()); - } + $product = $this->products[$id]; + if (!$product['enabled']) { + throw new NotFoundException('Unknown product'); + } - if ( $res === true ) { - $this->Flash->success($product['on_purchase']); + if ($this->request->is('post') + && isset($this->request->data['srcAcc']) + && is_numeric($this->request->data['srcAcc']) + && isset($this->request->data['pin']) + && is_numeric($this->request->data['pin']) + ) { + try { + $res = $this->BankApi->transfer( + $_POST['srcAcc'], + env('BANKWEB_WHITETEAM_ACCOUNT'), + $product['cost'], + $_POST['pin'] + ); + } catch (Exception $e) { + $this->Flash->danger($e->getMessage()); + } - if ( (bool)env('BANKWEB_SLACK_ENABLED') ) { - $this->_sendSlack($product['slack_message']); - } - } + if ($res === true) { + $this->Flash->success($product['on_purchase']); - return $this->redirect(['plugin' => 'bank_web', 'controller' => 'products', 'action' => 'index']); - } + if ((bool)env('BANKWEB_SLACK_ENABLED')) { + $this->sendSlack($product['slack_message']); + } + } - $this->set('item', $product); - $this->set('accounts', $this->BankApi->accounts()); - } -} \ No newline at end of file + return $this->redirect(['plugin' => 'bank_web', 'controller' => 'products', 'action' => 'index']); + } + + $this->set('item', $product); + $this->set('accounts', $this->BankApi->accounts()); + } +} diff --git a/app/Plugin/BankWeb/Model/AccountMapping.php b/app/Plugin/BankWeb/Model/AccountMapping.php index 3ab058d..bec6752 100644 --- a/app/Plugin/BankWeb/Model/AccountMapping.php +++ b/app/Plugin/BankWeb/Model/AccountMapping.php @@ -2,36 +2,38 @@ App::uses('BankWebAppModel', 'BankWeb.Model'); class AccountMapping extends BankWebAppModel { - public $belongsTo = ['Group']; - public $recursive = 1; - - public function getAccount($groups) { - // Check groups - $mapping = $this->find('all', [ - 'conditions' => [ - 'AccountMapping.group_id' => $groups, - ], - ]); - - // Re-organize the mappings - $maps = []; - foreach ( $mapping AS $m ) { - $maps[$m['AccountMapping']['group_id']] = $m; - } - - // Now let's deal with the groups - if ( !empty($maps) ) { - // We're going to assume (I'm sorry), that the last - // items in $groups is the closest thing being used - $wanted = array_reverse($groups); - - foreach ( $wanted AS $id ) { - if ( isset($maps[$id]) ) { - return $maps[$id]; - } - } - } - - return []; - } -} \ No newline at end of file + + public $belongsTo = ['Group']; + + public $recursive = 1; + + public function getAccount($groups) { + // Check groups + $mapping = $this->find('all', [ + 'conditions' => [ + 'AccountMapping.group_id' => $groups, + ], + ]); + + // Re-organize the mappings + $maps = []; + foreach ($mapping as $m) { + $maps[$m['AccountMapping']['group_id']] = $m; + } + + // Now let's deal with the groups + if (!empty($maps)) { + // We're going to assume (I'm sorry), that the last + // items in $groups is the closest thing being used + $wanted = array_reverse($groups); + + foreach ($wanted as $id) { + if (isset($maps[$id])) { + return $maps[$id]; + } + } + } + + return []; + } +} diff --git a/app/Plugin/BankWeb/Model/BankWebAppModel.php b/app/Plugin/BankWeb/Model/BankWebAppModel.php index 6603c1f..1caffa8 100644 --- a/app/Plugin/BankWeb/Model/BankWebAppModel.php +++ b/app/Plugin/BankWeb/Model/BankWebAppModel.php @@ -2,5 +2,5 @@ App::uses('AppModel', 'Model'); class BankWebAppModel extends AppModel { - -} \ No newline at end of file + +} diff --git a/app/Plugin/BankWeb/webroot/css/products.css b/app/Plugin/BankWeb/webroot/css/products.css index 769a80e..199c078 100644 --- a/app/Plugin/BankWeb/webroot/css/products.css +++ b/app/Plugin/BankWeb/webroot/css/products.css @@ -1,9 +1,9 @@ /*============================================================= Authour URL: www.designbootstrap.com - + http://www.designbootstrap.com/ - License: MIT + License: MIT ======================================================== */ .db-pricing-seven { diff --git a/app/Plugin/ScoreEngine/Config/routes.php b/app/Plugin/ScoreEngine/Config/routes.php index 0f981cc..6a7bbb3 100644 --- a/app/Plugin/ScoreEngine/Config/routes.php +++ b/app/Plugin/ScoreEngine/Config/routes.php @@ -4,15 +4,44 @@ */ // Scoreboard mapping -Router::connect('/scoreboard', ['plugin' => 'ScoreEngine', 'controller' => 'scoreboard', 'action' => 'index']); -Router::connect('/scoreboard/api', ['plugin' => 'ScoreEngine', 'controller' => 'scoreboard', 'action' => 'api']); -Router::connect('/scoreboard/overview', ['plugin' => 'ScoreEngine', 'controller' => 'scoreboard', 'action' => 'overview']); +Router::connect('/scoreboard', [ + 'plugin' => 'ScoreEngine', + 'controller' => 'scoreboard', + 'action' => 'index' +]); +Router::connect('/scoreboard/api', [ + 'plugin' => 'ScoreEngine', + 'controller' => 'scoreboard', + 'action' => 'api' +]); +Router::connect('/scoreboard/overview', [ + 'plugin' => 'ScoreEngine', + 'controller' => 'scoreboard', + 'action' => 'overview' +]); // ScoreEngine admin mapping -Router::connect('/admin/scoreengine', ['plugin' => 'ScoreEngine', 'controller' => 'scoreadmin', 'action' => 'index']); -Router::connect('/admin/scoreengine/:action/*', ['plugin' => 'ScoreEngine', 'controller' => 'scoreadmin']); +Router::connect('/admin/scoreengine', [ + 'plugin' => 'ScoreEngine', + 'controller' => 'scoreadmin', + 'action' => 'index' +]); +Router::connect('/admin/scoreengine/:action/*', [ + 'plugin' => 'ScoreEngine', + 'controller' => 'scoreadmin' +]); // Team Panel mapping -Router::connect('/team', ['plugin' => 'ScoreEngine', 'controller' => 'team', 'action' => 'index']); -Router::connect('/team/:action', ['plugin' => 'ScoreEngine', 'controller' => 'team']); -Router::connect('/team/:action/*', ['plugin' => 'ScoreEngine', 'controller' => 'team']); +Router::connect('/team', [ + 'plugin' => 'ScoreEngine', + 'controller' => 'team', + 'action' => 'index' +]); +Router::connect('/team/:action', [ + 'plugin' => 'ScoreEngine', + 'controller' => 'team' +]); +Router::connect('/team/:action/*', [ + 'plugin' => 'ScoreEngine', + 'controller' => 'team' +]); diff --git a/app/Plugin/ScoreEngine/Controller/ScoreEngineAppController.php b/app/Plugin/ScoreEngine/Controller/ScoreEngineAppController.php index a96b473..26c5512 100644 --- a/app/Plugin/ScoreEngine/Controller/ScoreEngineAppController.php +++ b/app/Plugin/ScoreEngine/Controller/ScoreEngineAppController.php @@ -2,5 +2,5 @@ App::uses('AppController', 'Controller'); class ScoreEngineAppController extends AppController { - -} \ No newline at end of file + +} diff --git a/app/Plugin/ScoreEngine/Controller/ScoreadminController.php b/app/Plugin/ScoreEngine/Controller/ScoreadminController.php index 5a4723e..8310612 100644 --- a/app/Plugin/ScoreEngine/Controller/ScoreadminController.php +++ b/app/Plugin/ScoreEngine/Controller/ScoreadminController.php @@ -2,171 +2,174 @@ App::uses('ScoreEngineAppController', 'ScoreEngine.Controller'); class ScoreadminController extends ScoreEngineAppController { - public $uses = ['ScoreEngine.Team', 'ScoreEngine.Check','ScoreEngine.Service', 'ScoreEngine.TeamService']; - - public function beforeFilter() { - parent::beforeFilter(); - - // Enforce staff only - $this->Auth->protect(env('GROUP_STAFF')); - - // Set the active menu item - $this->set('at_staff', true); - } - - /** - * ScoreEngine Admin Index Page - * - * @url /admin/scoreengine - * @url /score_engine/admin - * @url /admin/scoreengine/index - * @url /score_engine/admin/index - */ - public function index() { - $this->set('teams', $this->Team->find('all')); - } - - /** - * View Team Page - * - * @url /admin/scoreengine/team/ - * @url /score_engine/admin/team/ - */ - public function team($id=false) { - $team = $this->Team->findById($id); - if ( empty ($team) ) { - throw new NotFoundException('Unknown team'); - } - - $tid = $team['Team']['id']; - $this->set('team', $team); - $this->set('data', $this->Check->getTeamChecks($tid, false)); - $this->set('latest', $this->Check->getLastTeamCheck($tid)); - } - - /** - * View Team Service Page - * - * @url /admin/scoreengine/service// - * @url /score_engine/admin/config// - */ - public function service($tid=false, $sid=false) { - $team = $this->Team->findById($tid); - if ( empty($team) ) { - throw new NotFoundException('Unknown team'); - } - if ( $sid === false || !is_numeric($sid) ) { - throw new NotFoundException('Unknown service'); - } - - $oldVF = $this->Check->virtualFields; - $this->Check->virtualFields = []; - - $this->set('team', $team); - $this->set('data', $this->Check->find('all', [ - 'conditions' => [ - 'team_id' => $team['Team']['id'], - 'service_id' => $sid, - ], - 'limit' => 20, - 'order' => 'time DESC', - ])); - - $this->Check->virtualFields = $oldVF; - } - - /** - * Team Config Page - * - * @url /admin/scoreengine/config/ - * @url /score_engine/admin/config/ - */ - public function config($id=false) { - $team = $this->Team->findById($id); - if ( empty($team) ) { - throw new NotFoundException('Unknown team'); - } - - $this->set('team', $team); - $data = $this->TeamService->getData($team['Team']['id'], false); - - $updateOpt = function($id, $value) use(&$data) { - foreach ( $data AS $group => &$options ) { - foreach ( $options AS &$opt ) { - if ( $opt['id'] == $id ) { - $opt['value'] = $value; - } - } - } - - return false; - }; - - if ( $this->request->is('post') ) { - foreach ( $this->request->data AS $opt => $value ) { - $opt = (int) str_replace('opt', '', $opt); - if ( $opt < 0 || !is_numeric($opt) ) continue; - - // Only USERPASS is an array - if ( is_array($value) ) { - $value = $value['user'].'||'.$value['pass']; - } - - $this->TeamService->updateConfig($opt, $value); - $updateOpt($opt, $value); - } - - // Message - $this->Flash->success('Updated Score Engine Config!'); - } - - $this->set('data', $data); - } - - /** - * Export Grades - * - * @url /admin/scoreengine/export - * @url /score_engine/admin/export - */ - public function export() { - $teams = $this->Team->find('all'); - $services = $this->Service->find('all'); - $out = []; - $chkOrder = []; - - // Helper function to grab the right check - $grabCheckById = function($checks, $id) { - foreach ( $checks AS $c ) { - if ( $c['Service']['id'] == $id ) { - return $c; - } - } - }; - - // Build the header - $header = ['team_number']; - foreach ( $services AS $s ) { - $header[] = '"'.$s['Service']['name'].'"'; - $chkOrder[] = $s['Service']['id']; - } - $out[] = implode(',', $header); - - // Parse team scores - foreach ( $teams AS $t ) { - $tid = $t['Team']['id']; - $line = [$tid]; - - $checks = $this->Check->getTeamChecks($tid); - - foreach ( $chkOrder AS $id ) { - $chk = $grabCheckById($checks, $id); - $line[] = isset($chk['Check']['total_passed']) ? $chk['Check']['total_passed'] : 0; - } - - $out[] = implode(',', $line); - } - - return $this->ajaxResponse(implode(PHP_EOL, $out)); - } + + public $uses = ['ScoreEngine.Team', 'ScoreEngine.Check','ScoreEngine.Service', 'ScoreEngine.TeamService']; + + public function beforeFilter() { + parent::beforeFilter(); + + // Enforce staff only + $this->Auth->protect(env('GROUP_STAFF')); + + // Set the active menu item + $this->set('at_staff', true); + } + + /** + * ScoreEngine Admin Index Page + * + * @url /admin/scoreengine + * @url /score_engine/admin + * @url /admin/scoreengine/index + * @url /score_engine/admin/index + */ + public function index() { + $this->set('teams', $this->Team->find('all')); + } + + /** + * View Team Page + * + * @url /admin/scoreengine/team/ + * @url /score_engine/admin/team/ + */ + public function team($id = false) { + $team = $this->Team->findById($id); + if (empty($team)) { + throw new NotFoundException('Unknown team'); + } + + $tid = $team['Team']['id']; + $this->set('team', $team); + $this->set('data', $this->Check->getTeamChecks($tid, false)); + $this->set('latest', $this->Check->getLastTeamCheck($tid)); + } + + /** + * View Team Service Page + * + * @url /admin/scoreengine/service// + * @url /score_engine/admin/config// + */ + public function service($tid = false, $sid = false) { + $team = $this->Team->findById($tid); + if (empty($team)) { + throw new NotFoundException('Unknown team'); + } + if ($sid === false || !is_numeric($sid)) { + throw new NotFoundException('Unknown service'); + } + + $oldVF = $this->Check->virtualFields; + $this->Check->virtualFields = []; + + $this->set('team', $team); + $this->set('data', $this->Check->find('all', [ + 'conditions' => [ + 'team_id' => $team['Team']['id'], + 'service_id' => $sid, + ], + 'limit' => 20, + 'order' => 'time DESC', + ])); + + $this->Check->virtualFields = $oldVF; + } + + /** + * Team Config Page + * + * @url /admin/scoreengine/config/ + * @url /score_engine/admin/config/ + */ + public function config($id = false) { + $team = $this->Team->findById($id); + if (empty($team)) { + throw new NotFoundException('Unknown team'); + } + + $this->set('team', $team); + $data = $this->TeamService->getData($team['Team']['id'], false); + + $updateOpt = function ($id, $value) use (&$data) { + foreach ($data as $group => &$options) { + foreach ($options as &$opt) { + if ($opt['id'] == $id) { + $opt['value'] = $value; + } + } + } + + return false; + }; + + if ($this->request->is('post')) { + foreach ($this->request->data as $opt => $value) { + $opt = (int)str_replace('opt', '', $opt); + if ($opt < 0 || !is_numeric($opt)) { + continue; + } + + // Only USERPASS is an array + if (is_array($value)) { + $value = $value['user'].'||'.$value['pass']; + } + + $this->TeamService->updateConfig($opt, $value); + $updateOpt($opt, $value); + } + + // Message + $this->Flash->success('Updated Score Engine Config!'); + } + + $this->set('data', $data); + } + + /** + * Export Grades + * + * @url /admin/scoreengine/export + * @url /score_engine/admin/export + */ + public function export() { + $teams = $this->Team->find('all'); + $services = $this->Service->find('all'); + $out = []; + $chkOrder = []; + + // Helper function to grab the right check + $grabCheckById = function ($checks, $id) { + foreach ($checks as $c) { + if ($c['Service']['id'] == $id) { + return $c; + } + } + }; + + // Build the header + $header = ['team_number']; + foreach ($services as $s) { + $header[] = '"'.$s['Service']['name'].'"'; + $chkOrder[] = $s['Service']['id']; + } + $out[] = implode(',', $header); + + // Parse team scores + foreach ($teams as $t) { + $tid = $t['Team']['id']; + $line = [$tid]; + + $checks = $this->Check->getTeamChecks($tid); + + foreach ($chkOrder as $id) { + $chk = $grabCheckById($checks, $id); + $line[] = isset($chk['Check']['total_passed']) ? $chk['Check']['total_passed'] : 0; + } + + $out[] = implode(',', $line); + } + + return $this->ajaxResponse(implode(PHP_EOL, $out)); + } } diff --git a/app/Plugin/ScoreEngine/Controller/ScoreboardController.php b/app/Plugin/ScoreEngine/Controller/ScoreboardController.php index 7d9bfec..071aba7 100644 --- a/app/Plugin/ScoreEngine/Controller/ScoreboardController.php +++ b/app/Plugin/ScoreEngine/Controller/ScoreboardController.php @@ -2,100 +2,102 @@ App::uses('ScoreEngineAppController', 'ScoreEngine.Controller'); class ScoreboardController extends ScoreEngineAppController { - public $uses = [ - 'ScoreEngine.Check', 'ScoreEngine.Service', 'ScoreEngine.Team', 'ScoreEngine.Round', - 'Config', 'Submission', 'Schedule', 'Group' - ]; - /** - * ScoreBoard Overview Page - * - * @url /scoreboard - * @url /score_engine/scoreboard - * @url /score_engine/scoreboard/index - */ - public function index() { - $sponsors = $this->Config->getKey('competition.sponsors'); - if ( !empty($sponsors) ) { - $sponsors = json_decode($sponsors, true); - } + public $uses = [ + 'ScoreEngine.Check', 'ScoreEngine.Service', 'ScoreEngine.Team', 'ScoreEngine.Round', + 'Config', 'Submission', 'Schedule', 'Group' + ]; - $this->set('sponsors', $sponsors); - $this->set('at_scoreboard', true); - } + /** + * ScoreBoard Overview Page + * + * @url /scoreboard + * @url /score_engine/scoreboard + * @url /score_engine/scoreboard/index + */ + public function index() { + $sponsors = $this->Config->getKey('competition.sponsors'); + if (!empty($sponsors)) { + $sponsors = json_decode($sponsors, true); + } - /** - * ScoreBoard Overview Page - * - * @url /scoreboard/overview - * @url /score_engine/scoreboard/overview - */ - public function overview() { - // Require staff - $this->Auth->protect(env('GROUP_STAFF')); + $this->set('sponsors', $sponsors); + $this->set('at_scoreboard', true); + } - // Generate team mappings - $group_names = $this->Group->find('all', [ - 'conditions' => [ - 'Group.team_number IS NOT NULL', - ], - ]); - $team_mappings = []; - foreach ( $group_names AS $g ) { - $team_mappings[$g['Group']['team_number']] = $g['Group']['name']; - } + /** + * ScoreBoard Overview Page + * + * @url /scoreboard/overview + * @url /score_engine/scoreboard/overview + */ + public function overview() { + // Require staff + $this->Auth->protect(env('GROUP_STAFF')); - // Grab the check overview - $overview = $this->Check->getMaxCheck(false); + // Generate team mappings + $group_names = $this->Group->find('all', [ + 'conditions' => [ + 'Group.team_number IS NOT NULL', + ], + ]); + $team_mappings = []; + foreach ($group_names as $g) { + $team_mappings[$g['Group']['team_number']] = $g['Group']['name']; + } - // Grab the grade overview - $grades = $this->Submission->getGrades($this->Group->getChildren(env('GROUP_BLUE'))); - $grade_team_mappings = []; - foreach ( $grades AS $g ) { - $grade_team_mappings[$g['Group']['team_number']] = $g['Submission']['total_grade']; - } + // Grab the check overview + $overview = $this->Check->getMaxCheck(false); - // Grab the max check - $max_check = $this->Check->getMaxCheck(true); + // Grab the grade overview + $grades = $this->Submission->getGrades($this->Group->getChildren(env('GROUP_BLUE'))); + $grade_team_mappings = []; + foreach ($grades as $g) { + $grade_team_mappings[$g['Group']['team_number']] = $g['Submission']['total_grade']; + } - // Grab the max grade - $injects = $this->Schedule->getInjects(env('GROUP_BLUE')); - $max_grade = 0; - foreach ( $injects AS $i ) { - $max_grade += $i->getInjectMaxPoints(); - } + // Grab the max check + $max_check = $this->Check->getMaxCheck(true); - $this->set('at_staff', true); - $this->set('round', $this->Round->getLastRound()); - $this->set('overview', $overview); - $this->set('grades', $grades); - $this->set('grade_team_mappings', $grade_team_mappings); - $this->set('team_mappings', $team_mappings); - $this->set('max_grade', $max_grade); - $this->set('max_check', $max_check[0]['Check']['total_passed']); - } + // Grab the max grade + $injects = $this->Schedule->getInjects(env('GROUP_BLUE')); + $max_grade = 0; + foreach ($injects as $i) { + $max_grade += $i->getInjectMaxPoints(); + } - /** - * ScoreBoard API Content - * - * @url /scoreboard/api - * @url /score_engine/scoreboard/api - */ - public function api() { - $this->layout = 'ajax'; + $this->set('at_staff', true); + $this->set('round', $this->Round->getLastRound()); + $this->set('overview', $overview); + $this->set('grades', $grades); + $this->set('grade_team_mappings', $grade_team_mappings); + $this->set('team_mappings', $team_mappings); + $this->set('max_grade', $max_grade); + $this->set('max_check', $max_check[0]['Check']['total_passed']); + } - $active_injects = $this->Schedule->getInjects(env('GROUP_BLUE')); - foreach ( $active_injects AS $i => $inject ) { - if ( $inject->isExpired() ) unset($active_injects[$i]); - } + /** + * ScoreBoard API Content + * + * @url /scoreboard/api + * @url /score_engine/scoreboard/api + */ + public function api() { + $this->layout = 'ajax'; - // Setup the ScoreEngine EngineOutputter - $this->helpers['ScoreEngine.EngineOutputter']['data'] = $this->Check->getChecksTable( - $this->Team->findAllByEnabledAndCheckTeam(true, false), - $this->Service->findAllByEnabled(true) - ); + $active_injects = $this->Schedule->getInjects(env('GROUP_BLUE')); + foreach ($active_injects as $i => $inject) { + if ($inject->isExpired()) { unset($active_injects[$i]); + } + } - $this->set('active_injects', $active_injects); - $this->set('round', $this->Round->getLastRound()); - } -} \ No newline at end of file + // Setup the ScoreEngine EngineOutputter + $this->helpers['ScoreEngine.EngineOutputter']['data'] = $this->Check->getChecksTable( + $this->Team->findAllByEnabledAndCheckTeam(true, false), + $this->Service->findAllByEnabled(true) + ); + + $this->set('active_injects', $active_injects); + $this->set('round', $this->Round->getLastRound()); + } +} diff --git a/app/Plugin/ScoreEngine/Controller/TeamController.php b/app/Plugin/ScoreEngine/Controller/TeamController.php index 5360f53..b414729 100644 --- a/app/Plugin/ScoreEngine/Controller/TeamController.php +++ b/app/Plugin/ScoreEngine/Controller/TeamController.php @@ -2,152 +2,129 @@ App::uses('ScoreEngineAppController', 'ScoreEngine.Controller'); class TeamController extends ScoreEngineAppController { - public $uses = ['ScoreEngine.Check', 'ScoreEngine.TeamService']; - - /** - * The current user's team number - */ - private $team; - - /** - * IP Regex - * - * Source: http://stackoverflow.com/a/20270082 - */ - const IP_REGEX = "/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/"; - - /** - * Before Filter - * - * Locks the page to blue teams' group, - * as well as sets up the user's team number - */ - public function beforeFilter() { - parent::beforeFilter(); - - // Only blue teams may access - $this->Auth->protect(env('GROUP_BLUE')); - - // Set the team number - $this->team = $this->Auth->group('team_number'); - } - - /** - * Team Overview Page - * - * @url /team - * @url /score_engine/team - * @url /score_engine/team/index - */ - public function index() { - $this->set('data', $this->Check->getTeamChecks($this->team)); - $this->set('latest', $this->Check->getLastTeamCheck($this->team)); - } - - /** - * Service Overview Page - * - * @url /team/service/ - * @url /score_engine/team/service/ - */ - public function service($sid=false) { - if ( $sid === false || !is_numeric($sid) ) { - throw new NotFoundException('Unknown service'); - } - - $this->Check->virtualFields = []; - $this->set('data', $this->Check->find('all', [ - 'conditions' => [ - 'team_id' => $this->team, - 'service_id' => $sid, - 'Service.enabled' => true, - ], - 'limit' => 20, - 'order' => 'time DESC', - ])); - } - - /** - * Config Edit Page - * - * @url /team/edit - * @url /score_engine/team/edit - */ - public function config() { - $data = $this->TeamService->getData($this->team); - - $canEdit = function($id) use($data) { - foreach ( $data AS $group => $options ) { - foreach ( $options AS $opt ) { - if ( $opt['id'] == $id ) { - return $opt['edit']; - } - } - } - - return false; - }; - - $getOpt = function($id) use(&$data) { - foreach ( $data AS $group => &$options ) { - foreach ( $options AS $opt ) { - if ( $opt['id'] == $id ) { - return $opt['value']; - } - } - } - - return false; - }; - - $updateOpt = function($id, $value) use(&$data) { - foreach ( $data AS $group => &$options ) { - foreach ( $options AS &$opt ) { - if ( $opt['id'] == $id ) { - $opt['value'] = $value; - } - } - } - - return false; - }; - - if ( $this->request->is('post') ) { - foreach ( $this->request->data AS $opt => $value ) { - $opt = (int) str_replace('opt', '', $opt); - if ( $opt < 0 || !is_numeric($opt) ) continue; - if ( !$canEdit($opt) ) continue; - - // Only USERPASS is an array - if ( is_array($value) ) { - $value = $value['user'].'||'.$value['pass']; - } - - // Do some hacky magic with IPs to check subnets - $oldVal = $getOpt($opt); - - if ( preg_match(self::IP_REGEX, $oldVal, $match) ) { - $newSubnet = explode('.', $value); - array_pop($newSubnet); - $newSubnet = implode('.', $newSubnet); - - $oldSubnet = explode('.', $oldVal); - array_pop($oldSubnet); - $oldSubnet = implode('.', $oldSubnet); - - if ( $newSubnet != $oldSubnet ) { - continue; - } - } - - $this->TeamService->updateConfig($opt, $value); - $updateOpt($opt, $value); - } - - // Message - $this->Flash->success('Updated Score Engine Config!'); - } - - $this->set('data', $data); - } -} \ No newline at end of file + + public $uses = ['ScoreEngine.Check', 'ScoreEngine.TeamService']; + + /** + * The current user's team number + */ + private $team; + + /** + * IP Regex + * + * Source: http://stackoverflow.com/a/20270082 + */ + const IP_REGEX = "/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/"; + + /** + * Before Filter + * + * Locks the page to blue teams' group, + * as well as sets up the user's team number + */ + public function beforeFilter() { + parent::beforeFilter(); + + // Only blue teams may access + $this->Auth->protect(env('GROUP_BLUE')); + + // Set the team number + $this->team = $this->Auth->group('team_number'); + } + + /** + * Team Overview Page + * + * @url /team + * @url /score_engine/team + * @url /score_engine/team/index + */ + public function index() { + $this->set('data', $this->Check->getTeamChecks($this->team)); + $this->set('latest', $this->Check->getLastTeamCheck($this->team)); + } + + /** + * Service Overview Page + * + * @url /team/service/ + * @url /score_engine/team/service/ + */ + public function service($sid = false) { + if ($sid === false || !is_numeric($sid)) { + throw new NotFoundException('Unknown service'); + } + + $this->Check->virtualFields = []; + $this->set('data', $this->Check->find('all', [ + 'conditions' => [ + 'team_id' => $this->team, + 'service_id' => $sid, + 'Service.enabled' => true, + ], + 'limit' => 20, + 'order' => 'time DESC', + ])); + } + + /** + * Config Edit Page + * + * @url /team/edit + * @url /score_engine/team/edit + */ + public function config() { + $data = $this->TeamService->getData($this->team); + + // Generate mappings + $mappings = []; + foreach ($data as $group => $options) { + foreach ($options as $opt) { + $mappings[$opt['id']] = $opt; + } + } + + if ($this->request->is('post')) { + foreach ($this->request->data as $opt => $value) { + $opt = (int)str_replace('opt', '', $opt); + if ($opt < 0 || !is_numeric($opt)) { + continue; + } + if (!$mappings[$opt]['edit']) { + continue; + } + + // Only USERPASS is an array + if (is_array($value)) { + $value = $value['user'].'||'.$value['pass']; + } + + // Do some hacky magic with IPs to check subnets + $oldVal = $mappings[$opt]['value']; + + if (preg_match(self::IP_REGEX, $oldVal, $match)) { + $newSubnet = explode('.', $value); + array_pop($newSubnet); + $newSubnet = implode('.', $newSubnet); + + $oldSubnet = explode('.', $oldVal); + array_pop($oldSubnet); + $oldSubnet = implode('.', $oldSubnet); + + if ($newSubnet != $oldSubnet) { + continue; + } + } + + $this->TeamService->updateConfig($opt, $value); + } + + // Message + $this->Flash->success('Updated Score Engine Config!'); + $this->redirect(['plugin' => 'ScoreEngine', 'controller' => 'team', 'action' => 'config']); + } + + $this->set('data', $data); + } +} diff --git a/app/Plugin/ScoreEngine/Model/Check.php b/app/Plugin/ScoreEngine/Model/Check.php index 101add2..f24fbc0 100644 --- a/app/Plugin/ScoreEngine/Model/Check.php +++ b/app/Plugin/ScoreEngine/Model/Check.php @@ -2,130 +2,136 @@ App::uses('ScoreEngineAppModel', 'ScoreEngine.Model'); class Check extends ScoreEngineAppModel { - public $belongsTo = ['ScoreEngine.Service', 'ScoreEngine.Team']; - public $recursive = 1; - - public $virtualFields = [ - 'total_passed' => 'SUM(Check.passed = 1)', - 'total' => 'COUNT(Check.passed)', - ]; - - public function getChecksTable($teams, $services) { - $rtn = []; - - $enabled_services = []; - foreach ( $services AS $s ) { - $enabled_services[] = $s['Service']['id']; - } - - foreach ( $teams AS $t ) { - $team_name = $t['Team']['name']; - - $rtn[$team_name] = []; - foreach ( $services AS $s ) { - $service_name = $s['Service']['name']; - - $rtn[$team_name][$service_name] = null; - } - } - - $data = $this->find('all', [ - 'fields' => [ - 'Check.passed', 'Team.name', 'Service.name', - 'Service.id', - ], - - 'conditions' => [ - // We're going to use the highest number minus one - 'Check.round = (SELECT MAX(number) FROM rounds WHERE completed = 1)', - ], - - 'order' => [ - 'Team.id ASC', - 'Service.id ASC', - ], - ]); - - foreach ( $data AS $d ) { - $team_name = $d['Team']['name']; - $service_name = $d['Service']['name']; - - if ( !in_array($d['Service']['id'], $enabled_services) ) continue; - if ( !isset($rtn[$team_name]) ) continue; - - $rtn[$team_name][$service_name] = ((bool) $d['Check']['passed']); - } - return $rtn; - } - - public function getTeamChecks($tid, $onlyEnabled=true) { - $conditions = [ - 'fields' => [ - 'Check.total_passed', 'Check.total', - 'Service.name', 'Service.id', 'Service.enabled', - ], - 'joins' => [ - [ - 'table' => 'rounds', - 'alias' => 'Round', - 'type' => 'left', - 'conditions' => [ - 'Round.number = Check.round', - ], - ] - ], - 'conditions' => [ - 'Team.id' => $tid, - 'Round.completed' => true, - ], - 'group' => [ - 'Service.id', - ], - ]; - - if ( $onlyEnabled ) { - $conditions['conditions']['Service.enabled'] = true; - } - - return $this->find('all', $conditions); - } - - public function getLastTeamCheck($tid) { - $data = $this->find('all', [ - 'fields' => [ - 'Service.id', 'Service.name', 'Check.passed', - ], - - 'conditions' => [ - 'Team.id' => $tid, - 'Service.enabled' => true, - 'Check.round = (SELECT MAX(number) FROM rounds WHERE completed = 1)', - ], - ]); - - $rtn = []; - foreach ( $data AS $d ) { - $rtn[$d['Service']['name']] = $d['Check']; - } - - return $rtn; - } - - public function getMaxCheck($onlyCheckTeam=false) { - return $this->find('all', [ - 'fields' => [ - 'Check.total_passed', 'Check.total', - 'Team.id', 'Team.check_team', - ], - 'conditions' => [ - 'Team.check_team' => $onlyCheckTeam, - ], - 'group' => [ - 'Team.id', - ], - 'order' => [ - 'Check.total_passed DESC', - ], - ]); - } -} \ No newline at end of file + + public $belongsTo = ['ScoreEngine.Service', 'ScoreEngine.Team']; + + public $recursive = 1; + + public $virtualFields = [ + 'total_passed' => 'SUM(Check.passed = 1)', + 'total' => 'COUNT(Check.passed)', + ]; + + public function getChecksTable($teams, $services) { + $rtn = []; + + $enabled_services = []; + foreach ($services as $s) { + $enabled_services[] = $s['Service']['id']; + } + + foreach ($teams as $t) { + $team_name = $t['Team']['name']; + + $rtn[$team_name] = []; + foreach ($services as $s) { + $service_name = $s['Service']['name']; + + $rtn[$team_name][$service_name] = null; + } + } + + $data = $this->find('all', [ + 'fields' => [ + 'Check.passed', 'Team.name', 'Service.name', + 'Service.id', + ], + + 'conditions' => [ + // We're going to use the highest number minus one + 'Check.round = (SELECT MAX(number) FROM rounds WHERE completed = 1)', + ], + + 'order' => [ + 'Team.id ASC', + 'Service.id ASC', + ], + ]); + + foreach ($data as $d) { + $team_name = $d['Team']['name']; + $service_name = $d['Service']['name']; + + if (!in_array($d['Service']['id'], $enabled_services)) { + continue; + } + if (!isset($rtn[$team_name])) { + continue; + } + + $rtn[$team_name][$service_name] = ((bool)$d['Check']['passed']); + } + return $rtn; + } + + public function getTeamChecks($tid, $onlyEnabled = true) { + $conditions = [ + 'fields' => [ + 'Check.total_passed', 'Check.total', + 'Service.name', 'Service.id', 'Service.enabled', + ], + 'joins' => [ + [ + 'table' => 'rounds', + 'alias' => 'Round', + 'type' => 'left', + 'conditions' => [ + 'Round.number = Check.round', + ], + ] + ], + 'conditions' => [ + 'Team.id' => $tid, + 'Round.completed' => true, + ], + 'group' => [ + 'Service.id', + ], + ]; + + if ($onlyEnabled) { + $conditions['conditions']['Service.enabled'] = true; + } + + return $this->find('all', $conditions); + } + + public function getLastTeamCheck($tid) { + $data = $this->find('all', [ + 'fields' => [ + 'Service.id', 'Service.name', 'Check.passed', + ], + + 'conditions' => [ + 'Team.id' => $tid, + 'Service.enabled' => true, + 'Check.round = (SELECT MAX(number) FROM rounds WHERE completed = 1)', + ], + ]); + + $rtn = []; + foreach ($data as $d) { + $rtn[$d['Service']['name']] = $d['Check']; + } + + return $rtn; + } + + public function getMaxCheck($onlyCheckTeam = false) { + return $this->find('all', [ + 'fields' => [ + 'Check.total_passed', 'Check.total', + 'Team.id', 'Team.check_team', + ], + 'conditions' => [ + 'Team.check_team' => $onlyCheckTeam, + ], + 'group' => [ + 'Team.id', + ], + 'order' => [ + 'Check.total_passed DESC', + ], + ]); + } +} diff --git a/app/Plugin/ScoreEngine/Model/Round.php b/app/Plugin/ScoreEngine/Model/Round.php index 5f2be50..6914775 100644 --- a/app/Plugin/ScoreEngine/Model/Round.php +++ b/app/Plugin/ScoreEngine/Model/Round.php @@ -2,16 +2,16 @@ App::uses('ScoreEngineAppModel', 'ScoreEngine.Model'); class Round extends ScoreEngineAppModel { - public function getLastRound() { - $round = $this->find('first', [ - 'fields' => [ - 'MAX(Round.number) AS round' - ], - 'conditions' => [ - 'Round.completed' => true, - ], - ]); + public function getLastRound() { + $round = $this->find('first', [ + 'fields' => [ + 'MAX(Round.number) AS round' + ], + 'conditions' => [ + 'Round.completed' => true, + ], + ]); - return empty($round[0]['round']) ? 0 : $round[0]['round']; - } -} \ No newline at end of file + return empty($round[0]['round']) ? 0 : $round[0]['round']; + } +} diff --git a/app/Plugin/ScoreEngine/Model/ScoreEngineAppModel.php b/app/Plugin/ScoreEngine/Model/ScoreEngineAppModel.php index 514820b..13bd7ba 100644 --- a/app/Plugin/ScoreEngine/Model/ScoreEngineAppModel.php +++ b/app/Plugin/ScoreEngine/Model/ScoreEngineAppModel.php @@ -2,5 +2,6 @@ App::uses('AppModel', 'Model'); class ScoreEngineAppModel extends AppModel { - public $useDbConfig = 'scoreengine'; -} \ No newline at end of file + + public $useDbConfig = 'scoreengine'; +} diff --git a/app/Plugin/ScoreEngine/Model/Service.php b/app/Plugin/ScoreEngine/Model/Service.php index 3ec5d86..ade1760 100644 --- a/app/Plugin/ScoreEngine/Model/Service.php +++ b/app/Plugin/ScoreEngine/Model/Service.php @@ -2,5 +2,5 @@ App::uses('ScoreEngineAppModel', 'ScoreEngine.Model'); class Service extends ScoreEngineAppModel { - -} \ No newline at end of file + +} diff --git a/app/Plugin/ScoreEngine/Model/Team.php b/app/Plugin/ScoreEngine/Model/Team.php index 1e20877..f33918c 100644 --- a/app/Plugin/ScoreEngine/Model/Team.php +++ b/app/Plugin/ScoreEngine/Model/Team.php @@ -2,5 +2,5 @@ App::uses('ScoreEngineAppModel', 'ScoreEngine.Model'); class Team extends ScoreEngineAppModel { - -} \ No newline at end of file + +} diff --git a/app/Plugin/ScoreEngine/Model/TeamService.php b/app/Plugin/ScoreEngine/Model/TeamService.php index a364987..ff0c18b 100644 --- a/app/Plugin/ScoreEngine/Model/TeamService.php +++ b/app/Plugin/ScoreEngine/Model/TeamService.php @@ -2,64 +2,67 @@ App::uses('ScoreEngineAppModel', 'ScoreEngine.Model'); class TeamService extends ScoreEngineAppModel { - public $useTable = 'team_service'; - public $belongsTo = ['ScoreEngine.Team', 'ScoreEngine.Service']; - public $recursive = 1; - - public function getData($tid, $onlyEnabled=true) { - $conditions = [ - 'fields' => [ - 'TeamService.id', 'TeamService.key', 'TeamService.value', - 'TeamService.edit', 'TeamService.hidden', 'Service.name', 'Service.id', - 'Service.enabled', - ], - - 'conditions' => [ - 'Team.id' => $tid, - ], - ]; - - if ( $onlyEnabled ) { - $conditions['conditions']['Service.enabled'] = true; - } - - $data = $this->find('all', $conditions); - - $rtn = []; - foreach ( $data AS $d ) { - if ( !$d['Service']['enabled'] ) { - $d['Service']['name'] .= ' (Disabled)'; - } - - if ( !isset($rtn[$d['Service']['name']]) ) { - $rtn[$d['Service']['name']] = []; - } - - $rtn[$d['Service']['name']][] = $d['TeamService']; - } - - return $rtn; - } - - public function getConfig($tid, $sid, $key=false) { - $conditions = [ - 'Team.id' => $tid, - 'Service.id' => $sid, - ]; - - if ( $key !== false ) { - $conditions['TeamService.key'] = $key; - } - - return $this->find('all', [ - 'conditions' => $conditions, - ]); - } - - public function updateConfig($id, $value) { - $this->id = $id; - $this->save([ - 'value' => $value, - ]); - } -} \ No newline at end of file + + public $useTable = 'team_service'; + + public $belongsTo = ['ScoreEngine.Team', 'ScoreEngine.Service']; + + public $recursive = 1; + + public function getData($tid, $onlyEnabled = true) { + $conditions = [ + 'fields' => [ + 'TeamService.id', 'TeamService.key', 'TeamService.value', + 'TeamService.edit', 'TeamService.hidden', 'Service.name', 'Service.id', + 'Service.enabled', + ], + + 'conditions' => [ + 'Team.id' => $tid, + ], + ]; + + if ($onlyEnabled) { + $conditions['conditions']['Service.enabled'] = true; + } + + $data = $this->find('all', $conditions); + + $rtn = []; + foreach ($data as $d) { + if (!$d['Service']['enabled']) { + $d['Service']['name'] .= ' (Disabled)'; + } + + if (!isset($rtn[$d['Service']['name']])) { + $rtn[$d['Service']['name']] = []; + } + + $rtn[$d['Service']['name']][] = $d['TeamService']; + } + + return $rtn; + } + + public function getConfig($tid, $sid, $key = false) { + $conditions = [ + 'Team.id' => $tid, + 'Service.id' => $sid, + ]; + + if ($key !== false) { + $conditions['TeamService.key'] = $key; + } + + return $this->find('all', [ + 'conditions' => $conditions, + ]); + } + + public function updateConfig($id, $value) { + $this->id = $id; + $this->save([ + 'value' => $value, + ]); + } +} diff --git a/app/Plugin/ScoreEngine/View/Helper/EngineOutputterHelper.php b/app/Plugin/ScoreEngine/View/Helper/EngineOutputterHelper.php index 9c6e749..9195fea 100644 --- a/app/Plugin/ScoreEngine/View/Helper/EngineOutputterHelper.php +++ b/app/Plugin/ScoreEngine/View/Helper/EngineOutputterHelper.php @@ -3,41 +3,41 @@ class EngineOutputterHelper extends AppHelper { - public function generateScoreBoard() { - if ( !isset($this->settings['data']) ) { - throw new InternalErrorException('ScoreEngine.EngineOutputter not setup correctly'); - } - - $out = ''; - $out .= ''; - - // Grab the first element from the data array - // This is two lines due to a pass-by-reference warning - $services = array_values($this->settings['data']); - $services = array_shift($services); - - foreach ( $services AS $service_name => $status ) { - $out.= ''; - } - - $out.= ' '; - - foreach ( $this->settings['data'] AS $team_name => $services ) { - $out .= ''; - - foreach ( $services AS $service_name => $status ) { - if ( $status === null ) { - $class = 'info'; - } else { - $class = ($status ? 'success' : 'danger'); - } - - $out .= ''; - } - $out .= ''; - } - - $out .= '
Team'.$service_name.'
'.$team_name.'
'; - return $out; - } -} \ No newline at end of file + public function generateScoreBoard() { + if (!isset($this->settings['data'])) { + throw new InternalErrorException('ScoreEngine.EngineOutputter not setup correctly'); + } + + $out = ''; + $out .= ''; + + // Grab the first element from the data array + // This is two lines due to a pass-by-reference warning + $services = array_values($this->settings['data']); + $services = array_shift($services); + + foreach ($services as $service_name => $status) { + $out .= ''; + } + + $out .= ' '; + + foreach ($this->settings['data'] as $team_name => $services) { + $out .= ''; + + foreach ($services as $service_name => $status) { + if ($status === null) { + $class = 'info'; + } else { + $class = ($status ? 'success' : 'danger'); + } + + $out .= ''; + } + $out .= ''; + } + + $out .= '
Team'.$service_name.'
'.$team_name.'
'; + return $out; + } +} diff --git a/app/View/Helper/AuthHelper.php b/app/View/Helper/AuthHelper.php index 4ded408..1fd22c7 100644 --- a/app/View/Helper/AuthHelper.php +++ b/app/View/Helper/AuthHelper.php @@ -3,20 +3,20 @@ class AuthHelper extends AppHelper { - /** - * Magic bridge to the AuthComponent - * - * If a method being called does not exist, but - * it exists in AuthComponent, this will 'proxy' - * the method call to it. - * - * @param $name The method name being called - * @param $args An array of arguments - * @return mixed - */ - public function __call($name, $args) { - if ( method_exists($this->settings['auth'], $name) ) { - return call_user_func_array([$this->settings['auth'], $name], $args); - } - } -} \ No newline at end of file + /** + * Magic bridge to the AuthComponent + * + * If a method being called does not exist, but + * it exists in AuthComponent, this will 'proxy' + * the method call to it. + * + * @param $name The method name being called + * @param $args An array of arguments + * @return mixed + */ + public function __call($name, $args) { + if (method_exists($this->settings['auth'], $name)) { + return call_user_func_array([$this->settings['auth'], $name], $args); + } + } +} diff --git a/app/View/Helper/InjectStylerHelper.php b/app/View/Helper/InjectStylerHelper.php index 0a9569f..0b92c4f 100644 --- a/app/View/Helper/InjectStylerHelper.php +++ b/app/View/Helper/InjectStylerHelper.php @@ -2,152 +2,153 @@ App::uses('AppHelper', 'View/Helper'); class InjectStylerHelper extends AppHelper { - /** - * Instance of \InjectTypes\Manager - */ - private $typeManager; - - /** - * Instance of InjectAbstraction - */ - private $inject; - - const TYPE_OUTPUT_TPL = '
  • '. - '

    %s

'; - - /** - * Constructor for the InjectHelper - * - * Basically initializes the InjectTypes manager - */ - public function __construct(View $view, $settings = array()) { - parent::__construct($view, $settings); - - if ( !isset($settings['types']) || !isset($settings['inject']) ) { - throw new InternalErrorException('InjectStyler is missing types/inject settings'); - } - - $this->typeManager = new InjectTypes\Manager($settings['types']); - $this->setInject($settings['inject']); - } - - /** - * Set the current inject - * - * @param $inject The InjectAbstraction object - * @return void - */ - public function setInject($inject) { - $this->inject = $inject; - } - - /** - * Time output - * - * This will be the inject page's "start/end/duration" - * area. - * - * @param $inject The inject - * @return string The time content - */ - public function timeOutput($inject) { - $template = 'Time: %s
Due: %s
Duration: %s'; - - return sprintf($template, $inject->getStartString(), $inject->getEndString(), $inject->getDurationString()); - } - - /** - * Content Output - * - * Basically replaces some variables - * with actual content. Woah! - * - * @param $data The inject content - * @param $userdata Current user information - * @return string The inject content - */ - public function contentOutput($data, $userdata) { - if ( $userdata['Group']['team_number'] == null ) { - $team = 'X'; - $team_pad = 'XX'; - } else { - $team = $userdata['Group']['team_number']; - $team_pad = str_pad($team, 2, '0'); - } - - $find = ['#TEAM_NUMBER#', '#TEAM_NUMBER_PADDED#']; - $replace = [$team, $team_pad]; - - return str_replace($find, $replace, $data); - } - - /** - * Inject Type Submission Output - * - * @param $id Inject Type ID - * @return string The template - */ - public function typeOutput($id) { - $injectType = $this->typeManager->get($id); - - if ( $this->inject->isAcceptingSubmissions() ) { - $tpl = '
'; - $tpl .= ''; - $tpl .= $injectType->getTemplate(); - $tpl .= '
'; - - return $tpl; - } - - if ( $this->inject->isExpired() ) { - return sprintf(self::TYPE_OUTPUT_TPL, 'Submission for this inject has expired.'); - } - - if ( $this->inject->getSubmissionCount() >= $this->inject->getMaxSubmissions() ) { - return ($this->inject->getMaxSubmissions() > 1 - ? sprintf(self::TYPE_OUTPUT_TPL, 'Max submissions reached.') - : sprintf(self::TYPE_OUTPUT_TPL, 'This inject has already been submitted.')); - } - - return 'Unknown error'; - } - - /** - * Inject Type Submitted Output - * - * @param $id Inject Type ID - * @return string The template - */ - public function submittedOutput($id, $submissions) { - return $this->typeManager->get($id)->getSubmittedTemplate($submissions); - } - - /** - * Inject Type Grader Output - * - * @param $id Inject Type ID - * @return string The template - */ - public function graderOutput($id, $submission) { - return $this->typeManager->get($id)->getGraderTemplate($submission); - } - - /** - * Get Inject Type Name - * - * @param $id Inject Type ID - * @return string The name - */ - public function getName($id) { - return $this->typeManager->get($id)->getName(); - } - - /** - * Get All Types - * - * @return array The type objects - */ - public function getAllTypes() { - return $this->typeManager->getAll(); - } -} \ No newline at end of file + + /** + * Instance of \InjectTypes\Manager + */ + private $typeManager; + + /** + * Instance of InjectAbstraction + */ + private $inject; + + const TYPE_OUTPUT_TPL = '
  • '. + '

    %s

'; + + /** + * Constructor for the InjectHelper + * + * Basically initializes the InjectTypes manager + */ + public function __construct(View $view, $settings = []) { + parent::__construct($view, $settings); + + if (!isset($settings['types']) || !isset($settings['inject'])) { + throw new InternalErrorException('InjectStyler is missing types/inject settings'); + } + + $this->typeManager = new InjectTypes\Manager($settings['types']); + $this->setInject($settings['inject']); + } + + /** + * Set the current inject + * + * @param $inject The InjectAbstraction object + * @return void + */ + public function setInject($inject) { + $this->inject = $inject; + } + + /** + * Time output + * + * This will be the inject page's "start/end/duration" + * area. + * + * @param $inject The inject + * @return string The time content + */ + public function timeOutput($inject) { + $template = 'Time: %s
Due: %s
Duration: %s'; + + return sprintf($template, $inject->getStartString(), $inject->getEndString(), $inject->getDurationString()); + } + + /** + * Content Output + * + * Basically replaces some variables + * with actual content. Woah! + * + * @param $data The inject content + * @param $userdata Current user information + * @return string The inject content + */ + public function contentOutput($data, $userdata) { + if ($userdata['Group']['team_number'] == null) { + $team = 'X'; + $team_pad = 'XX'; + } else { + $team = $userdata['Group']['team_number']; + $team_pad = str_pad($team, 2, '0'); + } + + $find = ['#TEAM_NUMBER#', '#TEAM_NUMBER_PADDED#']; + $replace = [$team, $team_pad]; + + return str_replace($find, $replace, $data); + } + + /** + * Inject Type Submission Output + * + * @param $id Inject Type ID + * @return string The template + */ + public function typeOutput($id) { + $injectType = $this->typeManager->get($id); + + if ($this->inject->isAcceptingSubmissions()) { + $tpl = '
'; + $tpl .= ''; + $tpl .= $injectType->getTemplate(); + $tpl .= '
'; + + return $tpl; + } + + if ($this->inject->isExpired()) { + return sprintf(self::TYPE_OUTPUT_TPL, 'Submission for this inject has expired.'); + } + + if ($this->inject->getSubmissionCount() >= $this->inject->getMaxSubmissions()) { + $tpl_max = sprintf(self::TYPE_OUTPUT_TPL, 'Max submissions reached.'); + $tpl_sub = sprintf(self::TYPE_OUTPUT_TPL, 'This inject has already been submitted.'); + return ($this->inject->getMaxSubmissions() > 1 ? $tpl_max : $tpl_sub); + } + + return 'Unknown error'; + } + + /** + * Inject Type Submitted Output + * + * @param $id Inject Type ID + * @return string The template + */ + public function submittedOutput($id, $submissions) { + return $this->typeManager->get($id)->getSubmittedTemplate($submissions); + } + + /** + * Inject Type Grader Output + * + * @param $id Inject Type ID + * @return string The template + */ + public function graderOutput($id, $submission) { + return $this->typeManager->get($id)->getGraderTemplate($submission); + } + + /** + * Get Inject Type Name + * + * @param $id Inject Type ID + * @return string The name + */ + public function getName($id) { + return $this->typeManager->get($id)->getName(); + } + + /** + * Get All Types + * + * @return array The type objects + */ + public function getAllTypes() { + return $this->typeManager->getAll(); + } +} diff --git a/app/View/Helper/MiscHelper.php b/app/View/Helper/MiscHelper.php index 491f1d3..6716676 100644 --- a/app/View/Helper/MiscHelper.php +++ b/app/View/Helper/MiscHelper.php @@ -2,25 +2,26 @@ App::uses('AppHelper', 'View/Helper'); class MiscHelper extends AppHelper { - const NAVBAR_ITEM = '
  • %s
  • '; - const NAVBAR_MENU = ''; + const NAVBAR_ITEM = '
  • %s
  • '; + const NAVBAR_MENU = ''; - public function navbarItem($name, $url, $active=false) { - return sprintf(self::NAVBAR_ITEM, ($active ? 'active' : ''), $this->url($url), $name); - } + public function navbarItem($name, $url, $active = false) { + return sprintf(self::NAVBAR_ITEM, ($active ? 'active' : ''), $this->url($url), $name); + } - public function navbarDropdown($name, $active, $children) { - if ( empty(array_filter($children)) ) return ''; - - return sprintf(self::NAVBAR_MENU, ($active ? 'active' : ''), $name, implode('', $children)); - } + public function navbarDropdown($name, $active, $children) { + if (empty(array_filter($children))) { return ''; + } - public function date($format, $ts) { - $date = new DateTime('@'.$ts); - $date->setTimezone(new DateTimeZone(env('TIMEZONE_USER'))); + return sprintf(self::NAVBAR_MENU, ($active ? 'active' : ''), $name, implode('', $children)); + } - return $date->format($format); - } -} \ No newline at end of file + public function date($format, $ts) { + $date = new DateTime('@'.$ts); + $date->setTimezone(new DateTimeZone(env('TIMEZONE_USER'))); + + return $date->format($format); + } +} diff --git a/composer.json b/composer.json index c4a2891..04b24ef 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ "respect/validation": "^1.1" }, "require-dev": { - "cakephp/debug_kit" : "2.2.*" + "cakephp/debug_kit" : "2.2.*", + "squizlabs/php_codesniffer": "^2.8" }, "config": { "preferred-install": "dist" diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..900c324 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,72 @@ + + + ie2 coding standard + + ./app + + + + + + + + + + + + + + + + + + + + + warning + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ./app/Config/database.php + + + ./app/Console/Command/EngineShell.php + + \ No newline at end of file