├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── SimpleQuiz ├── Tests │ ├── Base │ │ └── InstallerTest.php │ └── QuizTest.php └── Utils │ ├── Base │ ├── IQuestion.php │ ├── IQuiz.php │ ├── ISession.php │ ├── ISimple.php │ ├── Installer.php │ ├── Mailer.php │ ├── SampleConfig.php │ ├── User.php │ └── Utils.php │ ├── Exceptions │ ├── LoginException.php │ └── RegisterException.php │ ├── LeaderBoard.php │ ├── QuestionStorage.php │ ├── Quiz.php │ ├── RadioQuestion.php │ ├── Session.php │ ├── Simple.php │ └── User │ ├── AdminUser.php │ ├── EndUser.php │ └── GuestUser.php ├── composer.json ├── composer.lock ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── images │ ├── ajax-loader.gif │ └── sq.png ├── index.php ├── res │ ├── bootstrap │ │ ├── assets │ │ │ ├── css │ │ │ │ ├── docs.css │ │ │ │ └── pygments-manni.css │ │ │ ├── ico │ │ │ │ ├── apple-touch-icon-114-precomposed.png │ │ │ │ ├── apple-touch-icon-144-precomposed.png │ │ │ │ ├── apple-touch-icon-57-precomposed.png │ │ │ │ ├── apple-touch-icon-72-precomposed.png │ │ │ │ └── favicon.png │ │ │ └── js │ │ │ │ ├── application.js │ │ │ │ ├── customizer.js │ │ │ │ ├── filesaver.js │ │ │ │ ├── holder.js │ │ │ │ ├── html5shiv.js │ │ │ │ ├── jquery.js │ │ │ │ ├── jszip.js │ │ │ │ ├── less.js │ │ │ │ ├── raw-files.js │ │ │ │ ├── respond.min.js │ │ │ │ └── uglify.js │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap.css │ │ │ └── bootstrap.min.css │ │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ │ └── js │ │ │ ├── bootstrap.js │ │ │ └── bootstrap.min.js │ ├── css │ │ └── quiz.css │ └── js │ │ ├── admin.js │ │ ├── form.js │ │ ├── general.js │ │ ├── login.js │ │ └── start.js └── web.config ├── routes ├── admin.php └── public.php ├── simple-quiz.sql └── templates ├── admin ├── editanswers.php ├── footer.php ├── header.php ├── index.php ├── login.php └── quiz.php ├── category.php ├── email ├── registerconfirm.html └── registerconfirm.txt ├── emailconfirmed.php ├── emailsent.php ├── index.php ├── login.php ├── quiz ├── error.php ├── footer.php ├── header.php ├── quiz.php ├── results.php └── test.php └── requirements.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | *.idea 3 | /SimpleQuiz/Utils/Base/Config.php 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - mysql -e 'create database `simple-quiz`;use `simple-quiz`;source simple-quiz.sql;' 3 | - composer self-update 4 | - composer install --no-interaction --prefer-source --no-dev 5 | language: php 6 | php: 7 | - "5.5" 8 | - "5.4" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | 180 | Copyright 2013 Ben Hall 181 | 182 | Licensed under the Apache License, Version 2.0 (the "License"); 183 | you may not use this file except in compliance with the License. 184 | You may obtain a copy of the License at 185 | 186 | http://www.apache.org/licenses/LICENSE-2.0 187 | 188 | Unless required by applicable law or agreed to in writing, software 189 | distributed under the License is distributed on an "AS IS" BASIS, 190 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 191 | See the License for the specific language governing permissions and 192 | limitations under the License. 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Simple-Quiz](https://simplequiz.co.uk) 2 | [![Software License](http://img.shields.io/badge/license-apache2-brightgreen.svg)](LICENSE) 3 | [![Build Status](https://travis-ci.org/ElanMan/simple-quiz.png?branch=master)](https://travis-ci.org/ElanMan/simple-quiz) 4 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/77dcca19-1dd0-4a47-86b7-1ac6142e1bbd/mini.png)](https://insight.sensiolabs.com/projects/77dcca19-1dd0-4a47-86b7-1ac6142e1bbd) 5 | 6 | Simple-Quiz is a simple framework for creating quizzes for the web, created and maintained by [Ben Hall](http://twitter.com/elanman). 7 | 8 | PHP version > 5.4 is required 9 | 10 | 11 | ## Quick start 12 | 13 | Three quick start options are available: 14 | 15 | * [Download the latest release (github)](https://github.com/elanman/simple-quiz/releases/latest). 16 | * [Download the latest release (sourceforge)](https://sourceforge.net/projects/simple-quiz/). 17 | * Clone the repo: `git clone https://github.com/elanman/simple-quiz.git`. 18 | 19 | 20 | ### What's included 21 | 22 | Within the download you'll find the following directories and files: 23 | 24 | 25 | / 26 | ├── SimpleQuiz/ 27 | │ ├── Utils/ 28 | │ │ ├── Base/ 29 | │ │ │ ├── SampleConfig.php 30 | │ │ │ ├── Installer.php 31 | │ │ │ ├── IQuestion.php 32 | │ │ │ ├── IQuiz.php 33 | │ │ │ ├── ISession.php 34 | │ │ │ ├── ISimple.php 35 | │ │ │ ├── User.php 36 | │ │ │ └── setup.php 37 | │ │ ├── Exceptions/ 38 | │ │ │ ├── LoginException.php 39 | │ │ │ ├── RegisterException.php 40 | │ │ ├── User/ 41 | │ │ │ ├── AdminUser.php 42 | │ │ │ ├── EndUser.php 43 | │ │ │ ├── GuestUser.php 44 | │ │ ├── LeaderBoard.php 45 | │ │ ├── Quiz.php 46 | │ │ ├── QuestionStorage.php 47 | │ │ ├── Session.php 48 | │ │ └── Simple.php 49 | │ │ └── RadioQuestion.php 50 | │ ├── Tests/ 51 | │ ├── Base/ 52 | │ │ ├── InstallerTest.php 53 | │ └── QuizTest.php 54 | ├── public/ (**this is your document root**) 55 | │ ├── images/ 56 | │ │ ├── ajax-loader.gif 57 | │ │ ├── sq.png 58 | │ ├── res/ 59 | │ │ ├── bootstrap/ 60 | │ │ │ ├── assets/ 61 | │ │ │ ├── dist/ 62 | │ │ ├── css/ 63 | │ │ │ ├── quiz.css 64 | │ │ ├── js/ 65 | │ │ │ ├── admin.js 66 | │ │ │ ├── form.js 67 | │ │ │ ├── general.js 68 | │ │ │ ├── login.js 69 | │ │ │ ├── start.js 70 | │ ├── .htaccess 71 | │ └── index.php 72 | ├── routes/ 73 | │ ├── admin.php 74 | │ ├── public.php 75 | ├── templates/ 76 | │ ├── admin/ 77 | │ │ ├── editanswers.php 78 | │ │ ├── footer.php 79 | │ │ ├── header.php 80 | │ │ ├── index.php 81 | │ │ ├── login.php 82 | │ │ └── quiz.php 83 | │ ├── email/ 84 | │ │ ├── registerconfirm.html 85 | │ │ └── registerconfirm.txt 86 | │ ├── quiz/ 87 | │ │ ├── error.php 88 | │ │ ├── footer.php 89 | │ │ ├── header.php 90 | │ │ ├── quiz.php 91 | │ │ ├── results.php 92 | │ │ ├── test.php 93 | │ ├── category.php 94 | │ ├── emailconfirmed.php 95 | │ ├── emailsent.php 96 | │ ├── index.php 97 | │ ├── login.php 98 | │ └── requirements.php 99 | ├── vendor/ 100 | ├── .gitignore 101 | ├── .travis.yml 102 | ├── composer.json 103 | ├── composer.lock 104 | ├── LICENSE 105 | ├── phpunit.xml 106 | ├── .gitignore 107 | ├── README.md 108 | └── simple-quiz.sql 109 | 110 | 111 | ## Installation 112 | 113 | ### Get The Code. 114 | * If you are downloading from SourceForge, all project dependencies are bundled with the project. 115 | If, however, you are cloning from GitHub or downloading the release zip file, you must run 'composer install' to 116 | download all of the dependencies. 117 | * If you don't know what composer is, take a look here: [Composer](https://getcomposer.org/) 118 | * Unpack the downloaded code zip archive. 119 | * Place the contents of the /public directory inside your document root. 120 | * All other directories should be placed outside of the document root and not accessible via a web browser (look at the above diagram to see the structure). 121 | * The mod_rewrite module (if using apache server) or [URL Rewrite](http://www.iis.net/downloads/microsoft/url-rewrite) module (if using IIS) will need to be enabled in 122 | your server configuration. 123 | * Create a MySQL database called 'simple-quiz' 124 | * Import simple-quiz.sql into MySQL using a tool like phpmyadmin or using the MySQL 'source' command. 125 | * Change credentials in /SimpleQuiz/Utils/Base/SampleConfig.php. 126 | * Rename SampleConfig.php to Config.php 127 | * Default web admin user is example@gmail.com with password of 123456 128 | * Navigate to the web accessible folder in your browser. 129 | 130 | ## Twitter Stuff 131 | + 132 | 133 | ## Authors 134 | 135 | **Ben Hall** 136 | 137 | + 138 | + 139 | 140 | 141 | 142 | 143 | ## Copyright and license 144 | 145 | Copyright 2013 Ben Hall under [the Apache 2.0 license](LICENSE). 146 | -------------------------------------------------------------------------------- /SimpleQuiz/Tests/Base/InstallerTest.php: -------------------------------------------------------------------------------- 1 | installer = new Installer(); 18 | } 19 | 20 | public function testgetRequirements() 21 | { 22 | $result = $this->installer->getRequirements(); 23 | $this->assertInternalType('array', $result); 24 | } 25 | } -------------------------------------------------------------------------------- /SimpleQuiz/Tests/QuizTest.php: -------------------------------------------------------------------------------- 1 | app = new Set(); 27 | $this->app->leaderboard = function() { 28 | return new LeaderBoard(); 29 | }; 30 | $this->quiz = new Quiz($this->app); 31 | } 32 | 33 | public function testCantSetIdToString() 34 | { 35 | $result = $this->quiz->setId('ghj'); 36 | 37 | $this->assertFalse($result); 38 | } 39 | 40 | public function testGetIdReturnsInt() 41 | { 42 | $this->quiz->setId(1); 43 | $result = $this->quiz->getId(); 44 | 45 | $this->assertInternalType('int', $result); 46 | } 47 | 48 | public function testGetQuestionReturnsObject() { 49 | 50 | $this->quiz->setId(8); 51 | $this->quiz->populateQuestions(); 52 | $question = $this->quiz->getQuestion(1); 53 | $this->assertInternalType('object', $question); 54 | } 55 | } -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Base/IQuestion.php: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Base/ISimple.php: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Base/Installer.php: -------------------------------------------------------------------------------- 1 | version = phpversion(); 16 | } 17 | 18 | public function getRequirements() { 19 | 20 | $this->versionimg = version_compare($this->version, '5.3.7', '>=') ? 'ok' : 'remove'; 21 | 22 | try { 23 | $this->random = file_exists('/dev/urandom'); 24 | } catch (\Exception $e) { 25 | $this->randomimg = 'remove'; 26 | $this->randommsg .= "\n" . $e->getMessage(); 27 | } 28 | 29 | $this->requirements['version'] = array('vers' => $this->version, 'img' => $this->versionimg, 'msg' => $this->versionmsg); 30 | $this->requirements['random'] = array('random' => $this->random, 'img' => $this->randomimg, 'msg' => $this->randommsg); 31 | 32 | return $this->requirements; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Base/Mailer.php: -------------------------------------------------------------------------------- 1 | setUsername(Config::$mailUser) 20 | ->setPassword(Config::$mailPass) 21 | ; 22 | 23 | $this->instance = \Swift_Mailer::newInstance($transport); 24 | $this->message = \Swift_Message::newInstance(); 25 | $this->message->setFrom(array(Config::$appEmail => Config::$appname)); 26 | } 27 | 28 | 29 | /** 30 | * @param User $user 31 | * @return bool 32 | */ 33 | public function sendConfirmationEmail(User $user) 34 | { 35 | // Build the message 36 | $this->message->setSubject('Welcome to Simple Quiz, please confirm your email address'); 37 | 38 | $replacements = array(); 39 | $confirmHash = sha1($user->getEmail() .mt_rand() . $user->getId()); 40 | 41 | $replacements[$user->getEmail()] = array( 42 | '{username}'=>$user->getName(), 43 | '{hash}'=> $confirmHash, 44 | '{sitename}' => Config::$siteurl 45 | ); 46 | 47 | $this->decorator = new \Swift_Plugins_DecoratorPlugin($replacements); 48 | 49 | $this->instance->registerPlugin($this->decorator); 50 | 51 | $this->readFromFile('registerconfirm'); 52 | $this->message->setTo(array($user->getEmail() => $user->getName())); 53 | 54 | // Send the message 55 | if ($this->instance->send($this->message) > 0) 56 | { 57 | $record = \ORM::for_table('users')->find_one($user->getId()); 58 | $record->set('confirmhash', $confirmHash); 59 | $record->set_expr('hashstamp', 'now()'); 60 | $record->save(); 61 | return true; 62 | } 63 | return false; 64 | } 65 | 66 | /** 67 | * @param $template 68 | * @return $this 69 | */ 70 | private function readFromFile($template) 71 | { 72 | $bodyhtml = file_get_contents("../templates/email/$template.html"); 73 | $bodytxt = file_get_contents("../templates/email/$template.txt"); 74 | $this->message->setBody($bodyhtml, 'text/html'); 75 | $this->message->addPart($bodytxt, 'text/plain'); 76 | return $this; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Base/SampleConfig.php: -------------------------------------------------------------------------------- 1 | email = $email; 18 | $this->name = $name; 19 | } 20 | 21 | public function setId($id) 22 | { 23 | $this->id = $id; 24 | } 25 | 26 | public function getId() 27 | { 28 | return $this->id; 29 | } 30 | 31 | public function isAdmin() 32 | { 33 | return $this instanceof AdminUser; 34 | } 35 | 36 | public function getQuizzes() 37 | { 38 | return $this->quizzes; 39 | } 40 | 41 | public function getScore($quizid) 42 | { 43 | 44 | } 45 | 46 | public function getName() 47 | { 48 | return $this->name; 49 | } 50 | 51 | public function setName($name) 52 | { 53 | $this->name = $name; 54 | } 55 | 56 | public function getEmail() 57 | { 58 | return $this->email; 59 | } 60 | 61 | public function setEmail($email) 62 | { 63 | $this->email = $email; 64 | } 65 | 66 | public function getPassword() 67 | { 68 | return $this->password; 69 | } 70 | 71 | public function setPassword($password) 72 | { 73 | $this->password = $password; 74 | } 75 | 76 | public function getHash() 77 | { 78 | return $this->hash; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Base/Utils.php: -------------------------------------------------------------------------------- 1 | left_outer_join('quiz_users', array('quiz_users.user_id', '=', 'users.id')) 12 | ->where('quiz_users.quiz_id', $quizid) 13 | ->order_by_desc('quiz_users.score') 14 | ->find_array(); 15 | 16 | if ($number) 17 | { 18 | usort($members, array('\SimpleQuiz\Utils\Base\Utils', 'memberSort') ); 19 | return array_slice($members, 0, $number, true); 20 | } 21 | 22 | return $members; 23 | } 24 | 25 | public function addMember($quizid, User $user,$score,$start,$end,$timetaken) 26 | { 27 | //this should be called at start of quiz and fail if user already exists 28 | //record should be updated at end of quiz with score etc 29 | $quser = \ORM::for_table('users')->where('name', $user->getName())->find_one(); 30 | $userid = $quser->id(); 31 | 32 | $quizuser = \ORM::for_table('quiz_users')->create(); 33 | $quizuser->set(array( 34 | 'quiz_id' => $quizid, 35 | 'user_id' => $userid, 36 | 'score' => $score, 37 | 'start_time' => $start, 38 | 'date_submitted' => $end, 39 | 'time_taken' => $timetaken 40 | )); 41 | $quizuser->save(); 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/QuestionStorage.php: -------------------------------------------------------------------------------- 1 | getNum() == $num) 18 | { 19 | return $question; 20 | } 21 | } 22 | 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Quiz.php: -------------------------------------------------------------------------------- 1 | _leaderboard = $container->leaderboard; 26 | $this->_questions = new QuestionStorage(); 27 | } 28 | 29 | /** 30 | * @param $id 31 | * @return bool 32 | */ 33 | public function setId($id) 34 | { 35 | $quizobj = \ORM::for_table('quizzes')->join('categories', array('quizzes.category', '=', 'categories.id'))->select_many('quizzes.name', 'quizzes.description', array('category' => 'categories.name'), 'quizzes.active')->find_one($id); 36 | 37 | if ($quizobj) { 38 | $this->_id = $id; 39 | $this->_name = $quizobj->name; 40 | $this->_description = $quizobj->description; 41 | $this->_category = $quizobj->category; 42 | $this->_active = $quizobj->active; 43 | 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | /** 51 | * @return int 52 | */ 53 | public function getId() 54 | { 55 | return (int) $this->_id; 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getName() 62 | { 63 | return ucwords($this->_name); 64 | } 65 | 66 | /** 67 | * @return mixed 68 | */ 69 | public function getDescription() 70 | { 71 | return $this->_description; 72 | } 73 | 74 | /** 75 | * @return bool 76 | */ 77 | public function isActive() 78 | { 79 | return $this->_active == 1 ? true : false; 80 | } 81 | 82 | 83 | /** 84 | * @param $questionid 85 | * @return bool 86 | */ 87 | public function getAnswers($questionid) 88 | { 89 | if ($questionid) 90 | { 91 | return $this->getQuestion($questionid)->getAnswers(); 92 | } 93 | else { 94 | throw new \InvalidArgumentException("You must supply a question id"); 95 | } 96 | 97 | return false; 98 | } 99 | 100 | /** 101 | * @return array 102 | */ 103 | public function getAllAnswersGroupedByQuestion() 104 | { 105 | //pull all answers from db grouped by question 106 | $obj = \ORM::for_table('answers')->raw_query("SELECT group_concat( a.text ORDER BY a.correct DESC SEPARATOR '~' ) as grouped FROM answers a where a.quiz_id = :quizid GROUP BY a.question_num", array('quizid' => $this->_id) )->find_array(); 107 | foreach ($obj as $answers) 108 | { 109 | $answerarray = explode('~', $answers['grouped']); 110 | array_push($this->_answers,$answerarray); 111 | } 112 | 113 | return $this->_answers; 114 | } 115 | 116 | /** 117 | * @param array $answers 118 | * @param $questionid 119 | * @return bool 120 | */ 121 | public function updateAnswers(Array $answers, $questionid) 122 | { 123 | $this->getQuestion($questionid)->updateAnswers($answers); 124 | 125 | return true; 126 | } 127 | 128 | /** 129 | * @param $questionid 130 | * @return bool 131 | */ 132 | public function deleteAnswers($questionid) 133 | { 134 | $this->getQuestion($questionid)->deleteAnswers(); 135 | return true; 136 | } 137 | 138 | /** 139 | * @param $questionid 140 | * @param array $answers 141 | * @return bool 142 | */ 143 | public function addAnswers($questionid, Array $answers) 144 | { 145 | $this->getQuestion($questionid)->addAnswers($answers); 146 | } 147 | 148 | /** 149 | * @param $text 150 | * @param $type 151 | * @param array $answers 152 | * @return bool 153 | */ 154 | public function addQuestion($text, $type, Array $answers) 155 | { 156 | $max = \ORM::for_table('questions')->where('quiz_id', $this->_id)->max('num'); 157 | $num = $max + 1; 158 | 159 | //insert new question 160 | $newquestion = \ORM::for_table('questions')->create( 161 | array( 162 | 'num' => $num, 163 | 'quiz_id' => $this->_id, 164 | 'text' => $text 165 | ) 166 | ); 167 | //save the new question in db then add to the question storage 168 | if ($newquestion->save()) 169 | { 170 | //create a question of desired type 171 | $questionType = __NAMESPACE__ . '\\' . ucfirst($type) . 'Question'; 172 | //create a new Question instance 173 | $this->_question = new $questionType($newquestion->id(),$num, $this->_id, $text); 174 | $this->_question->addAnswers($answers); 175 | $this->_questions->attach($this->_question); 176 | 177 | return true; 178 | } 179 | 180 | return false; 181 | } 182 | 183 | /** 184 | * @param $questionid 185 | * @param $text 186 | * @return bool 187 | */ 188 | public function updateQuestion($questionid, $text) 189 | { 190 | $this->getQuestion($questionid)->update($text); 191 | 192 | return true; 193 | } 194 | 195 | /** 196 | * @param $questionid 197 | * @return bool 198 | */ 199 | public function deleteQuestion($questionid) 200 | { 201 | //foreign_key constraints take care of deleting related answers 202 | $q = \ORM::for_table('questions')->where('quiz_id', $this->_id)->where('num', $questionid)->find_one(); 203 | $q->delete(); 204 | 205 | //reorder the num column in questions table 206 | //foreign_key constraints take care of updating related answers 207 | $toupdate = \ORM::for_table('questions')->where('quiz_id', $this->_id)->where_gt('num', $questionid)->find_many(); 208 | foreach ($toupdate as $question) { 209 | $question->num = $question->num - 1; 210 | } 211 | 212 | return $toupdate->save(); 213 | } 214 | 215 | /** 216 | * @param $questionid 217 | * @return bool|mixed|object 218 | */ 219 | public function getQuestion($questionid) 220 | { 221 | return $this->_questions->getById($questionid); 222 | } 223 | 224 | public function getQuestions() 225 | { 226 | return $this->_questions; 227 | } 228 | 229 | /** 230 | * @return int 231 | */ 232 | public function countQuestions() 233 | { 234 | return count($this->_questions); 235 | } 236 | 237 | /** 238 | * @return mixed 239 | */ 240 | public function getCategory() 241 | { 242 | return $this->_category; 243 | } 244 | 245 | 246 | /** 247 | * @return $this 248 | */ 249 | public function populateQuestions() 250 | { 251 | $quizquestions = \ORM::for_table('questions')->where('quiz_id', $this->_id)->order_by_asc('num')->find_array(); 252 | 253 | foreach ($quizquestions as $question) 254 | { 255 | /** 256 | * @todo make the instance name dynamic 257 | */ 258 | $questionObject = new RadioQuestion($question['id'], $question['num'], $this->_id, $question['text']); 259 | $this->_questions->attach($questionObject); 260 | } 261 | 262 | return $this; 263 | } 264 | 265 | //following 2 methods to be combined 266 | /** 267 | * 268 | */ 269 | public function populateUsers() 270 | { 271 | $this->_users = $this->_leaderboard->getMembers($this->_id); 272 | } 273 | 274 | /** 275 | * @return mixed 276 | */ 277 | public function getUsers() 278 | { 279 | return $this->_users; 280 | } 281 | 282 | /** 283 | * @param $num 284 | * @return mixed 285 | */ 286 | public function getLeaders($num) 287 | { 288 | return $this->_leaderboard->getMembers($this->_id, $num); 289 | } 290 | 291 | /** 292 | * @param User $user 293 | * @param $score 294 | * @param $start 295 | * @param $end 296 | * @param $timetaken 297 | * @return bool 298 | */ 299 | public function addQuizTaker(User $user,$score,$start,$end,$timetaken) 300 | { 301 | return $this->_leaderboard->addMember($this->_id, $user,$score,$start,$end,$timetaken); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/RadioQuestion.php: -------------------------------------------------------------------------------- 1 | _id = $_id; 20 | $this->_num = $_num; 21 | $this->_quizid = $_quizid; 22 | $this->_text = $_text; 23 | } 24 | 25 | 26 | public function getId() 27 | { 28 | return $this->_id; 29 | } 30 | 31 | public function getNum() 32 | { 33 | return $this->_num; 34 | } 35 | 36 | public function getText() 37 | { 38 | return $this->_text; 39 | } 40 | 41 | public function update($text) 42 | { 43 | $q = \ORM::for_table('questions')->where('quiz_id', $this->_quizid)->where('num', $this->_num)->find_one(); 44 | $q->set('text',$text); 45 | $q->save(); 46 | 47 | return true; 48 | } 49 | 50 | /** 51 | * @return array 52 | */ 53 | public function getAnswers() 54 | { 55 | //pull answers from db for only this question ordered by correct answer first 56 | $obj = \ORM::for_table('answers')->where('question_num', $this->_num)->where('quiz_id', $this->_quizid)->order_by_desc('correct')->find_many(); 57 | foreach ($obj as $answer) { 58 | array_push($this->_answers,$answer->text); 59 | } 60 | return $this->_answers; 61 | } 62 | 63 | /** 64 | * @param array $answers 65 | * @return bool 66 | */ 67 | public function addAnswers( Array $answers) 68 | { 69 | foreach ($answers as $answer) { 70 | $newanswer = \ORM::for_table('answers')->create(); 71 | $newanswer->question_num = $this->_num; 72 | $newanswer->text = $answer[0]; 73 | $newanswer->correct = $answer[1]; 74 | $newanswer->quiz_id = $this->_quizid; 75 | $newanswer->save(); 76 | } 77 | return true; 78 | } 79 | 80 | /** 81 | * @param array $answers 82 | * @return bool 83 | */ 84 | public function updateAnswers(Array $answers) 85 | { 86 | $this->deleteAnswers(); 87 | 88 | $this->addAnswers($answers); 89 | 90 | return true; 91 | } 92 | 93 | /** 94 | * @return bool 95 | */ 96 | public function deleteAnswers() 97 | { 98 | \ORM::for_table('answers')->where('quiz_id', $this->_quizid)->where('question_num', 99 | $this->_num)->delete_many(); 100 | 101 | return true; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Session.php: -------------------------------------------------------------------------------- 1 | find_one($id); 74 | if ($sql) { 75 | return $sql->data; 76 | } 77 | return ''; 78 | } 79 | 80 | public function write($id,$data) 81 | { 82 | $access = time(); 83 | $session = \ORM::for_table('sessions')->where('id', $id)->find_one(); 84 | 85 | if (! $session) { 86 | $newsession = \ORM::for_table('sessions')->create(); 87 | $newsession->set('id', $id); 88 | $newsession->set('access', $access); 89 | $newsession->set('data', $data); 90 | $newsession->save(); 91 | } else { 92 | $session->set('id', $id); 93 | $session->set('access', $access); 94 | $session->set('data', $data); 95 | $session->save(); 96 | } 97 | return true; 98 | } 99 | 100 | public function destroy($id) 101 | { 102 | // delete the session cookie if necessary. 103 | if (ini_get("session.use_cookies")) 104 | { 105 | $params = session_get_cookie_params(); 106 | setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]); 107 | } 108 | 109 | $session = \ORM::for_table('sessions')->find_one($id); 110 | $session->delete(); 111 | return true; 112 | } 113 | 114 | 115 | public function clean($max) 116 | { 117 | $old = time() - $max; 118 | \ORM::for_table('sessions')->where_lt('access', $old)->delete_many(); 119 | 120 | return true; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/Simple.php: -------------------------------------------------------------------------------- 1 | create(); 13 | $quiz->name = $quizmeta['name']; 14 | $quiz->description = $quizmeta['description']; 15 | $quiz->category = $quizmeta['category']; 16 | $quiz->active = $quizmeta['active']; 17 | $quiz->set_expr('created', 'NOW()'); 18 | $quiz->set_expr('updated', 'NOW()'); 19 | $quiz->save(); 20 | return true; 21 | } 22 | 23 | public function updateQuiz(Array $quizmeta) 24 | { 25 | $quiz = \ORM::for_table('quizzes')->find_one($quizmeta['id']); 26 | 27 | $quiz->set(array( 28 | 'name' => $quizmeta['name'], 29 | 'description' => $quizmeta['description'], 30 | 'category' => $quizmeta['category'], 31 | 'active' => $quizmeta['active'] 32 | )); 33 | $quiz->set_expr('updated', 'NOW()'); 34 | $quiz->save(); 35 | 36 | return true; 37 | } 38 | 39 | public function deleteQuiz($quizid) 40 | { 41 | $quiz = \ORM::for_table('quizzes')->find_one($quizid); 42 | $quiz->delete(); 43 | return true; 44 | } 45 | 46 | public function getQuizzes($active = true) { 47 | 48 | if ($active) { 49 | $quizzes = \ORM::for_table('quizzes')->join('categories', array('quizzes.category', '=', 'categories.id'))->select_many('quizzes.id', 'quizzes.name', 'quizzes.description', array('category' => 'categories.name'), 'quizzes.active')->where('active',1)->find_many(); 50 | } else { 51 | $quizzes = \ORM::for_table('quizzes')->join('categories', array('quizzes.category', '=', 'categories.id'))->select_many('quizzes.id', 'quizzes.name', 'quizzes.description', array('category' => 'categories.name'), 'quizzes.active')->find_many(); 52 | } 53 | return $quizzes; 54 | } 55 | 56 | public function getCategories($active = true) { 57 | if ($active) { 58 | $categories = \ORM::for_table('categories')->join('quizzes', array('quizzes.category', '=', 59 | 'categories.id'))->select_many('categories.id','categories.name','categories.description', 60 | 'quizzes.category','quizzes.active')->where('quizzes.active', 1)->find_many(); 61 | } 62 | else { 63 | $categories = \ORM::for_table('categories')->find_many(); 64 | } 65 | return $categories; 66 | } 67 | 68 | public function getCategory($id) { 69 | 70 | $category = \ORM::for_table('categories')->select_many('name','description')->find_one($id); 71 | 72 | return $category; 73 | } 74 | 75 | public function getCategoryQuizzes($id) { 76 | 77 | $quizzes = \ORM::for_table('quizzes')->join('categories', array('quizzes.category', '=', 'categories.id'))->select_many('quizzes.id', 'quizzes.name', 'quizzes.description', array('category' => 'categories.name'), 'quizzes.active')->where('quizzes.category', $id)->find_many(); 78 | 79 | return $quizzes; 80 | } 81 | 82 | public function getUsers($quizid = true) 83 | { 84 | if($quizid) 85 | { 86 | $users = \ORM::for_table('quiz_users')->join('quizzes', array('quiz_users.quiz_id', '=', 87 | 'quizzes.id'))->join('users', array('quiz_users.user_id', '=', 'users.id'))->select_many('users.name') 88 | ; 89 | 90 | return $users; 91 | } 92 | } 93 | 94 | public function quizUserExists($quizid, $userid){ 95 | 96 | return \ORM::for_table('quiz_users')->where( array('quiz_id' => $quizid, 'user_id' => $userid) )->find_one(); 97 | } 98 | 99 | /** 100 | * @param User $user 101 | * @return User $user 102 | * @throws RegisterException 103 | */ 104 | public function registerUser(User $user) 105 | { 106 | $name = $user->getName(); 107 | $email = $user->getEmail(); 108 | $password = $user->getPassword(); 109 | 110 | $userexists = \ORM::for_table('users')->where_any_is( 111 | array( 112 | array('name' => $name), 113 | array('email' => $email) 114 | ) 115 | )->find_one(); 116 | 117 | if ($userexists) 118 | { 119 | throw new RegisterException; 120 | } 121 | else 122 | { 123 | //register a new user 124 | $newuser = \ORM::for_table('users')->create(); 125 | $newuser->set('name', $name); 126 | $newuser->set('email', $email); 127 | $newuser->set('pass', $password); 128 | $newuser->save(); 129 | $user->setId($newuser->id()); 130 | 131 | return $user; 132 | } 133 | } 134 | 135 | public static function redirect(Slim $app, Session $session, $forward = false){ 136 | 137 | // redirect them to intended url if required 138 | if ($forward) 139 | { 140 | if ($session->get('urlRedirect')) 141 | { 142 | $tmp = $session->get('urlRedirect'); 143 | $session->remove('urlRedirect'); 144 | $app->redirect($app->request->getRootUri() . $tmp); 145 | } 146 | } 147 | else 148 | { 149 | //log them in and send to home page 150 | $app->redirect($app->request->getRootUri() . '/'); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/User/AdminUser.php: -------------------------------------------------------------------------------- 1 | quizzes; 20 | } 21 | 22 | public function setQuizzes($quizzes){ 23 | 24 | $this->quizzes = $quizzes; 25 | } 26 | 27 | public function getScore($quizid) 28 | { 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SimpleQuiz/Utils/User/GuestUser.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./SimpleQuiz/Tests/ 6 | 7 | 8 | 9 | 10 | vendor 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | #Options +FollowSymlinks 2 | RewriteEngine On 3 | #RewriteCond %{REQUEST_FILENAME} !-d 4 | #RewriteCond %{REQUEST_FILENAME}\.php -f 5 | #RewriteRule .* %{REQUEST_URI}.php [L] 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteRule ^ index.php [QSA,L] 8 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/favicon.ico -------------------------------------------------------------------------------- /public/images/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/images/ajax-loader.gif -------------------------------------------------------------------------------- /public/images/sq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/images/sq.png -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | true, 13 | 'log.enabled' => true, 14 | 'templates.path' => '../templates' 15 | )); 16 | 17 | $app->session = $session; 18 | 19 | require '../routes/admin.php'; 20 | require '../routes/public.php'; 21 | 22 | $app->leaderboard = function() { 23 | return new \SimpleQuiz\Utils\LeaderBoard(); 24 | }; 25 | 26 | $app->quiz = function ($app) { 27 | return new \SimpleQuiz\Utils\Quiz($app); 28 | }; 29 | 30 | $app->admin = function ($app) { 31 | return new \SimpleQuiz\Utils\Admin($app); 32 | }; 33 | 34 | $app->simple = function () { 35 | return new \SimpleQuiz\Utils\Simple(); 36 | }; 37 | 38 | $app->installer = function () { 39 | return new \SimpleQuiz\Utils\Base\Installer(); 40 | }; 41 | 42 | $app->hook('slim.before.dispatch', function() use ($app) { 43 | 44 | $user = null; 45 | 46 | if ($app->session->get('user')) { 47 | $user = $app->session->get('user'); 48 | } 49 | 50 | $app->view()->appendData(['user' => $user]); 51 | 52 | $root = $app->request->getRootUri(); 53 | $app->view()->appendData(['root' => $root]); 54 | }); 55 | 56 | $app->run(); 57 | -------------------------------------------------------------------------------- /public/res/bootstrap/assets/css/pygments-manni.css: -------------------------------------------------------------------------------- 1 | .hll { background-color: #ffffcc } 2 | /*{ background: #f0f3f3; }*/ 3 | .c { color: #999; } /* Comment */ 4 | .err { color: #AA0000; background-color: #FFAAAA } /* Error */ 5 | .k { color: #006699; } /* Keyword */ 6 | .o { color: #555555 } /* Operator */ 7 | .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ 8 | .cp { color: #009999 } /* Comment.Preproc */ 9 | .c1 { color: #999; } /* Comment.Single */ 10 | .cs { color: #999; } /* Comment.Special */ 11 | .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ 12 | .ge { font-style: italic } /* Generic.Emph */ 13 | .gr { color: #FF0000 } /* Generic.Error */ 14 | .gh { color: #003300; } /* Generic.Heading */ 15 | .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ 16 | .go { color: #AAAAAA } /* Generic.Output */ 17 | .gp { color: #000099; } /* Generic.Prompt */ 18 | .gs { } /* Generic.Strong */ 19 | .gu { color: #003300; } /* Generic.Subheading */ 20 | .gt { color: #99CC66 } /* Generic.Traceback */ 21 | .kc { color: #006699; } /* Keyword.Constant */ 22 | .kd { color: #006699; } /* Keyword.Declaration */ 23 | .kn { color: #006699; } /* Keyword.Namespace */ 24 | .kp { color: #006699 } /* Keyword.Pseudo */ 25 | .kr { color: #006699; } /* Keyword.Reserved */ 26 | .kt { color: #007788; } /* Keyword.Type */ 27 | .m { color: #FF6600 } /* Literal.Number */ 28 | .s { color: #d44950 } /* Literal.String */ 29 | .na { color: #4f9fcf } /* Name.Attribute */ 30 | .nb { color: #336666 } /* Name.Builtin */ 31 | .nc { color: #00AA88; } /* Name.Class */ 32 | .no { color: #336600 } /* Name.Constant */ 33 | .nd { color: #9999FF } /* Name.Decorator */ 34 | .ni { color: #999999; } /* Name.Entity */ 35 | .ne { color: #CC0000; } /* Name.Exception */ 36 | .nf { color: #CC00FF } /* Name.Function */ 37 | .nl { color: #9999FF } /* Name.Label */ 38 | .nn { color: #00CCFF; } /* Name.Namespace */ 39 | .nt { color: #2f6f9f; } /* Name.Tag */ 40 | .nv { color: #003333 } /* Name.Variable */ 41 | .ow { color: #000000; } /* Operator.Word */ 42 | .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .mf { color: #FF6600 } /* Literal.Number.Float */ 44 | .mh { color: #FF6600 } /* Literal.Number.Hex */ 45 | .mi { color: #FF6600 } /* Literal.Number.Integer */ 46 | .mo { color: #FF6600 } /* Literal.Number.Oct */ 47 | .sb { color: #CC3300 } /* Literal.String.Backtick */ 48 | .sc { color: #CC3300 } /* Literal.String.Char */ 49 | .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ 50 | .s2 { color: #CC3300 } /* Literal.String.Double */ 51 | .se { color: #CC3300; } /* Literal.String.Escape */ 52 | .sh { color: #CC3300 } /* Literal.String.Heredoc */ 53 | .si { color: #AA0000 } /* Literal.String.Interpol */ 54 | .sx { color: #CC3300 } /* Literal.String.Other */ 55 | .sr { color: #33AAAA } /* Literal.String.Regex */ 56 | .s1 { color: #CC3300 } /* Literal.String.Single */ 57 | .ss { color: #FFCC33 } /* Literal.String.Symbol */ 58 | .bp { color: #336666 } /* Name.Builtin.Pseudo */ 59 | .vc { color: #003333 } /* Name.Variable.Class */ 60 | .vg { color: #003333 } /* Name.Variable.Global */ 61 | .vi { color: #003333 } /* Name.Variable.Instance */ 62 | .il { color: #FF6600 } /* Literal.Number.Integer.Long */ 63 | 64 | .css .o, 65 | .css .o + .nt, 66 | .css .nt + .nt { color: #999; } 67 | -------------------------------------------------------------------------------- /public/res/bootstrap/assets/ico/apple-touch-icon-114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/res/bootstrap/assets/ico/apple-touch-icon-114-precomposed.png -------------------------------------------------------------------------------- /public/res/bootstrap/assets/ico/apple-touch-icon-144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/res/bootstrap/assets/ico/apple-touch-icon-144-precomposed.png -------------------------------------------------------------------------------- /public/res/bootstrap/assets/ico/apple-touch-icon-57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/res/bootstrap/assets/ico/apple-touch-icon-57-precomposed.png -------------------------------------------------------------------------------- /public/res/bootstrap/assets/ico/apple-touch-icon-72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/res/bootstrap/assets/ico/apple-touch-icon-72-precomposed.png -------------------------------------------------------------------------------- /public/res/bootstrap/assets/ico/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/res/bootstrap/assets/ico/favicon.png -------------------------------------------------------------------------------- /public/res/bootstrap/assets/js/application.js: -------------------------------------------------------------------------------- 1 | // NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT 2 | // IT'S ALL JUST JUNK FOR OUR DOCS! 3 | // ++++++++++++++++++++++++++++++++++++++++++ 4 | 5 | !function ($) { 6 | 7 | $(function(){ 8 | 9 | var $window = $(window) 10 | var $body = $(document.body) 11 | 12 | var navHeight = $('.navbar').outerHeight(true) + 10 13 | 14 | $body.scrollspy({ 15 | target: '.bs-sidebar', 16 | offset: navHeight 17 | }) 18 | 19 | $window.on('load', function () { 20 | $body.scrollspy('refresh') 21 | }) 22 | 23 | $('.bs-docs-container [href=#]').click(function (e) { 24 | e.preventDefault() 25 | }) 26 | 27 | // back to top 28 | setTimeout(function () { 29 | var $sideBar = $('.bs-sidebar') 30 | 31 | $sideBar.affix({ 32 | offset: { 33 | top: function () { 34 | var offsetTop = $sideBar.offset().top 35 | var sideBarMargin = parseInt($sideBar.children(0).css('margin-top'), 10) 36 | var navOuterHeight = $('.bs-docs-nav').height() 37 | 38 | return (this.top = offsetTop - navOuterHeight - sideBarMargin) 39 | } 40 | , bottom: function () { 41 | return (this.bottom = $('.bs-footer').outerHeight(true)) 42 | } 43 | } 44 | }) 45 | }, 100) 46 | 47 | setTimeout(function () { 48 | $('.bs-top').affix() 49 | }, 100) 50 | 51 | // tooltip demo 52 | $('.tooltip-demo').tooltip({ 53 | selector: "[data-toggle=tooltip]", 54 | container: "body" 55 | }) 56 | 57 | $('.tooltip-test').tooltip() 58 | $('.popover-test').popover() 59 | 60 | $('.bs-docs-navbar').tooltip({ 61 | selector: "a[data-toggle=tooltip]", 62 | container: ".bs-docs-navbar .nav" 63 | }) 64 | 65 | // popover demo 66 | $("[data-toggle=popover]") 67 | .popover() 68 | 69 | // button state demo 70 | $('#fat-btn') 71 | .click(function () { 72 | var btn = $(this) 73 | btn.button('loading') 74 | setTimeout(function () { 75 | btn.button('reset') 76 | }, 3000) 77 | }) 78 | 79 | // carousel demo 80 | $('.bs-docs-carousel-example').carousel() 81 | }) 82 | 83 | }(window.jQuery) 84 | -------------------------------------------------------------------------------- /public/res/bootstrap/assets/js/customizer.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { // wait for load in a dumb way because B-0 2 | var cw = '/*!\n * Bootstrap v3.0.0\n *\n * Copyright 2013 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n\n' 3 | 4 | function showError(msg, err) { 5 | $('
\ 6 |
\ 7 | ×\ 8 |

