diff --git a/README.md b/README.md new file mode 100644 index 0000000..e5e7e1c --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# KDE Event Registration + +A simple web application to help organise and manage the KDE Annual Conference: Akademy. There is support for multiple events as KDE organises other conferences and sptrints throughout the year. + +For each event, we normally require the same profile information from attendees, followed by a set of questions that may change from event to event. To aid this, a form definition is saved with a conference definition within the events table. + +## Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. + +### Prerequisites + +The decision was taken to built this application on a simple LAMP stack, making it easier to contribute features and bug fixes. + + * PHP Composer - https://getcomposer.org/ + * Bower - https://bower.io/ + * MySQL - https://www.mysql.com/ + * LDAP (For KDE Identity Integration) + +To get up and running: + + * Configure a baseline LDAP server. An (unmaintained) example of this can be found here: https://github.com/GeekSoc/gas-vagrant + * Create a database using the information in resources/schema.sql + * Create a single conference using resources/conference.sql + * Copy app/settings.php.dist to app/settings.php and configure appropriately. + * Run the application: `php -S 0.0.0.0:8000 -t public` + * Open a browser and start building: `open http://localhost:8000` diff --git a/app/configuration/baseform.json b/app/configuration/baseform.json new file mode 100644 index 0000000..f6664ea --- /dev/null +++ b/app/configuration/baseform.json @@ -0,0 +1,88 @@ +{ + "forms": [ + { + "field": "Email", + "type": "text" + }, + { + "field": "Full Name", + "type": "text" + }, + { + "field": "Irc Nick", + "type": "text" + }, + { + "field": "Country", + "type": "dropdown", + "data": "CountryList" + }, + { + "field": "Primary Role in KDE", + "type": "dropdown", + "data": { + "type": "list", + "values": [ + "Artist", + "Community", + "Developer", + "Promo", + "Translator", + "User", + "Other" + ] + } + }, + { + "field": "Other Roles in KDE", + "type": "checkboxes", + "data": { + "type": "list", + "values": [ + "Artist", + "Community", + "Developer", + "Promo", + "Translator", + "User", + "Other" + ] + } + }, + { + "field": "Emergency Contact Information", + "type": "textarea", + "hint": "Someone we can contact at any time in case of an emergency that involves you. Please provide as much information as you can such as name, phone number including country code, address and the person's relationship to you." + }, + { + "field": "Dietary requirements and allergies", + "type": "checkboxes", + "data": { + "type": "list", + "values": [ + "Gluten Free", + "Lactose Free", + "Nut Free", + "Vegan", + "Vegetarian", + "Other" + ] + } + }, + { + "field": "Other dietary requirements", + "type": "textarea", + "hint": "Please tell us of any other dietary requirements you have.", + "dependency": { + "field": "Dietary requirements and allergies", + "value": "Other" + } + }, + { + "field": "Health or allergy information", + "type": "textarea", + "hint": "Please tell us of any health problems or allergies." + } + ], + "submitlabel": "Save Profile" +} diff --git a/app/configuration/data/countrylist.json b/app/configuration/data/countrylist.json new file mode 100644 index 0000000..9804767 --- /dev/null +++ b/app/configuration/data/countrylist.json @@ -0,0 +1,262 @@ +{ + "data": [ + "Please Select Country", + "-------------", + "Afghanistan", + "\u00c5land Islands", + "Albania", + "Algeria", + "American Samoa", + "Andorra", + "Angola", + "Anguilla", + "Antarctica", + "Antigua and Barbuda", + "Argentina", + "Armenia", + "Aruba", + "Ascension Island", + "Australia", + "Austria", + "Azerbaijan", + "Bahamas", + "Bahrain", + "Bangladesh", + "Barbados", + "Belarus", + "Belgium", + "Belize", + "Benin", + "Bermuda", + "Bhutan", + "Bolivia", + "Bosnia and Herzegovina", + "Botswana", + "Brazil", + "British Indian Ocean Territory", + "British Virgin Islands", + "Brunei", + "Bulgaria", + "Burkina Faso", + "Burundi", + "Cambodia", + "Cameroon", + "Canada", + "Canary Islands", + "Cape Verde", + "Caribbean Netherlands", + "Cayman Islands", + "Central African Republic", + "Ceuta and Melilla", + "Chad", + "Chile", + "China", + "Christmas Island", + "Cocos (Keeling) Islands", + "Colombia", + "Comoros", + "Congo - Brazzaville", + "Congo - Kinshasa", + "Cook Islands", + "Costa Rica", + "C\u00f4te d\u2019Ivoire", + "Croatia", + "Cuba", + "Cura\u00e7ao", + "Cyprus", + "Czech Republic", + "Denmark", + "Diego Garcia", + "Djibouti", + "Dominica", + "Dominican Republic", + "Ecuador", + "Egypt", + "El Salvador", + "England", + "Equatorial Guinea", + "Eritrea", + "Estonia", + "Ethiopia", + "Falkland Islands", + "Faroe Islands", + "Fiji", + "Finland", + "France", + "French Guiana", + "French Polynesia", + "French Southern Territories", + "Gabon", + "Gambia", + "Georgia", + "Germany", + "Ghana", + "Gibraltar", + "Greece", + "Greenland", + "Grenada", + "Guadeloupe", + "Guam", + "Guatemala", + "Guernsey", + "Guinea", + "Guinea-Bissau", + "Guyana", + "Haiti", + "Honduras", + "Hong Kong SAR China", + "Hungary", + "Iceland", + "India", + "Indonesia", + "Iran", + "Iraq", + "Ireland", + "Isle of Man", + "Israel", + "Italy", + "Jamaica", + "Japan", + "Jersey", + "Jordan", + "Kazakhstan", + "Kenya", + "Kiribati", + "Kosovo", + "Kuwait", + "Kyrgyzstan", + "Laos", + "Latvia", + "Lebanon", + "Lesotho", + "Liberia", + "Libya", + "Liechtenstein", + "Lithuania", + "Luxembourg", + "Macau SAR China", + "Macedonia", + "Madagascar", + "Malawi", + "Malaysia", + "Maldives", + "Mali", + "Malta", + "Marshall Islands", + "Martinique", + "Mauritania", + "Mauritius", + "Mayotte", + "Mexico", + "Micronesia", + "Moldova", + "Monaco", + "Mongolia", + "Montenegro", + "Montserrat", + "Morocco", + "Mozambique", + "Myanmar (Burma)", + "Namibia", + "Nauru", + "Nepal", + "Netherlands", + "New Caledonia", + "New Zealand", + "Nicaragua", + "Niger", + "Nigeria", + "Niue", + "Norfolk Island", + "North Korea", + "Northen Ireland", + "Northern Mariana Islands", + "Norway", + "Oman", + "Pakistan", + "Palau", + "Palestinian Territories", + "Panama", + "Papua New Guinea", + "Paraguay", + "Peru", + "Philippines", + "Pitcairn Islands", + "Poland", + "Portugal", + "Puerto Rico", + "Qatar", + "R\u00e9union", + "Romania", + "Russia", + "Rwanda", + "Saint Barth\u00e9lemy", + "Saint Helena", + "Saint Kitts and Nevis", + "Saint Lucia", + "Saint Martin", + "Saint Pierre and Miquelon", + "Samoa", + "San Marino", + "S\u00e3o Tom\u00e9 and Pr\u00edncipe", + "Saudi Arabia", + "Scotland", + "Senegal", + "Serbia", + "Seychelles", + "Sierra Leone", + "Singapore", + "Sint Maarten", + "Slovakia", + "Slovenia", + "Solomon Islands", + "Somalia", + "South Africa", + "South Georgia & South Sandwich Islands", + "South Korea", + "South Sudan", + "Spain", + "Sri Lanka", + "St. Vincent & Grenadines", + "Sudan", + "Suriname", + "Svalbard and Jan Mayen", + "Swaziland", + "Sweden", + "Switzerland", + "Syria", + "Taiwan", + "Tajikistan", + "Tanzania", + "Thailand", + "Timor-Leste", + "Togo", + "Tokelau", + "Tonga", + "Trinidad and Tobago", + "Tristan da Cunha", + "Tunisia", + "Turkey", + "Turkmenistan", + "Turks and Caicos Islands", + "Tuvalu", + "U.S. Outlying Islands", + "U.S. Virgin Islands", + "Uganda", + "Ukraine", + "United Arab Emirates", + "United States", + "Uruguay", + "Uzbekistan", + "Vanuatu", + "Vatican City", + "Venezuela", + "Vietnam", + "Wallis and Futuna", + "Wales", + "Western Sahara", + "Yemen", + "Zambia", + "Zimbabwe" + ] +} \ No newline at end of file diff --git a/app/dependencies.php b/app/dependencies.php new file mode 100644 index 0000000..9b7fe8d --- /dev/null +++ b/app/dependencies.php @@ -0,0 +1,161 @@ +getContainer(); +} else { + $container = $app; +} + +// ----------------------------------------------------------------------------- +// Service providers +// ----------------------------------------------------------------------------- + +// Twig +$container['view'] = function ($c) { + $settings = $c->get('settings'); + $view = new Slim\Views\Twig($settings['view']['template_path'], $settings['view']['twig']); + + // Add extensions + $view->addExtension(new Slim\Views\TwigExtension($c->get('router'), $c->get('request')->getUri())); + $view->addExtension(new Twig_Extension_Debug()); + + return $view; +}; + +// Flash messages +$container['flash'] = function ($c) { + return new Slim\Flash\Messages; +}; + +// ----------------------------------------------------------------------------- +// Service factories +// ----------------------------------------------------------------------------- + +// monolog +$container['logger'] = function ($c) { + $settings = $c->get('settings'); + $logger = new Monolog\Logger($settings['logger']['name']); + $logger->pushProcessor(new Monolog\Processor\UidProcessor()); + $logger->pushHandler(new Monolog\Handler\StreamHandler($settings['logger']['path'], Monolog\Logger::DEBUG)); + return $logger; +}; + +// ----------------------------------------------------------------------------- +// Database +// ----------------------------------------------------------------------------- +$container['database'] = function ($c) { + if(method_exists($c, 'get')) { + $config = $c->get('settings')['database']; + } else { + $config = $c['settings']['database']; + } + + $cfg = new \Spot\Config(); + $cfg->addConnection('mysqli', [ + 'dbname' => $config['db'], + 'user' => $config['username'], + 'password' => $config['password'], + 'host' => $config['hostname'], + 'driver' => 'pdo_mysql', + 'charset' => 'utf8' + ]); + $spot = new \Spot\Locator($cfg); + + return $spot; +}; + +// ----------------------------------------------------------------------------- +// Utility Classes +// ----------------------------------------------------------------------------- + +$container['formbuilder'] = function ($c) { + return new App\Forms\FormBuilder($c->get('view')); +}; + +$container['loginmanager'] = function ($c) { + return new App\Login\LoginManager($c->get('settings')['login'], $c->get('database'), $c->get('settings')['login']['override']); +}; + +// ----------------------------------------------------------------------------- +// Action factories +// ----------------------------------------------------------------------------- + +$container[App\Action\HomeAction::class] = function ($c) { + return new App\Action\HomeAction($c->get('view'), + $c->get('logger'), + $c->get('formbuilder'), + $c->get('loginmanager'), + $c->get('database')); +}; + +$container[App\Action\LoginAction::class] = function ($c) { + return new App\Action\LoginAction($c->get('view'), + $c->get('logger'), + $c->get('loginmanager')); +}; + +$container[App\Action\LogoutAction::class] = function ($c) { + return new App\Action\LogoutAction($c->get('view'), + $c->get('logger'), + $c->get('loginmanager')); +}; + +$container[App\Action\ProfileAction::class] = function ($c) { + return new App\Action\ProfileAction($c->get('view'), + $c->get('logger'), + $c->get('formbuilder'), + $c->get('loginmanager'), + $c->get('database')); +}; + +$container[App\Action\RegisterAction::class] = function ($c) { + return new App\Action\RegisterAction($c->get('view'), + $c->get('logger'), + $c->get('formbuilder'), + $c->get('loginmanager'), + $c->get('database')); +}; + +$container[App\Action\CancelAction::class] = function ($c) { + return new App\Action\CancelAction($c->get('view'), + $c->get('logger'), + $c->get('formbuilder'), + $c->get('loginmanager'), + $c->get('database')); +}; + +$container[App\Action\MerchandiseAction::class] = function ($c) { + return new App\Action\MerchandiseAction($c->get('view'), + $c->get('logger'), + $c->get('loginmanager'), + $c->get('database')); +}; + +$container[App\Action\StatsAction::class] = function ($c) { + return new App\Action\StatsAction($c->get('view'), + $c->get('logger'), + $c->get('formbuilder'), + $c->get('loginmanager'), + $c->get('database')); +}; + +// ----------------------------------------------------------------------------- +// API Action factories +// ----------------------------------------------------------------------------- + +$container[App\Action\API\MerchandiseAction::class] = function ($c) { + return new App\Action\API\MerchandiseAction($c->get('view'), + $c->get('logger'), + $c->get('formbuilder'), + $c->get('loginmanager'), + $c->get('database')); +}; + +$container[App\Action\API\RegistrationAction::class] = function ($c) { + return new App\Action\API\RegistrationAction($c->get('view'), + $c->get('logger'), + $c->get('formbuilder'), + $c->get('loginmanager'), + $c->get('database')); +}; \ No newline at end of file diff --git a/app/middleware.php b/app/middleware.php new file mode 100644 index 0000000..116a0ff --- /dev/null +++ b/app/middleware.php @@ -0,0 +1,4 @@ +add(new \Slim\Csrf\Guard); diff --git a/app/routes.php b/app/routes.php new file mode 100644 index 0000000..1a5b7ce --- /dev/null +++ b/app/routes.php @@ -0,0 +1,46 @@ +get('/', App\Action\HomeAction::class) + ->setName('homepage'); + +$app->get('/login', App\Action\LoginAction::class) + ->setName('login'); + +$app->post('/login', App\Action\LoginAction::class) + ->setName('login'); + +$app->get('/logout', App\Action\LogoutAction::class) + ->setName('logout'); + +$app->get('/profile', App\Action\ProfileAction::class) + ->setName('profile'); + +$app->post('/profile', App\Action\ProfileAction::class) + ->setName('profile'); + +$app->get('/register/{conferenceSlug}', App\Action\RegisterAction::class) + ->setName('register'); + +$app->post('/register/{conferenceSlug}', App\Action\RegisterAction::class) + ->setName('register'); + +$app->get('/cancel/{conferenceSlug}', App\Action\CancelAction::class) + ->setName('cancel'); + +$app->get('/merchandise/{conferenceSlug}', App\Action\MerchandiseAction::class) + ->setName('merchandise'); + +$app->post('/merchandise/{conferenceSlug}', App\Action\MerchandiseAction::class) + ->setName('merchandise'); + +$app->get('/stats/{conferenceSlug}', App\Action\StatsAction::class) + ->setName('stats'); + +// API + +$app->get('/api/merchandise/{conferenceSlug}', App\Action\API\MerchandiseAction::class) + ->setName('api/merchandise'); + +$app->get('/api/registration/{conferenceSlug}', App\Action\API\RegistrationAction::class) + ->setName('api/register'); \ No newline at end of file diff --git a/app/settings.php.dist b/app/settings.php.dist new file mode 100644 index 0000000..2f9774f --- /dev/null +++ b/app/settings.php.dist @@ -0,0 +1,41 @@ + [ + // Slim Settings + 'determineRouteBeforeAppMiddleware' => false, + 'displayErrorDetails' => false, + + // View settings + 'view' => [ + 'template_path' => __DIR__ . '/templates', + 'twig' => [ + 'cache' => __DIR__ . '/../cache/twig', + 'debug' => true, + 'auto_reload' => true, + ], + ], + + // monolog settings + 'logger' => [ + 'name' => 'app', + 'path' => __DIR__ . '/../log/app.log', + ], + + // login manager + 'login' => [ + 'salt' => '', + 'ldap_host' => "127.0.0.1", + 'ldap_port' => '389', + 'base_dn' => "ou=people,dc=kde,dc=org", + 'override' => false + ], + + // database configuration + 'database' => [ + 'hostname' => '127.0.0.1', + 'username' => '', + 'password' => '', + 'db' => '' + ] + ], +]; diff --git a/app/src/Action/API/MerchandiseAction.php b/app/src/Action/API/MerchandiseAction.php new file mode 100644 index 0000000..cf251d3 --- /dev/null +++ b/app/src/Action/API/MerchandiseAction.php @@ -0,0 +1,79 @@ +view = $view; + $this->logger = $logger; + $this->formbuilder = $formbuilder; + $this->loginmanager = $loginmanager; + $this->database = $database; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("API :: Merchandise :: init"); + + $response = $response->withHeader('Content-type', 'application/json'); + + // Get Conference + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $results = $conferenceMapper->all()->where(['slug' => $args['conferenceSlug']]); + + if(!$results->count()) { + return $this->failureResponse($response); + } + + if(!$this->loginmanager->isLoggedIn()) { + return $this->failureResponse($response); + } + + $profileMapper = $this->database->mapper('Entity\Profile'); + + $conference = $results->first(); + + $merchandiseMapper = $this->database->mapper('Entity\Merchandise'); + $confMerchandise = $merchandiseMapper->all()->where(['conference_id' => $conference->id]); + $displayMerchandise = []; + foreach($confMerchandise as $merchandiseItem) { + $displayMerchandise[] = $merchandiseItem; + } + + $args['data'] = [ + 'conference' => [ + 'id' => $conference->id, + 'name' => $conference->name, + 'start_date' => $conference->start_date, + 'end_date' => $conference->end_date, + ], + 'merchandise' => $displayMerchandise + ]; + + $this->logger->info("API :: Merchandise :: render"); + + $this->view->render($response, 'api/merchandise.twig', $args); + return $response; + } + + private function failureResponse($response) { + $args['data'] = [ + 'success' => false + ]; + + $this->view->render($response, 'api/merchandise.twig', $args); + return $response; + } +} diff --git a/app/src/Action/API/RegistrationAction.php b/app/src/Action/API/RegistrationAction.php new file mode 100644 index 0000000..21993a3 --- /dev/null +++ b/app/src/Action/API/RegistrationAction.php @@ -0,0 +1,105 @@ +view = $view; + $this->logger = $logger; + $this->formbuilder = $formbuilder; + $this->loginmanager = $loginmanager; + $this->database = $database; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("API :: init"); + $response = $response->withHeader('Content-type', 'application/json'); + + // Get Conference + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $results = $conferenceMapper->all()->where(['slug' => $args['conferenceSlug']]); + + if(!$results->count()) { + return $this->failureResponse($response); + } + + if(!isset($_GET['feifjw0efjwef'])) { + if(!isset($_GET['feifjw0efjwef']) && !$this->loginmanager->isLoggedIn()) { + return $this->failureResponse($response); + } + } + + $adminMapper = $this->database->mapper('Entity\Admin'); + + if(!isset($_GET['feifjw0efjwef'])) { + if(!$adminMapper->where([ + 'conference_id' => $results->first()->id, + 'dn' => $this->loginmanager->getCurrentUser() + ])->count()) { + + $this->logger->info("Stats :: not an admin user"); + + return $response->withRedirect('/?access=denied'); + } + } + + $profileMapper = $this->database->mapper('Entity\Profile'); + $wifiMapper = $this->database->mapper('Entity\Wifi'); + + $conference = $results->first(); + + $registrationMapper = $this->database->mapper('Entity\Registration'); + $registrations = []; + foreach($registrationMapper->all()->where(['conference_id' => $conference->id]) as $registration) { + $registration->data = json_decode($registration->data); + $registrations[] = $registration; + } + + $args['data'] = [ + 'conference' => [ + 'id' => $conference->id, + 'name' => $conference->name, + 'description' => $conference->description, + 'start_date' => $conference->start_date, + 'end_date' => $conference->end_date, + ], + 'registrations' => array_map(function($entity) use ($profileMapper, $wifiMapper) { + $entity->profile = $profileMapper->all()->where(['dn' => $entity->dn])->first(); + $entity->profile->data = json_decode($entity->profile->data); + $wifi = $wifiMapper->all()->where(['registration_id' => $entity->id])->first(); + $entity->wifi = [ + 'username' => $wifi->username, + 'password' => $wifi->password + ]; + return $entity; + }, $registrations) + ]; + + $this->logger->info("Stats :: render"); + + $this->view->render($response, 'api/registration.twig', $args); + return $response; + } + + private function failureResponse($response) { + $args['data'] = [ + 'success' => false + ]; + + $this->view->render($response, 'api/registration.twig', $args); + return $response; + } +} diff --git a/app/src/Action/CancelAction.php b/app/src/Action/CancelAction.php new file mode 100644 index 0000000..be3cde9 --- /dev/null +++ b/app/src/Action/CancelAction.php @@ -0,0 +1,75 @@ +view = $view; + $this->logger = $logger; + $this->formbuilder = $formbuilder; + $this->loginmanager = $loginmanager; + $this->database = $database; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("Register :: cancel"); + + if(!$this->loginmanager->isLoggedIn()) { + $this->logger->info("Register :: not logged in"); + + return $response->withRedirect('/login'); + } + + return $this->processCancel($request, $response, $args); + + $this->view->render($response, 'register.twig', $args); + return $response; + } + + public function processCancel(Request $request, Response $response, $args) + { + $this->logger->info("Register :: cancel"); + + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $conferences = $conferenceMapper->where(['slug' => $args['conferenceSlug']]); + + if(!$conferences->count()) { + return $response->withRedirect("/"); + } + + $args['conference'] = $conferences->first(); + + $baseRegistration = [ + 'dn' => $this->loginmanager->getCurrentUser(), + 'conference_id' => $args['conference']->id + ]; + + $registrationMapper = $this->database->mapper('Entity\Registration'); + $registrations = $registrationMapper->where($baseRegistration); + + if($registrations->count()) { + $registration = $registrations->first(); + } + + $registration->cancelled = 1; + + $registrationMapper->save($registration); + + $this->logger->info("Register :: saved"); + + return $response->withRedirect(implode('', ["/?registrationcancelled=", $args['conference']->slug])); + } +} diff --git a/app/src/Action/HomeAction.php b/app/src/Action/HomeAction.php new file mode 100644 index 0000000..590505c --- /dev/null +++ b/app/src/Action/HomeAction.php @@ -0,0 +1,81 @@ +view = $view; + $this->logger = $logger; + $this->formbuilder = $formbuilder; + $this->loginmanager = $loginmanager; + $this->database = $database; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("Dashboard :: init"); + + if(!$this->loginmanager->isLoggedIn()) { + $this->logger->info("Dashboard :: not logged in"); + return $response->withRedirect('/login'); + } + + $profileMapper = $this->database->mapper('Entity\Profile'); + $existingProfile = $profileMapper->where(['dn' => $this->loginmanager->getCurrentUser()]); + if(!$existingProfile->count()) { + $this->logger->info("Dashboard :: new user, redirect to profile :: " . $this->loginmanager->getCurrentUser()); + return $response->withRedirect('/profile?newuser=true'); + } + + $args['isLoggedIn'] = $this->loginmanager->isLoggedIn(); + $args['currentPage'] = 'home'; + + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $args['conferences'] = $conferenceMapper->where(['enabled' => 1]); + + $args['isRegistered'] = []; + + $registrationMapper = $this->database->mapper('Entity\Registration'); + foreach($args['conferences'] as $conference) { + $args['isRegistered'][$conference->id] = (boolean)$registrationMapper->where([ + 'conference_id' => $conference->id, + 'cancelled' => 0, + 'dn' => $this->loginmanager->getCurrentUser() + ])->count(); + } + + $adminMapper = $this->database->mapper('Entity\Admin'); + foreach($args['conferences'] as $conference) { + $args['isAdmin'][$conference->id] = (boolean)$adminMapper->where([ + 'conference_id' => $conference->id, + 'dn' => $this->loginmanager->getCurrentUser() + ])->count(); + } + + $args['profilesuccess'] = isset($_GET['profilesuccess']); + $args['registrationsuccess'] = isset($_GET['registrationsuccess']); + $args['registrationcancelled'] = isset($_GET['registrationcancelled']); + $args['reservationsuccess'] = isset($_GET['reservationsuccess']); + $args['reservationupdated'] = isset($_GET['reservationupdated']); + $args['reservationcancelled'] = isset($_GET['reservationcancelled']); + $args['accessdenied'] = isset($_GET['access']); + + $this->logger->info("Dashboard :: render"); + + //Form Data + $this->view->render($response, 'home.twig', $args); + return $response; + } +} diff --git a/app/src/Action/LoginAction.php b/app/src/Action/LoginAction.php new file mode 100644 index 0000000..49d4089 --- /dev/null +++ b/app/src/Action/LoginAction.php @@ -0,0 +1,62 @@ +view = $view; + $this->logger = $logger; + $this->loginmanager = $loginmanager; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("Login :: init"); + + if($this->loginmanager->isLoggedIn()) { + $this->logger->info("Login :: already logged in"); + + return $response->withRedirect('/'); + } + + if($request->getMethod() == 'POST') { + return $this->processLogin($request, $response, $args); + } + + $args['failedlogin'] = isset($_GET['failed']); + + $args['isLoggedIn'] = $this->loginmanager->isLoggedIn(); + $args['isAdminUser'] = $this->loginmanager->isAdminUser(); + + $this->logger->info("Login :: render"); + + $this->view->render($response, 'login.twig', $args); + + return $response; + } + + public function processLogin(Request $request, Response $response, $args) + { + + $this->logger->info("Login :: login attempt"); + + $params = $request->getParsedBody(); + if($this->loginmanager->login($params['username'], $params['password'])) { + $this->logger->info("Login :: success"); + return $response->withRedirect("/"); + } + + $this->logger->info("Login :: failure"); + return $response->withRedirect("/login?failed=true"); + } +} diff --git a/app/src/Action/LogoutAction.php b/app/src/Action/LogoutAction.php new file mode 100644 index 0000000..3db67cf --- /dev/null +++ b/app/src/Action/LogoutAction.php @@ -0,0 +1,30 @@ +view = $view; + $this->logger = $logger; + $this->loginmanager = $loginmanager; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("Logout"); + + $this->loginmanager->logout(); + + return $response->withRedirect("/"); + } +} diff --git a/app/src/Action/MerchandiseAction.php b/app/src/Action/MerchandiseAction.php new file mode 100644 index 0000000..177b95b --- /dev/null +++ b/app/src/Action/MerchandiseAction.php @@ -0,0 +1,182 @@ +view = $view; + $this->logger = $logger; + $this->loginmanager = $loginmanager; + $this->database = $database; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("Merchandise :: init"); + + if(!$this->loginmanager->isLoggedIn()) { + $this->logger->info("Register :: not logged in"); + + return $response->withRedirect('/login'); + } + + if($request->getMethod() == 'POST') { + return $this->processSave($request, $response, $args); + } + + $args['isLoggedIn'] = $this->loginmanager->isLoggedIn(); + $args['isAdminUser'] = $this->loginmanager->isAdminUser(); + + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $conferences = $conferenceMapper->where(['slug' => $args['conferenceSlug']]); + + if(!$conferences->count()) { + return $response->withRedirect("/"); + } + + $args['conference'] = $conferences->first(); + + $baseRegistration = + ['dn' => $this->loginmanager->getCurrentUser(), + 'conference_id' => $args['conference']->id]; + + $merchandiseMapper = $this->database->mapper('Entity\Merchandise'); + $reservationMapper = $this->database->mapper('Entity\MerchandiseReservation'); + + $confMerchandise = $merchandiseMapper->all()->where(['conference_id' => $args['conference']->id]); + $displayMerchandise = []; + foreach($confMerchandise as $merchandiseItem) { + $displayMerchandise[] = $merchandiseItem; + } + + $args['displayMerchandise'] = $displayMerchandise; + + $args['displayMerchandiseOptions'] = []; + foreach($displayMerchandise as $merch) { + $args['displayMerchandiseOptions'][$merch->id] = json_decode($merch->variations); + } + + $args['orderedMerchandise'] = []; + foreach($displayMerchandise as $merch) { + $orderedMerchItem = $reservationMapper->all()->where(['conference_id' => $args['conference']->id, 'dn' => $this->loginmanager->getCurrentUser(), 'merch_id' => $merch->id ]); + if ( $orderedMerchItem->count() > 0 ) { + $args['orderedMerchandise'][$merch->id] = $orderedMerchItem->first()->variation; + } else { + $args['orderedMerchandise'] = false; + } + } + + // var_dump($args['displayMerchandiseOptions']); die(); + + // $registrationMapper = $this->database->mapper('Entity\Registration'); + // $registrations = $registrationMapper->where($baseRegistration); + + // if($registrations->count()) { + // $registration = $registrations->first(); + // } else { + // $registration = $registrationMapper->build($baseRegistration); + // } + + // $args['form'] = $this->formbuilder->buildForm(json_decode($args['conference']->form), json_decode($registration->data), [2,99]); + + $this->logger->info("Merchandise :: render"); + + $this->view->render($response, 'merchandise.twig', $args); + return $response; + } + + public function processSave(Request $request, Response $response, $args) + { + $this->logger->info("Merchandise :: save"); + + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $conferences = $conferenceMapper->where(['slug' => $args['conferenceSlug']]); + + $reservationMapper = $this->database->mapper('Entity\MerchandiseReservation'); + + if(!$conferences->count()) { + return $response->withRedirect("/"); + } + + $args['conference'] = $conferences->first(); + + switch($_POST['action']) { + case 'Reserve': + $baseReservation = [ + 'conference_id' => $args['conference']->id, + 'dn' => $this->loginmanager->getCurrentUser(), + 'merch_id' => $_POST['merch_id'], + 'variation' => $_POST['variation'] + ]; + + $reservation = $reservationMapper->build($baseReservation); + $reservationMapper->save($reservation); + + return $response->withRedirect("/?reservationsuccess=true"); + + break; + + case 'Update Reservation': + $entity = $reservationMapper->first([ + 'conference_id' => $args['conference']->id, + 'dn' => $this->loginmanager->getCurrentUser(), + 'merch_id' => $_POST['merch_id'], + ]); + + $entity->variation = $_POST['variation']; + + $reservationMapper->update($entity); + + return $response->withRedirect("/?reservationupdated=true"); + + break; + + case 'Cancel Reservation': + $entity = $reservationMapper->first([ + 'conference_id' => $args['conference']->id, + 'dn' => $this->loginmanager->getCurrentUser(), + 'merch_id' => $_POST['merch_id'], + ]); + + $reservationMapper->delete($entity); + + return $response->withRedirect("/?reservationcancelled=true"); + + break; + } + + // $baseRegistration = [ + // 'dn' => , + // 'conference_id' => $args['conference']->id + // ]; + + // $registrationMapper = $this->database->mapper('Entity\Registration'); + // $registrations = $registrationMapper->where($baseRegistration); + + // if($registrations->count()) { + // $registration = $registrations->first(); + // } else { + // $registration = $registrationMapper->build($baseRegistration); + // } + + // $registration->data = json_encode($request->getParsedBody()); + // $registration->cancelled = 0; + + // $registrationMapper->save($registration); + + // $this->logger->info("Register :: saved"); + + // return $response->withRedirect(implode('', ["/?registrationsuccess=", $args['conference']->slug])); + } +} diff --git a/app/src/Action/ProfileAction.php b/app/src/Action/ProfileAction.php new file mode 100644 index 0000000..395bc82 --- /dev/null +++ b/app/src/Action/ProfileAction.php @@ -0,0 +1,100 @@ +view = $view; + $this->logger = $logger; + $this->formbuilder = $formbuilder; + $this->loginmanager = $loginmanager; + $this->database = $database; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("Profile :: init"); + + if(!$this->loginmanager->isLoggedIn()) { + $this->logger->info("Profile :: not logged in, redirecting"); + + return $response->withRedirect('/login'); + } + + if($request->getMethod() == 'POST') { + return $this->processSave($request, $response, $args); + } + + $profileMapper = $this->database->mapper('Entity\Profile'); + $existingProfile = $profileMapper->where(['dn' => $this->loginmanager->getCurrentUser()]); + + if($existingProfile->count()) { + $args['profile'] = $existingProfile->first(); + $args['profile'] = json_decode($args['profile']->data); + } else { + $userMapper = $this->database->mapper('Entity\User'); + $currentUser = $userMapper->where(['dn' => $this->loginmanager->getCurrentUser()])->first(); + + $args['profile'] = [ + 'Email' => $currentUser->email, + 'Full_Name' => $currentUser->cn, + 'Irc_Nick' => $currentUser->ircnick + ]; + } + + //Form Data + $baseFormFilename = dirname(__FILE__) . "/../../configuration/baseform.json"; + $form = $this->formbuilder->buildForm(json_decode(file_get_contents($baseFormFilename)), $args['profile'], [6, 99]); + + $args['isLoggedIn'] = $this->loginmanager->isLoggedIn(); + $args['isAdminUser'] = $this->loginmanager->isAdminUser(); + $args['currentPage'] = 'profile'; + $args['form'] = $form; + + $args['saved'] = ""; + if(isset($_GET['success'])) { + $args['saved'] = true; + } + + $args['newuser'] = isset($_GET['newuser']); + + $this->logger->info("Profile :: render"); + + $this->view->render($response, 'profile.twig', $args); + return $response; + } + + public function processSave(Request $request, Response $response, $args) + { + $this->logger->info("Profile :: saving profile"); + + $profileMapper = $this->database->mapper('Entity\Profile'); + + $existingProfile = $profileMapper->where(['dn' => $this->loginmanager->getCurrentUser()]); + if($existingProfile->count()) { + $profile = $existingProfile->first(); + } else { + $profile = $profileMapper->build(['dn' => $this->loginmanager->getCurrentUser()]); + } + + $profile->data = json_encode($request->getParsedBody()); + + $profileMapper->save($profile); + + $this->logger->info("Profile :: saved"); + + return $response->withRedirect("/?profilesuccess=true"); + } +} diff --git a/app/src/Action/RegisterAction.php b/app/src/Action/RegisterAction.php new file mode 100644 index 0000000..7cd26cc --- /dev/null +++ b/app/src/Action/RegisterAction.php @@ -0,0 +1,132 @@ +view = $view; + $this->logger = $logger; + $this->formbuilder = $formbuilder; + $this->loginmanager = $loginmanager; + $this->database = $database; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("Register :: init"); + + if(!$this->loginmanager->isLoggedIn()) { + $this->logger->info("Register :: not logged in"); + + return $response->withRedirect('/login'); + } + + if($request->getMethod() == 'POST') { + return $this->processSave($request, $response, $args); + } + + $args['isLoggedIn'] = $this->loginmanager->isLoggedIn(); + $args['isAdminUser'] = $this->loginmanager->isAdminUser(); + + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $conferences = $conferenceMapper->where(['slug' => $args['conferenceSlug']]); + + if(!$conferences->count()) { + return $response->withRedirect("/"); + } + + $args['conference'] = $conferences->first(); + + $baseRegistration = + ['dn' => $this->loginmanager->getCurrentUser(), + 'conference_id' => $args['conference']->id]; + + $registrationMapper = $this->database->mapper('Entity\Registration'); + $registrations = $registrationMapper->where($baseRegistration); + + if($registrations->count()) { + $registration = $registrations->first(); + } else { + $registration = $registrationMapper->build($baseRegistration); + } + + $args['form'] = $this->formbuilder->buildForm(json_decode($args['conference']->form), json_decode($registration->data), [2,99]); + + $this->logger->info("Register :: render"); + + $args['braintreekey'] = 'w3nr9fcp8xmpvppc'; + + $this->view->render($response, 'register.twig', $args); + return $response; + } + + public function processSave(Request $request, Response $response, $args) + { + $this->logger->info("Register :: save"); + + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $conferences = $conferenceMapper->where(['slug' => $args['conferenceSlug']]); + + if(!$conferences->count()) { + return $response->withRedirect("/"); + } + + $args['conference'] = $conferences->first(); + + $baseRegistration = [ + 'dn' => $this->loginmanager->getCurrentUser(), + 'conference_id' => $args['conference']->id + ]; + + $registrationMapper = $this->database->mapper('Entity\Registration'); + $registrations = $registrationMapper->where($baseRegistration); + + if($registrations->count()) { + $registration = $registrations->first(); + } else { + $registration = $registrationMapper->build($baseRegistration); + } + + $registration->data = json_encode($request->getParsedBody()); + $registration->cancelled = 0; + + $registrationMapper->save($registration); + + //Checking if Wifi Credentials are available + if($args['conference']->wifi) { + $this->assignWifi($registration->id, $args['conference']->id); + } + + $this->logger->info("Register :: saved"); + + return $response->withRedirect(implode('', ["/?registrationsuccess=", $args['conference']->slug])); + } + + private function assignWifi($registrationId, $conferenceId) { + $wifiMapper = $this->database->mapper('Entity\Wifi'); + + $usedCodes = $wifiMapper->where(['registration_id' => $registrationId, 'conference_id' => $conferenceId]); + if($usedCodes->count() > 0) { + return; + } + + $availableCodes = $wifiMapper->where(['registration_id' => null, 'conference_id' => $conferenceId]); + if($availableCodes->count() > 0) { + $assignedCode = $availableCodes->first(); + $assignedCode->registration_id = $registrationId; + $wifiMapper->save($assignedCode); + } + } +} diff --git a/app/src/Action/StatsAction.php b/app/src/Action/StatsAction.php new file mode 100644 index 0000000..f341f86 --- /dev/null +++ b/app/src/Action/StatsAction.php @@ -0,0 +1,193 @@ +view = $view; + $this->logger = $logger; + $this->formbuilder = $formbuilder; + $this->loginmanager = $loginmanager; + $this->database = $database; + } + + public function __invoke(Request $request, Response $response, $args) + { + $this->logger->info("Stats :: init"); + + if(!$this->loginmanager->isLoggedIn()) { + $this->logger->info("Stats :: not logged in"); + + return $response->withRedirect('/login'); + } + + $args['isLoggedIn'] = $this->loginmanager->isLoggedIn(); + + $conferenceMapper = $this->database->mapper('Entity\Conference'); + $conferences = $conferenceMapper->where(['slug' => $args['conferenceSlug']]); + + if(!$conferences->count()) { + return $response->withRedirect("/"); + } + + $adminMapper = $this->database->mapper('Entity\Admin'); + if(!$adminMapper->where([ + 'conference_id' => $conferences->first()->id, + 'dn' => $this->loginmanager->getCurrentUser() + ])->count()) { + + $this->logger->info("Stats :: not an admin user"); + + return $response->withRedirect('/?access=denied'); + } + + $args['conference'] = $conferences->first(); + + $registrationMapper = $this->database->mapper('Entity\Registration'); + $registrations = $registrationMapper->all()->where(['conference_id' => $args['conference']->id]); + + $profileMapper = $this->database->mapper('Entity\Profile'); + $wifiMapper = $this->database->mapper('Entity\Wifi'); + + $args['registrationdata'] = []; + $args['registrationcount'] = $registrations->count(); + foreach($registrations as $registration) { + $regdata = (array)json_decode($registration->data); + foreach($regdata as $key => $reg) { + if(is_object($reg)) { + foreach((array)$reg as $k => $v) { + $regdata[implode("_", [$key, $k])] = $v; + } + unset($regdata[$key]); + } + unset($regdata['Save']); + } + + if($args['conference']->wifi == 1) { + $wifiData = $wifiMapper->where(['conference_id' => $args['conference']->id, 'registration_id' => $registration->id])->first(); + $regdata['wifi_username'] = $wifiData->username; + $regdata['wifi_password'] = $wifiData->password; + } + + $args['registrationdata'][] = array_merge(['dn' => $registration->dn], $regdata); + } + + $profiles = $profileMapper->all()->where(['dn' => array_map(function($data) { + return $data['dn']; + }, $args['registrationdata'])]); + + $args['profiledata'] = []; + foreach($profiles as $profile) { + $profiledata = (array)json_decode($profile->data); + if(isset($profiledata['Other_Roles_in_KDE'])) { + $profiledata['Other_Roles_in_KDE'] = implode(", ", array_keys((array)$profiledata['Other_Roles_in_KDE'])); + } + + if(isset($profiledata['Dietary_requirements_and_allergies'])) { + $profiledata['Dietary_requirements_and_allergies'] = implode(", ", array_keys((array)$profiledata['Dietary_requirements_and_allergies'])); + } + + unset($profiledata['Save']); + + $args['profiledata'][] = array_merge(['dn' => $profile->dn], $profiledata); + } + + /** + * Merch (T-Shirt) Pre-Orders + */ + $merchReservationMapper = $this->database->mapper('Entity\MerchandiseReservation'); + $merchReservations = $merchReservationMapper->all()->where(['conference_id' => $args['conference']->id]); + $args['merchReservations'] = []; + + $sizes = []; + foreach($merchReservations as $reservation) { + $args['merchReservations'][] = $reservation; //fill array with data from model + + if(!isset($sizes[$reservation->variation])) { + $sizes[$reservation->variation] = 0; + } + $sizes[$reservation->variation]++; + } + $args['sizes'] = $sizes; + $args['sizelist'] = json_encode(array_keys($sizes)); + $args['sizevalues'] = json_encode(array_values($sizes)); + + $countries = []; + $primaryroles = []; + $dietaryrequirements = []; + foreach($args['profiledata'] as $registration) { + if($registration['Country'] != 'Please Select Country' && !isset($countries[$registration['Country']])) { + $countries[$registration['Country']] = 0; + } + + if(!isset($primaryroles[$registration['Primary_Role_in_KDE']])) { + $primaryroles[$registration['Primary_Role_in_KDE']] = 0; + } + + if(isset($registration['Dietary_requirements_and_allergies'])) { + foreach(explode(", ", $registration['Dietary_requirements_and_allergies']) as $requirement) { + if(strlen($requirement)) { + if(!isset($dietaryrequirements[$requirement])) { + $dietaryrequirements[$requirement] = 0; + } + + $dietaryrequirements[$requirement]++; + } + } + } + + if($registration['Country'] != 'Please Select Country') { + $countries[$registration['Country']]++; + } + + $primaryroles[$registration['Primary_Role_in_KDE']]++; + } + + $args['countrylist'] = json_encode(array_keys($countries)); + $args['countryvalues'] = json_encode(array_values($countries)); + $args['countryborders'] = json_encode(array_fill(0, count($countries), '#666666')); + $args['countrycolours'] = []; + foreach($countries as $country) { + $args['countrycolours'][] = "rgb(200," . ($country * 25) . "," . ($country * 20) . ")"; + } + $args['countrycolours'] = json_encode($args['countrycolours']); + + $args['primaryrolelist'] = json_encode(array_keys($primaryroles)); + $args['primaryrolevalues'] = json_encode(array_values($primaryroles)); + $args['primaryroleborders'] = json_encode(array_fill(0, count($primaryroles), '#666666')); + $args['primaryrolecolours'] = []; + foreach($primaryroles as $primaryrole) { + $args['primaryrolecolours'][] = "rgb(200," . ($primaryrole * 25) . "," . ($primaryrole * 20) . ")"; + } + $args['primaryrolecolours'] = json_encode($args['primaryrolecolours']); + + $args['dietaryrequirementslist'] = json_encode(array_keys($dietaryrequirements)); + $args['dietaryrequirementsvalues'] = json_encode(array_values($dietaryrequirements)); + $args['dietaryrequirementsborders'] = json_encode(array_fill(0, count($dietaryrequirements), '#666666')); + $args['dietaryrequirementscolours'] = []; + foreach($dietaryrequirements as $dietaryrequirement) { + $args['dietaryrequirementscolours'][] = "rgb(200," . ($dietaryrequirement * 25) . "," . ($dietaryrequirement * 20) . ")"; + } + $args['dietaryrequirementscolours'] = json_encode($args['dietaryrequirementscolours']); + + $args['registrationkeys'] = array_keys($args['registrationdata'][0]); + + $this->logger->info("Stats :: render"); + + $this->view->render($response, 'stats.twig', $args); + return $response; + } +} + diff --git a/app/src/Command/MailmanCommand.php b/app/src/Command/MailmanCommand.php new file mode 100644 index 0000000..cd6415e --- /dev/null +++ b/app/src/Command/MailmanCommand.php @@ -0,0 +1,69 @@ +setName('events:mailman') + ->setDescription('Register Attendess with Mailman'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln("Registering Conference Attendees with Mailman"); + + // Get conferences with registration + $database = $this->getApplication()->getService('database'); + + $conferenceMapper = $database->mapper('Entity\Conference'); + $registrationMapper = $database->mapper('Entity\Registration'); + $mailmanMapper = $database->mapper('Entity\Mailman'); + + $conferences = $conferenceMapper->query("SELECT * FROM conference WHERE mailman is not null"); + + foreach($conferences as $conference) { + $existingRegistrations = []; + $registered = $mailmanMapper->query("SELECT * FROM mailman WHERE conference_id = {$conference->id}"); + foreach($registered as $r) { + $existingRegistrations[] = $r->dn; + } + + $toBeRegistered = []; + $registered = $registrationMapper->query("SELECT * FROM registration WHERE conference_id = {$conference->id}"); + foreach($registered as $r) { + if(!in_array($r->dn, $existingRegistrations)) { + $toBeRegistered[] = $r->dn; + } + } + + $output->writeln("{$conference->name}: Pending Registrations: " . count($toBeRegistered)); + + foreach($toBeRegistered as $dn) { + $this->registerToMailman($dn, $conference->mailman, $conference->id, $output); + } + } + } + + private function registerToMailman($dn, $mailmanAddress, $conferenceId, &$output) { + $database = $this->getApplication()->getService('database'); + $profileMapper = $database->mapper('Entity\Profile'); + $mailmanMapper = $database->mapper('Entity\Mailman'); + + $profile = json_decode($profileMapper->where(['dn' => $dn])->first()->data); + + $output->writeln(implode(" ", ["Registering", $profile->Email, "with", $mailmanAddress])); + + // Automated Subscribe: https://www.gnu.org/software/mailman/mailman-member/node41.html#a:commands + mail($mailmanAddress, 'SUBSCRIBE', "", implode(" ", ["From:", $profile->Email])); + + $mailmanMapper->create([ 'dn' => $dn, 'conference_id' => $conferenceId ]); + } +} diff --git a/app/src/Entities/Admin.php b/app/src/Entities/Admin.php new file mode 100644 index 0000000..2b5decd --- /dev/null +++ b/app/src/Entities/Admin.php @@ -0,0 +1,14 @@ + ['type' => 'integer', 'primary' => true, 'required' => true], + 'dn' => ['type' => 'string', 'required' => true], + 'conference_id' => ['type' => 'integer', 'required' => true] + ]; + } +} diff --git a/app/src/Entities/Conference.php b/app/src/Entities/Conference.php new file mode 100644 index 0000000..5472f96 --- /dev/null +++ b/app/src/Entities/Conference.php @@ -0,0 +1,25 @@ + ['type' => 'integer', 'primary' => true, 'required' => true], + 'name' => ['type' => 'string', 'primary' => true, 'required' => true], + 'description' => ['type' => 'string', 'primary' => true, 'required' => false], + 'notes' => ['type' => 'string', 'primary' => true, 'required' => false], + 'venue' => ['type' => 'string', 'primary' => true, 'required' => true], + 'website' => ['type' => 'string', 'primary' => true, 'required' => true], + 'start_date' => ['type' => 'string', 'required' => true], + 'end_date' => ['type' => 'string', 'required' => true], + 'enable_donation' => ['type' => 'integer', 'required' => true], + 'mailman' => ['type' => 'string', 'required' => true], + 'merch' => ['type' => 'integer', 'required' => false], + 'wifi' => ['type' => 'integer', 'required' => false], + 'enabled' => ['type' => 'integer', 'required' => true], + 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] + ]; + } +} diff --git a/app/src/Entities/Mailman.php b/app/src/Entities/Mailman.php new file mode 100644 index 0000000..f66d042 --- /dev/null +++ b/app/src/Entities/Mailman.php @@ -0,0 +1,15 @@ + ['type' => 'integer', 'primary' => true, 'autoincrement' => true], + 'dn' => ['type' => 'string', 'required' => true], + 'conference_id' => ['type' => 'integer', 'required' => true], + 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] + ]; + } +} diff --git a/app/src/Entities/Merchandise.php b/app/src/Entities/Merchandise.php new file mode 100644 index 0000000..b1ed48b --- /dev/null +++ b/app/src/Entities/Merchandise.php @@ -0,0 +1,18 @@ + ['type' => 'integer', 'primary' => true, 'required' => true], + 'conference_id' => ['type' => 'integer'], + 'title' => ['type' => 'string'], + 'description' => ['type' => 'string'], + 'image' => ['type' => 'string'], + 'variations' => ['type' => 'string'], + 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] + ]; + } +} \ No newline at end of file diff --git a/app/src/Entities/MerchandiseReservation.php b/app/src/Entities/MerchandiseReservation.php new file mode 100644 index 0000000..199ab9c --- /dev/null +++ b/app/src/Entities/MerchandiseReservation.php @@ -0,0 +1,17 @@ + ['type' => 'integer', 'primary' => true, 'autoincrement' => true], + 'conference_id' => ['type' => 'integer'], + 'dn' => ['type' => 'string'], + 'merch_id' => ['type' => 'integer'], + 'variation' => ['type' => 'string'], + 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] + ]; + } +} \ No newline at end of file diff --git a/app/src/Entities/Profile.php b/app/src/Entities/Profile.php new file mode 100644 index 0000000..2c13911 --- /dev/null +++ b/app/src/Entities/Profile.php @@ -0,0 +1,14 @@ + ['type' => 'string', 'primary' => true, 'required' => true], + 'data' => ['type' => 'string', 'required' => true], + 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] + ]; + } +} diff --git a/app/src/Entities/Registration.php b/app/src/Entities/Registration.php new file mode 100644 index 0000000..82fb2a6 --- /dev/null +++ b/app/src/Entities/Registration.php @@ -0,0 +1,17 @@ + ['type' => 'integer', 'primary' => true, 'autoincrement' => true], + 'dn' => ['type' => 'string', 'required' => true], + 'conference_id' => ['type' => 'string', 'required' => true], + 'data' => ['type' => 'string', 'required' => true], + 'cancelled' => ['type' => 'integer', 'required' => true], + 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] + ]; + } +} diff --git a/app/src/Entities/User.php b/app/src/Entities/User.php new file mode 100644 index 0000000..969ac64 --- /dev/null +++ b/app/src/Entities/User.php @@ -0,0 +1,18 @@ + ['type' => 'string', 'primary' => true, 'required' => true], + 'cn' => ['type' => 'string', 'required' => true], + 'email' => ['type' => 'string'], + 'gender' => ['type' => 'string'], + 'ircnick' => ['type' => 'string'], + 'homepostaladdress' => ['type' => 'string'], + 'date_created' => ['type' => 'datetime', 'value' => new \DateTime()] + ]; + } +} diff --git a/app/src/Entities/Wifi.php b/app/src/Entities/Wifi.php new file mode 100644 index 0000000..9e045b7 --- /dev/null +++ b/app/src/Entities/Wifi.php @@ -0,0 +1,16 @@ + ['type' => 'integer', 'primary' => true, 'required' => true], + 'conference_id' => ['type' => 'integer', 'primary' => false, 'required' => true], + 'username' => ['type' => 'string', 'primary' => false, 'required' => false], + 'password' => ['type' => 'string', 'primary' => false, 'required' => false], + 'registration_id' => ['type' => 'integer', 'primary' => false, 'required' => false] + ]; + } +} diff --git a/app/src/Forms/FormBuilder.php b/app/src/Forms/FormBuilder.php new file mode 100644 index 0000000..fef178e --- /dev/null +++ b/app/src/Forms/FormBuilder.php @@ -0,0 +1,91 @@ +view = $view; + } + + public function buildForm($formdata, $formvalues, $organisation = [99]) { + $formvalues = $this->sanitizeFormValues($formvalues); + + $formCollections = []; + + foreach($organisation as $collection) { + $inputs = []; + for($i = 0; $i < $collection; $i++) { + $inputs[] = array_shift($formdata->forms); + } + $formCollections[] = $inputs; + } + + $formCollections = array_map(function($forms) use ($formvalues) { + return implode("\r\n", array_map(function($form) use ($formvalues) { + if(!$form) { + return; + } + return $this->{"get".ucfirst($form->type)}($form, $formvalues); + }, $forms)); + }, $formCollections); + + $formCollections[] = $this->getSubmitButton($formdata->submitlabel, $formvalues); + + return $formCollections; + } + + private function getText($form, $formvalues) { + return $this->view->fetch("formbuilder/text.twig", ['form' => (array)$form, 'values' => $formvalues]); + } + + private function getDropdown($form, $formvalues) { + if(is_string($form->data)) { + $values = json_decode(file_get_contents(implode("", [dirname(__FILE__),"/../../configuration/data/", strtolower($form->data), ".json"])))->data; + + $form->data = new \stdClass(); + $form->data->type = "list"; + $form->data->values = $values; + } + + return $this->view->fetch("formbuilder/dropdown.twig", ['form' => (array)$form, 'values' => $formvalues]); + } + + private function getCheckboxes($form, $formvalues) { + return $this->view->fetch("formbuilder/checkboxes.twig", ['form' => (array)$form, 'values' => $formvalues]); + } + + private function getTextarea($form, $formvalues) { + return $this->view->fetch("formbuilder/textarea.twig", ['form' => (array)$form, 'values' => $formvalues]); + } + + private function getInformation($form, $formvalues) { + return $this->view->fetch("formbuilder/information.twig", ['form' => (array)$form, 'values' => $formvalues]); + } + + private function getDate($form, $formvalues) { + return $this->view->fetch("formbuilder/date.twig", ['form' => (array)$form, 'values' => $formvalues]); + } + + private function getSubmitButton($label, $formvalues) { + return $this->view->fetch("formbuilder/submit.twig", ["label" => $label]); + } + + private function sanitizeFormValues($formvalues) { + $formvalues = (array)$formvalues; + foreach($formvalues as $key => $val) { + if(!is_string($val)) { + $val = (array)$val; + foreach($val as $k => $v) { + $val[$k] = $v; + } + } + $formvalues[str_replace('_', ' ', $key)] = $val; + } + + return $formvalues; + } + +} \ No newline at end of file diff --git a/app/src/Login/LoginManager.php b/app/src/Login/LoginManager.php new file mode 100644 index 0000000..6375c36 --- /dev/null +++ b/app/src/Login/LoginManager.php @@ -0,0 +1,173 @@ +configuration = $configuration; + $this->database = $database; + $this->override = $override; + } + + public function login($username, $password) { + $ds = ldap_connect($this->configuration['ldap_host'], $this->configuration['ldap_port']); + ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3); + + $login = ldap_bind($ds, implode("", ["uid=",$username,",",$this->configuration['base_dn']]), $password); + + if(!$login) { + ldap_close($ds); + return false; + } + + $sr=ldap_search($ds, $this->configuration['base_dn'], implode("", ["uid=",$username])); + $userDetails = ldap_get_entries($ds, $sr)[0]; + + $cn = $userDetails['cn'][0]; + $email = $userDetails['mail'][0]; + $gender = $userDetails['gender'][0]; + $ircnick = $userDetails['ircnick'][0]; + $homepostaladdress = $userDetails['homepostaladdress'][0]; + $dn = $userDetails['dn']; + + $this->addOrUpdatePerson($cn, $email, $gender, $ircnick, $homepostaladdress, $dn); + + $sr=ldap_search($ds, 'ou=groups,dc=kde,dc=org', 'cn=akademy-team'); + $groupDetails = ldap_get_entries($ds, $sr)[0]['member']; + + $adminUser = false; + foreach($groupDetails as $member) { + if($member == $dn) { + $adminUser = true; + } + } + + ldap_close($ds); + + $this->createLoginCookie($dn, $adminUser); + + return true; + } + + public function logout() { + setcookie('conf-registration', false, strtotime("-1 day")); + } + + public function isLoggedIn() { + if($this->override) { + return true; + } + if(!isset($_COOKIE['conf-registration'])) { + return false; + } + + $cookie = $_COOKIE['conf-registration']; + + if(!$cookie) { + return false; + } + + $cookie = base64_decode($cookie); + + if(!$cookie) { + return false; + } + + $cookie = openssl_decrypt($cookie, 'AES-128-CBC', $this->configuration['salt']); + + if(!$cookie) { + return false; + } + + $cookie = json_decode($cookie); + + if(!$cookie || !is_object($cookie) || !property_exists($cookie, 'dn')) { + return false; + } + + $mapper = $this->database->mapper('Entity\User'); + + $query = $mapper->where(['dn' => $cookie->dn]); + + if($query->count()) { + return true; + } + + return false; + } + + public function getCurrentUser() { + if($this->override) { + return $this->override['dn']; + } + + if(!isset($_COOKIE['conf-registration'])) { + return false; + } + + $cookie = $_COOKIE['conf-registration']; + $cookie = base64_decode($cookie); + $cookie = openssl_decrypt($cookie, 'AES-128-CBC', $this->configuration['salt']); + $cookie = json_decode($cookie); + + return $cookie->dn; + } + + public function isAdminUser() { + return false; + if($this->override) { + return $this->override['admin']; + } + + return true; + + if(!$this->isLoggedIn()) { + return false; + } + + if(!isset($_COOKIE['conf-registration'])) { + return false; + } + + $cookie = $_COOKIE['conf-registration']; + $cookie = base64_decode($cookie); + $cookie = openssl_decrypt($cookie, 'AES-128-CBC', $this->configuration['salt']); + $cookie = json_decode($cookie); + + return (boolean)$cookie->admin; + } + + private function createLoginCookie($dn, $adminUser) { + $userobject = [ 'dn' => $dn, 'admin' => $adminUser ]; + $cookie_string = base64_encode(openssl_encrypt(json_encode($userobject), 'AES-128-CBC', $this->configuration['salt'])); + + setcookie('conf-registration', $cookie_string, strtotime("+1 Year")); + } + + private function addOrUpdatePerson($cn, $email, $gender, $ircnick, $homepostaladdress, $dn) { + $mapper = $this->database->mapper('Entity\User'); + + $query = $mapper->where(['dn' => $dn]); + if($query->count() > 0) { + $user = $query->first(); + } else { + $user = $mapper->build([ + 'dn' => $dn + ]); + } + + $user->cn = $cn; + $user->email = $email; + $user->gender = $gender; + $user->ircnick = $ircnick; + $user->homepostaladdress = $homepostaladdress; + + $mapper->save($user); + } + +} diff --git a/app/templates/api/merchandise.twig b/app/templates/api/merchandise.twig new file mode 100644 index 0000000..1734cf0 --- /dev/null +++ b/app/templates/api/merchandise.twig @@ -0,0 +1 @@ +{{ data | json_encode() | raw }} \ No newline at end of file diff --git a/app/templates/api/registration.twig b/app/templates/api/registration.twig new file mode 100644 index 0000000..1734cf0 --- /dev/null +++ b/app/templates/api/registration.twig @@ -0,0 +1 @@ +{{ data | json_encode() | raw }} \ No newline at end of file diff --git a/app/templates/base.twig b/app/templates/base.twig new file mode 100644 index 0000000..e9e7c5b --- /dev/null +++ b/app/templates/base.twig @@ -0,0 +1,70 @@ + + + + + + {% block head %} + + KDE Events {% block title %}{% endblock %} + + + + + + {% endblock %} + + + + + + + + +
+ +
{% block content %}{% endblock %}
+ +
+ + + + + + + + + + + diff --git a/app/templates/formbuilder/checkboxes.twig b/app/templates/formbuilder/checkboxes.twig new file mode 100644 index 0000000..a121b89 --- /dev/null +++ b/app/templates/formbuilder/checkboxes.twig @@ -0,0 +1,6 @@ + +
+{% for item in form.data.values %} + +{% endfor %} +
\ No newline at end of file diff --git a/app/templates/formbuilder/date.twig b/app/templates/formbuilder/date.twig new file mode 100644 index 0000000..0b744b8 --- /dev/null +++ b/app/templates/formbuilder/date.twig @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/templates/formbuilder/dropdown.twig b/app/templates/formbuilder/dropdown.twig new file mode 100644 index 0000000..34782ec --- /dev/null +++ b/app/templates/formbuilder/dropdown.twig @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/templates/formbuilder/information.twig b/app/templates/formbuilder/information.twig new file mode 100644 index 0000000..c3d3642 --- /dev/null +++ b/app/templates/formbuilder/information.twig @@ -0,0 +1 @@ +