' + msg + '

' + 9 | (err.extract ? '
' + err.extract.join('\n') + '
' : '') + '\ 10 |
\ 11 |
').appendTo('body').alert() 12 | throw err 13 | } 14 | 15 | function showCallout(msg, showUpTop) { 16 | var callout = $('
\ 17 |

Attention!

\ 18 |

' + msg + '

\ 19 |
') 20 | 21 | if (showUpTop) { 22 | callout.appendTo('.bs-docs-container') 23 | } else { 24 | callout.insertAfter('.bs-customize-download') 25 | } 26 | } 27 | 28 | function getQueryParam(key) { 29 | key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars 30 | var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)")); 31 | return match && decodeURIComponent(match[1].replace(/\+/g, " ")); 32 | } 33 | 34 | function createGist(configData) { 35 | var data = { 36 | "description": "Bootstrap Customizer Config", 37 | "public": true, 38 | "files": { 39 | "config.json": { 40 | "content": JSON.stringify(configData, null, 2) 41 | } 42 | } 43 | } 44 | $.ajax({ 45 | url: 'https://api.github.com/gists', 46 | type: 'POST', 47 | dataType: 'json', 48 | data: JSON.stringify(data) 49 | }) 50 | .success(function(result) { 51 | history.replaceState(false, document.title, window.location.origin + window.location.pathname + '?id=' + result.id) 52 | }) 53 | .error(function(err) { 54 | showError('Ruh roh! Could not save gist file, configuration not saved.', err) 55 | }) 56 | } 57 | 58 | function getCustomizerData() { 59 | var vars = {} 60 | 61 | $('#less-variables-section input') 62 | .each(function () { 63 | $(this).val() && (vars[ $(this).prev().text() ] = $(this).val()) 64 | }) 65 | 66 | var data = { 67 | vars: vars, 68 | css: $('#less-section input:checked') .map(function () { return this.value }).toArray(), 69 | js: $('#plugin-section input:checked').map(function () { return this.value }).toArray() 70 | } 71 | 72 | if ($.isEmptyObject(data.vars) && !data.css.length && !data.js.length) return 73 | 74 | return data 75 | } 76 | 77 | function parseUrl() { 78 | var id = getQueryParam('id') 79 | 80 | if (!id) return 81 | 82 | $.ajax({ 83 | url: 'https://api.github.com/gists/' + id, 84 | type: 'GET', 85 | dataType: 'json' 86 | }) 87 | .success(function(result) { 88 | var data = JSON.parse(result.files['config.json'].content) 89 | if (data.js) { 90 | $('#plugin-section input').each(function () { 91 | $(this).prop('checked', ~$.inArray(this.value, data.js)) 92 | }) 93 | } 94 | if (data.css) { 95 | $('#less-section input').each(function () { 96 | $(this).prop('checked', ~$.inArray(this.value, data.css)) 97 | }) 98 | } 99 | if (data.vars) { 100 | for (var i in data.vars) { 101 | $('input[data-var="' + i + '"]').val(data.vars[i]) 102 | } 103 | } 104 | }) 105 | .error(function(err) { 106 | showError('Error fetching bootstrap config file', err) 107 | }) 108 | } 109 | 110 | function generateZip(css, js, fonts, complete) { 111 | if (!css && !js) return showError('Ruh roh! No Bootstrap files selected.', new Error('no Bootstrap')) 112 | 113 | var zip = new JSZip() 114 | 115 | if (css) { 116 | var cssFolder = zip.folder('css') 117 | for (var fileName in css) { 118 | cssFolder.file(fileName, css[fileName]) 119 | } 120 | } 121 | 122 | if (js) { 123 | var jsFolder = zip.folder('js') 124 | for (var fileName in js) { 125 | jsFolder.file(fileName, js[fileName]) 126 | } 127 | } 128 | 129 | if (fonts) { 130 | var fontsFolder = zip.folder('fonts') 131 | for (var fileName in fonts) { 132 | fontsFolder.file(fileName, fonts[fileName]) 133 | } 134 | } 135 | 136 | var content = zip.generate({type:"blob"}) 137 | 138 | complete(content) 139 | } 140 | 141 | function generateCustomCSS(vars) { 142 | var result = '' 143 | 144 | for (var key in vars) { 145 | result += key + ': ' + vars[key] + ';\n' 146 | } 147 | 148 | return result + '\n\n' 149 | } 150 | 151 | function generateFonts() { 152 | var glyphicons = $('#less-section [value="glyphicons.less"]:checked') 153 | if (glyphicons.length) { 154 | return __fonts 155 | } 156 | } 157 | 158 | function generateCSS() { 159 | var $checked = $('#less-section input:checked') 160 | 161 | if (!$checked.length) return false 162 | 163 | var result = {} 164 | var vars = {} 165 | var css = '' 166 | 167 | $('#less-variables-section input') 168 | .each(function () { 169 | $(this).val() && (vars[ $(this).prev().text() ] = $(this).val()) 170 | }) 171 | 172 | css += __less['variables.less'] 173 | if (vars) css += generateCustomCSS(vars) 174 | css += __less['mixins.less'] 175 | css += __less['normalize.less'] 176 | css += __less['scaffolding.less'] 177 | css += $checked 178 | .map(function () { return __less[this.value] }) 179 | .toArray() 180 | .join('\n') 181 | 182 | css = css.replace(/@import[^\n]*/gi, '') //strip any imports 183 | 184 | try { 185 | var parser = new less.Parser({ 186 | paths: ['variables.less', 'mixins.less'] 187 | , optimization: 0 188 | , filename: 'bootstrap.css' 189 | }).parse(css, function (err, tree) { 190 | if (err) { 191 | return showError('Ruh roh! Could not parse less files.', err) 192 | } 193 | result = { 194 | 'bootstrap.css' : cw + tree.toCSS(), 195 | 'bootstrap.min.css' : cw + tree.toCSS({ compress: true }) 196 | } 197 | }) 198 | } catch (err) { 199 | return showError('Ruh roh! Could not parse less files.', err) 200 | } 201 | 202 | return result 203 | } 204 | 205 | function generateJavascript() { 206 | var $checked = $('#plugin-section input:checked') 207 | if (!$checked.length) return false 208 | 209 | var js = $checked 210 | .map(function () { return __js[this.value] }) 211 | .toArray() 212 | .join('\n') 213 | 214 | return { 215 | 'bootstrap.js': js, 216 | 'bootstrap.min.js': cw + uglify(js) 217 | } 218 | } 219 | 220 | var inputsComponent = $('#less-section input') 221 | var inputsPlugin = $('#plugin-section input') 222 | var inputsVariables = $('#less-variables-section input') 223 | 224 | $('#less-section .toggle').on('click', function (e) { 225 | e.preventDefault() 226 | inputsComponent.prop('checked', !inputsComponent.is(':checked')) 227 | }) 228 | 229 | $('#plugin-section .toggle').on('click', function (e) { 230 | e.preventDefault() 231 | inputsPlugin.prop('checked', !inputsPlugin.is(':checked')) 232 | }) 233 | 234 | $('#less-variables-section .toggle').on('click', function (e) { 235 | e.preventDefault() 236 | inputsVariables.val('') 237 | }) 238 | 239 | $('[data-dependencies]').on('click', function () { 240 | if (!$(this).is(':checked')) return 241 | var dependencies = this.getAttribute('data-dependencies') 242 | if (!dependencies) return 243 | dependencies = dependencies.split(',') 244 | for (var i = 0; i < dependencies.length; i++) { 245 | var dependency = $('[value="' + dependencies[i] + '"]') 246 | dependency && dependency.prop('checked', true) 247 | } 248 | }) 249 | 250 | $('[data-dependents]').on('click', function () { 251 | if ($(this).is(':checked')) return 252 | var dependents = this.getAttribute('data-dependents') 253 | if (!dependents) return 254 | dependents = dependents.split(',') 255 | for (var i = 0; i < dependents.length; i++) { 256 | var dependent = $('[value="' + dependents[i] + '"]') 257 | dependent && dependent.prop('checked', false) 258 | } 259 | }) 260 | 261 | var $compileBtn = $('#btn-compile') 262 | var $downloadBtn = $('#btn-download') 263 | 264 | $compileBtn.on('click', function (e) { 265 | e.preventDefault() 266 | 267 | $compileBtn.attr('disabled', 'disabled') 268 | 269 | generateZip(generateCSS(), generateJavascript(), generateFonts(), function (blob) { 270 | $compileBtn.removeAttr('disabled') 271 | saveAs(blob, "bootstrap.zip") 272 | createGist(getCustomizerData()) 273 | }) 274 | }) 275 | 276 | // browser support alerts 277 | if (!window.URL && navigator.userAgent.toLowerCase().indexOf('safari') != -1) { 278 | showCallout("Looks like you're using safari, which sadly doesn't have the best support\ 279 | for HTML5 blobs. Because of this your file will be downloaded with the name \"untitled\".\ 280 | However, if you check your downloads folder, just rename this \"untitled\" file\ 281 | to \"bootstrap.zip\" and you should be good to go!") 282 | } else if (!window.URL && !window.webkitURL) { 283 | $('.bs-docs-section, .bs-sidebar').css('display', 'none') 284 | 285 | showCallout("Looks like your current browser doesn't support the Bootstrap Customizer. Please take a second\ 286 | to upgrade to a more modern browser.", true) 287 | } 288 | 289 | parseUrl() 290 | } 291 | -------------------------------------------------------------------------------- /public/res/bootstrap/assets/js/filesaver.js: -------------------------------------------------------------------------------- 1 | /* Blob.js 2 | * A Blob implementation. 3 | * 2013-06-20 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * By Devin Samarin, https://github.com/eboyjr 7 | * License: X11/MIT 8 | * See LICENSE.md 9 | */ 10 | 11 | /*global self, unescape */ 12 | /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, 13 | plusplus: true */ 14 | 15 | /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ 16 | 17 | if (typeof Blob !== "function" || typeof URL === "undefined") 18 | if (typeof Blob === "function" && typeof webkitURL !== "undefined") self.URL = webkitURL; 19 | else var Blob = (function (view) { 20 | "use strict"; 21 | 22 | var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || view.MSBlobBuilder || (function(view) { 23 | var 24 | get_class = function(object) { 25 | return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; 26 | } 27 | , FakeBlobBuilder = function BlobBuilder() { 28 | this.data = []; 29 | } 30 | , FakeBlob = function Blob(data, type, encoding) { 31 | this.data = data; 32 | this.size = data.length; 33 | this.type = type; 34 | this.encoding = encoding; 35 | } 36 | , FBB_proto = FakeBlobBuilder.prototype 37 | , FB_proto = FakeBlob.prototype 38 | , FileReaderSync = view.FileReaderSync 39 | , FileException = function(type) { 40 | this.code = this[this.name = type]; 41 | } 42 | , file_ex_codes = ( 43 | "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " 44 | + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" 45 | ).split(" ") 46 | , file_ex_code = file_ex_codes.length 47 | , real_URL = view.URL || view.webkitURL || view 48 | , real_create_object_URL = real_URL.createObjectURL 49 | , real_revoke_object_URL = real_URL.revokeObjectURL 50 | , URL = real_URL 51 | , btoa = view.btoa 52 | , atob = view.atob 53 | 54 | , ArrayBuffer = view.ArrayBuffer 55 | , Uint8Array = view.Uint8Array 56 | ; 57 | FakeBlob.fake = FB_proto.fake = true; 58 | while (file_ex_code--) { 59 | FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; 60 | } 61 | if (!real_URL.createObjectURL) { 62 | URL = view.URL = {}; 63 | } 64 | URL.createObjectURL = function(blob) { 65 | var 66 | type = blob.type 67 | , data_URI_header 68 | ; 69 | if (type === null) { 70 | type = "application/octet-stream"; 71 | } 72 | if (blob instanceof FakeBlob) { 73 | data_URI_header = "data:" + type; 74 | if (blob.encoding === "base64") { 75 | return data_URI_header + ";base64," + blob.data; 76 | } else if (blob.encoding === "URI") { 77 | return data_URI_header + "," + decodeURIComponent(blob.data); 78 | } if (btoa) { 79 | return data_URI_header + ";base64," + btoa(blob.data); 80 | } else { 81 | return data_URI_header + "," + encodeURIComponent(blob.data); 82 | } 83 | } else if (real_create_object_URL) { 84 | return real_create_object_URL.call(real_URL, blob); 85 | } 86 | }; 87 | URL.revokeObjectURL = function(object_URL) { 88 | if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { 89 | real_revoke_object_URL.call(real_URL, object_URL); 90 | } 91 | }; 92 | FBB_proto.append = function(data/*, endings*/) { 93 | var bb = this.data; 94 | // decode data to a binary string 95 | if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { 96 | var 97 | str = "" 98 | , buf = new Uint8Array(data) 99 | , i = 0 100 | , buf_len = buf.length 101 | ; 102 | for (; i < buf_len; i++) { 103 | str += String.fromCharCode(buf[i]); 104 | } 105 | bb.push(str); 106 | } else if (get_class(data) === "Blob" || get_class(data) === "File") { 107 | if (FileReaderSync) { 108 | var fr = new FileReaderSync; 109 | bb.push(fr.readAsBinaryString(data)); 110 | } else { 111 | // async FileReader won't work as BlobBuilder is sync 112 | throw new FileException("NOT_READABLE_ERR"); 113 | } 114 | } else if (data instanceof FakeBlob) { 115 | if (data.encoding === "base64" && atob) { 116 | bb.push(atob(data.data)); 117 | } else if (data.encoding === "URI") { 118 | bb.push(decodeURIComponent(data.data)); 119 | } else if (data.encoding === "raw") { 120 | bb.push(data.data); 121 | } 122 | } else { 123 | if (typeof data !== "string") { 124 | data += ""; // convert unsupported types to strings 125 | } 126 | // decode UTF-16 to binary string 127 | bb.push(unescape(encodeURIComponent(data))); 128 | } 129 | }; 130 | FBB_proto.getBlob = function(type) { 131 | if (!arguments.length) { 132 | type = null; 133 | } 134 | return new FakeBlob(this.data.join(""), type, "raw"); 135 | }; 136 | FBB_proto.toString = function() { 137 | return "[object BlobBuilder]"; 138 | }; 139 | FB_proto.slice = function(start, end, type) { 140 | var args = arguments.length; 141 | if (args < 3) { 142 | type = null; 143 | } 144 | return new FakeBlob( 145 | this.data.slice(start, args > 1 ? end : this.data.length) 146 | , type 147 | , this.encoding 148 | ); 149 | }; 150 | FB_proto.toString = function() { 151 | return "[object Blob]"; 152 | }; 153 | return FakeBlobBuilder; 154 | }(view)); 155 | 156 | return function Blob(blobParts, options) { 157 | var type = options ? (options.type || "") : ""; 158 | var builder = new BlobBuilder(); 159 | if (blobParts) { 160 | for (var i = 0, len = blobParts.length; i < len; i++) { 161 | builder.append(blobParts[i]); 162 | } 163 | } 164 | return builder.getBlob(type); 165 | }; 166 | }(self)); 167 | 168 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 169 | var saveAs=saveAs||(navigator.msSaveOrOpenBlob&&navigator.msSaveOrOpenBlob.bind(navigator))||(function(h){"use strict";var r=h.document,l=function(){return h.URL||h.webkitURL||h},e=h.URL||h.webkitURL||h,n=r.createElementNS("http://www.w3.org/1999/xhtml","a"),g=!h.externalHost&&"download" in n,j=function(t){var s=r.createEvent("MouseEvents");s.initMouseEvent("click",true,false,h,0,0,0,0,0,false,false,false,false,0,null);t.dispatchEvent(s)},o=h.webkitRequestFileSystem,p=h.requestFileSystem||o||h.mozRequestFileSystem,m=function(s){(h.setImmediate||h.setTimeout)(function(){throw s},0)},c="application/octet-stream",k=0,b=[],i=function(){var t=b.length;while(t--){var s=b[t];if(typeof s==="string"){e.revokeObjectURL(s)}else{s.remove()}}b.length=0},q=function(t,s,w){s=[].concat(s);var v=s.length;while(v--){var x=t["on"+s[v]];if(typeof x==="function"){try{x.call(t,w||t)}catch(u){m(u)}}}},f=function(t,u){var v=this,B=t.type,E=false,x,w,s=function(){var F=l().createObjectURL(t);b.push(F);return F},A=function(){q(v,"writestart progress write writeend".split(" "))},D=function(){if(E||!x){x=s(t)}if(w){w.location.href=x}else{window.open(x,"_blank")}v.readyState=v.DONE;A()},z=function(F){return function(){if(v.readyState!==v.DONE){return F.apply(this,arguments)}}},y={create:true,exclusive:false},C;v.readyState=v.INIT;if(!u){u="download"}if(g){x=s(t);n.href=x;n.download=u;j(n);v.readyState=v.DONE;A();return}if(h.chrome&&B&&B!==c){C=t.slice||t.webkitSlice;t=C.call(t,0,t.size,c);E=true}if(o&&u!=="download"){u+=".download"}if(B===c||o){w=h}if(!p){D();return}k+=t.size;p(h.TEMPORARY,k,z(function(F){F.root.getDirectory("saved",y,z(function(G){var H=function(){G.getFile(u,y,z(function(I){I.createWriter(z(function(J){J.onwriteend=function(K){w.location.href=I.toURL();b.push(I);v.readyState=v.DONE;q(v,"writeend",K)};J.onerror=function(){var K=J.error;if(K.code!==K.ABORT_ERR){D()}};"writestart progress write abort".split(" ").forEach(function(K){J["on"+K]=v["on"+K]});J.write(t);v.abort=function(){J.abort();v.readyState=v.DONE};v.readyState=v.WRITING}),D)}),D)};G.getFile(u,{create:false},z(function(I){I.remove();H()}),z(function(I){if(I.code===I.NOT_FOUND_ERR){H()}else{D()}}))}),D)}),D)},d=f.prototype,a=function(s,t){return new f(s,t)};d.abort=function(){var s=this;s.readyState=s.DONE;q(s,"abort")};d.readyState=d.INIT=0;d.WRITING=1;d.DONE=2;d.error=d.onwritestart=d.onprogress=d.onwrite=d.onabort=d.onerror=d.onwriteend=null;h.addEventListener("unload",i,false);return a}(self)); -------------------------------------------------------------------------------- /public/res/bootstrap/assets/js/holder.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Holder - 2.0 - client side image placeholders 4 | (c) 2012-2013 Ivan Malopinsky / http://imsky.co 5 | 6 | Provided under the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 7 | Commercial use requires attribution. 8 | 9 | */ 10 | 11 | var Holder = Holder || {}; 12 | (function (app, win) { 13 | 14 | var preempted = false, 15 | fallback = false, 16 | canvas = document.createElement('canvas'); 17 | 18 | //getElementsByClassName polyfill 19 | document.getElementsByClassName||(document.getElementsByClassName=function(e){var t=document,n,r,i,s=[];if(t.querySelectorAll)return t.querySelectorAll("."+e);if(t.evaluate){r=".//*[contains(concat(' ', @class, ' '), ' "+e+" ')]",n=t.evaluate(r,t,null,0,null);while(i=n.iterateNext())s.push(i)}else{n=t.getElementsByTagName("*"),r=new RegExp("(^|\\s)"+e+"(\\s|$)");for(i=0;i= 0.75) { 72 | text_height = Math.floor(text_height * 0.75 * (width/text_width)); 73 | } 74 | //Resetting font size if necessary 75 | ctx.font = "bold " + (text_height * ratio) + "px " + font; 76 | ctx.fillText(text, (width / 2), (height / 2), width); 77 | return canvas.toDataURL("image/png"); 78 | } 79 | 80 | function render(mode, el, holder, src) { 81 | var dimensions = holder.dimensions, 82 | theme = holder.theme, 83 | text = holder.text ? decodeURIComponent(holder.text) : holder.text; 84 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 85 | theme = (text ? extend(theme, { 86 | text: text 87 | }) : theme); 88 | theme = (holder.font ? extend(theme, { 89 | font: holder.font 90 | }) : theme); 91 | if (mode == "image") { 92 | el.setAttribute("data-src", src); 93 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 94 | if (fallback || !holder.auto) { 95 | el.style.width = dimensions.width + "px"; 96 | el.style.height = dimensions.height + "px"; 97 | } 98 | if (fallback) { 99 | el.style.backgroundColor = theme.background; 100 | } else { 101 | el.setAttribute("src", draw(ctx, dimensions, theme, ratio)); 102 | } 103 | } else if (mode == "background") { 104 | if (!fallback) { 105 | el.style.backgroundImage = "url(" + draw(ctx, dimensions, theme, ratio) + ")"; 106 | el.style.backgroundSize = dimensions.width + "px " + dimensions.height + "px"; 107 | } 108 | } else if (mode == "fluid") { 109 | el.setAttribute("data-src", src); 110 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 111 | if (dimensions.height.substr(-1) == "%") { 112 | el.style.height = dimensions.height 113 | } else { 114 | el.style.height = dimensions.height + "px" 115 | } 116 | if (dimensions.width.substr(-1) == "%") { 117 | el.style.width = dimensions.width 118 | } else { 119 | el.style.width = dimensions.width + "px" 120 | } 121 | if (el.style.display == "inline" || el.style.display == "") { 122 | el.style.display = "block"; 123 | } 124 | if (fallback) { 125 | el.style.backgroundColor = theme.background; 126 | } else { 127 | el.holderData = holder; 128 | fluid_images.push(el); 129 | fluid_update(el); 130 | } 131 | } 132 | }; 133 | 134 | function fluid_update(element) { 135 | var images; 136 | if (element.nodeType == null) { 137 | images = fluid_images; 138 | } else { 139 | images = [element] 140 | } 141 | for (i in images) { 142 | var el = images[i] 143 | if (el.holderData) { 144 | var holder = el.holderData; 145 | el.setAttribute("src", draw(ctx, { 146 | height: el.clientHeight, 147 | width: el.clientWidth 148 | }, holder.theme, ratio)); 149 | } 150 | } 151 | } 152 | 153 | function parse_flags(flags, options) { 154 | 155 | var ret = { 156 | theme: settings.themes.gray 157 | }, render = false; 158 | 159 | for (sl = flags.length, j = 0; j < sl; j++) { 160 | var flag = flags[j]; 161 | if (app.flags.dimensions.match(flag)) { 162 | render = true; 163 | ret.dimensions = app.flags.dimensions.output(flag); 164 | } else if (app.flags.fluid.match(flag)) { 165 | render = true; 166 | ret.dimensions = app.flags.fluid.output(flag); 167 | ret.fluid = true; 168 | } else if (app.flags.colors.match(flag)) { 169 | ret.theme = app.flags.colors.output(flag); 170 | } else if (options.themes[flag]) { 171 | //If a theme is specified, it will override custom colors 172 | ret.theme = options.themes[flag]; 173 | } else if (app.flags.text.match(flag)) { 174 | ret.text = app.flags.text.output(flag); 175 | } else if (app.flags.font.match(flag)) { 176 | ret.font = app.flags.font.output(flag); 177 | } else if (app.flags.auto.match(flag)) { 178 | ret.auto = true; 179 | } 180 | } 181 | 182 | return render ? ret : false; 183 | 184 | }; 185 | 186 | 187 | 188 | if (!canvas.getContext) { 189 | fallback = true; 190 | } else { 191 | if (canvas.toDataURL("image/png") 192 | .indexOf("data:image/png") < 0) { 193 | //Android doesn't support data URI 194 | fallback = true; 195 | } else { 196 | var ctx = canvas.getContext("2d"); 197 | } 198 | } 199 | 200 | var dpr = 1, bsr = 1; 201 | 202 | if(!fallback){ 203 | dpr = window.devicePixelRatio || 1, 204 | bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; 205 | } 206 | 207 | var ratio = dpr / bsr; 208 | 209 | var fluid_images = []; 210 | 211 | var settings = { 212 | domain: "holder.js", 213 | images: "img", 214 | bgnodes: ".holderjs", 215 | themes: { 216 | "gray": { 217 | background: "#eee", 218 | foreground: "#aaa", 219 | size: 12 220 | }, 221 | "social": { 222 | background: "#3a5a97", 223 | foreground: "#fff", 224 | size: 12 225 | }, 226 | "industrial": { 227 | background: "#434A52", 228 | foreground: "#C2F200", 229 | size: 12 230 | } 231 | }, 232 | stylesheet: ".holderjs-fluid {font-size:16px;font-weight:bold;text-align:center;font-family:sans-serif;margin:0}" 233 | }; 234 | 235 | 236 | app.flags = { 237 | dimensions: { 238 | regex: /^(\d+)x(\d+)$/, 239 | output: function (val) { 240 | var exec = this.regex.exec(val); 241 | return { 242 | width: +exec[1], 243 | height: +exec[2] 244 | } 245 | } 246 | }, 247 | fluid: { 248 | regex: /^([0-9%]+)x([0-9%]+)$/, 249 | output: function (val) { 250 | var exec = this.regex.exec(val); 251 | return { 252 | width: exec[1], 253 | height: exec[2] 254 | } 255 | } 256 | }, 257 | colors: { 258 | regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, 259 | output: function (val) { 260 | var exec = this.regex.exec(val); 261 | return { 262 | size: settings.themes.gray.size, 263 | foreground: "#" + exec[2], 264 | background: "#" + exec[1] 265 | } 266 | } 267 | }, 268 | text: { 269 | regex: /text\:(.*)/, 270 | output: function (val) { 271 | return this.regex.exec(val)[1]; 272 | } 273 | }, 274 | font: { 275 | regex: /font\:(.*)/, 276 | output: function (val) { 277 | return this.regex.exec(val)[1]; 278 | } 279 | }, 280 | auto: { 281 | regex: /^auto$/ 282 | } 283 | } 284 | 285 | for (var flag in app.flags) { 286 | if (!app.flags.hasOwnProperty(flag)) continue; 287 | app.flags[flag].match = function (val) { 288 | return val.match(this.regex) 289 | } 290 | } 291 | 292 | app.add_theme = function (name, theme) { 293 | name != null && theme != null && (settings.themes[name] = theme); 294 | return app; 295 | }; 296 | 297 | app.add_image = function (src, el) { 298 | var node = selector(el); 299 | if (node.length) { 300 | for (var i = 0, l = node.length; i < l; i++) { 301 | var img = document.createElement("img") 302 | img.setAttribute("data-src", src); 303 | node[i].appendChild(img); 304 | } 305 | } 306 | return app; 307 | }; 308 | 309 | app.run = function (o) { 310 | var options = extend(settings, o), 311 | images = [], imageNodes = [], bgnodes = []; 312 | 313 | if(typeof(options.images) == "string"){ 314 | imageNodes = selector(options.images); 315 | } 316 | else if (window.NodeList && options.images instanceof window.NodeList) { 317 | imageNodes = options.images; 318 | } else if (window.Node && options.images instanceof window.Node) { 319 | imageNodes = [options.images]; 320 | } 321 | 322 | if(typeof(options.bgnodes) == "string"){ 323 | bgnodes = selector(options.bgnodes); 324 | } else if (window.NodeList && options.elements instanceof window.NodeList) { 325 | bgnodes = options.bgnodes; 326 | } else if (window.Node && options.bgnodes instanceof window.Node) { 327 | bgnodes = [options.bgnodes]; 328 | } 329 | 330 | preempted = true; 331 | 332 | for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]); 333 | 334 | var holdercss = document.getElementById("holderjs-style"); 335 | if (!holdercss) { 336 | holdercss = document.createElement("style"); 337 | holdercss.setAttribute("id", "holderjs-style"); 338 | holdercss.type = "text/css"; 339 | document.getElementsByTagName("head")[0].appendChild(holdercss); 340 | } 341 | 342 | if (!options.nocss) { 343 | if (holdercss.styleSheet) { 344 | holdercss.styleSheet.cssText += options.stylesheet; 345 | } else { 346 | holdercss.appendChild(document.createTextNode(options.stylesheet)); 347 | } 348 | } 349 | 350 | var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); 351 | 352 | for (var l = bgnodes.length, i = 0; i < l; i++) { 353 | var src = window.getComputedStyle(bgnodes[i], null) 354 | .getPropertyValue("background-image"); 355 | var flags = src.match(cssregex); 356 | var bgsrc = bgnodes[i].getAttribute("data-background-src"); 357 | 358 | if (flags) { 359 | var holder = parse_flags(flags[1].split("/"), options); 360 | if (holder) { 361 | render("background", bgnodes[i], holder, src); 362 | } 363 | } 364 | else if(bgsrc != null){ 365 | var holder = parse_flags(bgsrc.substr(bgsrc.lastIndexOf(options.domain) + options.domain.length + 1) 366 | .split("/"), options); 367 | if(holder){ 368 | render("background", bgnodes[i], holder, src); 369 | } 370 | } 371 | } 372 | 373 | for (l = images.length, i = 0; i < l; i++) { 374 | 375 | var attr_src = attr_data_src = src = null; 376 | 377 | try{ 378 | attr_src = images[i].getAttribute("src"); 379 | attr_datasrc = images[i].getAttribute("data-src"); 380 | }catch(e){} 381 | 382 | if (attr_datasrc == null && !! attr_src && attr_src.indexOf(options.domain) >= 0) { 383 | src = attr_src; 384 | } else if ( !! attr_datasrc && attr_datasrc.indexOf(options.domain) >= 0) { 385 | src = attr_datasrc; 386 | } 387 | 388 | if (src) { 389 | var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1) 390 | .split("/"), options); 391 | if (holder) { 392 | if (holder.fluid) { 393 | render("fluid", images[i], holder, src) 394 | } else { 395 | render("image", images[i], holder, src); 396 | } 397 | } 398 | } 399 | } 400 | return app; 401 | }; 402 | 403 | contentLoaded(win, function () { 404 | if (window.addEventListener) { 405 | window.addEventListener("resize", fluid_update, false); 406 | window.addEventListener("orientationchange", fluid_update, false); 407 | } else { 408 | window.attachEvent("onresize", fluid_update) 409 | } 410 | preempted || app.run(); 411 | }); 412 | 413 | if (typeof define === "function" && define.amd) { 414 | define("Holder", [], function () { 415 | return app; 416 | }); 417 | } 418 | 419 | })(Holder, window); 420 | -------------------------------------------------------------------------------- /public/res/bootstrap/assets/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d #mq-test-1 { width: 42px; }',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(document); 4 | 5 | /*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 6 | (function(a){"use strict";function x(){u(!0)}var b={};a.respond=b,b.update=function(){},b.mediaQueriesSupported=a.matchMedia&&a.matchMedia("only all").matches,b.mediaQueriesSupported;var q,r,t,c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=c.getElementsByTagName("base")[0],l=j.getElementsByTagName("link"),m=[],n=function(){for(var b=0;l.length>b;b++){var c=l[b],d=c.href,e=c.media,f=c.rel&&"stylesheet"===c.rel.toLowerCase();d&&f&&!h[d]&&(c.styleSheet&&c.styleSheet.rawCssText?(p(c.styleSheet.rawCssText,d,e),h[d]=!0):(!/^([a-zA-Z:]*\/\/)/.test(d)&&!k||d.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&m.push({href:d,media:e}))}o()},o=function(){if(m.length){var a=m.shift();v(a.href,function(b){p(b,a.href,a.media),h[a.href]=!0,setTimeout(function(){o()},0)})}},p=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi),g=d&&d.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c;b.length&&(b+="/"),i&&(g=1);for(var j=0;g>j;j++){var k,l,m,n;i?(k=c,f.push(h(a))):(k=d[j].match(/@media *([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),m=k.split(","),n=m.length;for(var o=0;n>o;o++)l=m[o],e.push({media:l.split("(")[0].match(/(only\s+)?([a-zA-Z]+)\s?/)&&RegExp.$2||"all",rules:f.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},s=function(){var a,b=c.createElement("div"),e=c.body,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",e||(e=f=c.createElement("body"),e.style.background="none"),e.appendChild(b),d.insertBefore(e,d.firstChild),a=b.offsetWidth,f?d.removeChild(e):e.removeChild(b),a=t=parseFloat(a)},u=function(a){var b="clientWidth",h=d[b],k="CSS1Compat"===c.compatMode&&h||c.body[b]||h,m={},n=l[l.length-1],o=(new Date).getTime();if(a&&q&&i>o-q)return clearTimeout(r),r=setTimeout(u,i),void 0;q=o;for(var p in e)if(e.hasOwnProperty(p)){var v=e[p],w=v.minw,x=v.maxw,y=null===w,z=null===x,A="em";w&&(w=parseFloat(w)*(w.indexOf(A)>-1?t||s():1)),x&&(x=parseFloat(x)*(x.indexOf(A)>-1?t||s():1)),v.hasquery&&(y&&z||!(y||k>=w)||!(z||x>=k))||(m[v.media]||(m[v.media]=[]),m[v.media].push(f[v.rules]))}for(var B in g)g.hasOwnProperty(B)&&g[B]&&g[B].parentNode===j&&j.removeChild(g[B]);for(var C in m)if(m.hasOwnProperty(C)){var D=c.createElement("style"),E=m[C].join("\n");D.type="text/css",D.media=C,j.insertBefore(D,n.nextSibling),D.styleSheet?D.styleSheet.cssText=E:D.appendChild(c.createTextNode(E)),g.push(D)}},v=function(a,b){var c=w();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},w=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}();n(),b.update=n,a.addEventListener?a.addEventListener("resize",x,!1):a.attachEvent&&a.attachEvent("onresize",x)})(this); 7 | -------------------------------------------------------------------------------- /public/res/bootstrap/dist/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,0%,#e6e6e6,100%);background-image:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background-image:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;border-color:#e0e0e0;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0)}.btn-default:active,.btn-default.active{background-color:#e6e6e6;border-color:#e0e0e0}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;border-color:#2d6ca2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.btn-primary:active,.btn-primary.active{background-color:#3071a9;border-color:#2d6ca2}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;border-color:#419641;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.btn-success:active,.btn-success.active{background-color:#449d44;border-color:#419641}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;border-color:#eb9316;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.btn-warning:active,.btn-warning.active{background-color:#ec971f;border-color:#eb9316}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;border-color:#c12e2a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.btn-danger:active,.btn-danger.active{background-color:#c9302c;border-color:#c12e2a}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;border-color:#2aabd2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.btn-info:active,.btn-info.active{background-color:#31b0d5;border-color:#2aabd2}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff,0%,#f8f8f8,100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar .navbar-nav>.active>a{background-color:#f8f8f8}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c,0%,#222,100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0)}.navbar-inverse .navbar-nav>.active>a{background-color:#222}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#c8e5bc,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#b9def0,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#f8efc0,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede,0%,#e7c3c3,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca,0%,#3278b3,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5,0%,#e8e8e8,100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#d0e9c6,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#c4e3f3,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#faf2cc,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede,0%,#ebcccc,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} -------------------------------------------------------------------------------- /public/res/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/res/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/res/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/res/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/res/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WillyJimmyDev/simple-quiz/53b68b3e31ea60f4e342d280a805d8425f1d4363/public/res/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/res/css/quiz.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | * Simple-Quiz by @elanman 4 | * Copyright 2013 Ben Hall. 5 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | */ 8 | 9 | /*.navbar-static-top { 10 | margin-bottom: 19px; 11 | }*/ 12 | 13 | /* Pad the edges of the mobile views a bit */ 14 | .quiz { 15 | padding-left: 15px; 16 | padding-right: 15px; 17 | } 18 | #intro { 19 | border-left:1px #ccc dashed; 20 | } 21 | #leaders-score { 22 | padding:10px; 23 | background: #f8f8f8; 24 | margin-bottom: 20px; 25 | } 26 | ul { 27 | list-style: none; 28 | } 29 | #finalscore { 30 | text-align:center; 31 | } 32 | #finalscore h2.userscore { 33 | font-size: 4em; 34 | } 35 | #finalscore p#verdict { 36 | font-size: 2em; 37 | } 38 | li.correct,li.correctuser { 39 | color: green; 40 | } 41 | li.wrong { 42 | color: red; 43 | } 44 | strong.currentuser { 45 | color:green; 46 | } 47 | .form-signin { 48 | max-width: 330px; 49 | padding: 15px; 50 | margin: 0 auto; 51 | } 52 | .form-signin .form-signin-heading, 53 | .form-signin .checkbox { 54 | margin-bottom: 10px; 55 | } 56 | .form-signin .checkbox { 57 | font-weight: normal; 58 | } 59 | .form-signin .form-control { 60 | position: relative; 61 | font-size: 16px; 62 | height: auto; 63 | padding: 10px; 64 | -webkit-box-sizing: border-box; 65 | -moz-box-sizing: border-box; 66 | box-sizing: border-box; 67 | } 68 | .form-signin .form-control:focus { 69 | z-index: 2; 70 | } 71 | .form-signin input[type="text"] { 72 | margin-bottom: -1px; 73 | border-bottom-left-radius: 0; 74 | border-bottom-right-radius: 0; 75 | } 76 | .form-signin input[type="password"] { 77 | margin-bottom: 10px; 78 | border-top-left-radius: 0; 79 | border-top-right-radius: 0; 80 | } 81 | #contextual, #contextual2 {display:none;} 82 | a#editor {cursor:pointer;} 83 | #updater, #ajaxupdater {display:none;position: absolute;width:30%; text-align: center;left:25%;} 84 | p.signed {padding-right:20px;} 85 | .helper { display:none;} 86 | #submitstart{padding: 10px 30px;} -------------------------------------------------------------------------------- /public/res/js/admin.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $('#updater').fadeIn('slow').delay(2000).fadeOut('slow'); 3 | var context = $('#contextual'); 4 | var context2 = $('#contextual2'); 5 | var addanswer = $('#addanswer'); 6 | var addquestion = $('#addquestion'); 7 | var addquiz = $('#addquiz'); 8 | var questionaddform = $('#questionadd'); 9 | var aform = $('form#answeredit'); 10 | var saveprompt = "
Click 'Save' to make the changes permanent.
"; 11 | 12 | $('table#newanswers').on('click', '.remove', function() { 13 | var parenttr = $(this).parents('tr'); 14 | if (parenttr.find('input.correct').is(':checked')) { 15 | context2.html('
You can\'t delete an answer if it is marked as correct.
'); 16 | context2.show().delay( 2000 ).fadeOut( 400 ); 17 | } else { 18 | parenttr.fadeOut(800).remove(); 19 | $.each( $('.answer-row:visible'), function(index, value) { 20 | $(this).find('.correct').val(index); 21 | console.log(index + 1); 22 | }); 23 | context.html(saveprompt); 24 | context.fadeIn(); 25 | } 26 | 27 | }); 28 | 29 | $('table#answers').on('click', '.remove', function() { 30 | var parenttr = $(this).parents('tr'); 31 | if (parenttr.find('input.correct').is(':checked')) { 32 | context2.html('
You can\'t delete an answer if it is marked as correct.
'); 33 | context2.show().delay( 2000 ).fadeOut( 400 ); 34 | } else { 35 | parenttr.fadeOut(800).remove(); 36 | $.each( $('.answer-row:visible'), function(index, value) { 37 | $(this).find('.correct').val(index); 38 | console.log(index + 1); 39 | }); 40 | context.html(saveprompt); 41 | context.fadeIn(); 42 | } 43 | 44 | }); 45 | 46 | $('table#questions').on('click', '.remove', function() { 47 | 48 | var questionid = $(this).attr("data-question-id"); 49 | var quizid = $(this).attr("data-quiz-id"); 50 | 51 | if (window.confirm("This can't be undone. OK?") ) { 52 | 53 | var parenttr = $(this).parents('tr.question'); 54 | 55 | parenttr.find('td').html(''); 56 | 57 | $.ajax({ 58 | url: location.pathname, 59 | type: "POST", 60 | cache: false, 61 | data : {'_METHOD' : 'DELETE', quizid : quizid, questionid : questionid}, 62 | dataType: "json", 63 | success: function(response) { 64 | if (typeof response.success !== 'undefined') { 65 | 66 | parenttr.fadeOut('slow').remove(); 67 | $.each( $('tr.question:visible'), function(index, value) { 68 | $(this).find('.edit').attr("data-question-id",index); 69 | $(this).find('.remove').attr("data-question-id",index); 70 | var regex = /question\/\d+\/edit/; 71 | index++; 72 | var oldhref = $(this).find('.answerlink').attr("href"); 73 | var newhref = oldhref.replace(regex, "question/" + index + "/edit"); 74 | $(this).find('.answerlink').attr("href", newhref); 75 | 76 | }); 77 | // flash success message 78 | $('#ajaxupdater').addClass("alert-success").html(response.success).show('slow').delay(2000).hide('slow'); 79 | } else { 80 | $('#ajaxupdater').addClass("alert-danger").html(response.error).show('slow').delay(2000).hide('slow'); 81 | } 82 | } 83 | }); 84 | } 85 | 86 | 87 | }); 88 | 89 | $('table#questions').on('click', 'button.edit', function() { 90 | var id = $(this).attr("data-question-id"); 91 | var q = $(this).parents('tr').find('td.question').text(); 92 | $('#questioninput').val(q); 93 | $('#questionid').val(id); 94 | $('#qmodal').modal(); 95 | 96 | }); 97 | 98 | $('table#quizzes').on('click', '.remove', function() { 99 | 100 | //console.log('http://' + location.hostname + '/admin/quiz/'); 101 | //console.log(location.pathname + 'quiz/'); 102 | //console.log(location.pathname); 103 | var quizid = $(this).attr("data-quiz-id"); 104 | 105 | if (window.confirm("This can't be undone. OK?") ) { 106 | 107 | var parenttr = $(this).parents('tr.quiz'); 108 | 109 | parenttr.find('td').html(''); 110 | 111 | $.ajax({ 112 | url: location.pathname + 'quiz/', 113 | type: "POST", 114 | cache: false, 115 | data : {'_METHOD' : 'DELETE', quizid : quizid}, 116 | dataType: "json", 117 | success: function(response) { 118 | if (typeof response.success !== 'undefined') { 119 | 120 | parenttr.fadeOut('slow').remove(); 121 | // flash success message 122 | $('#ajaxupdater').addClass("alert-success").html(response.success).show('slow').delay(2000).hide('slow'); 123 | } else { 124 | $('#ajaxupdater').addClass("alert-danger").html(response.error).show('slow').delay(2000).hide('slow'); 125 | } 126 | } 127 | }); 128 | } 129 | 130 | 131 | }); 132 | 133 | // the 'save changes' button inside the modal 134 | $('#questionedit').on('submit', function(e) { 135 | if ($('#questioninput').val() === '') { 136 | e.preventDefault(); 137 | $('#questionedit .helper').fadeIn('slow').delay(2000).fadeOut('slow'); 138 | } 139 | }); 140 | 141 | //the button to add another answer for this question 142 | addanswer.on('click', function() { 143 | $.each( $('.answer-row:visible'), function(index, value) { 144 | $(this).find('.correct').val(index); 145 | 146 | }); 147 | var numanswers = $('.answer-row:visible').length; 148 | var newansweritem = $('tr.template').clone().removeClass('template'); 149 | newansweritem.find('.answerinput').attr('name', 'answer[]'); 150 | newansweritem.find('.correct').val(numanswers); 151 | 152 | $('tbody').append(newansweritem); 153 | newansweritem.fadeIn(800); 154 | context.html(saveprompt); 155 | context.fadeIn(); 156 | }); 157 | 158 | //the button to add another question for this quiz 159 | addquestion.on('click', function() { 160 | $('#q-add-modal').modal(); 161 | }); 162 | 163 | //the button to add another question for this quiz 164 | addquiz.on('click', function() { 165 | $('#quiz-add-modal').modal(); 166 | }); 167 | 168 | // on answer form submission 169 | aform.on('submit', function(e) { 170 | 171 | $.each( $('.answer-row:visible'), function(index, value) { 172 | if ( $(this).find("input[type='text']").val() === '' ) { 173 | console.log("empty input"); 174 | e.preventDefault(); 175 | context2.html('
Answers can\'t be empty.
'); 176 | context2.show().delay( 2000 ).fadeOut( 400 ); 177 | } 178 | 179 | }); 180 | }); 181 | 182 | //add a new question modal 183 | questionaddform.on('submit', function(e) { 184 | // if question text is empty 185 | if ($('#newquestioninput').val() === '') { 186 | e.preventDefault(); 187 | $('#questionadd .helper').fadeIn('slow').delay(2000).fadeOut('slow'); 188 | } 189 | //if any of the answers is empty 190 | $.each( $('#newanswers .answer-row:visible'), function(index, value) { 191 | if ( $(this).find("input[type='text']").val() === '' ) { 192 | console.log("empty input"); 193 | e.preventDefault(); 194 | } 195 | 196 | }); 197 | }); 198 | 199 | 200 | $('#editquiz').on('click', function() { 201 | $('#quiz-edit-modal').modal(); 202 | }) 203 | 204 | }); -------------------------------------------------------------------------------- /public/res/js/form.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $('#submit').prop('disabled',true); 3 | var inputs = $('#questionBox input'); 4 | $.each(inputs, function() { 5 | $(this).on('click', function() { 6 | $('#submit').prop('disabled',false); 7 | }); 8 | }); 9 | }); -------------------------------------------------------------------------------- /public/res/js/general.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $('#updater').fadeIn('slow').delay(2000).fadeOut('slow'); 3 | }); 4 | -------------------------------------------------------------------------------- /public/res/js/login.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $('#register-button').on('click', function() { 3 | $('#login-form').slideUp(); 4 | $('#register-form').slideDown(); 5 | return false; 6 | }); 7 | }); -------------------------------------------------------------------------------- /public/res/js/start.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $('#questionBox').on('submit',function() { 3 | return checkForm(); 4 | }); 5 | $('#username').on('focus', function() { 6 | if ( $('#helper').text() !== '' ) { 7 | $('#helper').text(''); 8 | } 9 | }); 10 | }); 11 | 12 | function checkForm() { 13 | var username = $('#username').val(); 14 | if ((username === '') || (username === 'Username') || (username.length < 3) || (username.length > 10)) { 15 | $('#helper').text('To register, please enter a username between 3 and 10 characters in length'); 16 | return false; 17 | } 18 | return true; 19 | } 20 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /routes/admin.php: -------------------------------------------------------------------------------- 1 | session->get('user') instanceof \SimpleQuiz\Utils\User\AdminUser) 10 | { 11 | $errors['loginerror'] = 'You do not have Administrator access.'; 12 | $app->session->set('urlRedirect', $app->request()->getPathInfo()); 13 | $app->flash('errors', $errors); 14 | $app->redirect($app->request->getRootUri() . '/login/'); 15 | } 16 | } 17 | else 18 | { 19 | //stops non-registered users and admin user from taking quizzes too 20 | if (! $app->session->get('user') instanceof \SimpleQuiz\Utils\User\EndUser) 21 | { 22 | $errors['loginerror'] = 'You need to login to take a quiz'; 23 | $app->session->set('urlRedirect', $app->request()->getPathInfo()); 24 | $app->flash('errors', $errors); 25 | $app->redirect($app->request->getRootUri() . '/login/'); 26 | } 27 | } 28 | }; 29 | }; 30 | 31 | $app->get("/logout/", function () use ($app) { 32 | $session = $app->session; 33 | $session->end(); 34 | $app->redirect($app->request->getRootUri().'/'); 35 | }); 36 | 37 | $app->get('/admin/', $authenticate($app, true), function () use ($app) { 38 | 39 | $simple = $app->simple; 40 | $quizzes = $simple->getQuizzes(false); 41 | $categories = $simple->getCategories(false); 42 | 43 | $app->render('admin/index.php', array('quizzes' => $quizzes, 'categories' => $categories)); 44 | }); 45 | 46 | $app->post("/admin/quiz/", $authenticate($app, true), function() use ($app) { 47 | 48 | $quizmeta = array(); 49 | 50 | $quizname = trim($app->request->post('quizname')); 51 | $quizdescription = trim($app->request->post('description')); 52 | $quizcategory = trim($app->request->post('category')); 53 | $active = (int) trim($app->request()->post('active')); 54 | 55 | if ( ($quizname !== '') && ($quizdescription !== '') ) { 56 | $quizmeta['name'] = ucwords($quizname); 57 | $quizmeta['description'] = $quizdescription; 58 | $quizmeta['category'] = $quizcategory; 59 | $quizmeta['active'] = $active; 60 | 61 | $simple = $app->simple; 62 | 63 | if ($simple->addQuiz($quizmeta)) { 64 | $app->flash('success', 'Quiz has been created successfully'); 65 | 66 | $app->redirect($app->request->getRootUri().'/admin/'); 67 | } else { 68 | //problem adding quiz 69 | $app->flash('error', 'Problem creating the quiz'); 70 | $app->redirect($app->request->getRootUri().'/admin/'); 71 | } 72 | } else { 73 | //problem with post inputs 74 | $app->flash('error', 'Problem creating the quiz. Something wrong wth inputs'); 75 | $app->redirect($app->request->getRootUri().'/admin/'); 76 | } 77 | 78 | }); 79 | 80 | $app->put("/admin/quiz/", $authenticate($app, true), function() use ($app) { 81 | 82 | $quizmeta = array(); 83 | 84 | $quizid = trim($app->request->put('quizid')); 85 | $quizname = trim($app->request->put('quizname')); 86 | $quizdescription = trim($app->request->put('description')); 87 | $quizcategory = trim($app->request->post('category')); 88 | $active = (int) trim($app->request()->put('active')); 89 | 90 | if ( ($quizname !== '') && ($quizdescription !== '') && (ctype_digit($quizid)) ) { 91 | 92 | $quizmeta['id'] = $quizid; 93 | $quizmeta['name'] = ucwords($quizname); 94 | $quizmeta['description'] = $quizdescription; 95 | $quizmeta['category'] = $quizcategory; 96 | $quizmeta['active'] = $active; 97 | 98 | $simple = $app->simple; 99 | 100 | if ($simple->updateQuiz($quizmeta)) { 101 | $app->flash('success', 'Quiz has been updated'); 102 | 103 | $app->redirect($app->request->getRootUri().'/admin/'); 104 | } else { 105 | //problem adding quiz 106 | $app->flash('error', 'Problem updating the quiz'); 107 | $app->redirect($app->request->getRootUri().'/admin/'); 108 | } 109 | } else { 110 | //problem with post inputs 111 | $app->flash('error', 'Problem updating the quiz. Something wrong wth inputs'); 112 | $app->redirect($app->request->getRootUri().'/admin/'); 113 | } 114 | 115 | }); 116 | 117 | $app->delete("/admin/quiz/", $authenticate($app, true), function() use ($app) { 118 | 119 | $quizid = trim($app->request->post('quizid')); 120 | 121 | if (ctype_digit($quizid) ) { 122 | 123 | $simple = $app->simple; 124 | try { 125 | $simple->deleteQuiz($quizid); 126 | } catch (Exception $e ) { 127 | echo json_encode(array('error' => $e->getMessage())); 128 | } 129 | echo json_encode(array('success' => 'Quiz has been deleted successfully')); 130 | $app->stop(); 131 | 132 | } else { 133 | echo json_encode(array('error' => 'non-int quiz')); 134 | } 135 | 136 | }); 137 | 138 | $app->get("/admin/quiz/:id/", $authenticate($app, true), function($id) use ($app) { 139 | 140 | $quiz = $app->quiz; 141 | 142 | if ($quiz->setId($id)) { 143 | $quiz->populateQuestions(); 144 | $quiz->populateUsers(); 145 | $categories = $app->simple->getCategories(false); 146 | 147 | $app->render('admin/quiz.php', array('quiz' => $quiz, 'categories' => $categories)); 148 | } 149 | 150 | })->conditions(array('id' => '\d+')); 151 | 152 | 153 | $app->put("/admin/quiz/:id/", $authenticate($app, true), function($id) use ($app) { 154 | 155 | $questionid = $app->request->put('questionid'); 156 | $text = $app->request->put('questiontext'); 157 | 158 | if (! ctype_digit($id)) { 159 | $app->redirect($app->request->getRootUri().'/admin/'); 160 | } 161 | 162 | $quiz = $app->quiz; 163 | 164 | if ($quiz->setId($id)) { 165 | 166 | $quiz->populateQuestions(); 167 | 168 | $categories = $app->simple->getCategories(false); 169 | 170 | if ( (! ctype_digit($questionid)) || (trim($text) == '') ) { 171 | $app->redirect($app->request->getRootUri().'/admin/'); 172 | } 173 | 174 | try { 175 | $quiz->updateQuestion($questionid, $text); 176 | $app->flashnow('success', 'Question saved successfully'); 177 | } catch (Exception $e ) { 178 | $app->flashnow('error', $e->getMessage()); 179 | } 180 | $quiz->populateUsers(); 181 | $app->render('admin/quiz.php', array('quiz' => $quiz, 'categories' => $categories)); 182 | 183 | } 184 | 185 | }); 186 | 187 | $app->post("/admin/quiz/:id/", $authenticate($app, true), function($id) use ($app) { 188 | 189 | if (! ctype_digit($id)) { 190 | $app->redirect($app->request->getRootUri().'/admin/'); 191 | } 192 | 193 | $quiz = $app->quiz; 194 | 195 | $question = trim($app->request->post('questiontext')); 196 | $correct = (int) trim($app->request()->post('correct')); 197 | $answerarray = $app->request()->post('answer'); 198 | 199 | if ($quiz->setId($id)) { 200 | $quiz->populateQuestions(); 201 | $categories = $app->simple->getCategories(false); 202 | $i = 0; 203 | foreach ($answerarray as $answer) { 204 | if (trim($answer) == '') { 205 | $app->flashnow('error', "Answers can't be empty"); 206 | $app->render('admin/quiz.php', array('quiz' => $quiz, 'categories' => $categories)); 207 | $app->stop(); 208 | } 209 | if ($i == $correct) { 210 | $correctAnswer = 1; 211 | } else { 212 | $correctAnswer = 0; 213 | } 214 | $answers[] = array($answer, $correctAnswer); 215 | 216 | $i++; 217 | } 218 | try { 219 | $quiz->addQuestion($question, 'radio', $answers); 220 | $app->flashnow('success', 'New Question saved successfully'); 221 | } catch (Exception $e ) { 222 | $app->flashnow('error', 'An error occurred creating a new question'); 223 | $app->flashnow('error', $e->getMessage()); 224 | } 225 | $quiz->populateUsers(); 226 | 227 | $app->render('admin/quiz.php', array('quiz' => $quiz, 'categories' => $categories)); 228 | } else { 229 | echo 'oops'; 230 | } 231 | 232 | }); 233 | 234 | $app->delete("/admin/quiz/:id/", $authenticate($app, true), function($id) use ($app) { 235 | 236 | $questionid = $app->request->post('questionid'); 237 | 238 | if (! ctype_digit($id)) { 239 | $app->redirect($app->request->getRootUri().'/admin/'); 240 | } 241 | 242 | $quiz = $app->quiz; 243 | 244 | if ($quiz->setId($id)) { 245 | 246 | try { 247 | $quiz->deleteQuestion($questionid); 248 | } catch (Exception $e ) { 249 | echo json_encode(array('error' => $e->getMessage())); 250 | } 251 | echo json_encode(array('success' => 'Question successfully deleted')); 252 | $app->stop(); 253 | } 254 | 255 | }); 256 | 257 | $app->get("/admin/quiz/:quizid/question/:questionid/edit/", $authenticate($app, true), function($quizid, 258 | $questionid) use ($app) { 259 | 260 | $quiz = $app->quiz; 261 | 262 | if ($quiz->setId($quizid)) { 263 | $quiz->populateQuestions(); 264 | $question = $quiz->getQuestion($questionid); 265 | $answers = $quiz->getAnswers($questionid); 266 | $app->render('admin/editanswers.php', array('quizid' => $quizid,'questionid' => $questionid, 'question' => $question, 'answers' => $answers)); 267 | } else { 268 | echo 'oops'; 269 | } 270 | 271 | })->conditions(array('quizid' => '\d+', 'questionid' => '\d+')); 272 | 273 | $app->put("/admin/quiz/:quizid/question/:questionid/edit/", $authenticate($app, true), function($quizid, 274 | $questionid) use ($app) { 275 | 276 | if ( (! ctype_digit($quizid)) || (! ctype_digit($questionid))) { 277 | $app->redirect($app->request->getRootUri().'/admin/'); 278 | } 279 | 280 | $quiz = $app->quiz; 281 | 282 | $correct = (int) trim($app->request()->put('correct')); 283 | $answerarray = $app->request()->put('answer'); 284 | 285 | if ($quiz->setId($quizid)) { 286 | $quiz->populateQuestions(); 287 | $question = $quiz->getQuestion($questionid); 288 | $i = 0; 289 | foreach ($answerarray as $answer) { 290 | if (trim($answer) == '') { 291 | $app->flashnow('error', 'Answers can\'t be empty'); 292 | $answers = $quiz->getAnswers($questionid); 293 | $app->render('admin/editanswers.php', array('quizid' => $quizid,'questionid' => $questionid, 'question' => $question, 'answers' => $answers)); 294 | $app->stop(); 295 | } 296 | if ($i == $correct) { 297 | $correctAnswer = 1; 298 | } else { 299 | $correctAnswer = 0; 300 | } 301 | $answers[] = array($answer, $correctAnswer); 302 | 303 | $i++; 304 | } 305 | try { 306 | $quiz->updateAnswers($answers, $questionid); 307 | $app->flashnow('success', 'Answers saved successfully'); 308 | } catch (Exception $e ) { 309 | $app->flashnow('error', 'An error occurred'); 310 | } 311 | $answers = $quiz->getAnswers($questionid); 312 | $app->render('admin/editanswers.php', array('quizid' => $quizid,'questionid' => $questionid, 'question' => $question, 'answers' => $answers)); 313 | } else { 314 | echo 'oops'; 315 | } 316 | }); 317 | -------------------------------------------------------------------------------- /routes/public.php: -------------------------------------------------------------------------------- 1 | get('/', function () use ($app) { 3 | $simple = $app->simple; 4 | $quizzes = $simple->getQuizzes(); 5 | $categories = $simple->getCategories(); 6 | 7 | $session = $app->session; 8 | 9 | $app->render('index.php', array('quizzes' => $quizzes, 'categories' => $categories, 'session' => $session)); 10 | }); 11 | 12 | $app->get('/requirements/', function () use ($app) { 13 | 14 | $installer = $app->installer; 15 | $requirements = $installer->getRequirements(); 16 | $simple = $app->simple; 17 | $categories = $simple->getCategories(); 18 | 19 | $app->render('requirements.php', array( 'categories' => $categories,'requirements' => $requirements)); 20 | 21 | }); 22 | 23 | //route for 'get' on login or register 24 | $app->get('/:route/', function () use ($app) { 25 | 26 | $flash = $app->view()->getData('flash'); 27 | $errors = isset($flash['errors']) ? $flash['errors'] : false; 28 | 29 | $simple = $app->simple; 30 | $quizzes = $simple->getQuizzes(); 31 | $categories = $simple->getCategories(); 32 | 33 | $session = $app->session; 34 | if ($session->get('user')) 35 | { 36 | $app->redirect($app->request->getRootUri() . '/'); 37 | } 38 | 39 | $app->render('login.php', array('quizzes' => $quizzes, 'categories' => $categories, 'session' => $session, 40 | 'errors' => $errors)); 41 | })->conditions(array("route" => "(login|register)")); 42 | 43 | $app->post('/login/', function () use ($app) { 44 | 45 | $simple = $app->simple; 46 | $session = $app->session; 47 | $errors = array(); 48 | 49 | $email = trim($app->request()->post('email')); 50 | $password = trim($app->request()->post('password')); 51 | 52 | //need to check for 'emptiness' of inputs and display message instead of querying db 53 | if ((! empty($email)) && (! empty($password) ) ) 54 | { 55 | if (! filter_var($email, FILTER_VALIDATE_EMAIL)) 56 | { 57 | $errors['loginerror'] = "The email address was invalid. Please try again."; 58 | } 59 | else 60 | { 61 | //pull details for this registered email 62 | if ($authsql = \ORM::for_table('users')->select_many('id','pass','name','level')->where('email', 63 | $email)->find_one()) 64 | { 65 | //verify the password against hash 66 | if (! password_verify($password, $authsql->pass)) 67 | { 68 | $errors['loginerror'] = "The email or password do not match those in our system. Please try again."; 69 | } 70 | else 71 | { 72 | if ($authsql->level == 1) 73 | { 74 | //we have an admin user 75 | $user = new \SimpleQuiz\Utils\User\AdminUser($email, $authsql->name); 76 | } 77 | else 78 | { 79 | //registered user 80 | $user = new \SimpleQuiz\Utils\User\EndUser($email, $authsql->name); 81 | } 82 | 83 | $user->setId($authsql->id); 84 | 85 | $session->set('user', $user); 86 | $session->regenerate(); 87 | } 88 | } 89 | else 90 | { 91 | $errors['loginerror'] = "The email or password do not match those in our system. Please try again."; 92 | } 93 | } 94 | } 95 | else 96 | { 97 | $errors['loginerror'] = "Please check your email address and password and try again."; 98 | } 99 | 100 | if (count($errors) > 0) 101 | { 102 | $app->flash('errors', $errors); 103 | $session->remove('user'); 104 | $app->redirect($app->request->getRootUri() . '/login/'); 105 | } 106 | 107 | $simple::redirect($app, $session); 108 | }); 109 | 110 | $app->post('/register/', function () use ($app) { 111 | 112 | $simple = $app->simple; 113 | $quizzes = $simple->getQuizzes(true); 114 | $categories = $simple->getCategories(); 115 | $session = $app->session; 116 | $errors = array(); 117 | 118 | $username = trim($app->request()->post('username')); 119 | $email = trim($app->request()->post('email')); 120 | $password = trim($app->request()->post('regpassword')); 121 | $confpassword = trim($app->request()->post('regpasswordconf')); 122 | 123 | if ((! empty($email)) && (! empty($password) ) && ($password == $confpassword) ) 124 | { 125 | if (! filter_var($email, FILTER_VALIDATE_EMAIL)) 126 | { 127 | $errors['registererror'] = "The email address was invalid. Please try again."; 128 | } 129 | else 130 | { 131 | $user = new \SimpleQuiz\Utils\User\EndUser($email, $username); 132 | $user->setPassword(password_hash($password,1)); 133 | 134 | try 135 | { 136 | $user = $simple->registerUser($user); 137 | 138 | $mailer = new \SimpleQuiz\Utils\Base\Mailer(); 139 | $mailer->sendConfirmationEmail($user); 140 | } 141 | catch (\Swift_TransportException $e) 142 | { 143 | $errors['registererror'] = 'There was an error trying to send a confirmation email. Please contact an 144 | Administrator'; 145 | } 146 | catch (\SimpleQuiz\Utils\Exceptions\RegisterException $e) 147 | { 148 | $errors['registererror'] = $e->getMessage(); 149 | } 150 | } 151 | } 152 | else 153 | { 154 | $errors['registererror'] = "There was an error with your username or password. Please try again."; 155 | } 156 | 157 | if (count($errors) > 0) 158 | { 159 | $app->flash('errors', $errors); 160 | $session->remove('user'); 161 | $app->redirect($app->request->getRootUri() . '/login/'); 162 | } 163 | 164 | $app->render('emailsent.php', array('quizzes' => $quizzes, 'categories' => $categories, 165 | 'session' => $session)); 166 | }); 167 | 168 | $app->get('/confirm-registration/:hash', function($hash) use ($app) { 169 | 170 | $simple = $app->simple; 171 | $quizzes = $simple->getQuizzes(true); 172 | $categories = $simple->getCategories(); 173 | $session = $app->session; 174 | 175 | $user = \ORM::for_table('users')->select_many('id', 'hashstamp')->where('confirmhash', $hash)->where_raw('TIMESTAMPDIFF(HOUR, hashstamp, NOW()) < 24')->find_one(); 176 | if ($user) { 177 | 178 | $user->set('hashstamp'); //null 179 | $user->set('confirmhash'); //null 180 | $user->set('confirmed', 1); 181 | $user->save(); 182 | 183 | $app->render('emailconfirmed.php', array( 184 | 'quizzes' => $quizzes, 185 | 'categories' => $categories, 186 | 'session' => $session 187 | )); 188 | } else { 189 | //no user with that hash or timestamp expired. Redirect to home page 190 | $app->redirect($app->request->getRootUri().'/'); 191 | } 192 | })->conditions(array("hash" => "[0-9a-f]{40}")); 193 | 194 | $app->get('/categories/', function () use ($app) { 195 | $simple = $app->simple; 196 | $quizzes = $simple->getQuizzes(true); 197 | $categories = $simple->getCategories(); 198 | 199 | $session = $app->session; 200 | 201 | $app->render('index.php', array('quizzes' => $quizzes, 'categories' => $categories, 'session' => $session)); 202 | }); 203 | 204 | $app->get('/categories/:id', function ($id) use ($app) { 205 | $simple = $app->simple; 206 | $category = $simple->getCategory($id); 207 | $quizzes = $simple->getCategoryQuizzes($id); 208 | $categories = $simple->getCategories(); 209 | 210 | $session = $app->session; 211 | if( $category ) 212 | { 213 | $app->render('category.php', array('category' => $category, 'quizzes' => $quizzes, 'categories' => $categories, 'session' => $session)); 214 | } 215 | else 216 | { 217 | $quizzes = $simple->getQuizzes(true); 218 | $app->render('index.php', array('quizzes' => $quizzes, 'categories' => $categories, 'session' => $session)); 219 | } 220 | })->conditions(array('id' => '\d+')); 221 | 222 | $app->get('/quiz/:id/', function ($id) use ($app) { 223 | 224 | $flash = $app->view()->getData('flash'); 225 | $error = null; 226 | 227 | if (isset($flash['usererror'])) { 228 | $error = $flash['usererror']; 229 | } 230 | if (isset($flash['quizerror'])) { 231 | $error = $flash['quizerror']; 232 | } 233 | 234 | $quiz = $app->quiz; 235 | 236 | $session = $app->session; 237 | 238 | $simple = $app->simple; 239 | $categories = $simple->getCategories(); 240 | 241 | if ($quiz->setId($id)) { 242 | /** 243 | * @todo remove all session shite below and store in db 244 | */ 245 | $quiz->populateQuestions(); 246 | $quiz->populateUsers(); 247 | $session->set('quizid', $id); 248 | $session->set('score', 0); 249 | $session->set('correct', array()); 250 | $session->set('wrong', array()); 251 | $session->set('finished', 'no'); 252 | $session->set('num', 0); 253 | $session->set('last', null); 254 | $session->set('timetaken', null); 255 | $session->set('starttime', null); 256 | 257 | $app->render('quiz/quiz.php', array('quiz' => $quiz, 'categories' => $categories, 'session' => $session, 'error' => $error)); 258 | } else { 259 | $app->flashnow('quizerror','There has been an error. Please return to the main quiz menu and try again'); 260 | $app->render('quiz/error.php', array( 'categories' => $categories,'session' => $session)); 261 | } 262 | })->conditions(array('id' => '\d+')); 263 | 264 | $app->post('/quiz/process/', $authenticate($app), function () use ($app) { 265 | 266 | $simple = $app->simple; 267 | $id = $app->request()->post('quizid'); 268 | $starter = $app->request()->post('starter'); 269 | 270 | if (! ctype_digit($id)) { 271 | $app->redirect($app->request->getRootUri().'/'); 272 | } 273 | 274 | $session = $app->session; 275 | 276 | if (isset($starter)) //beginning of the quiz 277 | { 278 | if ($simple->quizUserExists($id, $session->get('user')->getId())) 279 | { 280 | $app->flash('quizerror', "You've already taken this quiz"); 281 | $app->redirect($app->request->getRootUri() . '/quiz/' . $id); 282 | } 283 | $session->set('score', 0); 284 | $session->set('correct', array()); 285 | $session->set('wrong', array()); 286 | $session->set('finished', 'no'); 287 | $session->set('num', 0); 288 | $session->set('starttime', date('Y-m-d H:i:s')); 289 | 290 | $app->redirect($app->request->getRootUri() . '/quiz/' . $id . '/test'); 291 | } 292 | else 293 | { //quiz logic 294 | 295 | /** 296 | * @todo check if serialised quiz exists in session before instantiating new 297 | */ 298 | $quiz = $app->quiz; 299 | 300 | $simple = $app->simple; 301 | $categories = $simple->getCategories(); 302 | 303 | if ($quiz->setId($id)) 304 | { 305 | 306 | $quiz->populateUsers(); 307 | 308 | $num = $app->request()->post('num'); 309 | $answers = $app->request()->post('answers'); 310 | 311 | $nonce = $app->request()->post('nonce'); 312 | 313 | //check for a valid nonce to prevent cached submissions e.g (back button) 314 | if ($session->get('nonce') != $nonce) 315 | { 316 | $app->redirect($app->request->getRootUri() . '/quiz/' . $id . '/test'); 317 | } 318 | 319 | $quiz->populateQuestions(); 320 | $quiz->populateUsers(); 321 | $session->set('num', (int) $num); 322 | 323 | $numquestions = $quiz->countQuestions(); 324 | $quizanswers = $quiz->getAnswers($num); 325 | 326 | if ($answers == $quizanswers[0]) 327 | { //first answer in array is correct one 328 | $score = $session->get('score'); 329 | $score++; 330 | $session->set('score', $score); 331 | $_SESSION['correct'][ $num ] = array($answers); 332 | } else 333 | { 334 | $_SESSION['wrong'][ $num ] = array($answers); 335 | } 336 | if ($_SESSION['num'] < $numquestions) 337 | { 338 | $_SESSION['num']++; 339 | } else 340 | { 341 | $_SESSION['last'] = true; 342 | $_SESSION['finished'] = 'yes'; 343 | } 344 | $app->redirect($app->request->getRootUri() . '/quiz/' . $id . '/test'); 345 | } else 346 | { 347 | $app->flashnow('quizerror', 'There has been an error. Please return to the main quiz menu and try again'); 348 | $app->render('quiz/error.php', array('categories' => $categories, 'session' => $session)); 349 | } 350 | } 351 | }); 352 | 353 | $app->get('/quiz/:id/test/', $authenticate($app), function ($id) use ($app) { 354 | 355 | $session = $app->session; 356 | 357 | $simple = $app->simple; 358 | $categories = $simple->getCategories(); 359 | 360 | if ( $session->get('quizid') !== $id) { 361 | $app->flashnow('quizerror','There has been an error. Please return to the main quiz menu and try again'); 362 | $app->render('quiz/error.php', array( 'categories' => $categories,'session' => $session)); 363 | $app->stop(); 364 | } 365 | 366 | $quiz = $app->quiz; 367 | 368 | /** 369 | * @todo implement serialize() on quiz object and store in session 370 | */ 371 | if ($quiz->setId($id)) { 372 | $quiz->populateQuestions()->populateUsers(); 373 | 374 | $timetaken = ''; 375 | 376 | $nonce = md5(uniqid()); 377 | $session->set('nonce', $nonce); 378 | 379 | $num = $session->get('num') ? $session->get('num') : 1; 380 | 381 | if (isset($_SESSION['last']) && $_SESSION['last'] === true) { 382 | 383 | $session->set('nonce', null); 384 | 385 | //first two vars formatted for insertion into database as datetime fields 386 | $starttime = $session->get('starttime'); 387 | $endtime = date('Y-m-d H:i:s'); 388 | 389 | //store $timetaken in session 390 | if (!isset($_SESSION['timetaken'])) { 391 | 392 | $end = time(); 393 | $start = strtotime($starttime); 394 | $time = $end - $start; 395 | 396 | $timetaken = SimpleQuiz\Utils\Base\Utils::calculateTimeTaken($time); 397 | 398 | $_SESSION['timetaken'] = $timetaken; 399 | $quiz->addQuizTaker($session->get('user'), $session->get('score'), $starttime, $endtime, $time); 400 | 401 | } else { 402 | $timetaken = $_SESSION['timetaken']; 403 | } 404 | } 405 | 406 | $app->render('quiz/test.php', array('quiz' => $quiz, 'num' => $num, 'nonce' => $nonce, 407 | 'timetaken' => $timetaken, 'categories' => 408 | $categories, 'session' => $session)); 409 | } else { 410 | $app->flashnow('quizerror','The quiz you have selected does not exist. Return to the main menu to try again'); 411 | $app->render('quiz/error.php', array( 'categories' => $categories,'session' => $session)); 412 | $app->stop(); 413 | } 414 | })->conditions(array('id' => '\d+')); 415 | 416 | $app->get('/quiz/:id/results/', function ($id) use ($app) { 417 | 418 | $quiz = $app->quiz; 419 | 420 | $session = $app->session; 421 | 422 | $simple = $app->simple; 423 | $categories = $simple->getCategories(); 424 | 425 | if ($session->get('finished') != 'yes') { 426 | $app->redirect($app->request->getRootUri().'/'); 427 | } 428 | 429 | if ($session->get('quizid') !== $id) { 430 | $app->flashnow('quizerror','There has been an error. Please return to the main quiz menu and try again'); 431 | $app->render('quiz/error.php', array('quiz' => $quiz, 'categories' => $categories, 'session' => $session)); 432 | $app->stop(); 433 | } 434 | 435 | if ($quiz->setId($id)) { 436 | $quiz->populateQuestions(); 437 | $quiz->populateUsers(); 438 | $session->set('last', null); 439 | 440 | $app->render('quiz/results.php', array('quiz' => $quiz, 'categories' => $categories, 'session' => $session)); 441 | } else { 442 | $app->flashnow('quizerror','The quiz you have selected does not exist. Return to the main menu to try again'); 443 | $app->render('quiz/error.php', array('quiz' => $quiz, 'categories' => $categories, 'session' => $session)); 444 | $app->stop(); 445 | } 446 | })->conditions(array('id' => '\d+')); 447 | -------------------------------------------------------------------------------- /templates/admin/editanswers.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 |
7 | 8 | 9 | '.$flash["success"].'
'; } ?> 10 | '.$flash["error"].'
'; } ?> 11 |