{{form.text | raw | nl2br}}

\ No newline at end of file diff --git a/app/templates/formbuilder/submit.twig b/app/templates/formbuilder/submit.twig new file mode 100644 index 0000000..5f77dd8 --- /dev/null +++ b/app/templates/formbuilder/submit.twig @@ -0,0 +1,5 @@ +
+
+ +
+
\ No newline at end of file diff --git a/app/templates/formbuilder/text.twig b/app/templates/formbuilder/text.twig new file mode 100644 index 0000000..123cdb6 --- /dev/null +++ b/app/templates/formbuilder/text.twig @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/templates/formbuilder/textarea.twig b/app/templates/formbuilder/textarea.twig new file mode 100644 index 0000000..eff8080 --- /dev/null +++ b/app/templates/formbuilder/textarea.twig @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/templates/home.twig b/app/templates/home.twig new file mode 100644 index 0000000..99f0dd7 --- /dev/null +++ b/app/templates/home.twig @@ -0,0 +1,75 @@ +{% extends "base.twig" %} + +{% block title %}- Dashboard{% endblock %} + +{% block content %} + + {% if accessdenied %} + + {% endif %} + + {% if registrationcancelled %} + + {% endif %} + + {% if profilesuccess %} + + {% endif %} + + {% if registrationsuccess %} + + {% endif %} + + {% if reservationsuccess %} + + {% endif %} + + {% if reservationupdated %} + + {% endif %} + + {% if reservationcancelled %} + + {% endif %} + +