Edit Answers:

12 |
13 |
14 |

getText(); ?>

15 |
16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 28 | 36 | 37 | 40 | 41 | 44 | 52 | 53 | 54 |
Correct AnswerText
26 | > 27 | 29 |
30 | 31 | 32 | 33 | 34 |
35 |
55 |

56 | 57 | 58 |

59 | 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /templates/admin/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /templates/admin/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Simple Quiz :: Admin 9 | 10 | 14 | 15 | 16 | 17 | 18 | 46 | -------------------------------------------------------------------------------- /templates/admin/index.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 |
7 | '.$flash["success"].'
'; } ?> 8 | '.$flash["error"].'
'; } ?> 9 |
10 |

Welcome Quizmaster!

11 |

Be careful; with great power comes great responsibility.

12 |

Quizzes

13 | 0): ?> 14 | 15 | 16 | 17 | 18 | 19 | active == 1 ? 'glyphicon-ok-circle' : 'glyphicon-remove-circle'; 22 | echo ''; 23 | endforeach; 24 | ?> 25 | 26 |
NameDescriptionCategoryActiveActions
' . $quiz->name. ''.$quiz->description.''.$quiz->category.'
27 | 28 |

There aren't any quizzes at the moment. Why not create one now?

29 |

Just click the 'Create New Quiz' button below...

30 | 31 |

32 | 34 |

35 |
36 | 37 | 38 | 39 | 40 | 41 | 79 | 80 | -------------------------------------------------------------------------------- /templates/admin/login.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Simple Quiz :: Login 8 | 9 | 13 | 14 | 15 | 16 | 17 | 37 |
38 |

39 | 46 |
47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /templates/admin/quiz.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 |
7 | '.$flash["success"].'
'; } ?> 8 | '.$flash["error"].'
'; } ?> 9 |
10 | 11 |
12 |
13 | 14 |

Quiz Details:

15 |
16 |
    17 |
  • Name: getName(); ?>
  • 18 |
  • Description: getDescription(); ?>
  • 19 |
  • Category: getCategory(); ?>
  • 20 |
  • Active? isActive() ? '' : '' ?>
  • 21 |
  • Number Of Questions: countQuestions(); ?>
  • 23 |
  • Times Taken: getUsers()); ?>
  • 24 |
25 | 26 |
27 |

Questions:

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | getQuestions() as $question) :?> 38 | 39 | 40 | 49 | 50 | 51 | 52 |
QuestionActions
getText(); ?> 41 | 43 | 46 | 48 |
53 |
54 | 55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 87 | 88 | 89 | 159 | 160 | 161 | 202 | 203 | -------------------------------------------------------------------------------- /templates/category.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 |
7 |