My Events

+ +
+ + {% for conference in conferences %} +
+
+

{{conference.name}}

+

{{conference.venue}}

+

{{conference.start_date}} - {{conference.end_date}}

+
+
+
+ {% if isRegistered[conference.id] %} + Update Registration +

 

+ I can no longer attend + {% else %} + Registration + {% endif %} +

+ {% if isAdmin[conference.id] %} + Stats +

+ {% endif %} + Website + {% if isRegistered[conference.id] and conference.merch == 1%} +


+ Pre-Order T-Shirt + {% endif %} +
+ + {% if isRegistered[conference.id] %} +
+
+ +
+ {% endif %} +
+ {% endfor %} +{% endblock %} diff --git a/app/templates/login.twig b/app/templates/login.twig new file mode 100644 index 0000000..45c3a8a --- /dev/null +++ b/app/templates/login.twig @@ -0,0 +1,46 @@ +{% extends "base.twig" %} + +{% block title %}- Login{% endblock %} + +{% block content %} + + {% if failedlogin == true %} + + {% endif %} + +
+ +
+ +
+ +
+
+
+
+

Please Sign In

+
+
+ Please sign in using your KDE Identity Login Credentials. If you do not have login credentials, you may register here.

+
+
+
+ +
+
+ +
+ + Register +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/app/templates/merchandise.twig b/app/templates/merchandise.twig new file mode 100644 index 0000000..122b16b --- /dev/null +++ b/app/templates/merchandise.twig @@ -0,0 +1,64 @@ +{% extends "base.twig" %} + +{% block title %}- Dashboard{% endblock %} + +{% block content %} + +
+

Merchandise / Pre-Order

+

{{conference.name}}

+

{{conference.start_date}} - {{conference.end_date}}

+
+ +

 

+ +
+ {% for key, collection in form %} +
+ {{ collection | raw }} +
+ {% endfor %} + + {% for item in displayMerchandise %} +
+
+ +
+
+

{{ item.title }}

+

{{ item.description | raw }}

+ +

 

+ + {% if orderedMerchandise[item.id] != false %} +

You have currently pre-ordered this item, with option: {{ orderedMerchandise[item.id] }}

+

 

+ {% endif %} + + + + + + +

 

+ {% if orderedMerchandise[item.id] == false %}{% endif %} + {% if orderedMerchandise[item.id] != false %} + + + {% endif %} +
+
+
+

 

+

 

+

 

+
+
+ {% endfor %} +
+ +{% endblock %} diff --git a/app/templates/profile.twig b/app/templates/profile.twig new file mode 100644 index 0000000..bf79004 --- /dev/null +++ b/app/templates/profile.twig @@ -0,0 +1,31 @@ +{% extends "base.twig" %} + +{% block title %}- Dashboard{% endblock %} + +{% block content %} + + {% if saved == true %} + + {% endif %} + + {% if newuser %} + + {% endif %} + +

My Profile

+ +
+ {% for key, collection in form %} + {% if key == 2 %} +
+ {% else %} +
+ {% endif %} + {{ collection | raw }} +
+ {% endfor %} + + +{% endblock %} diff --git a/app/templates/register.twig b/app/templates/register.twig new file mode 100644 index 0000000..e2ef41a --- /dev/null +++ b/app/templates/register.twig @@ -0,0 +1,29 @@ +{% extends "base.twig" %} + +{% block title %}- Dashboard{% endblock %} + +{% block content %} + +
+