8 | 9 |

10 |
11 |

name; ?>

12 |

description; ?>.

13 |
14 |
15 | 16 | 30 |
31 | 32 |
33 | Hey {username}, welcome to Simple Quiz 2 |

To complete your registration, please confirm your email address by clicking on the following link:

3 |

Confirm Your Email

4 |

This link will expire after 24 hours.

-------------------------------------------------------------------------------- /templates/email/registerconfirm.txt: -------------------------------------------------------------------------------- 1 | Hey {username}, welcome to Simple Quiz. 2 | 3 | To complete your registration, please confirm your email address by going to the following URL: 4 | 5 | {sitename}confirm-registration/{hash} 6 | 7 | This link will expire after 24 hours. -------------------------------------------------------------------------------- /templates/emailconfirmed.php: -------------------------------------------------------------------------------- 1 | 4 |
5 |
6 |
7 |

8 | 9 |

10 |
11 |

You have successfully confirmed your email address. You can now Login

14 |
15 |
16 | 17 | 29 |
30 | 31 |
32 | 4 |
5 |
6 |
7 |

8 | 9 |

10 |
11 |

Thank you for registering. Please check your inbox. An email has been sent to your email 12 | address. You will need to confirm your email before you can take a quiz.

13 |
14 |
15 | 16 | 29 |
30 | 31 |
32 | 4 |
5 |
6 |
7 |