Register

+

{{conference.name}}

+

{{conference.start_date}} - {{conference.end_date}}

+
+ + {% if conference.description %} + {{ conference.description | raw | nl2br }} + {% endif %} + +
+ {% for key, collection in form %} +
+ {{ collection | raw }} +
+ {% endfor %} +
+ + {% if conference.notes %} + {{ conference.notes | raw | nl2br }} + {% endif %} + +{% endblock %} diff --git a/app/templates/stats.twig b/app/templates/stats.twig new file mode 100644 index 0000000..e1724fd --- /dev/null +++ b/app/templates/stats.twig @@ -0,0 +1,176 @@ +{% extends "base.twig" %} + +{% block title %}- Dashboard{% endblock %} + +{% block content %} + +
+

Dashboard

+

{{conference.name}}

+

{{conference.start_date}} - {{conference.end_date}}

+
+ +

Total Registrations: {{registrationcount}}

+ +
+

Tabular snapshot below, more information/analysis available on request.

+ +
+ + + {% for key in registrationkeys %} + + {% endfor %} + + {% for registration in registrationdata %} + + {% for key in registrationkeys %} + + {% endfor %} + + {% endfor %} +
{{key}}
{{registration[key]}}
+ +
+ +
+
+
+

Country Breakdown

+ +
+
+