8 | 9 |

10 |
11 |

Simple Quiz

12 |

A simple framework for creating and displaying quizzes. Written in PHP.

13 |
14 |
15 | 16 | 30 |
31 | 32 |
33 | 2 | 3 | 4 | 5 | 6 | 7 | Simple Quiz :: Login 8 | 9 | 13 | 14 | 15 | 16 | 17 | 34 |
35 |

36 |

37 |
38 |
39 | 46 | 54 |
55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /templates/quiz/error.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 11 |
12 |
13 |
14 | /res/bootstrap/assets/js/jquery.js"> 2 | 3 | get('num') === 0 ): ?> 4 | 5 | 6 | get('last')) : ?> 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /templates/quiz/header.php: -------------------------------------------------------------------------------- 1 | getName() : 'Simple Quiz'; 3 | ?> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <?php echo $title; ?> 12 | 13 | 17 | 18 | 19 | 20 | 21 | 59 | -------------------------------------------------------------------------------- /templates/quiz/quiz.php: -------------------------------------------------------------------------------- 1 | countQuestions(); 4 | $leadersToShow = 10; 5 | ?> 6 |
7 |
8 |
9 | '.$error 10 | .'
'; } ?> 11 |

getName(); ?>

12 |

getDescription(); ?>

13 |