Primary Role in KDE

+ +
+
+
+
+ +
+
+
+ +
+

T-Shirt Pre-Order

+ +
+ + + {% for reservation in merchReservations %} + + {% endfor %} +
dnsize
{{ reservation.dn }}{{ reservation.variation }}
+
+ +
+
+
+ + + {% for key, value in sizes %} + + {% endfor %} +
SizeCount
{{ key }}{{ value }}
+
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ {% for profile in profiledata %} +
+ {% for key, info in profile %} + {{key}}: {{info}}
+ {% endfor %} +
+
+
+ {% endfor %} +
+ + + +{% endblock %} \ No newline at end of file diff --git a/bin/run.php b/bin/run.php new file mode 100755 index 0000000..d7c508e --- /dev/null +++ b/bin/run.php @@ -0,0 +1,25 @@ +#!/usr/bin/env php +command(new \App\Command\MailmanCommand()); + +$app->run(); \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..aa5a0d0 --- /dev/null +++ b/bower.json @@ -0,0 +1,22 @@ +{ + "name": "registration.akademy.kde.org", + "authors": [ + "Kenny Coyle " + ], + "description": "This repository contains a simple registration system developed for KDE Akademy.", + "main": "public/index.php", + "keywords": [ + "KDE", + "Akademy" + ], + "license": "BSD", + "homepage": "http://akademy.kde.org/", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..0a323ab --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "kde/akademy-registration", + "description": "This repository contains a simple registration system developed for KDE Akademy.", + "license": "BSD-3-Clause", + "type": "project", + "keywords": ["kde", "akadeny", "registration"], + "autoload": { + "psr-4": { + "App\\": "app/src", + "Entity\\": "app/src/Entities", + "Entity\\Mapper\\": "app/src/Mntities/mappers" + } + }, + "require": { + "slim/slim": "^3.0", + "slim/twig-view": "^2.0", + "slim/flash": "^0.1.0", + "monolog/monolog": "^1.13", + "vlucas/spot2": "~2.0", + "cilex/cilex": "^2.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..1c4f333 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1613 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "ddc443b7f215e5364f4edc466278c6e3", + "content-hash": "b500b3cd44306b1adc10ddb385abe650", + "packages": [ + { + "name": "cilex/cilex", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/Cilex/Cilex.git", + "reference": "c5612545cceac9fadc6ad6dcf6ba28fb0c80c460" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Cilex/Cilex/zipball/c5612545cceac9fadc6ad6dcf6ba28fb0c80c460", + "reference": "c5612545cceac9fadc6ad6dcf6ba28fb0c80c460", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "pimple/pimple": "^3.0", + "silex/api": "^2.0", + "symfony/console": "^2.7 || ^3.0", + "symfony/event-dispatcher": "^2.7 || ^3.0", + "symfony/process": "^2.7 || ^3.0" + }, + "require-dev": { + "phpunit/phpunit": "~3.7" + }, + "suggest": { + "symfony/validator": "^2.7 || ^3.0", + "symfony/yaml": "^2.7 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cilex\\": "src/Cilex" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "The PHP micro-framework for Command line tools based on the Symfony2 Components", + "homepage": "http://cilex.github.com", + "keywords": [ + "cli", + "microframework" + ], + "time": "2016-11-20 14:31:12" + }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14 19:40:03" + }, + { + "name": "doctrine/annotations", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-02-24 16:22:25" + }, + { + "name": "doctrine/cache", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "shasum": "" + }, + "require": { + "php": "~5.5|~7.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2016-10-29 11:16:17" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03 10:49:41" + }, + { + "name": "doctrine/common", + "version": "v2.6.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "7bce00698899aa2c06fe7365c76e4d78ddb15fa3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/7bce00698899aa2c06fe7365c76e4d78ddb15fa3", + "reference": "7bce00698899aa2c06fe7365c76e4d78ddb15fa3", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~5.5|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ], + "time": "2016-11-30 16:50:46" + }, + { + "name": "doctrine/dbal", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/abbdfd1cff43a7b99d027af3be709bc8fc7d4769", + "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769", + "shasum": "" + }, + "require": { + "doctrine/common": ">=2.4,<2.7-dev", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "symfony/console": "2.*" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ], + "time": "2016-01-05 22:11:12" + }, + { + "name": "doctrine/inflector", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", + "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ], + "time": "2015-11-06 14:35:42" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09 13:34:57" + }, + { + "name": "monolog/monolog", + "version": "1.22.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", + "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "~5.3" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2017-03-13 07:08:03" + }, + { + "name": "nikic/fast-route", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "b5f95749071c82a8e0f58586987627054400cdf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/b5f95749071c82a8e0f58586987627054400cdf6", + "reference": "b5f95749071c82a8e0f58586987627054400cdf6", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "time": "2017-01-19 11:35:12" + }, + { + "name": "pimple/pimple", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2015-09-11 15:10:35" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14 16:28:37" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06 14:39:51" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "sabre/event", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-event.git", + "reference": "337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-event/zipball/337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff", + "reference": "337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff", + "shasum": "" + }, + "require": { + "php": ">=5.4.1" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~0.0.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "sabre/event is a library for lightweight event-based programming", + "homepage": "http://sabre.io/event/", + "keywords": [ + "EventEmitter", + "events", + "hooks", + "plugin", + "promise", + "signal" + ], + "time": "2015-05-19 10:24:22" + }, + { + "name": "silex/api", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex-Api.git", + "reference": "b11d209850abcd951589eaf3ee789a5a4caeb9ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex-Api/zipball/b11d209850abcd951589eaf3ee789a5a4caeb9ef", + "reference": "b11d209850abcd951589eaf3ee789a5a4caeb9ef", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "pimple/pimple": "~3.0" + }, + "suggest": { + "silex/silex": "For BootableProviderInterface and ControllerProviderInterface", + "symfony/event-dispatcher": "For EventListenerProviderInterface" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Silex\\Api\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "The Silex interfaces", + "homepage": "http://silex.sensiolabs.org", + "keywords": [ + "microframework" + ], + "time": "2016-12-06 08:06:50" + }, + { + "name": "slim/flash", + "version": "0.1.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Flash.git", + "reference": "1995ed53b77b8eeb67adf032de93c319f76aa5cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Flash/zipball/1995ed53b77b8eeb67adf032de93c319f76aa5cd", + "reference": "1995ed53b77b8eeb67adf032de93c319f76aa5cd", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Flash\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework Flash message service provider", + "homepage": "http://slimframework.com", + "keywords": [ + "flash", + "framework", + "message", + "provider", + "slim" + ], + "time": "2015-08-16 22:49:06" + }, + { + "name": "slim/slim", + "version": "3.8.1", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "5385302707530b2bccee1769613ad769859b826d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/5385302707530b2bccee1769613ad769859b826d", + "reference": "5385302707530b2bccee1769613ad769859b826d", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "nikic/fast-route": "^1.0", + "php": ">=5.5.0", + "pimple/pimple": "^3.0", + "psr/container": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "time": "2017-03-19 17:55:20" + }, + { + "name": "slim/twig-view", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Twig-View.git", + "reference": "a743ac45e2a089942159dda499c5ef5bc5f6bfa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/a743ac45e2a089942159dda499c5ef5bc5f6bfa6", + "reference": "a743ac45e2a089942159dda499c5ef5bc5f6bfa6", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/http-message": "^1.0", + "twig/twig": "^1.18|^2.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 view helper built on top of the Twig 2 templating component", + "homepage": "http://slimframework.com", + "keywords": [ + "framework", + "slim", + "template", + "twig", + "view" + ], + "time": "2017-01-25 20:38:39" + }, + { + "name": "symfony/console", + "version": "v3.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", + "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/debug": "~2.8|~3.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.8|~3.0", + "symfony/filesystem": "~2.8|~3.0", + "symfony/process": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/filesystem": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2017-04-26 01:39:17" + }, + { + "name": "symfony/debug", + "version": "v3.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/fd6eeee656a5a7b384d56f1072243fe1c0e81686", + "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.8|~3.0", + "symfony/http-kernel": "~2.8|~3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2017-04-19 20:17:50" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "b8a401f733b43251e1d088c589368b2a94155e40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b8a401f733b43251e1d088c589368b2a94155e40", + "reference": "b8a401f733b43251e1d088c589368b2a94155e40", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/dependency-injection": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/stopwatch": "~2.8|~3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2017-05-01 14:58:48" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", + "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-11-14 01:06:16" + }, + { + "name": "symfony/process", + "version": "v3.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0", + "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2017-04-12 14:13:17" + }, + { + "name": "twig/twig", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "9935b662e24d6e634da88901ab534cc12e8c728f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9935b662e24d6e634da88901ab534cc12e8c728f", + "reference": "9935b662e24d6e634da88901ab534cc12e8c728f", + "shasum": "" + }, + "require": { + "php": ">=5.2.7" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.32-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ], + "time": "2017-02-27 00:07:03" + }, + { + "name": "vlucas/spot2", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/spotorm/spot2.git", + "reference": "94fd8c2ae93a62cdc36782c223dbc6ead7f54839" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spotorm/spot2/zipball/94fd8c2ae93a62cdc36782c223dbc6ead7f54839", + "reference": "94fd8c2ae93a62cdc36782c223dbc6ead7f54839", + "shasum": "" + }, + "require": { + "doctrine/dbal": "2.5.4", + "php": ">=5.4.0", + "sabre/event": "~2.0", + "vlucas/valitron": "~1.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spot\\": "lib/", + "SpotTest\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Simple DataMapper built on top of Doctrine DBAL", + "homepage": "https://github.com/vlucas/spot2", + "keywords": [ + "database", + "datamapper", + "dbal", + "doctrine", + "entity", + "mapper", + "model", + "orm" + ], + "time": "2016-05-21 04:38:40" + }, + { + "name": "vlucas/valitron", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/valitron.git", + "reference": "b33c79116260637337187b7125f955ae26d306cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/valitron/zipball/b33c79116260637337187b7125f955ae26d306cc", + "reference": "b33c79116260637337187b7125f955ae26d306cc", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Valitron": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Simple, elegant, stand-alone validation library with NO dependencies", + "homepage": "http://github.com/vlucas/valitron", + "keywords": [ + "valid", + "validation", + "validator" + ], + "time": "2017-02-23 08:31:59" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/composer.phar b/composer.phar new file mode 100755 index 0000000..3577931 Binary files /dev/null and b/composer.phar differ diff --git a/log/__keep__.txt b/log/__keep__.txt new file mode 100644 index 0000000..01c6809 --- /dev/null +++ b/log/__keep__.txt @@ -0,0 +1 @@ +Do not delete file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..0071532 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,16 @@ +# For production, put your rewrite rules directly into your VirtualHost +# directive and turn off AllowOverride. + + + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} -s [OR] + RewriteCond %{REQUEST_FILENAME} -l [OR] + RewriteCond %{REQUEST_FILENAME} -d + RewriteRule ^.*$ - [NC,L] + + + RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$ + RewriteRule ^(.*) - [E=BASE:%1] + RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L] + diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..44d4580 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,41 @@ +body { + margin: 50px 0 0 0; + padding: 0; + width: 100%; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + text-align: center; + color: #aaa; + font-size: 18px; +} + +h1 { + color: #719e40; + letter-spacing: -3px; + font-family: 'Lato', sans-serif; + font-size: 100px; + font-weight: 200; + margin-bottom: 0; +} + +.header-logo { + width: 30px; + height: 30px; + display: inline-block; + margin-right: 5px; +} + +.profile-form { + text-align: left; +} + +.profile-form > label.check { + margin-top: 5px; +} + +.profile-form > label { + margin-top: 15px; +} + +.column-left { + text-align: left; +} \ No newline at end of file diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..89e009f --- /dev/null +++ b/public/index.php @@ -0,0 +1,29 @@ +run(); diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..1fc6a4e --- /dev/null +++ b/public/js/main.js @@ -0,0 +1,13 @@ +var configureDatepickers = function() { + $('.datepicker-control').datepicker({ format: 'dd/mm/yyyy', weekStart: 1, startDate: "2017-07-01" }); +} + +var init = function() { + configureDatepickers(); + + if(typeof initPage !== undefined) { + initPage(); + } +} + +$(document).ready(init); diff --git a/resources/conference.sql b/resources/conference.sql new file mode 100644 index 0000000..b1568b9 --- /dev/null +++ b/resources/conference.sql @@ -0,0 +1,4 @@ +# Create a single conference with basic questions +INSERT INTO `conference` (`id`, `slug`, `name`, `description`, `notes`, `venue`, `website`, `start_date`, `end_date`, `enable_donation`, `form`, `mailman`, `enabled`, `date_created`) +VALUES + (3, 'akademy2018', 'Akademy 2018', 'On registration, your email address will be signed up to the akademy-attendees mailing list.', NULL, 'University of Technology (TU Wien) in Vienna, Austria', 'http://akademy.kde.org/2018/', '11/8/2018', '17/8/2018', NULL, '{\n \"forms\": [\n {\n \"field\": \"Arrival Date\",\n \"type\": \"date\"\n },\n {\n \"field\": \"Departure Date\",\n \"type\": \"date\"\n }\n ],\n \"submitlabel\": \"Register\"\n}', 'akademy-attendees-request@kde.org', 1, '2017-05-04 21:20:34'); \ No newline at end of file diff --git a/resources/schema.sql b/resources/schema.sql new file mode 100644 index 0000000..9d9fbee --- /dev/null +++ b/resources/schema.sql @@ -0,0 +1,124 @@ +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +# Dump of table admins +# ------------------------------------------------------------ + +CREATE TABLE `admins` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `dn` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `conference_id` int(11) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table conference +# ------------------------------------------------------------ + +CREATE TABLE `conference` ( + `id` int(10) unsigned NOT NULL, + `slug` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `description` text COLLATE utf8_unicode_ci, + `notes` text COLLATE utf8_unicode_ci, + `venue` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `website` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `start_date` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `end_date` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `enable_donation` int(11) DEFAULT NULL, + `form` text COLLATE utf8_unicode_ci, + `mailman` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `enabled` tinyint(4) DEFAULT NULL, + `date_created` datetime DEFAULT NULL, + PRIMARY KEY (`id`,`name`,`venue`,`website`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table mailman +# ------------------------------------------------------------ + +CREATE TABLE `mailman` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `dn` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `conference_id` int(10) unsigned NOT NULL, + `date_created` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table merchandise +# ------------------------------------------------------------ + +CREATE TABLE `merchandise` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `conference_id` int(11) DEFAULT NULL, + `title` varchar(255) DEFAULT NULL, + `description` text, + `image` varchar(255) DEFAULT NULL, + `price` float DEFAULT NULL, + `date_created` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +# Dump of table profile +# ------------------------------------------------------------ + +CREATE TABLE `profile` ( + `dn` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `data` text COLLATE utf8_unicode_ci NOT NULL, + `date_created` datetime DEFAULT NULL, + PRIMARY KEY (`dn`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table registration +# ------------------------------------------------------------ + +CREATE TABLE `registration` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `dn` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `conference_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `data` text COLLATE utf8_unicode_ci NOT NULL, + `cancelled` int(11) DEFAULT '0', + `apToken` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `date_created` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + + +# Dump of table users +# ------------------------------------------------------------ + +CREATE TABLE `users` ( + `dn` varchar(255) NOT NULL DEFAULT '', + `cn` varchar(255) DEFAULT NULL, + `email` varchar(255) DEFAULT NULL, + `gender` varchar(255) DEFAULT NULL, + `ircnick` varchar(255) DEFAULT NULL, + `homepostaladdress` varchar(255) DEFAULT NULL, + `date_created` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`dn`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; \ No newline at end of file