Once you have started the quiz, you must answer all of the questions. If you try to go back to the previous question, your score will be reset and you will be sent back to the start.

14 |

For each question, choose an answer and click 'Submit Answer'. You'll then be given the next question.

15 |

This quiz has questions.

16 |

You'll get your score at the end of the quiz.

17 |
18 |

Top Scorers

19 |
20 |
    21 | getLeaders($leadersToShow); 23 | $counter = 1; 24 | foreach ($leaders as $leader) : 25 | 26 | echo '
  • ' . $leader["name"] . ': ' . $leader["score"] . '/' . $numquestions . '
  • '; 27 | 28 | //Use modulus to create new sub-list if required. 29 | if ($counter % (round($leadersToShow/2)) == 0) : 30 | echo '
' . PHP_EOL . '
    ' . PHP_EOL; 31 | endif; 32 | 33 | $counter++; 34 | 35 | endforeach; 36 | ?> 37 |
38 |
39 |
40 |
41 |
42 |

43 | 44 | 46 |

47 |
48 |
49 |
50 | 51 |
52 | 53 | 54 | 2 |
3 |
4 |
5 |

The Results Page For getName(); ?>

6 | countQuestions(); 9 | 10 | foreach ($quiz->getAllAnswersGroupedByQuestion() as $answergroup) : 11 | if ($x % 2 !== 0) { echo '
';} 12 | echo '
'; 13 | echo '

Question' . ($x) . ': ' . $quiz->getQuestion($x)->getText() . '

'; 14 | echo '
    ' . PHP_EOL; 15 | $y = 0; 16 | foreach( $answergroup as $answer) : 17 | if (isset($_SESSION['correct'][$x])): 18 | //first in array(correct by default) AND chosen by user 19 | if ( ($y === 0) && ( in_array( $answer, $_SESSION['correct'][$x]) ) ) : 20 | echo '
  1. '. $answer. ' (Correct!)
  2. ' . PHP_EOL; 21 | //correct but not chosen by user 22 | elseif ($y === 0) : 23 | echo '
  3. ' . $answer . '
  4. ' . PHP_EOL; 24 | //wrong, not chosen by user 25 | else : 26 | echo "
  5. $answer
  6. \n"; 27 | endif; 28 | 29 | //wrong AND chosen by user 30 | else : 31 | if ( in_array( $answer, $_SESSION['wrong'][$x])) : 32 | echo '
  7. ' . $answer . ' (Woops!)
  8. ' . PHP_EOL; 33 | //correct but not chosen by user 34 | elseif ($y === 0) : 35 | echo '
  9. ' . $answer . '
  10. ' . PHP_EOL; 36 | //wrong, not chosen by user 37 | else : 38 | echo "
  11. $answer
  12. \n"; 39 | endif; 40 | endif; 41 | 42 | $y++; 43 | endforeach; 44 | echo '
'; 45 | echo '
'; 46 | 47 | //move on to next set of answers 48 | $x++; 49 | endforeach; ?> 50 |
51 |
52 |

Top Scorers

53 |
    54 | getLeaders(30); 56 | $counter = 1; 57 | foreach ($leaders as $leader) : 58 | $name = ''; 59 | //if current user, bolden the username 60 | if ($leader['name'] == $user->getName()) : 61 | $name = '' . $leader['name'] . ''; 62 | else: 63 | $name = $leader['name']; 64 | endif; 65 | $percentage = round(( (int) $leader['score'] / (int) $numquestions ) * 100); 66 | echo '
  • ' . $name. ': ' . $percentage . '%
  • '; 67 | 68 | $counter++; 69 | 70 | endforeach; 71 | ?> 72 |
73 |
74 |
75 |
76 | 2 |
3 |
4 |
5 | get('last') ) : 7 | $question = $quiz->getQuestion($num); 8 | $answers = $quiz->getAnswers($num); 9 | ?> 10 |

Current tester: getName(); ?>

11 |

Question :

12 |

getText(); ?>

13 |
14 |
    15 | ' .PHP_EOL; 22 | echo '' . PHP_EOL; 23 | $acount++; 24 | } 25 | ?> 26 |
27 |

28 | 29 | 30 | 31 | 32 |

33 |
34 | get('score') / (int) $quiz->countQuestions()) * 100); 36 | ?> 37 |
38 |

getName(); ?> answered get('score'); ?> correct out 39 | of a possible countQuestions(); ?>

40 |

%

41 |

Time Taken:

42 | 43 |

See how you 44 | compare!

45 |
46 | 47 |
48 |
49 |
50 | 2 |
3 |
4 |
5 |

6 | 7 |

8 |
9 |

Checking Dependencies

10 |
    11 |
  • PHP Version: '; ?>

    12 |

    ()

    13 |
  • 14 |
  • 15 |

    Access To Randomness: '; ?>

    16 |

    ()

    17 |
  • 18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | --------------------------------------------------------------------------------