From bc1650d3cb24c4b892415dab5a6794fee3fb5ea8 Mon Sep 17 00:00:00 2001 From: coderkun Date: Thu, 24 Apr 2014 12:38:52 +0200 Subject: [PATCH] do not use empty() without write-context --- .hgignore | 6 + .htaccess | 50 ++ agents/BottomlevelAgent.inc | 25 + agents/IntermediateAgent.inc | 48 + agents/ToplevelAgent.inc | 395 +++++++++ agents/bottomlevel/MenuAgent.inc | 37 + .../QuestgroupshierarchypathAgent.inc | 35 + agents/bottomlevel/SeminarybarAgent.inc | 35 + agents/bottomlevel/SeminarymenuAgent.inc | 35 + agents/bottomlevel/UserrolesAgent.inc | 35 + agents/intermediate/AchievementsAgent.inc | 35 + agents/intermediate/CharactergroupsAgent.inc | 35 + .../CharactergroupsquestsAgent.inc | 35 + agents/intermediate/CharactersAgent.inc | 43 + agents/intermediate/ErrorAgent.inc | 35 + agents/intermediate/IntroductionAgent.inc | 35 + agents/intermediate/LibraryAgent.inc | 35 + agents/intermediate/MediaAgent.inc | 35 + agents/intermediate/QuestgroupsAgent.inc | 36 + agents/intermediate/QuestsAgent.inc | 54 ++ agents/intermediate/SeminariesAgent.inc | 35 + agents/intermediate/UploadsAgent.inc | 35 + agents/intermediate/UsersAgent.inc | 44 + agents/toplevel/BinaryAgent.inc | 41 + agents/toplevel/FaultAgent.inc | 35 + agents/toplevel/HtmlAgent.inc | 70 ++ apis/WebApi.inc | 250 ++++++ app/Controller.inc | 106 +++ app/Model.inc | 42 + app/QuesttypeAgent.inc | 267 ++++++ app/QuesttypeController.inc | 361 ++++++++ app/QuesttypeModel.inc | 154 ++++ app/QuesttypeView.inc | 76 ++ app/TextFormatter.inc | 141 +++ app/ToplevelAgent.inc | 36 + app/Utils.inc | 112 +++ app/controllers/IntermediateController.inc | 139 +++ app/controllers/SeminaryController.inc | 290 +++++++ app/exceptions/FileUploadException.inc | 75 ++ app/exceptions/MaxFilesizeException.inc | 51 ++ .../QuesttypeAgentNotFoundException.inc | 77 ++ .../QuesttypeAgentNotValidException.inc | 77 ++ .../QuesttypeControllerNotFoundException.inc | 77 ++ .../QuesttypeControllerNotValidException.inc | 77 ++ .../QuesttypeModelNotFoundException.inc | 77 ++ .../QuesttypeModelNotValidException.inc | 77 ++ .../SubmissionNotValidException.inc | 77 ++ app/exceptions/WrongFiletypeException.inc | 75 ++ app/lib/Password.inc | 316 +++++++ bootstrap.inc | 33 + configs/AppConfig.inc | 200 +++++ configs/CoreConfig.inc | 167 ++++ controllers/AchievementsController.inc | 172 ++++ controllers/BinaryController.inc | 37 + controllers/CharactergroupsController.inc | 150 ++++ .../CharactergroupsquestsController.inc | 91 ++ controllers/CharactersController.inc | 388 +++++++++ controllers/ErrorController.inc | 51 ++ controllers/FaultController.inc | 37 + controllers/HtmlController.inc | 59 ++ controllers/IntroductionController.inc | 37 + controllers/LibraryController.inc | 135 +++ controllers/MediaController.inc | 432 +++++++++ controllers/MenuController.inc | 52 ++ controllers/QuestgroupsController.inc | 227 +++++ .../QuestgroupshierarchypathController.inc | 90 ++ controllers/QuestsController.inc | 817 ++++++++++++++++++ controllers/SeminariesController.inc | 254 ++++++ controllers/SeminarybarController.inc | 86 ++ controllers/SeminarymenuController.inc | 52 ++ controllers/UploadsController.inc | 171 ++++ controllers/UserrolesController.inc | 47 + controllers/UsersController.inc | 371 ++++++++ .../components/AchievementComponent.inc | 41 + controllers/components/AuthComponent.inc | 79 ++ .../components/ValidationComponent.inc | 140 +++ core/Agent.inc | 607 +++++++++++++ core/Api.inc | 163 ++++ core/Autoloader.inc | 98 +++ core/ClassLoader.inc | 129 +++ core/Component.inc | 85 ++ core/Config.inc | 49 ++ core/Controller.inc | 433 ++++++++++ core/Driver.inc | 96 ++ core/Exception.inc | 65 ++ core/Linker.inc | 311 +++++++ core/Logger.inc | 132 +++ core/Model.inc | 141 +++ core/Request.inc | 64 ++ core/Response.inc | 158 ++++ core/View.inc | 124 +++ core/WebUtils.inc | 75 ++ drivers/DatabaseDriver.inc | 87 ++ drivers/MysqliDriver.inc | 169 ++++ exceptions/AccessDeniedException.inc | 51 ++ exceptions/ActionNotFoundException.inc | 77 ++ exceptions/AgentNotFoundException.inc | 67 ++ exceptions/AgentNotValidException.inc | 67 ++ exceptions/ClassNotFoundException.inc | 77 ++ exceptions/ClassNotValidException.inc | 77 ++ exceptions/ComponentNotFoundException.inc | 67 ++ exceptions/ComponentNotValidException.inc | 67 ++ exceptions/ControllerNotFoundException.inc | 67 ++ exceptions/ControllerNotValidException.inc | 67 ++ exceptions/DatamodelException.inc | 99 +++ exceptions/DriverNotFoundException.inc | 68 ++ exceptions/DriverNotValidException.inc | 68 ++ exceptions/FatalDatamodelException.inc | 96 ++ exceptions/IdNotFoundException.inc | 77 ++ exceptions/LayoutNotFoundException.inc | 68 ++ exceptions/LayoutNotValidException.inc | 67 ++ exceptions/ModelNotFoundException.inc | 67 ++ exceptions/ModelNotValidException.inc | 68 ++ exceptions/ParamsNotValidException.inc | 77 ++ exceptions/ServiceUnavailableException.inc | 77 ++ exceptions/ViewNotFoundException.inc | 77 ++ locale/de_DE/LC_MESSAGES/The Legend of Z.mo | Bin 0 -> 9727 bytes locale/de_DE/LC_MESSAGES/The Legend of Z.po | 749 ++++++++++++++++ logs/empty | 0 media/empty | 0 models/AchievementsModel.inc | 571 ++++++++++++ models/AvatarsModel.inc | 92 ++ models/CharactergroupsModel.inc | 197 +++++ models/CharactergroupsquestsModel.inc | 136 +++ models/CharacterrolesModel.inc | 100 +++ models/CharactersModel.inc | 519 +++++++++++ models/CharactertypesModel.inc | 57 ++ models/DatabaseModel.inc | 83 ++ models/MediaModel.inc | 195 +++++ models/QuestgroupsModel.inc | 662 ++++++++++++++ models/QuestgroupshierarchyModel.inc | 128 +++ models/QuestsModel.inc | 487 +++++++++++ models/QuesttextsModel.inc | 220 +++++ models/QuesttopicsModel.inc | 154 ++++ models/QuesttypesModel.inc | 77 ++ models/SeminariesModel.inc | 195 +++++ models/SeminarycharacterfieldsModel.inc | 78 ++ models/UploadsModel.inc | 141 +++ models/UserrolesModel.inc | 77 ++ models/UsersModel.inc | 348 ++++++++ .../bossfight/BossfightQuesttypeAgent.inc | 24 + .../BossfightQuesttypeController.inc | 258 ++++++ .../bossfight/BossfightQuesttypeModel.inc | 183 ++++ questtypes/bossfight/html/quest.tpl | 51 ++ questtypes/bossfight/html/submission.tpl | 38 + .../choiceinput/ChoiceinputQuesttypeAgent.inc | 24 + .../ChoiceinputQuesttypeController.inc | 171 ++++ .../choiceinput/ChoiceinputQuesttypeModel.inc | 153 ++++ questtypes/choiceinput/html/quest.tpl | 15 + questtypes/choiceinput/html/submission.tpl | 12 + .../crossword/CrosswordQuesttypeAgent.inc | 24 + .../CrosswordQuesttypeController.inc | 365 ++++++++ .../crossword/CrosswordQuesttypeModel.inc | 93 ++ questtypes/crossword/html/quest.tpl | 49 ++ questtypes/crossword/html/submission.tpl | 48 + .../dragndrop/DragndropQuesttypeAgent.inc | 24 + .../DragndropQuesttypeController.inc | 217 +++++ .../dragndrop/DragndropQuesttypeModel.inc | 180 ++++ questtypes/dragndrop/html/quest.tpl | 17 + questtypes/dragndrop/html/submission.tpl | 15 + .../MultiplechoiceQuesttypeAgent.inc | 24 + .../MultiplechoiceQuesttypeController.inc | 276 ++++++ .../MultiplechoiceQuesttypeModel.inc | 159 ++++ questtypes/multiplechoice/html/quest.tpl | 21 + questtypes/multiplechoice/html/submission.tpl | 16 + questtypes/submit/SubmitQuesttypeAgent.inc | 24 + .../submit/SubmitQuesttypeController.inc | 221 +++++ questtypes/submit/SubmitQuesttypeModel.inc | 142 +++ questtypes/submit/html/quest.tpl | 52 ++ questtypes/submit/html/submission.tpl | 33 + .../textinput/TextinputQuesttypeAgent.inc | 24 + .../TextinputQuesttypeController.inc | 185 ++++ .../textinput/TextinputQuesttypeModel.inc | 117 +++ questtypes/textinput/html/quest.tpl | 11 + questtypes/textinput/html/submission.tpl | 7 + requests/WebRequest.inc | 401 +++++++++ responses/WebResponse.inc | 250 ++++++ seminarymedia/empty | 0 seminaryuploads/empty | 0 tmp/empty | 0 uploads/empty | 0 views/binary/binary.tpl | 1 + views/binary/error/index.tpl | 1 + views/binary/media/achievement.tpl | 1 + views/binary/media/avatar.tpl | 1 + views/binary/media/index.tpl | 1 + views/binary/media/seminary.tpl | 1 + views/binary/media/seminaryheader.tpl | 1 + views/binary/uploads/seminary.tpl | 1 + views/error.tpl | 13 + views/fault/error/index.tpl | 2 + views/fault/fault.tpl | 16 + views/html/achievements/index.tpl | 84 ++ views/html/charactergroups/group.tpl | 50 ++ views/html/charactergroups/groupsgroup.tpl | 25 + views/html/charactergroups/index.tpl | 14 + views/html/charactergroupsquests/quest.tpl | 53 ++ views/html/characters/character.tpl | 110 +++ views/html/characters/index.tpl | 23 + views/html/characters/manage.tpl | 53 ++ views/html/characters/register.tpl | 84 ++ views/html/error/index.tpl | 17 + views/html/html.tpl | 63 ++ views/html/introduction/index.tpl | 50 ++ views/html/library/index.tpl | 28 + views/html/library/topic.tpl | 30 + views/html/menu/index.tpl | 9 + views/html/questgroups/create.tpl | 16 + views/html/questgroups/questgroup.tpl | 72 ++ views/html/questgroupshierarchypath/index.tpl | 17 + views/html/quests/create.tpl | 41 + views/html/quests/createmedia.tpl | 19 + views/html/quests/index.tpl | 39 + views/html/quests/quest.tpl | 116 +++ views/html/quests/submission.tpl | 13 + views/html/quests/submissions.tpl | 37 + views/html/seminaries/create.tpl | 15 + views/html/seminaries/delete.tpl | 13 + views/html/seminaries/edit.tpl | 15 + views/html/seminaries/index.tpl | 31 + views/html/seminaries/seminary.tpl | 40 + views/html/seminarybar/index.tpl | 48 + views/html/seminarymenu/index.tpl | 5 + views/html/userroles/user.tpl | 5 + views/html/users/create.tpl | 18 + views/html/users/delete.tpl | 8 + views/html/users/edit.tpl | 18 + views/html/users/index.tpl | 9 + views/html/users/login.tpl | 18 + views/html/users/logout.tpl | 0 views/html/users/register.tpl | 90 ++ views/html/users/user.tpl | 35 + views/inlineerror.tpl | 1 + www/.htaccess | 8 + www/css/desktop.css | 361 ++++++++ www/error403.html | 14 + www/error404.html | 14 + www/error500.html | 14 + www/index.php | 45 + www/js/dnd.js | 54 ++ www/js/imagelightbox.min.js | 6 + www/js/jquery.nicescroll.min.js | 111 +++ 242 files changed, 24652 insertions(+) create mode 100644 .hgignore create mode 100644 .htaccess create mode 100644 agents/BottomlevelAgent.inc create mode 100644 agents/IntermediateAgent.inc create mode 100644 agents/ToplevelAgent.inc create mode 100644 agents/bottomlevel/MenuAgent.inc create mode 100644 agents/bottomlevel/QuestgroupshierarchypathAgent.inc create mode 100644 agents/bottomlevel/SeminarybarAgent.inc create mode 100644 agents/bottomlevel/SeminarymenuAgent.inc create mode 100644 agents/bottomlevel/UserrolesAgent.inc create mode 100644 agents/intermediate/AchievementsAgent.inc create mode 100644 agents/intermediate/CharactergroupsAgent.inc create mode 100644 agents/intermediate/CharactergroupsquestsAgent.inc create mode 100644 agents/intermediate/CharactersAgent.inc create mode 100644 agents/intermediate/ErrorAgent.inc create mode 100644 agents/intermediate/IntroductionAgent.inc create mode 100644 agents/intermediate/LibraryAgent.inc create mode 100644 agents/intermediate/MediaAgent.inc create mode 100644 agents/intermediate/QuestgroupsAgent.inc create mode 100644 agents/intermediate/QuestsAgent.inc create mode 100644 agents/intermediate/SeminariesAgent.inc create mode 100644 agents/intermediate/UploadsAgent.inc create mode 100644 agents/intermediate/UsersAgent.inc create mode 100644 agents/toplevel/BinaryAgent.inc create mode 100644 agents/toplevel/FaultAgent.inc create mode 100644 agents/toplevel/HtmlAgent.inc create mode 100644 apis/WebApi.inc create mode 100644 app/Controller.inc create mode 100644 app/Model.inc create mode 100644 app/QuesttypeAgent.inc create mode 100644 app/QuesttypeController.inc create mode 100644 app/QuesttypeModel.inc create mode 100644 app/QuesttypeView.inc create mode 100644 app/TextFormatter.inc create mode 100644 app/ToplevelAgent.inc create mode 100644 app/Utils.inc create mode 100644 app/controllers/IntermediateController.inc create mode 100644 app/controllers/SeminaryController.inc create mode 100644 app/exceptions/FileUploadException.inc create mode 100644 app/exceptions/MaxFilesizeException.inc create mode 100644 app/exceptions/QuesttypeAgentNotFoundException.inc create mode 100644 app/exceptions/QuesttypeAgentNotValidException.inc create mode 100644 app/exceptions/QuesttypeControllerNotFoundException.inc create mode 100644 app/exceptions/QuesttypeControllerNotValidException.inc create mode 100644 app/exceptions/QuesttypeModelNotFoundException.inc create mode 100644 app/exceptions/QuesttypeModelNotValidException.inc create mode 100644 app/exceptions/SubmissionNotValidException.inc create mode 100644 app/exceptions/WrongFiletypeException.inc create mode 100644 app/lib/Password.inc create mode 100644 bootstrap.inc create mode 100644 configs/AppConfig.inc create mode 100644 configs/CoreConfig.inc create mode 100644 controllers/AchievementsController.inc create mode 100644 controllers/BinaryController.inc create mode 100644 controllers/CharactergroupsController.inc create mode 100644 controllers/CharactergroupsquestsController.inc create mode 100644 controllers/CharactersController.inc create mode 100644 controllers/ErrorController.inc create mode 100644 controllers/FaultController.inc create mode 100644 controllers/HtmlController.inc create mode 100644 controllers/IntroductionController.inc create mode 100644 controllers/LibraryController.inc create mode 100644 controllers/MediaController.inc create mode 100644 controllers/MenuController.inc create mode 100644 controllers/QuestgroupsController.inc create mode 100644 controllers/QuestgroupshierarchypathController.inc create mode 100644 controllers/QuestsController.inc create mode 100644 controllers/SeminariesController.inc create mode 100644 controllers/SeminarybarController.inc create mode 100644 controllers/SeminarymenuController.inc create mode 100644 controllers/UploadsController.inc create mode 100644 controllers/UserrolesController.inc create mode 100644 controllers/UsersController.inc create mode 100644 controllers/components/AchievementComponent.inc create mode 100644 controllers/components/AuthComponent.inc create mode 100644 controllers/components/ValidationComponent.inc create mode 100644 core/Agent.inc create mode 100644 core/Api.inc create mode 100644 core/Autoloader.inc create mode 100644 core/ClassLoader.inc create mode 100644 core/Component.inc create mode 100644 core/Config.inc create mode 100644 core/Controller.inc create mode 100644 core/Driver.inc create mode 100644 core/Exception.inc create mode 100644 core/Linker.inc create mode 100644 core/Logger.inc create mode 100644 core/Model.inc create mode 100644 core/Request.inc create mode 100644 core/Response.inc create mode 100644 core/View.inc create mode 100644 core/WebUtils.inc create mode 100644 drivers/DatabaseDriver.inc create mode 100644 drivers/MysqliDriver.inc create mode 100644 exceptions/AccessDeniedException.inc create mode 100644 exceptions/ActionNotFoundException.inc create mode 100644 exceptions/AgentNotFoundException.inc create mode 100644 exceptions/AgentNotValidException.inc create mode 100644 exceptions/ClassNotFoundException.inc create mode 100644 exceptions/ClassNotValidException.inc create mode 100644 exceptions/ComponentNotFoundException.inc create mode 100644 exceptions/ComponentNotValidException.inc create mode 100644 exceptions/ControllerNotFoundException.inc create mode 100644 exceptions/ControllerNotValidException.inc create mode 100644 exceptions/DatamodelException.inc create mode 100644 exceptions/DriverNotFoundException.inc create mode 100644 exceptions/DriverNotValidException.inc create mode 100644 exceptions/FatalDatamodelException.inc create mode 100644 exceptions/IdNotFoundException.inc create mode 100644 exceptions/LayoutNotFoundException.inc create mode 100644 exceptions/LayoutNotValidException.inc create mode 100644 exceptions/ModelNotFoundException.inc create mode 100644 exceptions/ModelNotValidException.inc create mode 100644 exceptions/ParamsNotValidException.inc create mode 100644 exceptions/ServiceUnavailableException.inc create mode 100644 exceptions/ViewNotFoundException.inc create mode 100644 locale/de_DE/LC_MESSAGES/The Legend of Z.mo create mode 100644 locale/de_DE/LC_MESSAGES/The Legend of Z.po create mode 100644 logs/empty create mode 100644 media/empty create mode 100644 models/AchievementsModel.inc create mode 100644 models/AvatarsModel.inc create mode 100644 models/CharactergroupsModel.inc create mode 100644 models/CharactergroupsquestsModel.inc create mode 100644 models/CharacterrolesModel.inc create mode 100644 models/CharactersModel.inc create mode 100644 models/CharactertypesModel.inc create mode 100644 models/DatabaseModel.inc create mode 100644 models/MediaModel.inc create mode 100644 models/QuestgroupsModel.inc create mode 100644 models/QuestgroupshierarchyModel.inc create mode 100644 models/QuestsModel.inc create mode 100644 models/QuesttextsModel.inc create mode 100644 models/QuesttopicsModel.inc create mode 100644 models/QuesttypesModel.inc create mode 100644 models/SeminariesModel.inc create mode 100644 models/SeminarycharacterfieldsModel.inc create mode 100644 models/UploadsModel.inc create mode 100644 models/UserrolesModel.inc create mode 100644 models/UsersModel.inc create mode 100644 questtypes/bossfight/BossfightQuesttypeAgent.inc create mode 100644 questtypes/bossfight/BossfightQuesttypeController.inc create mode 100644 questtypes/bossfight/BossfightQuesttypeModel.inc create mode 100644 questtypes/bossfight/html/quest.tpl create mode 100644 questtypes/bossfight/html/submission.tpl create mode 100644 questtypes/choiceinput/ChoiceinputQuesttypeAgent.inc create mode 100644 questtypes/choiceinput/ChoiceinputQuesttypeController.inc create mode 100644 questtypes/choiceinput/ChoiceinputQuesttypeModel.inc create mode 100644 questtypes/choiceinput/html/quest.tpl create mode 100644 questtypes/choiceinput/html/submission.tpl create mode 100644 questtypes/crossword/CrosswordQuesttypeAgent.inc create mode 100644 questtypes/crossword/CrosswordQuesttypeController.inc create mode 100644 questtypes/crossword/CrosswordQuesttypeModel.inc create mode 100644 questtypes/crossword/html/quest.tpl create mode 100644 questtypes/crossword/html/submission.tpl create mode 100644 questtypes/dragndrop/DragndropQuesttypeAgent.inc create mode 100644 questtypes/dragndrop/DragndropQuesttypeController.inc create mode 100644 questtypes/dragndrop/DragndropQuesttypeModel.inc create mode 100644 questtypes/dragndrop/html/quest.tpl create mode 100644 questtypes/dragndrop/html/submission.tpl create mode 100644 questtypes/multiplechoice/MultiplechoiceQuesttypeAgent.inc create mode 100644 questtypes/multiplechoice/MultiplechoiceQuesttypeController.inc create mode 100644 questtypes/multiplechoice/MultiplechoiceQuesttypeModel.inc create mode 100644 questtypes/multiplechoice/html/quest.tpl create mode 100644 questtypes/multiplechoice/html/submission.tpl create mode 100644 questtypes/submit/SubmitQuesttypeAgent.inc create mode 100644 questtypes/submit/SubmitQuesttypeController.inc create mode 100644 questtypes/submit/SubmitQuesttypeModel.inc create mode 100644 questtypes/submit/html/quest.tpl create mode 100644 questtypes/submit/html/submission.tpl create mode 100644 questtypes/textinput/TextinputQuesttypeAgent.inc create mode 100644 questtypes/textinput/TextinputQuesttypeController.inc create mode 100644 questtypes/textinput/TextinputQuesttypeModel.inc create mode 100644 questtypes/textinput/html/quest.tpl create mode 100644 questtypes/textinput/html/submission.tpl create mode 100644 requests/WebRequest.inc create mode 100644 responses/WebResponse.inc create mode 100644 seminarymedia/empty create mode 100644 seminaryuploads/empty create mode 100644 tmp/empty create mode 100644 uploads/empty create mode 100644 views/binary/binary.tpl create mode 100644 views/binary/error/index.tpl create mode 100644 views/binary/media/achievement.tpl create mode 100644 views/binary/media/avatar.tpl create mode 100644 views/binary/media/index.tpl create mode 100644 views/binary/media/seminary.tpl create mode 100644 views/binary/media/seminaryheader.tpl create mode 100644 views/binary/uploads/seminary.tpl create mode 100644 views/error.tpl create mode 100644 views/fault/error/index.tpl create mode 100644 views/fault/fault.tpl create mode 100644 views/html/achievements/index.tpl create mode 100644 views/html/charactergroups/group.tpl create mode 100644 views/html/charactergroups/groupsgroup.tpl create mode 100644 views/html/charactergroups/index.tpl create mode 100644 views/html/charactergroupsquests/quest.tpl create mode 100644 views/html/characters/character.tpl create mode 100644 views/html/characters/index.tpl create mode 100644 views/html/characters/manage.tpl create mode 100644 views/html/characters/register.tpl create mode 100644 views/html/error/index.tpl create mode 100644 views/html/html.tpl create mode 100644 views/html/introduction/index.tpl create mode 100644 views/html/library/index.tpl create mode 100644 views/html/library/topic.tpl create mode 100644 views/html/menu/index.tpl create mode 100644 views/html/questgroups/create.tpl create mode 100644 views/html/questgroups/questgroup.tpl create mode 100644 views/html/questgroupshierarchypath/index.tpl create mode 100644 views/html/quests/create.tpl create mode 100644 views/html/quests/createmedia.tpl create mode 100644 views/html/quests/index.tpl create mode 100644 views/html/quests/quest.tpl create mode 100644 views/html/quests/submission.tpl create mode 100644 views/html/quests/submissions.tpl create mode 100644 views/html/seminaries/create.tpl create mode 100644 views/html/seminaries/delete.tpl create mode 100644 views/html/seminaries/edit.tpl create mode 100644 views/html/seminaries/index.tpl create mode 100644 views/html/seminaries/seminary.tpl create mode 100644 views/html/seminarybar/index.tpl create mode 100644 views/html/seminarymenu/index.tpl create mode 100644 views/html/userroles/user.tpl create mode 100644 views/html/users/create.tpl create mode 100644 views/html/users/delete.tpl create mode 100644 views/html/users/edit.tpl create mode 100644 views/html/users/index.tpl create mode 100644 views/html/users/login.tpl create mode 100644 views/html/users/logout.tpl create mode 100644 views/html/users/register.tpl create mode 100644 views/html/users/user.tpl create mode 100644 views/inlineerror.tpl create mode 100644 www/.htaccess create mode 100644 www/css/desktop.css create mode 100644 www/error403.html create mode 100644 www/error404.html create mode 100644 www/error500.html create mode 100644 www/index.php create mode 100644 www/js/dnd.js create mode 100644 www/js/imagelightbox.min.js create mode 100644 www/js/jquery.nicescroll.min.js diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..60d03278 --- /dev/null +++ b/.hgignore @@ -0,0 +1,6 @@ +syntax: regexp +^media/* +^tmp/* +^uploads/* +^seminarymedia/* +^seminaryuploads/* diff --git a/.htaccess b/.htaccess new file mode 100644 index 00000000..cb3fb9ef --- /dev/null +++ b/.htaccess @@ -0,0 +1,50 @@ +Options -Indexes -MultiViews + +ErrorDocument 403 /www/error403.html +ErrorDocument 404 /www/error404.html +ErrorDocument 500 /www/error500.html + + + + + Require all granted + + + Require all denied + + + + Require all denied + + + + Require all denied + + + + + Allow From All + + + Order Deny,Allow + Deny From All + + + + Order Deny,Allow + Deny From All + + + + Order Deny,Allow + Deny From All + + + + + + RewriteEngine On + + RewriteBase / + RewriteRule ^(.*)$ www/$1 [L] + diff --git a/agents/BottomlevelAgent.inc b/agents/BottomlevelAgent.inc new file mode 100644 index 00000000..37816614 --- /dev/null +++ b/agents/BottomlevelAgent.inc @@ -0,0 +1,25 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\agents; + + + /** + * The BottomlevelAgent is the standard Agent and can have indefinite + * SubAgents. + * + * @author coderkun + */ + abstract class BottomlevelAgent extends \nre\core\Agent + { + } + +?> diff --git a/agents/IntermediateAgent.inc b/agents/IntermediateAgent.inc new file mode 100644 index 00000000..7139653d --- /dev/null +++ b/agents/IntermediateAgent.inc @@ -0,0 +1,48 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\agents; + + + /** + * The IntermediateAgent assumes the task of a module. There is only one + * IntermediateAgent per request. + * + * @author coderkun + */ + abstract class IntermediateAgent extends \nre\core\Agent + { + + + + + /** + * Get the layout if it was explicitly defined. + * + * @return string Layout of the IntermediateAgent + */ + public static function getLayout($agentName) + { + // Determine classname + $className = Autoloader::concatClassNames($agentName, 'Agent'); + + // Check property + if(isset($className::$layout)) { + return $className::$layout; + } + + + return null; + } + + } + +?> diff --git a/agents/ToplevelAgent.inc b/agents/ToplevelAgent.inc new file mode 100644 index 00000000..9d6d6f8a --- /dev/null +++ b/agents/ToplevelAgent.inc @@ -0,0 +1,395 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\agents; + + + /** + * The ToplevelAgent assumes the task of a FrontController. There is + * only one per request. + * + * @author coderkun + */ + class ToplevelAgent extends \nre\core\Agent + { + /** + * Stage: Load + * + * @var string + */ + const STAGE_LOAD = 'load'; + /** + * Stage: Run + * + * @var string + */ + const STAGE_RUN = 'run'; + + /** + * Current request + * + * @var Request + */ + private $request; + /** + * Current response + * + * @var Response + */ + private $response; + /** + * Layout instace + * + * @var Layout + */ + private $layout = null; + /** + * IntermediateAgent instance + * + * @var IntermediateAgent + */ + private $intermediateAgent = null; + + + + + /** + * Construct a ToplevelAgent. + * + * @throws ServiceUnavailableException + * @throws DatamodelException + * @throws DriverNotValidException + * @throws DriverNotFoundException + * @throws ViewNotFoundException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ControllerNotValidException + * @throws ControllerNotFoundException + * @param Request $request Current request + * @param Response $respone Current response + * @param Logger $log Log-system + */ + protected function __construct(\nre\core\Request $request, \nre\core\Response $response, \nre\core\Logger $log=null) + { + // Store values + $this->request = $request; + $this->response = $response; + + + // Create response + $response = clone $response; + $response->clearParams(1); + $response->addParams( + null, + \nre\configs\CoreConfig::$defaults['action'] + ); + + // Call parent constructor + parent::__construct($request, $response, $log, true); + + + // Load IntermediateAgent + $this->loadIntermediateAgent(); + } + + + + + /** + * Run the Controller of this Agent and its SubAgents. + * + * @throws ServiceUnavailableException + * @param Request $request Current request + * @param Response $response Current response + * @return Exception Last occurred exception of SubAgents + */ + public function run(\nre\core\Request $request, \nre\core\Response $response) + { + try { + return $this->_run($request, $response); + } + catch(\nre\exceptions\AccessDeniedException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_FORBIDDEN, self::STAGE_RUN); + } + catch(\nre\exceptions\ParamsNotValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND, self::STAGE_RUN); + } + catch(\nre\exceptions\IdNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND, self::STAGE_RUN); + } + catch(\nre\exceptions\DatamodelException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE, self::STAGE_RUN); + } + catch(\nre\exceptions\ActionNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND, self::STAGE_RUN); + } + } + + + /** + * Generate output of the Controller of this Agent and its + * SubAgents. + * + * @param array $data View data + * @return string Generated output + */ + public function render($data=array()) + { + // Render IntermediateAgent + $data = array(); + $data['intermediate'] = $this->intermediateAgent->render(); + + + // Render ToplevelAgent + return parent::render($data); + } + + + /** + * Return the IntermediateAgent. + * + * @return IntermediateAgent IntermediateAgent + */ + public function getIntermediateAgent() + { + return $this->intermediateAgent; + } + + + + + /** + * Load a SubAgent and add it. + * + * @throws ServiceUnavailableException + * @throws FatalDatamodelException + * @throws AgentNotFoundException + * @throws AgentNotValidException + * @param string $agentName Name of the Agent to load + * @param mixed … Additional parameters for the agent + */ + protected function addSubAgent($agentName) + { + try { + call_user_func_array( + array( + $this, + '_addSubAgent' + ), + func_get_args() + ); + } + catch(\nre\exceptions\DatamodelException $e) { + throw new \nre\exceptions\FatalDatamodelException($e->getDatamodelMessage(), $e->getDatamodelErrorNumber()); + } + } + + + + + /** + * Load IntermediateAgent defined by the current request. + * + * @throws ServiceUnavailableException + */ + private function loadIntermediateAgent() + { + try { + $this->_loadIntermediateAgent(); + } + catch(\nre\exceptions\ViewNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND); + } + catch(\nre\exceptions\DatamodelException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\DriverNotValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\DriverNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\ModelNotValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\ModelNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\ControllerNotValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\ControllerNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND); + } + catch(\nre\exceptions\AgentNotValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\AgentNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND); + } + } + + + /** + * Load IntermediateAgent defined by the current request. + * + * @throws ServiceUnavailableException + */ + private function _loadIntermediateAgent() + { + // Determine IntermediateAgent + $agentName = $this->response->getParam(1); + if(is_null($agentName)) { + $agentName = $this->request->getParam(1, 'intermediate'); + $this->response->addParam($agentName); + } + + // Load IntermediateAgent + IntermediateAgent::load($agentName); + + + // Determine Action + $action = $this->response->getParam(2); + if(is_null($action)) { + $action = $this->request->getParam(2, 'action'); + $this->response->addParam($action); + } + + // Construct IntermediateAgent + $this->intermediateAgent = \nre\agents\IntermediateAgent::factory( + $agentName, + $this->request, + $this->response, + $this->log + ); + } + + + /** + * Run the Controller of this Agent and its SubAgents. + * + * @throws AccessDeniedException + * @throws IdNotFoundException + * @throws ServiceUnavailableException + * @throws DatamodelException + * @param Request $request Current request + * @param Response $response Current response + * @return Exception Last occurred exception of SubAgents + */ + private function _run(\nre\core\Request $request, \nre\core\Response $response) + { + // Run IntermediateAgent + $this->runIntermediateAgent(); + + + // TODO Request instead of response? + $response = clone $response; + $response->clearParams(2); + $response->addParam(\nre\configs\CoreConfig::$defaults['action']); + + + // Run ToplevelAgent + return parent::run($request, $response); + } + + + /** + * Run IntermediateAgent. + * + * @throws AccessDeniedException + * @throws ParamsNotValidException + * @throws IdNotFoundException + * @throws ServiceUnavailableException + * @throws DatamodelException + */ + private function runIntermediateAgent() + { + $this->intermediateAgent->run( + $this->request, + $this->response + ); + } + + + /** + * Handle an error that occurred during + * loading/cnostructing/running of the IntermediateAgent. + * + * @throws ServiceUnavailableException + * @param Exception $exception Occurred exception + * @param int $httpStatusCode HTTP-statuscode + * @param string $stage Stage of execution + */ + private function error($exception, $httpStatusCode, $stage=self::STAGE_LOAD) + { + // Log error + $this->log($exception, \nre\core\Logger::LOGMODE_AUTO); + + + try { + // Define ErrorAgent + $this->response->clearParams(1); + $this->response->addParams( + \nre\configs\AppConfig::$defaults['intermediate-error'], + \nre\configs\CoreConfig::$defaults['action'], + $httpStatusCode + ); + + // Load ErrorAgent + $this->_loadIntermediateAgent(); + + // Run ErrorAgent + if($stage == self::STAGE_RUN) { + $this->_run($this->request, $this->response); + } + } + catch(\nre\exceptions\ActionNotFoundException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\DatamodelException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\DriverNotValidException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\DriverNotFoundException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\ModelNotValidException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\ModelNotFoundException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\ViewNotFoundException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\ControllerNotValidException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\ControllerNotFoundException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\AgentNotValidException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(\nre\exceptions\AgentNotFoundException $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + catch(Exception $e) { + throw new \nre\exceptions\ServiceUnavailableException($e); + } + } + + + } + +?> diff --git a/agents/bottomlevel/MenuAgent.inc b/agents/bottomlevel/MenuAgent.inc new file mode 100644 index 00000000..49512791 --- /dev/null +++ b/agents/bottomlevel/MenuAgent.inc @@ -0,0 +1,37 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\bottomlevel; + + + /** + * Agent to display a menu. + * + * @author Oliver Hanraths + */ + class MenuAgent extends \nre\agents\BottomlevelAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + // Add Seminary menu + $this->addSubAgent('Seminarymenu'); + } + + } + +?> diff --git a/agents/bottomlevel/QuestgroupshierarchypathAgent.inc b/agents/bottomlevel/QuestgroupshierarchypathAgent.inc new file mode 100644 index 00000000..a2cb8086 --- /dev/null +++ b/agents/bottomlevel/QuestgroupshierarchypathAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\bottomlevel; + + + /** + * Agent to display the Questgroups hierarchy path. + * + * @author Oliver Hanraths + */ + class QuestgroupshierarchypathAgent extends \nre\agents\BottomlevelAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/bottomlevel/SeminarybarAgent.inc b/agents/bottomlevel/SeminarybarAgent.inc new file mode 100644 index 00000000..10315ab5 --- /dev/null +++ b/agents/bottomlevel/SeminarybarAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\bottomlevel; + + + /** + * Agent to display a sidebar with Seminary related information. + * + * @author Oliver Hanraths + */ + class SeminarybarAgent extends \nre\agents\BottomlevelAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/bottomlevel/SeminarymenuAgent.inc b/agents/bottomlevel/SeminarymenuAgent.inc new file mode 100644 index 00000000..375eab1e --- /dev/null +++ b/agents/bottomlevel/SeminarymenuAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\bottomlevel; + + + /** + * Agent to display a menu with Seminary related links. + * + * @author Oliver Hanraths + */ + class SeminarymenuAgent extends \nre\agents\BottomlevelAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/bottomlevel/UserrolesAgent.inc b/agents/bottomlevel/UserrolesAgent.inc new file mode 100644 index 00000000..b6d6e65b --- /dev/null +++ b/agents/bottomlevel/UserrolesAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\bottomlevel; + + + /** + * Agent to display and manage userroles. + * + * @author Oliver Hanraths + */ + class UserrolesAgent extends \nre\agents\BottomlevelAgent + { + + + + + /** + * Action: user. + */ + public function user(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/AchievementsAgent.inc b/agents/intermediate/AchievementsAgent.inc new file mode 100644 index 00000000..e6b965f9 --- /dev/null +++ b/agents/intermediate/AchievementsAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to list Achievements. + * + * @author Oliver Hanraths + */ + class AchievementsAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/CharactergroupsAgent.inc b/agents/intermediate/CharactergroupsAgent.inc new file mode 100644 index 00000000..77ac5f5a --- /dev/null +++ b/agents/intermediate/CharactergroupsAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to display Character groups. + * + * @author Oliver Hanraths + */ + class CharactergroupsAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/CharactergroupsquestsAgent.inc b/agents/intermediate/CharactergroupsquestsAgent.inc new file mode 100644 index 00000000..bfc87de1 --- /dev/null +++ b/agents/intermediate/CharactergroupsquestsAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to display Character groups Quests. + * + * @author Oliver Hanraths + */ + class CharactergroupsquestsAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/CharactersAgent.inc b/agents/intermediate/CharactersAgent.inc new file mode 100644 index 00000000..25102198 --- /dev/null +++ b/agents/intermediate/CharactersAgent.inc @@ -0,0 +1,43 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to list registered Characters and their data. + * + * @author Oliver Hanraths + */ + class CharactersAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + + /** + * Action: character. + */ + public function character(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/ErrorAgent.inc b/agents/intermediate/ErrorAgent.inc new file mode 100644 index 00000000..85bb6f95 --- /dev/null +++ b/agents/intermediate/ErrorAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to show an error page. + * + * @author Oliver Hanraths + */ + class ErrorAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/IntroductionAgent.inc b/agents/intermediate/IntroductionAgent.inc new file mode 100644 index 00000000..3a06fdff --- /dev/null +++ b/agents/intermediate/IntroductionAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to show an introduction page. + * + * @author Oliver Hanraths + */ + class IntroductionAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/LibraryAgent.inc b/agents/intermediate/LibraryAgent.inc new file mode 100644 index 00000000..aedc890a --- /dev/null +++ b/agents/intermediate/LibraryAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to list Quest topics. + * + * @author Oliver Hanraths + */ + class LibraryAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/MediaAgent.inc b/agents/intermediate/MediaAgent.inc new file mode 100644 index 00000000..c3fd35ae --- /dev/null +++ b/agents/intermediate/MediaAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to process and show media. + * + * @author Oliver Hanraths + */ + class MediaAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/QuestgroupsAgent.inc b/agents/intermediate/QuestgroupsAgent.inc new file mode 100644 index 00000000..52ecd4e1 --- /dev/null +++ b/agents/intermediate/QuestgroupsAgent.inc @@ -0,0 +1,36 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to display Questgroups. + * + * @author Oliver Hanraths + */ + class QuestgroupsAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: questgroup. + */ + public function questgroup(\nre\core\Request $request, \nre\core\Response $response) + { + $this->addSubAgent('Questgroupshierarchypath', 'index', $request->getParam(3), $request->getParam(4)); + } + + } + +?> diff --git a/agents/intermediate/QuestsAgent.inc b/agents/intermediate/QuestsAgent.inc new file mode 100644 index 00000000..273a47ab --- /dev/null +++ b/agents/intermediate/QuestsAgent.inc @@ -0,0 +1,54 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to display Quests. + * + * @author Oliver Hanraths + */ + class QuestsAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: quest. + */ + public function quest(\nre\core\Request $request, \nre\core\Response $response) + { + $this->addSubAgent('Questgroupshierarchypath', 'index', $request->getParam(3), $request->getParam(4), true); + } + + + /** + * Action: submissions. + */ + public function submissions(\nre\core\Request $request, \nre\core\Response $response) + { + $this->addSubAgent('Questgroupshierarchypath', 'index', $request->getParam(3), $request->getParam(4), true); + } + + + /** + * Action: submission. + */ + public function submission(\nre\core\Request $request, \nre\core\Response $response) + { + $this->addSubAgent('Questgroupshierarchypath', 'index', $request->getParam(3), $request->getParam(4), true); + } + + } + +?> diff --git a/agents/intermediate/SeminariesAgent.inc b/agents/intermediate/SeminariesAgent.inc new file mode 100644 index 00000000..53f08124 --- /dev/null +++ b/agents/intermediate/SeminariesAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to list registered seminaries. + * + * @author Oliver Hanraths + */ + class SeminariesAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/UploadsAgent.inc b/agents/intermediate/UploadsAgent.inc new file mode 100644 index 00000000..457b6a49 --- /dev/null +++ b/agents/intermediate/UploadsAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to process and show user uploads. + * + * @author Oliver Hanraths + */ + class UploadsAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/intermediate/UsersAgent.inc b/agents/intermediate/UsersAgent.inc new file mode 100644 index 00000000..a9094490 --- /dev/null +++ b/agents/intermediate/UsersAgent.inc @@ -0,0 +1,44 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\intermediate; + + + /** + * Agent to list registered users and their data. + * + * @author Oliver Hanraths + */ + class UsersAgent extends \nre\agents\IntermediateAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + + /** + * Action: user. + */ + public function user(\nre\core\Request $request, \nre\core\Response $response) + { + $this->addSubAgent('Userroles', 'user'); + } + + } + +?> diff --git a/agents/toplevel/BinaryAgent.inc b/agents/toplevel/BinaryAgent.inc new file mode 100644 index 00000000..f2d6d33e --- /dev/null +++ b/agents/toplevel/BinaryAgent.inc @@ -0,0 +1,41 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\toplevel; + + + /** + * Agent to display binary data (e. g. images). + * + * @author Oliver Hanraths + */ + class BinaryAgent extends \hhu\z\ToplevelAgent + { + + + + + protected function __construct(\nre\core\Request $request, \nre\core\Response $response, \nre\core\Logger $log=null) + { + parent::__construct($request, $response, $log); + } + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/toplevel/FaultAgent.inc b/agents/toplevel/FaultAgent.inc new file mode 100644 index 00000000..1ceb682e --- /dev/null +++ b/agents/toplevel/FaultAgent.inc @@ -0,0 +1,35 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\toplevel; + + + /** + * Agent to display a toplevel error page. + * + * @author Oliver Hanraths + */ + class FaultAgent extends \nre\agents\ToplevelAgent + { + + + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + } + + } + +?> diff --git a/agents/toplevel/HtmlAgent.inc b/agents/toplevel/HtmlAgent.inc new file mode 100644 index 00000000..049ba904 --- /dev/null +++ b/agents/toplevel/HtmlAgent.inc @@ -0,0 +1,70 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\agents\toplevel; + + + /** + * Agent to display a HTML-page. + * + * @author Oliver Hanraths + */ + class HtmlAgent extends \hhu\z\ToplevelAgent + { + + + + + protected function __construct(\nre\core\Request $request, \nre\core\Response $response, \nre\core\Logger $log=null) + { + parent::__construct($request, $response, $log); + + + $this->setLanguage($request); + } + + + /** + * Action: index. + */ + public function index(\nre\core\Request $request, \nre\core\Response $response) + { + // Add menu + $this->addSubAgent('Menu'); + + // Add Seminary sidebar + $this->addSubAgent('Seminarybar'); + } + + + + + private function setLanguage(\nre\core\Request $request) + { + // Set domain + $domain = \nre\configs\AppConfig::$app['name']; + + // Get language + $locale = $request->getGetParam('lang', 'language'); + if(is_null($locale)) { + return; + } + + // Load translation + putenv("LC_ALL=$locale"); + setlocale(LC_ALL, $locale); + bindtextdomain($domain, ROOT.DS.\nre\configs\AppConfig::$dirs['locale']); + textdomain($domain); + } + + } + +?> diff --git a/apis/WebApi.inc b/apis/WebApi.inc new file mode 100644 index 00000000..6851ee2d --- /dev/null +++ b/apis/WebApi.inc @@ -0,0 +1,250 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\apis; + + + /** + * WebApi-implementation. + * + * This class runs and renders an web-applictaion. + * + * @author coderkun + */ + class WebApi extends \nre\core\Api + { + + + + + /** + * Construct a new WebApi. + */ + public function __construct() + { + parent::__construct( + new \nre\requests\WebRequest(), + new \nre\responses\WebResponse() + ); + + // Add routes + $this->addRoutes(); + + // Disable screen logging for AJAX requests + if($this->request->getParam(0, 'toplevel') == 'ajax') { + $this->log->disableAutoLogToScreen(); + } + } + + + + + /** + * Run application. + * + * This method runs the application and handles all errors. + */ + public function run() + { + try { + $exception = parent::run(); + + if(!is_null($exception)) { + $this->errorService($exception); + } + } + catch(\nre\exceptions\ServiceUnavailableException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\ActionNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND); + } + catch(\nre\exceptions\FatalDatamodelException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\DatamodelException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\DriverNotValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\DriverNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\ModelNotValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\ModelNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\ViewNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND); + } + catch(\nre\exceptions\ControllerNotValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\ControllerNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND); + } + catch(\nre\exceptions\AgentNoaatValidException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_SERVICE_UNAVAILABLE); + } + catch(\nre\exceptions\AgentNotFoundException $e) { + $this->error($e, \nre\core\WebUtils::HTTP_NOT_FOUND); + } + catch(\nre\exceptions\ClassNotValidException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + $this->errorService($e); + } + } + + + /** + * Render output. + */ + public function render() + { + // Generate output + parent::render(); + + + // Set HTTP-header + $this->response->header(); + + // Show output + echo $this->response->getOutput(); + } + + + + + /** + * Add routes (normal and reverse) defined in the AppConfig. + */ + private function addRoutes() + { + // Normal routes + if(property_exists('\nre\configs\AppConfig', 'routes')) { + foreach(\nre\configs\AppConfig::$routes as &$route) { + $this->request->addRoute($route[0], $route[1], $route[2]); + } + } + + // Reverse routes + if(property_exists('\nre\configs\AppConfig', 'reverseRoutes')) { + foreach(\nre\configs\AppConfig::$reverseRoutes as &$route) { + $this->request->addReverseRoute($route[0], $route[1], $route[2]); + } + } + + // Revalidate request + $this->request->revalidate(); + } + + + /** + * Handle an error that orrcurred during the + * loading/constructing/running of the ToplevelAgent. + * + * @param Exception $exception Occurred exception + * @param int $httpStatusCode HTTP-statuscode + */ + private function error(\nre\core\Exception $exception, $httpStatusCode) + { + // Log error message + $this->log($exception, \nre\core\Logger::LOGMODE_AUTO); + + try { + // Set agent for handling errors + $this->response->clearParams(); + $this->response->addParams( + \nre\configs\AppConfig::$defaults['toplevel-error'], + \nre\configs\AppConfig::$defaults['intermediate-error'], + \nre\configs\CoreConfig::$defaults['action'], + $httpStatusCode + ); + + // Run this agent + parent::run(); + } + catch(\nre\exceptions\ServiceUnavailableException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\ActionNotFoundException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\DatamodelException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\DriverNotValidException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\DriverNotFoundException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\ModelNotValidException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\ModelNotFoundException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\ViewNotFoundException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\ControllerNotValidException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\ControllerNotFoundException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\AgentNotValidException $e) { + $this->errorService($e); + } + catch(\nre\exceptions\AgentNotFoundException $e) { + $this->errorService($e); + } + catch(Exception $e) { + $this->errorService($e); + } + } + + + /** + * Handle a error which cannot be handles by the system (and + * HTTP 503). + * + * $param Exception $exception Occurred exception + */ + private function errorService($exception) + { + // Log error message + $this->log($exception, \nre\core\Logger::LOGMODE_AUTO); + + // Set HTTP-rtatuscode + $this->response->addHeader(\nre\core\WebUtils::getHttpHeader(503)); + + + // Read and print static error file + $fileName = ROOT.DS.\nre\configs\CoreConfig::getClassDir('views').DS.\nre\configs\CoreConfig::$defaults['errorFile'].\nre\configs\CoreConfig::getFileExt('views'); + ob_start(); + include($fileName); + $this->response->setOutput(ob_get_clean()); + + + // Prevent further execution + $this->response->setExit(); + } + + } + +?> diff --git a/app/Controller.inc b/app/Controller.inc new file mode 100644 index 00000000..461c9fd1 --- /dev/null +++ b/app/Controller.inc @@ -0,0 +1,106 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Abstract class for implementing an application Controller. + * + * @author Oliver Hanraths + */ + abstract class Controller extends \nre\core\Controller + { + /** + * Required components + * + * @var array + */ + public $components = array('auth'); + /** + * Linker instance + * + * @var Linker + */ + protected $linker = null; + + + + + /** + * Construct a new application Controller. + * + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ViewNotFoundException + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + * @param Agent $agent Corresponding Agent + */ + public function __construct($layoutName, $action, $agent) + { + parent::__construct($layoutName, $action, $agent); + } + + + + /** + * Prefilter that is executed before running the Controller. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Create linker + $this->linker = new \nre\core\Linker($this->request); + + // Create text formatter + $this->set('t', new \hhu\z\TextFormatter($this->linker)); + + // Create date and time and number formatter + $this->set('dateFormatter', new \IntlDateFormatter( + \nre\core\Config::getDefault('locale'), + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::NONE, + NULL + )); + $this->set('timeFormatter', new \IntlDateFormatter( + \nre\core\Config::getDefault('locale'), + \IntlDateFormatter::NONE, + \IntlDateFormatter::SHORT, + NULL + )); + $this->set('numberFormatter', new \NumberFormatter( + \nre\core\Config::getDefault('locale'), + \NumberFormatter::DEFAULT_STYLE + )); + } + + + /** + * Postfilter that is executed after running the Controller. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function postFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::postFilter($request, $response); + } + + } + +?> diff --git a/app/Model.inc b/app/Model.inc new file mode 100644 index 00000000..32835d04 --- /dev/null +++ b/app/Model.inc @@ -0,0 +1,42 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Abstract class for implementing an application Model. + * + * @author Oliver Hanraths + */ + class Model extends \nre\models\DatabaseModel + { + + + + + /** + * Construct a new application Model. + * + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + */ + public function __construct() + { + parent::__construct('mysqli', \nre\configs\AppConfig::$database); + } + + } + +?> diff --git a/app/QuesttypeAgent.inc b/app/QuesttypeAgent.inc new file mode 100644 index 00000000..23c4d1b2 --- /dev/null +++ b/app/QuesttypeAgent.inc @@ -0,0 +1,267 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Abstract class for implementing a QuesttypeAgent. + * + * @author Oliver Hanraths + */ + abstract class QuesttypeAgent extends \nre\agents\BottomlevelAgent + { + /** + * Current request + * + * @var Request + */ + private $request; + /** + * Current response + * + * @var Response + */ + private $response; + + + + + /** + * Load a QuesttypeAgent. + * + * @static + * @throws QuesttypeAgentNotFoundException + * @throws QuesttypeAgentNotValidException + * @param string $questtypeName Name of the QuesttypeAgent to load + */ + public static function load($questtypeName) + { + // Determine full classname + $className = self::getClassName($questtypeName); + + try { + // Load class + static::loadClass($questtypeName, $className); + + // Validate class + static::checkClass($className, get_class()); + } + catch(\nre\exceptions\ClassNotValidException $e) { + throw new \hhu\z\exceptions\QuesttypeAgentNotValidException($e->getClassName()); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + throw new \hhu\z\exceptions\QuesttypeAgentNotFoundException($e->getClassName()); + } + } + + + /** + * Instantiate a QuesttypeAgent (Factory Pattern). + * + * @static + * @throws DatamodelException + * @throws DriverNotValidException + * @throws DriverNotFoundException + * @throws ViewNotFoundException + * @throws QuesttypeModelNotValidException + * @throws QuesttypeModelNotFoundException + * @throws QuesttypeControllerNotValidException + * @throws QuesttypeControllerNotFoundException + * @param string $questtypeName Name of the QuesttypeAgent to instantiate + * @param Request $request Current request + * @param Response $respone Current respone + * @param Logger $log Log-system + */ + public static function factory($questtypeName, \nre\core\Request $request, \nre\core\Response $response, \nre\core\Logger $log=null) + { + // Determine full classname + $className = self::getClassName($questtypeName); + + // Construct and return Questmodule + return new $className($request, $response, $log); + } + + + /** + * Determine the Agent-classname for the given Questtype-name. + * + * @static + * @param string $questtypeName Questtype-name to get Agent-classname of + * @return string Classname for the Questtype-name + */ + private static function getClassName($questtypeName) + { + $className = \nre\core\ClassLoader::concatClassNames($questtypeName, \nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::stripNamespace(get_class())), 'agent'); + + + return \nre\configs\AppConfig::$app['namespace']."questtypes\\$className"; + } + + + /** + * Load the class of a QuesttypeAgent. + * + * @static + * @throws ClassNotFoundException + * @param string $questtypeName Name of the QuesttypeAgent to load + * @param string $fullClassName Name of the class to load + */ + private static function loadClass($questtypeName, $fullClassName) + { + // Determine folder to look in + $className = explode('\\', $fullClassName); + $className = array_pop($className); + + // Determine filename + $fileName = ROOT.DS.\nre\configs\AppConfig::$dirs['questtypes'].DS.strtolower($questtypeName).DS.$className.\nre\configs\CoreConfig::getFileExt('includes'); + + // Check file + if(!file_exists($fileName)) + { + throw new \nre\exceptions\ClassNotFoundException( + $fullClassName + ); + } + + // Include file + include_once($fileName); + } + + + /** + * Check inheritance of the QuesttypeAgent-class. + * + * @static + * @throws ClassNotValidException + * @param string $className Name of the class to check + * @param string $parentClassName Name of the parent class + */ + public static function checkClass($className, $parentClassName) + { + // Check if class is subclass of parent class + if(!is_subclass_of($className, $parentClassName)) { + throw new \nre\exceptions\ClassNotValidException( + $className + ); + } + } + + + + + /** + * Construct a new QuesttypeAgent. + * + * @throws DatamodelException + * @throws DriverNotValidException + * @throws DriverNotFoundException + * @throws ViewNotFoundException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws QuesttypeModelNotValidException + * @throws QuesttypeModelNotFoundException + * @throws QuesttypeControllerNotValidException + * @throws QuesttypeControllerNotFoundException + * @param Request $request Current request + * @param Response $respone Current response + * @param Logger $log Log-system + */ + protected function __construct(\nre\core\Request $request, \nre\core\Response $response, \nre\core\Logger $log=null) + { + // Store values + $this->request = $request; + $this->response = $response; + + + // Call parent constructor + parent::__construct($request, $response, $log); + } + + + + + /** + * Save the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + $this->controller->saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers); + } + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + return $this->controller->matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers); + } + + + + + /** + * Load the Controller of this Agent. + * + * @throws DatamodelException + * @throws DriverNotValidException + * @throws DriverNotFoundException + * @throws ViewNotFoundException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws QuesttypeModelNotValidException + * @throws QuesttypeModelNotFoundException + * @throws QuesttypeControllerNotValidException + * @throws QuesttypeControllerNotFoundException + */ + protected function loadController() + { + // Determine Controller name + $controllerName = \nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::getClassName(get_class($this))); + + // Determine ToplevelAgent + $toplevelAgentName = $this->response->getParam(0); + if(is_null($toplevelAgentName)) { + $toplevelAgentName = $this->request->getParam(0, 'toplevel'); + $this->response->addParam($toplevelAgentName); + } + + // Determine Action + $action = $this->response->getParam(2); + if(is_null($action)) { + $action = $this->request->getParam(2, 'action'); + $this->response->addParam($action); + } + + + // Load Controller + \hhu\z\QuesttypeController::load($controllerName); + + // Construct Controller + $this->controller = QuesttypeController::factory($controllerName, $toplevelAgentName, $action, $this); + } + + } + +?> diff --git a/app/QuesttypeController.inc b/app/QuesttypeController.inc new file mode 100644 index 00000000..9b13512e --- /dev/null +++ b/app/QuesttypeController.inc @@ -0,0 +1,361 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Abstract class for implementing a QuesttypeController. + * + * @author Oliver Hanraths + */ + abstract class QuesttypeController extends \hhu\z\Controller + { + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'questgroups', 'quests', 'characters'); + + + + + /** + * Save the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public abstract function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers); + + + /** + * Save additional data for the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $data Additional (POST-) data + */ + public abstract function saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $data); + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + * @return boolean True/false for a right/wrong answer or null for moderator evaluation + */ + public abstract function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers); + + + /** + * Action: quest. + * + * Show the task of a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param Exception $exception Character submission exception + */ + public abstract function quest($seminary, $questgroup, $quest, $character, $exception); + + + + /** + * Action: submission. + * + * Show the submission of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + */ + public abstract function submission($seminary, $questgroup, $quest, $character); + + + + + /** + * Load a QuesttypeController. + * + * @static + * @throws QuesttypeControllerNotFoundException + * @throws QuesttypeControllerNotValidException + * @param string $controllerName Name of the QuesttypeController to load + */ + public static function load($controllerName) + { + // Determine full classname + $className = self::getClassName($controllerName); + + try { + // Load class + static::loadClass($controllerName, $className); + + // Validate class + static::checkClass($className, get_class()); + } + catch(\nre\exceptions\ClassNotValidException $e) { + throw new \hhu\z\exceptions\QuesttypeControllerNotValidException($e->getClassName()); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + throw new \hhu\z\exceptions\QuesttypeControllerNotFoundException($e->getClassName()); + } + } + + + /** + * Instantiate a QuesttypeController (Factory Pattern). + * + * @static + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws QuesttypeModelNotValidException + * @throws QuesttypeModelNotFoundException + * @throws ViewNotFoundException + * @param string $controllerName Name of the QuesttypeController to instantiate + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + */ + public static function factory($controllerName, $layoutName, $action, $agent) + { + // Determine full classname + $className = self::getClassName($controllerName); + + // Construct and return Controller + return new $className($layoutName, $action, $agent); + } + + + /** + * Determine the Controller-classname for the given Questtype-name. + * + * @static + * @param string $questtypeName Questtype-name to get Controller-classname of + * @return string Classname for the Questtype-name + */ + private static function getClassName($questtypeName) + { + $className = \nre\core\ClassLoader::concatClassNames($questtypeName, \nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::stripNamespace(get_class())), 'controller'); + + + return \nre\configs\AppConfig::$app['namespace']."questtypes\\$className"; + } + + + /** + * Load the class of a QuesttypeController + * + * @static + * @throws ClassNotFoundException + * @param string $questtypeName Name of the QuesttypeController to load + * @param string $fullClassName Name of the class to load + */ + private static function loadClass($questtypeName, $fullClassName) + { + // Determine folder to look in + $className = explode('\\', $fullClassName); + $className = array_pop($className); + + // Determine filename + $fileName = ROOT.DS.\nre\configs\AppConfig::$dirs['questtypes'].DS.strtolower($questtypeName).DS.$className.\nre\configs\CoreConfig::getFileExt('includes'); + + // Check file + if(!file_exists($fileName)) + { + throw new \nre\exceptions\ClassNotFoundException( + $fullClassName + ); + } + + // Include file + include_once($fileName); + } + + + /** + * Check inheritance of the QuesttypeController-class. + * + * @static + * @throws ClassNotValidException + * @param string $className Name of the class to check + * @param string $parentClassName Name of the parent class + */ + public static function checkClass($className, $parentClassName) + { + // Check if class is subclass of parent class + if(!is_subclass_of($className, $parentClassName)) { + throw new \nre\exceptions\ClassNotValidException( + $className + ); + } + } + + + + + /** + * Construct a new application Controller. + * + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws QuesttypeModelNotValidException + * @throws QuesttypeModelNotFoundException + * @throws ViewNotFoundException + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + * @param Agent $agent Corresponding Agent + */ + public function __construct($layoutName, $action, $agent) + { + parent::__construct($layoutName, $action, $agent); + } + + + + + /** + * Load the Models of this Controller. + * + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws QuesttypeModelNotValidException + * @throws QuesttypeModelNotFoundException + */ + protected function loadModels() + { + // Load default models + parent::loadModels(); + + // Load QuesttypeModel + $this->loadModel(); + } + + + /** + * Load the Model of the Questtype. + * + * @throws QuesttypeModelNotValidException + * @throws QuesttypeModelNotFoundException + */ + private function loadModel() + { + // Determine Model + $model = \nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::stripNamespace(get_class($this)))); + + // Load class + QuesttypeModel::load($model); + + // Construct Model + $modelName = ucfirst(strtolower($model)); + $this->$modelName = QuesttypeModel::factory($model); + } + + + /** + * Load the View of this QuesttypeController. + * + * @throws ViewNotFoundException + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + */ + protected function loadView($layoutName, $action) + { + // Check Layout name + if(is_null($layoutName)) { + return; + } + + // Determine controller name + $controllerName = \nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::getClassName(get_class($this))); + + + // Load view + $this->view = QuesttypeView::loadAndFactory($layoutName, $controllerName, $action); + } + + + /** + * Mark the current Quest as solved and redirect to solved page. + */ + protected function setQuestSolved() + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($this->request->getParam(3)); + + // Get Questgroup + $questgroup = $this->Questgroups->getQuestgroupByUrl($seminary['id'], $this->request->getParam(4)); + + // Get Quest + $quest = $this->Quests->getQuestByUrl($seminary['id'], $questgroup['id'], $this->request->getParam(5)); + + // Character + $character = $this->Characters->getCharacterForUserAndSeminary($this->Auth->getUserId(), $seminary['id']); + + // Set solved + $this->Quests->setQuestSolved($quest['id'], $character['id']); + + + // Redirect + $this->redirect($this->linker->link('Epilog', $sidequest != null ? 6 : 5, true, array('status'=>'solved'))); + } + + + /** + * Mark the current Quest as unsolved and redirect to unsolved + * page. + */ + protected function setQuestUnsolved() + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($this->request->getParam(3)); + + // Get Questgroup + $questgroup = $this->Questgroups->getQuestgroupByUrl($seminary['id'], $this->request->getParam(4)); + + // Get Quest + $quest = $this->Quests->getQuestByUrl($seminary['id'], $questgroup['id'], $this->request->getParam(5)); + + // Character + $character = $this->Characters->getCharacterForUserAndSeminary($this->Auth->getUserId(), $seminary['id']); + + // Set solved + $this->Quests->setQuestUnsolved($quest['id'], $character['id']); + + + // Redirect + $this->redirect($this->linker->link('Prolog', $sidequest != null ? 6 : 5, true, array('status'=>'unsolved'))); + } + + } + +?> diff --git a/app/QuesttypeModel.inc b/app/QuesttypeModel.inc new file mode 100644 index 00000000..d30699fb --- /dev/null +++ b/app/QuesttypeModel.inc @@ -0,0 +1,154 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Abstract class for implementing a QuesttypeModel. + * + * @author Oliver Hanraths + */ + abstract class QuesttypeModel extends \hhu\z\Model + { + + + + + /** + * Load a Model. + * + * @static + * @throws QuesttypeModelNotFoundException + * @throws QuesttypeModelNotValidException + * @param string $modelName Name of the QuesttypeModel to load + */ + public static function load($modelName) + { + // Determine full classname + $className = self::getClassName($modelName); + + try { + // Load class + static::loadClass($modelName, $className); + + // Validate class + static::checkClass($className, get_class()); + } + catch(\nre\exceptions\ClassNotValidException $e) { + throw new \hhu\z\exceptions\QuesttypeModelNotValidException($e->getClassName()); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + throw new \hhu\z\exceptions\QuesttypeModelNotFoundException($e->getClassName()); + } + } + + + /** + * Instantiate a QuesttypeModel (Factory Pattern). + * + * @static + * @param string $questtypeName Name of the QuesttypeModel to instantiate + */ + public static function factory($questtypeName) + { + // Determine full classname + $className = self::getClassName($questtypeName); + + // Construct and return Model + return new $className(); + } + + + /** + * Determine the Model-classname for the given Questtype-name. + * + * @static + * @param string $questtypeName Questtype-name to get Model-classname of + * @return string Classname for the Questtype-name + */ + private static function getClassName($questtypeName) + { + $className = \nre\core\ClassLoader::concatClassNames($questtypeName, \nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::stripNamespace(get_class())), 'model'); + + + return \nre\configs\AppConfig::$app['namespace']."questtypes\\$className"; + } + + + /** + * Load the class of a QuesttypeModel. + * + * @static + * @throws ClassNotFoundException + * @param string $questtypeName Name of the QuesttypeModel to load + * @param string $fullClassName Name of the class to load + */ + private static function loadClass($questtypeName, $fullClassName) + { + // Determine folder to look in + $className = explode('\\', $fullClassName); + $className = array_pop($className); + + // Determine filename + $fileName = ROOT.DS.\nre\configs\AppConfig::$dirs['questtypes'].DS.strtolower($questtypeName).DS.$className.\nre\configs\CoreConfig::getFileExt('includes'); + + // Check file + if(!file_exists($fileName)) + { + throw new \nre\exceptions\ClassNotFoundException( + $fullClassName + ); + } + + // Include file + include_once($fileName); + } + + + /** + * Check inheritance of the QuesttypeModel-class. + * + * @static + * @throws ClassNotValidException + * @param string $className Name of the class to check + * @param string $parentClassName Name of the parent class + */ + public static function checkClass($className, $parentClassName) + { + // Check if class is subclass of parent class + if(!is_subclass_of($className, $parentClassName)) { + throw new \nre\exceptions\ClassNotValidException( + $className + ); + } + } + + + + + /** + * Construct a new QuesttypeModel. + * + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws QuesttypeModelNotValidException + * @throws QuesttypeModelNotFoundException + */ + public function __construct() + { + parent::__construct(); + } + + } + +?> diff --git a/app/QuesttypeView.inc b/app/QuesttypeView.inc new file mode 100644 index 00000000..8e77ee00 --- /dev/null +++ b/app/QuesttypeView.inc @@ -0,0 +1,76 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Abstract class for implementing a QuesttypeView. + * + * @author Oliver Hanraths + */ + class QuesttypeView extends \nre\core\View + { + + + + + /** + * Load and instantiate the QuesttypeView of a QuesttypeAgent. + * + * @throws ViewNotFoundException + * @param string $layoutName Name of Layout in use + * @param string $agentName Name of the Agent + * @param string $action Current Action + * @param bool $isToplevel Agent is a ToplevelAgent + */ + public static function loadAndFactory($layoutName, $agentName=null, $action=null, $isToplevel=false) + { + return new QuesttypeView($layoutName, $agentName, $action, $isToplevel); + } + + + + + /** + * Construct a new QuesttypeView. + * + * @throws ViewNotFoundException + * @param string $layoutName Name of Layout in use + * @param string $agentName Name of the Agent + * @param string $action Current Action + * @param bool $isToplevel Agent is a ToplevelAgent + */ + protected function __construct($layoutName, $agentName=null, $action=null, $isToplevel=false) + { + // Create template filename + // LayoutName + $fileName = ROOT.DS.\nre\configs\AppConfig::$dirs['questtypes'].DS.strtolower($agentName).DS.strtolower($layoutName).DS; + + // Action + $fileName .= strtolower($action); + + // File extension + $fileName .= \nre\configs\CoreConfig::getFileExt('views'); + + + // Check template file + if(!file_exists($fileName)) { + throw new \nre\exceptions\ViewNotFoundException($fileName); + } + + // Save filename + $this->templateFilename = $fileName; + } + + } + +?> diff --git a/app/TextFormatter.inc b/app/TextFormatter.inc new file mode 100644 index 00000000..e11a42ed --- /dev/null +++ b/app/TextFormatter.inc @@ -0,0 +1,141 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Class to format text with different syntax tags. + * + * @author Oliver Hanraths + */ + class TextFormatter + { + /** + * Linker to create links. + * + * @var Linker + */ + private $linker; + /** + * Media-Model to retrieve media data + * + * @static + * @var model + */ + private static $Media = null; + + + + + /** + * Create a new text formatter. + * + * @param Linker $linker Linker to create links with + */ + public function __construct(\nre\core\Linker $linker) + { + $this->linker = $linker; + } + + + + + /** + * Format a string. + * + * @param string $string String to format + * @return string Formatted string + */ + public function t($string) + { + // Remove chars + $string = htmlspecialchars($string, ENT_NOQUOTES); + + // Create tables + $string = str_replace('[table]', '

', $string); + $string = str_replace('[/table]', '

', $string); + $string = str_replace('[tr]', '', $string); + $string = str_replace('[/tr]', '', $string); + $string = str_replace('[th]', '', $string); + $string = str_replace('[/th]', '', $string); + $string = str_replace('[td]', '', $string); + $string = str_replace('[/td]', '', $string); + + // Create links + $string = preg_replace('!(^|\s)"([^"]+)":(https?://[^\s]+)(\s|$)!i', '$1$2$4', $string); + $string = preg_replace('!(^|\s)(https?://[^\s]+)(\s|$)!i', '$1$2$4', $string); + + // Handle Seminarymedia + $seminarymedia = array(); + preg_match_all('/\[seminarymedia:(\d+)\]/iu', $string, $matches); //, PREG_SET_ORDER | PREG_OFFSET_CAPTURE); + $seminarymediaIds = array_unique($matches[1]); + foreach($seminarymediaIds as &$seminarymediaId) + { + $replacement = null; + if(!is_null(\hhu\z\controllers\SeminaryController::$seminary) && $this->loadMediaModel()) + { + try { + $medium = self::$Media->getSeminaryMediaById($seminarymediaId); + $replacement = sprintf( + '%s', + $this->linker->link(array('media','seminary', \hhu\z\controllers\SeminaryController::$seminary['url'],$medium['url'])), + $medium['description'] + ); + } + catch(\nre\exceptions\IdNotFoundException $e) { + } + } + + $seminarymedia[$seminarymediaId] = $replacement; + } + foreach($seminarymedia as $seminarymediaId => $replacement) { + $string = str_replace("[seminarymedia:$seminarymediaId]", $replacement, $string); + } + + + // Return processed string + return nl2br($string); + } + + + + + /** + * Load the Media-Model if it is not loaded + * + * @return boolean Whether the Media-Model has been loaded or not + */ + private function loadMediaModel() + { + // Do not load Model if it has already been loaded + if(!is_null(self::$Media)) { + return true; + } + + try { + // Load class + Model::load('media'); + + // Construct Model + self::$Media = Model::factory('media'); + } + catch(\Exception $e) { + } + + + // Return whether Media-Model has been loaded or not + return !is_null(self::$Media); + } + + } + +?> diff --git a/app/ToplevelAgent.inc b/app/ToplevelAgent.inc new file mode 100644 index 00000000..97aea57b --- /dev/null +++ b/app/ToplevelAgent.inc @@ -0,0 +1,36 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Abstract class for implementing an application Controller. + * + * @author Oliver Hanraths + */ + abstract class ToplevelAgent extends \nre\agents\ToplevelAgent + { + + + + + protected function __construct(\nre\core\Request $request, \nre\core\Response $response, \nre\core\Logger $log=null) + { + parent::__construct($request, $response, $log); + + + // Set timezone + date_default_timezone_set(\nre\configs\AppConfig::$app['timeZone']); + } + } + +?> diff --git a/app/Utils.inc b/app/Utils.inc new file mode 100644 index 00000000..6f0475df --- /dev/null +++ b/app/Utils.inc @@ -0,0 +1,112 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z; + + + /** + * Class for implementing utility methods. + * + * @author Oliver Hanraths + */ + class Utils + { + + + /** + * Mask HTML-chars for save output. + * + * @static + * @param string $string String to be masked + * @return string Masked string + */ + public static function t($string) + { + return nl2br(htmlspecialchars($string)); + } + + + /** + * ‚htmlspecialchars‘ with support for UTF-8. + * + * @static + * @param string $string String to be masked + * @return string Masked string + */ + public static function htmlspecialchars_utf8($string) + { + return htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); + } + + + /** + * Cut a string to the given length but only word boundaries. + * + * @static + * @param string $string String to cut + * @param int $length Length to cut string + * @param int $scope Maximum length to cut string regardless word boundaries + * @return string Cutted string + */ + public static function shortenString($string, $length, $scope) + { + // Determine length + $length = min($length, strlen($string)); + + // Look for word boundary + if(($pos = strpos($string, ' ', $length)) !== false) + { + // Check if boundary is outside of scope + if($pos > $length + $scope) { + $pos = strrpos(substr($string, 0, $pos), ' '); + } + } + else { + $pos = strlen($string); + } + + + // Cut string and return it + return substr($string, 0, $pos); + } + + + /** + * Send an e‑mail. + * + * @param string $from Sender of mail + * @param mixed $to One (string) or many (array) receivers + * @param string $subject Subject of mail + * @param string $message Message of mail + * @param boolean $html Whether mail should be formatted as HTML or not + * @return Whether mail has been send or not + */ + public static function sendMail($from, $to, $subject, $message, $html=false) + { + // Set receivers + $to = is_array($to) ? implode(',', $to) : $to; + + // Set header + $headers = array(); + $headers[] = 'Content-type: text/'.($html ? 'html' : 'plain').'; charset=UTF-8'; + if(!is_null($from)) { + $headers[] = "From: $from"; + } + $header = implode("\r\n", $headers)."\r\n"; + + + // Send mail + return mail($to, $subject, $message, $header); + } + + } + +?> diff --git a/app/controllers/IntermediateController.inc b/app/controllers/IntermediateController.inc new file mode 100644 index 00000000..19647d7e --- /dev/null +++ b/app/controllers/IntermediateController.inc @@ -0,0 +1,139 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Abstract class for implementing a Controller of an IntermediateAgent. + * + * @author Oliver Hanraths + */ + abstract class IntermediateController extends \hhu\z\Controller + { + /** + * Required models + * + * @var array + */ + public $models = array('users', 'userroles', 'seminaries', 'characters'); + /** + * Current user + * + * @var array + */ + public static $user = null; + + + + + /** + * Construct a new IntermediateController. + * + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ViewNotFoundException + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + * @param Agent $agent Corresponding Agent + */ + public function __construct($layoutName, $action, $agent) + { + parent::__construct($layoutName, $action, $agent); + } + + + + /** + * Prefilter that is executed before running the Controller. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Get userdata + try { + self::$user = $this->Users->getUserById($this->Auth->getUserId()); + self::$user['roles'] = array_map(function($r) { return $r['name']; }, $this->Userroles->getUserrolesForUserById(self::$user['id'])); + } + catch(\nre\exceptions\IdNotFoundException $e) { + } + + // Check permissions + $this->checkPermission($request, $response); + + // Set userdata + $this->set('loggedUser', self::$user); + } + + + /** + * Postfilter that is executed after running the Controller. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function postFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::postFilter($request, $response); + } + + + + + /** + * Check user permissions. + * + * @throws AccessDeniedException + */ + private function checkPermission(\nre\core\Request $request, \nre\core\Response $response) + { + // Determine user + $userRoles = array('guest'); + if(!is_null(self::$user)) { + $userRoles = self::$user['roles']; + } + + + // Do not check error pages + if($response->getParam(0, 'toplevel') == \nre\core\Config::getDefault('toplevel-error')) { + return; + } + if($response->getParam(1, 'intermediate') == \nre\core\Config::getDefault('intermediate-error')) { + return; + } + + // Determine permissions of Intermediate Controller for current action + $controller = $this->agent->controller; + $action = $this->request->getParam(2, 'action'); + if(!property_exists($controller, 'permissions')) { + return; // Allow if nothing is specified + } + if(!array_key_exists($action, $controller->permissions)) { + return; // Allow if Action is not specified + } + $permissions = $controller->permissions[$action]; + + + // Check permissions + if(count(array_intersect($userRoles, $permissions)) == 0) { + throw new \nre\exceptions\AccessDeniedException(); + } + } + + } + +?> diff --git a/app/controllers/SeminaryController.inc b/app/controllers/SeminaryController.inc new file mode 100644 index 00000000..b20f4d3c --- /dev/null +++ b/app/controllers/SeminaryController.inc @@ -0,0 +1,290 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Abstract class for implementing a Controller for a Seminary and its + * concepts. + * + * @author Oliver Hanraths + */ + abstract class SeminaryController extends \hhu\z\controllers\IntermediateController + { + /** + * Required components + * + * @var array + */ + public $components = array('achievement', 'auth'); + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'characters', 'characterroles', 'achievements'); + /** + * Current Seminary + * + * var array + */ + public static $seminary = null; + /** + * Character of current user and Seminary + * + * @var array + */ + public static $character = null; + + + + + /** + * Construct a new Seminary Controller. + * + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ViewNotFoundException + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + * @param Agent $agent Corresponding Agent + */ + public function __construct($layoutName, $action, $agent) + { + parent::__construct($layoutName, $action, $agent); + } + + + + /** + * Prefilter that is executed before running the Controller. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Get Seminary and Character data + try { + self::$seminary = $this->Seminaries->getSeminaryByUrl($this->request->getParam(3)); + if(!is_null(self::$user)) + { + self::$character = $this->Characters->getCharacterForUserAndSeminary(self::$user['id'], self::$seminary['id']); + self::$character['characterroles'] = array_map(function($r) { return $r['name']; }, $this->Characterroles->getCharacterrolesForCharacterById(self::$character['id'])); + } + } + catch(\nre\exceptions\IdNotFoundException $e) { + } + + // Check permissions + $this->checkPermission($request, $response); + + // Check achievements + $this->checkAchievements($request, $response); + + // Set Seminary and Character data + $this->set('loggedSeminary', self::$seminary); + $this->set('loggedCharacter', self::$character); + } + + + /** + * Postfilter that is executed after running the Controller. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function postFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::postFilter($request, $response); + } + + + + + /** + * Check user permissions. + * + * @throws AccessDeniedException + */ + private function checkPermission(\nre\core\Request $request, \nre\core\Response $response) + { + // Do not check index page + if(is_null($request->getParam(3))) { + return; + } + + + // Determine permissions for current action + $action = $this->request->getParam(2, 'action'); + if(!property_exists($this, 'seminaryPermissions')) { + return; // Allow if nothing is specified + } + if(!array_key_exists($action, $this->seminaryPermissions)) { + return; // Allow if Action is not specified + } + $permissions = $this->seminaryPermissions[$action]; + + + // Check permissions + if(is_null(self::$character) || !array_key_exists('characterroles', self::$character) || count(array_intersect(self::$character['characterroles'], $permissions)) == 0) { + throw new \nre\exceptions\AccessDeniedException(); + } + } + + + /** + * Check for newly achieved Achievements. + */ + private function checkAchievements(\nre\core\Request $request, \nre\core\Response $response) + { + // Check if Character is present + if(is_null(self::$character)) { + return; + } + + // Get unachieved Achievments + $achievements = $this->Achievements->getUnachhievedAchievementsForCharacter(self::$seminary['id'], self::$character['id']); + if(in_array('user', self::$character['characterroles'])) { + $achievements = array_merge($achievements, $this->Achievements->getUnachievedOnlyOnceAchievementsForSeminary(self::$seminary['id'])); + } + + // Check conditions + foreach($achievements as &$achievement) + { + // Check deadline + if($achievement['deadline'] < date('Y-m-d H:i:s')) { + continue; + } + + // Get conditions + $conditions = array(); + $progress = 0; + switch($achievement['condition']) + { + // Date conditions + case 'date': + $conditionsDate = $this->Achievements->getAchievementConditionsDate($achievement['id']); + foreach($conditionsDate as &$condition) + { + $conditions[] = array( + 'func' => 'checkAchievementConditionDate', + 'params' => array( + $condition['select'] + ) + ); + } + break; + // Character conditions + case 'character': + $conditionsCharacter = $this->Achievements->getAchievementConditionsCharacter($achievement['id']); + foreach($conditionsCharacter as &$condition) + { + $conditions[] = array( + 'func' => 'checkAchievementConditionCharacter', + 'params' => array( + $condition['field'], + $condition['value'], + self::$character['id'] + ) + ); + } + break; + // Quest conditions + case 'quest': + $conditionsQuest = $this->Achievements->getAchievementConditionsQuest($achievement['id']); + foreach($conditionsQuest as &$condition) + { + $conditions[] = array( + 'func' => 'checkAchievementConditionQuest', + 'params' => array( + $condition['field'], + $condition['count'], + $condition['value'], + $condition['status'], + $condition['groupby'], + $condition['quest_id'], + self::$character['id'] + ) + ); + } + break; + // Achievement conditions + case 'achievement': + $conditionsAchievement = $this->Achievements->getAchievementConditionsAchievement($achievement['id']); + foreach($conditionsAchievement as &$condition) + { + $conditions[] = array( + 'func' => 'checkAchievementConditionAchievement', + 'params' => array( + $condition['field'], + $condition['count'], + $condition['value'], + $condition['groupby'], + $condition['meta_achievement_id'], + self::$character['id'] + ) + ); + } + break; + } + + // Do not achieve Achievements without conditions + if(empty($conditions)) { + continue; + } + + // Check conditions + $achieved = ($achievement['all_conditions'] == 1); + foreach($conditions as &$condition) + { + // Calculate result of condition + $result = call_user_func_array( + array( + $this->Achievements, + $condition['func'] + ), + $condition['params'] + ); + + // The overall result and abort if possible + if($achievement['all_conditions']) + { + if(!$result) { + $achieved = false; + break; + } + } + else + { + if($result) { + $achieved = true; + break; + } + } + + } + + // Set status + if($achieved) { + $this->Achievements->setAchievementAchieved($achievement['id'], self::$character['id']); + } + } + } + + } + +?> diff --git a/app/exceptions/FileUploadException.inc b/app/exceptions/FileUploadException.inc new file mode 100644 index 00000000..3fb62e6f --- /dev/null +++ b/app/exceptions/FileUploadException.inc @@ -0,0 +1,75 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: File upload went wrong + * + * @author Oliver Hanraths + */ + class FileUploadException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 203; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'File upload went wrong'; + + /** + * Nested message + * + * @var string + */ + private $nestedMessage; + + + + + /** + * Construct a new exception. + */ + function __construct($nestedMessage=null, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $nestedMessage + ); + + // Store values + $this->nestedMessage = $nestedMessage; + } + + + + + /** + * Get nested message. + * + * @return Nested message + */ + public function getNestedMessage() + { + return $this->nestedMessage; + } + + } + +?> diff --git a/app/exceptions/MaxFilesizeException.inc b/app/exceptions/MaxFilesizeException.inc new file mode 100644 index 00000000..f16f335e --- /dev/null +++ b/app/exceptions/MaxFilesizeException.inc @@ -0,0 +1,51 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: File exceeds size maximum. + * + * @author Oliver Hanraths + */ + class MaxFilesizeException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 202; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'File exceeds size maximum'; + + + + + /** + * Construct a new exception. + */ + function __construct($message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code + ); + } + + } + +?> diff --git a/app/exceptions/QuesttypeAgentNotFoundException.inc b/app/exceptions/QuesttypeAgentNotFoundException.inc new file mode 100644 index 00000000..5f6f4ea9 --- /dev/null +++ b/app/exceptions/QuesttypeAgentNotFoundException.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: QuesttypeAgent not found. + * + * @author Oliver Hanraths + */ + class QuesttypeAgentNotFoundException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 101; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'QuesttypeAgent not found'; + + /** + * Name of the class that was not found + * + * @var string + */ + private $questtypeName; + + + + + /** + * Construct a new exception. + * + * @param string $questtypeName Name of the QuesttypeAgent that was not found + */ + function __construct($questtypeName, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $questtypeName + ); + + // Store values + $this->questtypeName = $questtypeName; + } + + + + + /** + * Get the name of the QuesttypeAgent that was not found. + * + * @return string Name of the QuesttypeAgent that was not found + */ + public function getClassName() + { + return $this->questtypeName; + } + + } + +?> diff --git a/app/exceptions/QuesttypeAgentNotValidException.inc b/app/exceptions/QuesttypeAgentNotValidException.inc new file mode 100644 index 00000000..8fa7237f --- /dev/null +++ b/app/exceptions/QuesttypeAgentNotValidException.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: QuesttypeAgent not valid. + * + * @author Oliver Hanraths + */ + class QuesttypeAgentNotValidException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 102; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'QuesttypeAgent not valid'; + + /** + * Name of the invalid class + * + * @var string + */ + private $questtypeName; + + + + + /** + * Construct a new exception. + * + * @param string $questtypeName Name of the invalid QuesttypeAgent + */ + function __construct($questtypeName, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $questtypeName + ); + + // Store value + $this->questtypeName = $questtypeName; + } + + + + + /** + * Get the name of the invalid QuesttypeAgent. + * + * @return string Name of the invalid QuesttypeAgent + */ + public function getClassName() + { + return $this->questtypeName; + } + + } + +?> diff --git a/app/exceptions/QuesttypeControllerNotFoundException.inc b/app/exceptions/QuesttypeControllerNotFoundException.inc new file mode 100644 index 00000000..6d3a4a36 --- /dev/null +++ b/app/exceptions/QuesttypeControllerNotFoundException.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: QuesttypeController not found. + * + * @author Oliver Hanraths + */ + class QuesttypeControllerNotFoundException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 103; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'QuesttypeController not found'; + + /** + * Name of the class that was not found + * + * @var string + */ + private $questtypeName; + + + + + /** + * Construct a new exception. + * + * @param string $questtypeName Name of the QuesttypeController that was not found + */ + function __construct($questtypeName, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $questtypeName + ); + + // Store values + $this->questtypeName = $questtypeName; + } + + + + + /** + * Get the name of the QuesttypeController that was not found. + * + * @return string Name of the QuesttypeController that was not found + */ + public function getClassName() + { + return $this->questtypeName; + } + + } + +?> diff --git a/app/exceptions/QuesttypeControllerNotValidException.inc b/app/exceptions/QuesttypeControllerNotValidException.inc new file mode 100644 index 00000000..8c6735b8 --- /dev/null +++ b/app/exceptions/QuesttypeControllerNotValidException.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: QuesttypeController not valid. + * + * @author Oliver Hanraths + */ + class QuesttypeControllerNotValidException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 104; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'QuesttypeController not valid'; + + /** + * Name of the invalid class + * + * @var string + */ + private $questtypeName; + + + + + /** + * Construct a new exception. + * + * @param string $questtypeName Name of the invalid QuesttypeController + */ + function __construct($questtypeName, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $questtypeName + ); + + // Store value + $this->questtypeName = $questtypeName; + } + + + + + /** + * Get the name of the invalid QuesttypeController. + * + * @return string Name of the invalid QuesttypeController + */ + public function getClassName() + { + return $this->questtypeName; + } + + } + +?> diff --git a/app/exceptions/QuesttypeModelNotFoundException.inc b/app/exceptions/QuesttypeModelNotFoundException.inc new file mode 100644 index 00000000..a2aa01c8 --- /dev/null +++ b/app/exceptions/QuesttypeModelNotFoundException.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: QuesttypeModel not found. + * + * @author Oliver Hanraths + */ + class QuesttypeModelNotFoundException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 105; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'QuesttypeModel not found'; + + /** + * Name of the class that was not found + * + * @var string + */ + private $questtypeName; + + + + + /** + * Construct a new exception. + * + * @param string $questtypeName Name of the QuesttypeModel that was not found + */ + function __construct($questtypeName, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $questtypeName + ); + + // Store values + $this->questtypeName = $questtypeName; + } + + + + + /** + * Get the name of the QuesttypeModel that was not found. + * + * @return string Name of the QuesttypeModel that was not found + */ + public function getClassName() + { + return $this->questtypeName; + } + + } + +?> diff --git a/app/exceptions/QuesttypeModelNotValidException.inc b/app/exceptions/QuesttypeModelNotValidException.inc new file mode 100644 index 00000000..4a78beb6 --- /dev/null +++ b/app/exceptions/QuesttypeModelNotValidException.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: QuesttypeModel not valid. + * + * @author Oliver Hanraths + */ + class QuesttypeModelNotValidException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 106; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'QuesttypeModel not valid'; + + /** + * Name of the invalid class + * + * @var string + */ + private $questtypeName; + + + + + /** + * Construct a new exception. + * + * @param string $questtypeName Name of the invalid QuesttypeModel + */ + function __construct($questtypeName, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $questtypeName + ); + + // Store value + $this->questtypeName = $questtypeName; + } + + + + + /** + * Get the name of the invalid QuesttypeModel. + * + * @return string Name of the invalid QuesttypeModel + */ + public function getClassName() + { + return $this->questtypeName; + } + + } + +?> diff --git a/app/exceptions/SubmissionNotValidException.inc b/app/exceptions/SubmissionNotValidException.inc new file mode 100644 index 00000000..e2923bdf --- /dev/null +++ b/app/exceptions/SubmissionNotValidException.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: Character submission not valid. + * + * @author Oliver Hanraths + */ + class SubmissionNotValidException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 200; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'Character submission not valid'; + + /** + * Nested exception + * + * @var Exception + */ + private $nestedException; + + + + + /** + * Construct a new exception. + * + * @param string $nestedException Nested exception + */ + function __construct($nestedException, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $nestedException + ); + + // Store value + $this->nestedException = $nestedException; + } + + + + + /** + * Get Nested exception. + * + * @return string Nested exception + */ + public function getNestedException() + { + return $this->nestedException; + } + + } + +?> diff --git a/app/exceptions/WrongFiletypeException.inc b/app/exceptions/WrongFiletypeException.inc new file mode 100644 index 00000000..131356b8 --- /dev/null +++ b/app/exceptions/WrongFiletypeException.inc @@ -0,0 +1,75 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\exceptions; + + + /** + * Exception: File has wrong filetype. + * + * @author Oliver Hanraths + */ + class WrongFiletypeException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 201; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'File has wrong type “%s”'; + + /** + * Type of file + * + * @var string + */ + private $type; + + + + + /** + * Construct a new exception. + */ + function __construct($type, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $type + ); + + // Store values + $this->type = $type; + } + + + + + /** + * Get type of file. + * + * @return Type of file + */ + public function getType() + { + return $this->type; + } + + } + +?> diff --git a/app/lib/Password.inc b/app/lib/Password.inc new file mode 100644 index 00000000..f9acf7d5 --- /dev/null +++ b/app/lib/Password.inc @@ -0,0 +1,316 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\lib + { + + + /** + * Class to ensure that Compatibility library below is loaded. + * + * @author Oliver Hanraths + */ + class Password + { + + /** + * Call this function to ensure this file is loaded. + */ + public static function load() + { + } + + } + + } + + + + +/** + * A Compatibility library with PHP 5.5's simplified password hashing API. + * + * @author Anthony Ferrara + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +namespace { + +if (!defined('PASSWORD_DEFAULT')) { + + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + $resultLength = 0; + switch ($algo) { + case PASSWORD_BCRYPT: + // Note that this is a C constant, but not exposed to PHP, so we don't define it here. + $cost = 10; + if (isset($options['cost'])) { + $cost = $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + // The length of salt to generate + $raw_salt_len = 16; + // The length required in the final serialization + $required_salt_len = 22; + $hash_format = sprintf("$2y$%02d$", $cost); + // The expected length of the final crypt() output + $resultLength = 60; + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + $salt_requires_encoding = false; + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt_requires_encoding = true; + } + } else { + $buffer = ''; + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && @is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = PasswordCompat\binary\_strlen($buffer); + while ($read < $raw_salt_len) { + $buffer .= fread($f, $raw_salt_len - $read); + $read = PasswordCompat\binary\_strlen($buffer); + } + fclose($f); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + } + if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) { + $bl = PasswordCompat\binary\_strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = $buffer; + $salt_requires_encoding = true; + } + if ($salt_requires_encoding) { + // encode string with the Base64 variant used by crypt + $base64_digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + $bcrypt64_digits = + './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $base64_string = base64_encode($salt); + $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); + } + $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => 10, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, "$2y$%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] != $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? $options['cost'] : 10; + if ($cost != $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } +} + +} + +namespace PasswordCompat\binary { + /** + * Count the number of bytes in a string + * + * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension. + * In this case, strlen() will count the number of *characters* based on the internal encoding. A + * sequence of bytes might be regarded as a single multibyte character. + * + * @param string $binary_string The input string + * + * @internal + * @return int The number of bytes + */ + function _strlen($binary_string) { + if (function_exists('mb_strlen')) { + return mb_strlen($binary_string, '8bit'); + } + return strlen($binary_string); + } + + /** + * Get a substring based on byte limits + * + * @see _strlen() + * + * @param string $binary_string The input string + * @param int $start + * @param int $length + * + * @internal + * @return string The substring + */ + function _substr($binary_string, $start, $length) { + if (function_exists('mb_substr')) { + return mb_substr($binary_string, $start, $length, '8bit'); + } + return substr($binary_string, $start, $length); + } + +} diff --git a/bootstrap.inc b/bootstrap.inc new file mode 100644 index 00000000..55647ac0 --- /dev/null +++ b/bootstrap.inc @@ -0,0 +1,33 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + // Include required classes + require_once(ROOT.DS.'configs'.DS.'CoreConfig.inc'); + require_once(ROOT.DS.\nre\configs\CoreConfig::getClassDir('core').DS.'Autoloader.inc'); + + + // Set PHP-logging + ini_set('error_log', ROOT.DS.\nre\configs\CoreConfig::getClassDir('logs').DS.'php'.\nre\configs\CoreConfig::getFileExt('logs')); + + // Register autoloader + \nre\core\Autoloader::register(); + + + // Initialize WebApi + $webApi = new \nre\apis\WebApi(); + + // Run WebApi + $webApi->run(); + + // Render output + $webApi->render(); + +?> diff --git a/configs/AppConfig.inc b/configs/AppConfig.inc new file mode 100644 index 00000000..3575314e --- /dev/null +++ b/configs/AppConfig.inc @@ -0,0 +1,200 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace nre\configs; + + + /** + * Application configuration. + * + * This class contains static variables with configuration values for + * the specific application. + * + * @author Oliver Hanraths + */ + final class AppConfig + { + + /** + * Application values + * + * @static + * @var array + */ + public static $app = array( + 'name' => 'The Legend of Z', + 'namespace' => 'hhu\\z\\', + 'timeZone' => 'Europe/Berlin', + 'mailsender' => 'noreply@zyren.inf-d.de' + ); + + + /** + * Default values + * + * @static + * @var array + */ + public static $defaults = array( + 'toplevel' => 'html', + 'toplevel-error' => 'fault', + 'intermediate' => 'introduction', + 'intermediate-error' => 'error', + 'language' => 'de_DE.utf8', + 'locale' => 'de-DE' + ); + + + /** + * Directories + * + * @static + * @var array + */ + public static $dirs = array( + 'locale' => 'locale', + 'media' => 'media', + 'seminarymedia' => 'seminarymedia', + 'questtypes' => 'questtypes', + 'temporary' => 'tmp', + 'uploads' => 'uploads', + 'seminaryuploads' => 'seminaryuploads' + ); + + + /** + * Media sizes + * + * @static + * @var array + */ + public static $media = array( + 'questgroup' => array( + 'width' => 480, + 'height' => 5000 + ), + 'avatar' => array( + 'width' => 500, + 'height' => 500 + ) + ); + + + /** + * Miscellaneous settings + * + * @static + * @var array + */ + public static $misc = array( + 'ranking_range' => 2, + 'achievements_range' => 3 + ); + + + /** + * Validation settings for user input + * + * @static + * @var array + */ + public static $validation = array( + 'username' => array( + 'minlength' => 5, + 'maxlength' => 32, + 'regex' => '/^\w*$/' + ), + 'email' => array( + 'regex' => '/^\S+@[\w\d.-]{2,}\.[\w]{2,6}$/iU' + ), + 'prename' => array( + 'minlength' => 2, + 'maxlength' => 32, + 'regex' => '/^\S*$/' + ), + 'surname' => array( + 'minlength' => 2, + 'maxlength' => 32, + 'regex' => '/^\S*$/' + ), + 'password' => array( + 'minlength' => 5, + 'maxlength' => 64 + ), + 'charactername' => array( + 'minlength' => 5, + 'maxlength' => 32, + 'regex' => '/^\w*$/' + ) + ); + + + /** + * Routes + * + * @static + * @var array + */ + public static $routes = array( + array('css/?(.*)', 'css/$1?layout=stylesheet', true), + array('users/([^/]+)/(edit|delete)', 'users/$2/$1', true), + array('users/(?!(index|login|register|logout|create|edit|delete))', 'users/user/$1', true), + array('seminaries/([^/]+)/(edit|delete)', 'seminaries/$2/$1', true), + array('seminaries/(?!(index|create|edit|delete))', 'seminaries/seminary/$1', true), + /*// z/ ⇒ z/seminaries/seminary/ + array('^([^/]+)/*$', 'seminaries/seminary/$1', true), + // z// ⇒ z/questgroups/questgroup// + array('^([^/]+)/([^/]+)/?$', 'questgropus/questgroup/$1/$2', true), + // z/// ⇒ z/quests/quest/// + array('^([^/]+)/([^/]+)/([^/]+)/?$', 'quests/quest/$1/$2/3', true)*/ + array('characters/(?!(index|character|register|manage))', 'characters/index/$1', true), + array('charactergroups/(?!(index|groupsgroup|group))', 'charactergroups/index/$1', true), + array('charactergroupsquests/(?!(quest))', 'charactergroupsquests/quest/$1', true), + array('media/(.*)', 'media/$1?layout=binary', false), + array('uploads/(.*)', 'uploads/$1?layout=binary', false), + array('uploads/(?!(index|seminary))', 'uploads/index/$1', true) + ); + + + /** + * Reverse routes + * + * @static + * @var array + */ + public static $reverseRoutes = array( + array('users/user/(.*)', 'users/$1', true), + array('users/([^/]+)/(.*)', 'users/$2/$1', true), + array('seminaries/seminary/(.*)', 'seminaries/$1', false), + //array('seminaries/seminary/(.*)', '$1', false) + array('characters/index/(.*)', 'characters/$1', true), + array('charactergroups/index/(.*)', 'charactergroups/$1', true), + array('charactergroupsquests/quest/(.*)', 'charactergroupsquests/$1', true), + array('uploads/index/(.*)', 'uploads/$1', true) + ); + + + /** + * Database connection settings + * + * @static + * @var array + */ + public static $database = array( + 'user' => 'z', + 'host' => 'localhost', + 'password' => 'legendofZ', + 'db' => 'z' + ); + + } + +?> diff --git a/configs/CoreConfig.inc b/configs/CoreConfig.inc new file mode 100644 index 00000000..45bc7e4a --- /dev/null +++ b/configs/CoreConfig.inc @@ -0,0 +1,167 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\configs; + + + /** + * Core configuration. + * + * This class contains static variables with configuration values. + * + * @author coderkun + */ + final class CoreConfig + { + + /** + * Core values + * + * @static + * @var array + */ + public static $core = array( + 'namespace' => 'nre\\', + ); + + + /** + * Directories + * + * @static + * @var array + */ + public static $dirs = array( + 'core' => 'core', + 'publicDir' => 'www' + ); + + + /** + * File extensions + * + * @static + * @var array + */ + public static $fileExts = array( + 'default' => 'inc', + 'views' => 'tpl', + 'logs' => 'log', + ); + + + /** + * Default values + * + * @static + * @var array + */ + public static $defaults = array( + 'action' => 'index', + 'errorFile' => 'error', + 'inlineErrorFile' => 'inlineerror' + ); + + + /** + * Miscellaneous settings + * + * @static + * @var array + */ + public static $misc = array( + 'fileExtDot' => '.' + ); + + + /** + * Logging settings + * + * @static + * @var array + */ + public static $log = array( + 'filename' => 'errors', + 'format' => 'Fehler %d: %s in %s, Zeile %d' + ); + + + /** + * Class-specific settings + * + * @static + * @var array + */ + public static $classes = array( + 'linker' => array( + 'url' => array( + 'length' => 128, + 'delimiter' => '-' + ) + ) + ); + + + + + /** + * Determine the directory for a specific classtype. + * + * @param string $classType Classtype to get directory of + * @return string Directory of given classtype + */ + public static function getClassDir($classType) + { + // Default directory (for core classes) + $classDir = self::$dirs['core']; + + // Configurable directory + if(array_key_exists($classType, self::$dirs)) { + $classDir = self::$dirs[$classType]; + } + else + { + // Default directory for classtype + if(is_dir(ROOT.DS.$classType)) { + $classDir = $classType; + } + } + + + // Return directory + return $classDir; + } + + + /** + * Determine the file extension for a specific filetype. + * + * @param string $fileType Filetype to get file extension of + * @return string File extension of given filetype + */ + public static function getFileExt($fileType) + { + // Default file extension + $fileExt = self::$fileExts['default']; + + // Configurable file extension + if(array_key_exists($fileType, self::$fileExts)) { + $fileExt = self::$fileExts[$fileType]; + } + + + // Return file extension + return self::$misc['fileExtDot'].$fileExt; + } + + } + +?> diff --git a/controllers/AchievementsController.inc b/controllers/AchievementsController.inc new file mode 100644 index 00000000..89b3f703 --- /dev/null +++ b/controllers/AchievementsController.inc @@ -0,0 +1,172 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to list Achievements. + * + * @author Oliver Hanraths + */ + class AchievementsController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('achievements', 'seminaries', 'media', 'characters'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator', 'user') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'index' => array('admin', 'moderator', 'user') + ); + + + + + /** + * Action: index. + * + * List Achievements of a Seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of Seminary + */ + public function index($seminaryUrl) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Character + $character = SeminaryController::$character; + + // Get seldom Achievements + $seldomAchievements = $this->Achievements->getSeldomAchievements($seminary['id'], \nre\configs\AppConfig::$misc['achievements_range'], false); + foreach($seldomAchievements as &$achievement) { + $achievement['achieved'] = $this->Achievements->hasCharacterAchievedAchievement($achievement['id'], $character['id']); + } + + // Get Characters with the most Achievements + $successfulCharacters = $this->Characters->getCharactersWithMostAchievements($seminary['id'], \nre\configs\AppConfig::$misc['achievements_range'], false); + + // Get achieved Achievements + $achievedAchievements = $this->Achievements->getAchievedAchievementsForCharacter($character['id'], false); + + // Get unachieved Achievements + $unachievedAchievements = $this->Achievements->getUnachhievedAchievementsForCharacter($seminary['id'], $character['id'], true, false); + foreach($unachievedAchievements as &$achievement) + { + // Get Character progress + if($achievement['progress']) + { + $conditions = array(); + switch($achievement['condition']) + { + // Character conditions + case 'character': + $conditionsCharacter = $this->Achievements->getAchievementConditionsCharacter($achievement['id']); + foreach($conditionsCharacter as &$condition) + { + $conditions[] = array( + 'func' => 'getAchievementConditionCharacterProgress', + 'params' => array( + $condition['field'], + $condition['value'], + $character['id'] + ) + ); + } + break; + // Quest conditions + case 'quest': + $conditionsQuest = $this->Achievements->getAchievementConditionsQuest($achievement['id']); + foreach($conditionsQuest as &$condition) + { + $conditions[] = array( + 'func' => 'getAchievementConditionQuestProgress', + 'params' => array( + $condition['field'], + $condition['count'], + $condition['value'], + $condition['status'], + $condition['groupby'], + $condition['quest_id'], + $character['id'] + ) + ); + } + break; + // Achievement conditions + case 'achievement': + $conditionsAchievement = $this->Achievements->getAchievementConditionsAchievement($achievement['id']); + foreach($conditionsAchievement as &$condition) + { + $conditions[] = array( + 'func' => 'getAchievementConditionAchievementProgress', + 'params' => array( + $condition['field'], + $condition['count'], + $condition['value'], + $condition['groupby'], + $condition['meta_achievement_id'], + $character['id'] + ) + ); + } + break; + } + + $characterProgresses = array(); + foreach($conditions as &$condition) + { + // Calculate progress of condition + $characterProgresses[] = call_user_func_array( + array( + $this->Achievements, + $condition['func'] + ), + $condition['params'] + ); + } + + $achievement['characterProgress'] = array_sum($characterProgresses) / count($characterProgresses); + } + } + + // Get ranking + $character['rank'] = $this->Achievements->getCountRank($seminary['id'], count($achievedAchievements)); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('character', $character); + $this->set('seldomAchievements', $seldomAchievements); + $this->set('successfulCharacters', $successfulCharacters); + $this->set('achievedAchievements', $achievedAchievements); + $this->set('unachievedAchievements', $unachievedAchievements); + } + + } + +?> diff --git a/controllers/BinaryController.inc b/controllers/BinaryController.inc new file mode 100644 index 00000000..0d4396ef --- /dev/null +++ b/controllers/BinaryController.inc @@ -0,0 +1,37 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the BinaryAgent to show binary data. + * + * @author Oliver Hanraths + */ + class BinaryController extends \hhu\z\controllers\IntermediateController + { + + + + + /** + * Action: index. + * + * Create binary data. + */ + public function index() + { + } + + } + +?> diff --git a/controllers/CharactergroupsController.inc b/controllers/CharactergroupsController.inc new file mode 100644 index 00000000..0811e6c9 --- /dev/null +++ b/controllers/CharactergroupsController.inc @@ -0,0 +1,150 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the CharactergroupsAgent to display Character groups. + * + * @author Oliver Hanraths + */ + class CharactergroupsController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'charactergroups', 'charactergroupsquests', 'avatars', 'media'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'quest' => array('admin', 'moderator', 'user') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'quest' => array('admin', 'moderator', 'user') + ); + + + + + /** + * Action: index. + * + * Show Character groups-groups for a Seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a Seminary + */ + public function index($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Character groups-groups + $groupsgroups = $this->Charactergroups->getGroupsroupsForSeminary($seminary['id']); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('groupsgroups', $groupsgroups); + } + + + /** + * Action: groupsgroups. + * + * Show Character groups for a Character groups-group of a + * Seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a Seminary + * @param string $groupsgroupUrl URL-Title of a Character groups-group + */ + public function groupsgroup($seminaryUrl, $groupsgroupUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Character groups-group + $groupsgroup = $this->Charactergroups->getGroupsgroupByUrl($seminary['id'], $groupsgroupUrl); + + // Get Character groups + $groups = $this->Charactergroups->getGroupsForGroupsgroup($groupsgroup['id']); + + // Get Character groups-group Quests + $quests = $this->Charactergroupsquests->getQuestsForCharactergroupsgroup($groupsgroup['id']); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('groupsgroup', $groupsgroup); + $this->set('groups', $groups); + $this->set('quests', $quests); + } + + + /** + * Action: group. + * + * Show a Character group for a Character groups-group of a + * Seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a Seminary + * @param string $groupsgroupUrl URL-Title of a Character groups-group + * @param string $groupUrl URL-Title of a Character group + */ + public function group($seminaryUrl, $groupsgroupUrl, $groupUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Character groups-group + $groupsgroup = $this->Charactergroups->getGroupsgroupByUrl($seminary['id'], $groupsgroupUrl); + + // Get Character group + $group = $this->Charactergroups->getGroupByUrl($groupsgroup['id'], $groupUrl); + $group['characters'] = $this->Characters->getCharactersForGroup($group['id']); + $group['rank'] = $this->Charactergroups->getXPRank($groupsgroup['id'], $group['xps']); + + // Get Character avatars + foreach($group['characters'] as &$character) + { + $avatar = $this->Avatars->getAvatarById($character['avatar_id']); + if(!is_null($avatar['small_avatarpicture_id'])) { + $character['small_avatar'] = $this->Media->getSeminaryMediaById($avatar['small_avatarpicture_id']); + } + } + + // Get Character groups Quests + $quests = $this->Charactergroupsquests->getQuestsForGroup($group['id']); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('groupsgroup', $groupsgroup); + $this->set('group', $group); + $this->set('quests', $quests); + } + + } + +?> diff --git a/controllers/CharactergroupsquestsController.inc b/controllers/CharactergroupsquestsController.inc new file mode 100644 index 00000000..0285db3e --- /dev/null +++ b/controllers/CharactergroupsquestsController.inc @@ -0,0 +1,91 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the CharactergroupsquestsAgent to display Character + * groups Quests. + * + * @author Oliver Hanraths + */ + class CharactergroupsquestsController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'charactergroups', 'charactergroupsquests', 'media'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'quest' => array('admin', 'moderator', 'user') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'quest' => array('admin', 'moderator', 'user') + ); + + + + + /** + * Action: quest. + * + * Show a Character groups Quest for a Character groups-group + * of a Seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a Seminary + * @param string $groupsgroupUrl URL-Title of a Character groups-group + * @param string $questUrl URL-Title of a Character groups Quest + */ + public function quest($seminaryUrl, $groupsgroupUrl, $questUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Character groups-group + $groupsgroup = $this->Charactergroups->getGroupsgroupByUrl($seminary['id'], $groupsgroupUrl); + + // Get Character groups-group Quests + $quest = $this->Charactergroupsquests->getQuestByUrl($groupsgroup['id'], $questUrl); + + // Get Character groups-groups + $groups = $this->Charactergroupsquests->getGroupsForQuest($quest['id']); + + // Media + $questmedia = null; + if(!is_null($quest['questsmedia_id'])) { + $questmedia = $this->Media->getSeminaryMediaById($quest['questsmedia_id']); + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('groupsgroup', $groupsgroup); + $this->set('quest', $quest); + $this->set('groups', $groups); + $this->set('media', $questmedia); + } + + } + +?> diff --git a/controllers/CharactersController.inc b/controllers/CharactersController.inc new file mode 100644 index 00000000..a50a2663 --- /dev/null +++ b/controllers/CharactersController.inc @@ -0,0 +1,388 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to list registered users and their data. + * + * @author Oliver Hanraths + */ + class CharactersController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'characters', 'users', 'charactergroups', 'charactertypes', 'seminarycharacterfields', 'avatars', 'media', 'quests', 'questgroups', 'questtopics'); + /** + * Required components + * + * @var array + */ + public $components = array('validation'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator'), + 'character' => array('admin', 'moderator', 'user'), + 'register' => array('admin', 'moderator', 'user'), + 'manage' => array('admin', 'moderator') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'index' => array('admin', 'moderator'), + 'character' => array('admin', 'moderator', 'user'), + 'manage' => array('admin', 'moderator') + ); + + + + + /** + * Action: index. + * + * List registered Characters for a Seminary + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a Seminary + */ + public function index($seminaryUrl) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get registered Characters + $characters = $this->Characters->getCharactersForSeminary($seminary['id']); + + // Additional Character information + foreach($characters as &$character) + { + // Level + $character['xplevel'] = $this->Characters->getXPLevelOfCharacters($character['id']); + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('characters', $characters); + } + + + /** + * Action: character. + * + * Show a Charater and its details. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a Seminary + * @param string $characterUrl URL-name of a Charater + */ + public function character($seminaryUrl, $characterUrl) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + $seminary['achievable_xps'] = $this->Seminaries->getTotalXPs($seminary['id']); + + // Get Character + $character = $this->Characters->getCharacterByUrl($seminary['id'], $characterUrl); + $character['quest_xps'] = $this->Characters->getQuestXPsOfCharacter($character['id']); + $character['xplevel'] = $this->Characters->getXPLevelOfCharacters($character['id']); + $character['rank'] = $this->Characters->getXPRank($seminary['id'], $character['xps']); + + // Get Seminarycharacterfields + $characterfields = $this->Seminarycharacterfields->getFieldsForCharacter($character['id']); + + // Get User + $user = $this->Users->getUserById($character['user_id']); + + // Get Character groups + $groups = $this->Charactergroups->getGroupsForCharacter($character['id']); + + // Get Achievements + $achievements = $this->Achievements->getAchievedAchievementsForCharacter($character['id']); + + // Get Achievements with deadline (milestones) + $milestones = $this->Achievements->getDeadlineAchievements($seminary['id']); + foreach($milestones as &$milestone) { + $milestone['achieved'] = $this->Achievements->hasCharacterAchievedAchievement($milestone['id'], $character['id']); + } + + // Get ranking + $ranking = array( + 'superior' => $this->Characters->getSuperiorCharacters($seminary['id'], $character['xps'], \nre\configs\AppConfig::$misc['ranking_range']), + 'inferior' => $this->Characters->getInferiorCharacters($seminary['id'], $character['xps'], \nre\configs\AppConfig::$misc['ranking_range']) + ); + + // Get Quest topics + $questtopics = $this->Questtopics->getQuesttopicsForSeminary($seminary['id']); + foreach($questtopics as &$questtopic) + { + $questtopic['questcount'] = $this->Questtopics->getQuestCountForQuesttopic($questtopic['id']); + $questtopic['characterQuestcount'] = $this->Questtopics->getCharacterQuestCountForQuesttopic($questtopic['id'], $character['id']); + } + + // Get “last” Quest + $lastQuest = null; + if(count(array_intersect(array('admin', 'moderator'), \hhu\z\controllers\SeminaryController::$character['characterroles'])) > 0) + { + $lastQuest = $this->Quests->getLastQuestForCharacter($character['id']); + if(!is_null($lastQuest)) { + $lastQuest['questgroup'] = $this->Questgroups->getQuestgroupById($lastQuest['questgroup_id']); + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('character', $character); + $this->set('characterfields', $characterfields); + $this->set('user', $user); + $this->set('groups', $groups); + $this->set('achievements', $achievements); + $this->set('milestones', $milestones); + $this->set('ranking', $ranking); + $this->set('questtopics', $questtopics); + $this->set('lastQuest', $lastQuest); + } + + + /** + * Acton: register. + * + * Register a new character for a Seminary. + * + * @throws IdNotFoundException + * @throws ParamsNotValidException + * @param string $seminaryUrl URL-Title of a Seminary + */ + public function register($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Check for already existing Character + try { + $this->Characters->getCharacterForUserAndSeminary($this->Auth->getUserId(), $seminary['id']); + throw new \nre\exceptions\AccessDeniedException(); + } + catch(\nre\exceptions\IdNotFoundException $e) { + // The should be the case + } + + + // Character types + $types = $this->Charactertypes->getCharacterTypesForSeminary($seminary['id']); + + // Character fields + $fields = $this->Seminarycharacterfields->getFieldsForSeminary($seminary['id']); + + // Register Character + $charactername = ''; + $validation = true; + $fieldsValidation = true; + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('create'))) + { + // Validate Character properties + $validation = $this->Validation->validateParams($this->request->getPostParams(), array('charactername')); + $charactername = $this->request->getPostParam('charactername'); + if($this->Characters->characterNameExists($charactername)) { + $validation = $this->Validation->addValidationResult($validation, 'charactername', 'exist', true); + } + + // Validate type + $typeIndex = null; + foreach($types as $index => &$type) + { + $type['selected'] = ($type['url'] == $this->request->getPostParam('type')); + if($type['selected']) { + $typeIndex = $index; + } + } + if(is_null($typeIndex)) { + throw new \nre\exceptions\ParamsNotValidException($typeIndex); + } + + // Validate fields + $fieldsValues = $this->request->getPostParam('fields'); + foreach($fields as &$field) + { + if(!array_key_exists($field['url'], $fieldsValues)) { + throw new \nre\exceptions\ParamsNotValidException($index); + } + $field['uservalue'] = $fieldsValues[$field['url']]; + if($field['required']) + { + $fieldValidation = $this->Validation->validate($fieldsValues[$field['url']], array('regex'=>$field['regex'])); + if($fieldValidation !== true) + { + if(!is_array($fieldsValidation)) { + $fieldsValidation = array(); + } + $fieldsValidation[$field['url']] = $fieldValidation; + } + } + } + + // Register + if($validation === true && $fieldsValidation === true) + { + $characterId = $this->Characters->createCharacter($this->Auth->getUserId(), $types[$typeIndex]['id'], $charactername); + + // Add Seminary fields + foreach($fields as &$field) { + if(!empty($fieldsValues[$field['url']])) { + $this->Characters->setSeminaryFieldOfCharacter($characterId, $field['id'], $fieldsValues[$field['url']]); + } + } + + // Send mail + $this->sendRegistrationMail($charactername); + + // Redirect + $this->redirect($this->linker->link(array('seminaries'))); + } + } + + // Get XP-levels + $xplevels = $this->Characters->getXPLevelsForSeminary($seminary['id']); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('types', $types); + $this->set('fields', $fields); + $this->set('charactername', $charactername); + $this->set('validation', $validation); + $this->set('fieldsValidation', $fieldsValidation); + $this->set('xplevels', $xplevels); + } + + + /** + * Action: manage. + * + * Manage Characters. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a Seminary + */ + public function manage($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Do action + $selectedCharacters = array(); + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('actions')) && count($this->request->getPostParam('actions')) > 0 && !is_null($this->request->getPostParam('characters')) && count($this->request->getPostParam('characters')) > 0) + { + $actions = $this->request->getPostParam('actions'); + $action = array_keys($actions)[0]; + $selectedCharacters = $this->request->getPostParam('characters'); + + switch($action) + { + // Add/remove role to/from Characters + case 'addrole': + case 'removerole': + // Determine role and check permissions + $role = null; + switch($actions[$action]) + { + case _('Admin'): + if(count(array_intersect(array('admin', 'moderator'), \hhu\z\controllers\IntermediateController::$user['roles'])) <= 0 || !in_array('admin', \hhu\z\controllers\SeminaryController::$character['characterroles'])) { + throw new \nre\exceptions\AccessDeniedException(); + } + $role = 'admin'; + break; + case _('Moderator'): + if(count(array_intersect(array('admin', 'moderator'), \hhu\z\controllers\IntermediateController::$user['roles'])) <= 0 || !in_array('admin', \hhu\z\controllers\SeminaryController::$character['characterroles'])) { + throw new \nre\exceptions\AccessDeniedException(); + } + $role = 'moderator'; + break; + case _('User'): + if(count(array_intersect(array('admin', 'moderator'), \hhu\z\controllers\IntermediateController::$user['roles'])) <= 0 || count(array_intersect(array('admin', 'moderator'), \hhu\z\controllers\SeminaryController::$character['characterroles'])) <= 0) { + throw new \nre\exceptions\AccessDeniedException(); + } + $role = 'user'; + break; + } + + // Add role + if($action == 'addrole') { + foreach($selectedCharacters as &$characterId) { + $this->Characterroles->addCharacterroleToCharacter($characterId, $role); + } + } + // Remove role + else { + foreach($selectedCharacters as &$characterId) { + $this->Characterroles->removeCharacterroleFromCharacter($characterId, $role); + } + } + break; + } + } + + // Get registered Characters + $characters = $this->Characters->getCharactersForSeminary($seminary['id']); + foreach($characters as &$character) + { + $character['xplevel'] = $this->Characters->getXPLevelOfCharacters($character['id']); + $character['characterroles'] = array_map(function($r) { return $r['name']; }, $this->Characterroles->getCharacterrolesForCharacterById($character['id'])); + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('characters', $characters); + $this->set('selectedCharacters', $selectedCharacters); + } + + + + + /** + * Send mail for new Character registration. + * + * @param string $charactername Name of newly registered Character + */ + private function sendRegistrationMail($charactername) + { + $sender = \nre\configs\AppConfig::$app['mailsender']; + if(empty($sender)) { + return; + } + + // Send notification mail to system moderators + $subject = sprintf('new Character registration: %s', $charactername); + $message = sprintf('User “%s” <%s> has registered a new Character “%s” for the Seminary “%s”', self::$user['username'], self::$user['email'], $charactername, self::$seminary['title']); + $characters = $this->Characters->getCharactersWithCharacterRole(self::$seminary['id'], 'moderator'); + foreach($characters as &$character) + { + $moderator = $this->Users->getUserById($character['user_id']); + \hhu\z\Utils::sendMail($sender, $moderator['email'], $subject, $message); + } + } + + } + +?> diff --git a/controllers/ErrorController.inc b/controllers/ErrorController.inc new file mode 100644 index 00000000..a27e3afd --- /dev/null +++ b/controllers/ErrorController.inc @@ -0,0 +1,51 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to show an error page. + * + * @author Oliver Hanraths + */ + class ErrorController extends \hhu\z\Controller + { + + + + + + + /** + * Action: index. + * + * Set HTTP-header and print an error message. + * + * @param int $httpStatusCode HTTP-statuscode of the error that occurred + */ + public function index($httpStatusCode) + { + // Set HTTP-header + if(!array_key_exists($httpStatusCode, \nre\core\WebUtils::$httpStrings)) { + $httpStatusCode = 200; + } + $this->response->addHeader(\nre\core\WebUtils::getHttpHeader($httpStatusCode)); + + // Display statuscode and message + $this->set('code', $httpStatusCode); + $this->set('string', \nre\core\WebUtils::$httpStrings[$httpStatusCode]); + $this->set('userId', $this->Auth->getUserId()); + } + + } + +?> diff --git a/controllers/FaultController.inc b/controllers/FaultController.inc new file mode 100644 index 00000000..be01fea7 --- /dev/null +++ b/controllers/FaultController.inc @@ -0,0 +1,37 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to display a toplevel error page. + * + * @author Oliver Hanraths + */ + class FaultController extends \nre\core\Controller + { + + + + + /** + * Action: index. + * + * Show the error message. + */ + public function index() + { + } + + } + +?> diff --git a/controllers/HtmlController.inc b/controllers/HtmlController.inc new file mode 100644 index 00000000..2d7eef25 --- /dev/null +++ b/controllers/HtmlController.inc @@ -0,0 +1,59 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the HtmlAgent to display a HTML-page. + * + * @author Oliver Hanraths + */ + class HtmlController extends \hhu\z\Controller + { + + + + + /** + * Prefilter. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Set content-type + $this->response->addHeader("Content-type: text/html; charset=utf-8"); + } + + + /** + * Action: index. + * + * Create the HTML-structure. + */ + public function index() + { + // Set the name of the current IntermediateAgent as page title + $this->set('title', $this->request->getParam(1, 'intermediate')); + + // Set userdata + $this->set('loggedUser', IntermediateController::$user); + $this->set('loggedSeminary', SeminaryController::$seminary); + $this->set('loggedCharacter', SeminaryController::$character); + } + + } + +?> diff --git a/controllers/IntroductionController.inc b/controllers/IntroductionController.inc new file mode 100644 index 00000000..c1a07f41 --- /dev/null +++ b/controllers/IntroductionController.inc @@ -0,0 +1,37 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to show an introduction page. + * + * @author Oliver Hanraths + */ + class IntroductionController extends \hhu\z\controllers\IntermediateController + { + + + + + /** + * Action: index. + */ + public function index() + { + // Pass data to view + $this->set('userId', $this->Auth->getUserId()); + } + + } + +?> diff --git a/controllers/LibraryController.inc b/controllers/LibraryController.inc new file mode 100644 index 00000000..1fee6e2c --- /dev/null +++ b/controllers/LibraryController.inc @@ -0,0 +1,135 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the LibraryAgent to list Quest topics. + * + * @author Oliver Hanraths + */ + class LibraryController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('questtopics', 'seminaries', 'quests', 'questgroups'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator', 'user') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'index' => array('admin', 'moderator', 'user') + ); + + + + + /** + * Action: index. + * + * List Questtopics of a Seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of Seminary + */ + public function index($seminaryUrl) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Character + $character = SeminaryController::$character; + + // Get Quest topics + $totalQuestcount = 0; + $totalCharacterQuestcount = 0; + $questtopics = $this->Questtopics->getQuesttopicsForSeminary($seminary['id']); + foreach($questtopics as &$questtopic) + { + // Get Quest count + $questtopic['questcount'] = $this->Questtopics->getQuestCountForQuesttopic($questtopic['id']); + $totalQuestcount += $questtopic['questcount']; + + // Get Character progress + $questtopic['characterQuestcount'] = $this->Questtopics->getCharacterQuestCountForQuesttopic($questtopic['id'], $character['id']); + $totalCharacterQuestcount += $questtopic['characterQuestcount']; + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('totalQuestcount', $totalQuestcount); + $this->set('totalCharacterQuestcount', $totalCharacterQuestcount); + $this->set('questtopics', $questtopics); + } + + + /** + * Action: topic. + * + * Show a Questtopic and its Quests with Questsubtopics. + * + * @throws IdNotFoundException + * @param string $eminaryUrl URL-Title of Seminary + * @param string $questtopicUrl URL-Title of Questtopic + */ + public function topic($seminaryUrl, $questtopicUrl) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Character + $character = SeminaryController::$character; + + // Get Questtopic + $questtopic = $this->Questtopics->getQuesttopicByUrl($seminary['id'], $questtopicUrl); + $questtopic['questcount'] = $this->Questtopics->getQuestCountForQuesttopic($questtopic['id']); + $questtopic['characterQuestcount'] = $this->Questtopics->getCharacterQuestCountForQuesttopic($questtopic['id'], $character['id']); + + // Get Quests + $quests = array(); + foreach($this->Quests->getQuestsForQuesttopic($questtopic['id']) as $quest) + { + if($this->Quests->hasCharacterEnteredQuest($quest['id'], $character['id'])) + { + // Get Questgroup + $quest['questgroup'] = $this->Questgroups->getQuestgroupById($quest['questgroup_id']); + + // Get Subtopics + $quest['subtopics'] = $this->Questtopics->getQuestsubtopicsForQuest($quest['id']); + + $quests[] = $quest; + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('questtopic', $questtopic); + $this->set('quests', $quests); + } + + } + +?> diff --git a/controllers/MediaController.inc b/controllers/MediaController.inc new file mode 100644 index 00000000..c81511d4 --- /dev/null +++ b/controllers/MediaController.inc @@ -0,0 +1,432 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the MediaAgent to process and show Media. + * + * @author Oliver Hanraths + */ + class MediaController extends \hhu\z\controllers\SeminaryController + { + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator', 'user', 'guest'), + 'seminaryheader' => array('admin', 'moderator', 'user'), + 'seminary' => array('admin', 'moderator', 'user'), + 'avatar' => array('admin', 'moderator', 'user'), + 'achievement' => array('admin', 'moderator', 'user') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'seminary' => array('admin', 'moderator', 'user', 'guest'), + 'achievement' => array('admin', 'moderator', 'user', 'guest') + ); + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'achievements', 'media', 'avatars'); + + + + + /** + * Prefilter. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Set headers for caching control + $response->addHeader("Pragma: public"); + $response->addHeader("Cache-control: public, max-age=".(60*60*24)); + $response->addHeader("Expires: ".gmdate('r', time()+(60*60*24))); + $response->addHeader("Date: ".gmdate(\DateTime::RFC822)); + } + + + /** + * Action: index + * + * Display a medium. + * + * @param string $mediaUrl URL-name of the medium + * @param string $action Action for processing the media + */ + public function index($mediaUrl, $action=null) + { + // Get Media + $media = $this->Media->getMediaByUrl($mediaUrl); + + // Get file + $file = $this->getMediaFile($media, $action); + if(is_null($media)) { + return; + } + + + // Pass data to view + $this->set('media', $media); + $this->set('file', $file); + } + + + /** + * Action: seminaryheader + * + * Display the header of a Seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-title of the Seminary + * @param string $action Action for processing the media + */ + public function seminaryheader($seminaryUrl, $action=null) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get media + $media = $this->Media->getSeminaryMediaById($seminary['seminarymedia_id']); + + // Get file + $file = $this->getMediaFile($media, $action); + if(is_null($file)) { + return; + } + + + // Pass data to view + $this->set('media', $media); + $this->set('file', $file); + } + + + /** + * Action: seminary. + * + * Display a Seminary medium. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-title of the Seminary + * @param string $mediaUrl URL-name of the medium + * @param string $action Action for processing the media + */ + public function seminary($seminaryUrl, $mediaUrl, $action=null) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Media + $media = $this->Media->getSeminaryMediaByUrl($seminary['id'], $mediaUrl); + + // Get file + $file = $this->getMediaFile($media, $action); + if(is_null($file)) { + return; + } + + + // Pass data to view + $this->set('media', $media); + $this->set('file', $file); + } + + + /** + * Action: avatar. + * + * Display an Avatar as full size or portrait. + * + * @throws ParamsNotValidException + * @throws IdNotFoundException + * @param string $seminaryUrl URL-title of the Seminary + * @param string $charactertypeUrl URL-title of Character type + * @param int $xplevel XP-level + * @param string $action Size to show (avatar or portrait) + */ + public function avatar($seminaryUrl, $charactertypeUrl, $xplevel, $action='avatar') + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Avatar + $avatar = $this->Avatars->getAvatarByTypeAndLevel($seminary['id'], $charactertypeUrl, $xplevel); + + // Get media + switch($action) + { + case null: + case 'avatar': + $media = $this->Media->getSeminaryMediaById($avatar['avatarpicture_id']); + $file = $this->getMediaFile($media, 'avatar'); + break; + case 'portrait': + $media = $this->Media->getSeminaryMediaById($avatar['small_avatarpicture_id']); + $file = $this->getMediaFile($media); + break; + default: + throw new \nre\exceptions\ParamsNotValidException($action); + break; + } + + // Get file + if(is_null($file)) { + return; + } + + + // Pass data to view + $this->set('media', $media); + $this->set('file', $file); + } + + + /** + * Action: achievement + * + * Display the achievement of a Seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-title of the Seminary + * @param string $achievementUrl URL-title of the Achievement + * @param string $action Action for processing the media + */ + public function achievement($seminaryUrl, $achievementUrl) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Character + $character = SeminaryController::$character; + + // Get Achievement + $achievement = $this->Achievements->getAchievementByUrl($seminary['id'], $achievementUrl); + + // Get media + $index = ''; + if(is_null($character) || !$this->Achievements->hasCharacterAchievedAchievement($achievement['id'], $character['id'])) { + $index = 'unachieved_achievementsmedia_id'; + } + else { + $index = 'achieved_achievementsmedia_id'; + } + if(is_null($achievement[$index])) { + throw new \nre\exceptions\IdNotFoundException($achievementUrl); + } + $media = $this->Media->getSeminaryMediaById($achievement[$index]); + + // Get file + $file = $this->getMediaFile($media, null); + if(is_null($file)) { + return; + } + + + // Pass data to view + $this->set('media', $media); + $this->set('file', $file); + } + + + + + /** + * Determine file information and set the HTTP-header for + * caching accordingly. + * + * @param string $fileName Filename + * @return boolean HTTP-status 304 was set (in cache) + */ + private function setCacheHeaders($fileName) + { + // Determine last change of file + $fileLastModified = gmdate('r', filemtime($fileName)); + + // Generate E-Tag + $fileEtag = hash('sha256', $fileLastModified.$fileName); + + + // Set header + $this->response->addHeader("Last-Modified: ".$fileLastModified); + $this->response->addHeader("Etag: ".$fileEtag); + // HTTP-status + $headerModifiedSince = $this->request->getServerParam('HTTP_IF_MODIFIED_SINCE'); + $headerNoneMatch = $this->request->getServerParam('HTTP_IF_NONE_MATCH'); + if( + !is_null($headerModifiedSince) && $fileLastModified < strtotime($headerModifiedSince) && + !is_null($headerNoneMatch) && $headerNoneMatch == $fileEtag + ) { + $this->response->setExit(true); + $this->response->addHeader(\nre\core\WebUtils::getHttpHeader(304)); + + return true; + } + + + return false; + } + + + /** + * Determine the file for a medium and process it if necessary. + * + * @throws IdNotFoundException + * @throws ParamsNotValidException + * @param array $media Medium to get file for + * @param string $action Action for processing the media + * @return object File for the medium (or null if medium is cached) + */ + private function getMediaFile($media, $action=null) + { + // Get format + $format = explode('/', $media['mimetype']); + $format = $format[1]; + + // Set content-type + $this->response->addHeader("Content-type: ".$media['mimetype'].""); + + // Set filename + $media['filename'] = ROOT.DS.\nre\configs\AppConfig::$dirs['seminarymedia'].DS.$media['id']; + if(!file_exists($media['filename'])) { + throw new \nre\exceptions\IdNotFoundException($mediaUrl); + } + + // Cache + if($this->setCacheHeaders($media['filename'])) { + return null; + } + + // Load and process file + $file = null; + switch($action) + { + // No action + case null: + // Do not process the file + $file = file_get_contents($media['filename']); + break; + case 'questgroup': + if(!in_array(strtoupper($format), self::getImageTypes())) { + $file = file_get_contents($media['filename']); + } + else + { + $file = self::resizeImage( + $media['filename'], + $format, + \nre\configs\AppConfig::$media['questgroup']['width'], + \nre\configs\AppConfig::$media['questgroup']['height'] + ); + } + break; + case 'avatar': + $file = self::resizeImage( + $media['filename'], + $format, + \nre\configs\AppConfig::$media['avatar']['width'], + \nre\configs\AppConfig::$media['avatar']['height'] + ); + break; + default: + throw new ParamsNotValidException($action); + break; + } + + + // Return file + return $file; + } + + + /** + * Get supported image types. + * + * @return array List of supported image types + */ + private static function getImageTypes() + { + $im = new \Imagick(); + + + return $im->queryFormats(); + } + + + /** + * Resize an image. + * + * @param string $fileName Absolute pathname of image to resize + * @param string $mimeType Mimetype of target image + * @param int $width Max. width to resize to + * @param int $height Max. height to resize to + * @return mixed Resized image + */ + private static function resizeImage($fileName, $mimeType, $width, $height) + { + // Read image from cache + $tempFileName = ROOT.DS.\nre\configs\AppConfig::$dirs['temporary'].DS.'media-'.basename($fileName).'-'.$width.'x'.$height; + if(file_exists($tempFileName)) + { + // Check age of file + if(date('r', filemtime($tempFileName)+(60*60*24)) > date('r', time())) { + // Too old, delete + unlink($tempFileName); + } + else { + // Valid, read and return + return file_get_contents($tempFileName); + } + } + + + // ImageMagick + $im = new \Imagick($fileName); + + // Calculate new size + $geometry = $im->getImageGeometry(); + if($geometry['width'] < $width) { + $width = $geometry['width']; + } + if($geometry['height'] < $height) { + $height = $geometry['width']; + } + + // Process + $im->thumbnailImage($width, $height, true); + $im->contrastImage(1); + $im->setImageFormat($mimeType); + + // Save temporary file + $im->writeImage($tempFileName); + + + // Return resized image + return $im; + } + + } + +?> diff --git a/controllers/MenuController.inc b/controllers/MenuController.inc new file mode 100644 index 00000000..6c6a7d58 --- /dev/null +++ b/controllers/MenuController.inc @@ -0,0 +1,52 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to display a menu. + * + * @author Oliver Hanraths + */ + class MenuController extends \hhu\z\Controller + { + + + + + /** + * Prefilter. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Set userdata + $this->set('loggedUser', IntermediateController::$user); + $this->set('loggedCharacter', SeminaryController::$character); + $this->set('loggedSeminary', SeminaryController::$seminary); + } + + + /** + * Action: index. + */ + public function index() + { + } + + } + +?> diff --git a/controllers/QuestgroupsController.inc b/controllers/QuestgroupsController.inc new file mode 100644 index 00000000..e8325c73 --- /dev/null +++ b/controllers/QuestgroupsController.inc @@ -0,0 +1,227 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the QuestgroupsAgent to display Questgroups. + * + * @author Oliver Hanraths + */ + class QuestgroupsController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'questgroupshierarchy', 'questgroups', 'quests', 'questtexts', 'media'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'questgroup' => array('admin', 'moderator', 'user'), + 'create' => array('admin', 'moderator', 'user') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'questgroup' => array('admin', 'moderator', 'user'), + 'create' => array('admin', 'moderator') + ); + + + + + /** + * Action: questgroup. + * + * Display a Questgroup and its data. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a Seminary + * @param string $questgroupUrl URL-Title of a Questgroup + */ + public function questgroup($seminaryUrl, $questgroupUrl) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Questgroup + $questgroup = $this->Questgroups->getQuestgroupByUrl($seminary['id'], $questgroupUrl); + + // Get Questgrouphierarchy + $questgroup['hierarchy'] = $this->Questgroupshierarchy->getHierarchyForQuestgroup($questgroup['id']); + + // Get Character + $character = $this->Characters->getCharacterForUserAndSeminary($this->Auth->getUserId(), $seminary['id']); + + // Check permission + if(count(array_intersect(array('admin','moderator'), SeminaryController::$character['characterroles'])) == 0) + { + $previousQuestgroup = $this->Questgroups->getPreviousQuestgroup($questgroup['id']); + if(!is_null($previousQuestgroup)) { + if(!$this->Questgroups->hasCharacterSolvedQuestgroup($previousQuestgroup['id'], $character['id'])) { + throw new \nre\exceptions\AccessDeniedException(); + } + } + } + + // Get child Questgroupshierarchy + $childQuestgroupshierarchy = null; + if(!empty($questgroup['hierarchy'])) + { + $childQuestgroupshierarchy = $this->Questgroupshierarchy->getChildQuestgroupshierarchy($questgroup['hierarchy']['id']); + foreach($childQuestgroupshierarchy as &$hierarchy) + { + // Get Questgroups + $hierarchy['questgroups'] = $this->Questgroups->getQuestgroupsForHierarchy($hierarchy['id'], $questgroup['id']); + + // Get additional data + foreach($hierarchy['questgroups'] as $i => &$group) + { + $group['solved'] = $this->Questgroups->hasCharacterSolvedQuestgroup($group['id'], $character['id']); + + // Check permission of Questgroups + if($i >= 1 && count(array_intersect(array('admin','moderator'), SeminaryController::$character['characterroles'])) == 0) + { + if(!$hierarchy['questgroups'][$i-1]['solved']) + { + $hierarchy['questgroups'] = array_slice($hierarchy['questgroups'], 0, $i); + break; + } + } + + // Get cumulated data + $data = $this->Questgroups->getCumulatedDataForQuestgroup($group['id'], $character['id']); + $group['xps'] = $data['xps']; + $group['character_xps'] = $data['character_xps']; + } + } + } + + // Get texts + $questgroupTexts = $this->Questgroups->getQuestgroupTexts($questgroup['id']); + + // Get Character XPs + $questgroup['character_xps'] = $this->Questgroups->getAchievedXPsForQuestgroup($questgroup['id'], $character['id']); + + // Media + $picture = null; + if(!is_null($questgroup['questgroupspicture_id'])) + { + $picture = $this->Media->getSeminaryMediaById($questgroup['questgroupspicture_id']); + } + + + // Get Quests + $quests = array(); + if(count($childQuestgroupshierarchy) == 0) + { + $currentQuest = null; + do { + // Get next Quest + if(is_null($currentQuest)) { + $currentQuest = $this->Quests->getFirstQuestOfQuestgroup($questgroup['id']); + } + else { + $nextQuests = $this->Quests->getNextQuests($currentQuest['id']); + $currentQuest = null; + foreach($nextQuests as &$nextQuest) { + if($this->Quests->hasCharacterEnteredQuest($nextQuest['id'], $character['id'])) { + $currentQuest = $nextQuest; + break; + } + } + } + + // Add additional data + if(!is_null($currentQuest)) + { + // Set status + $currentQuest['solved'] = $this->Quests->hasCharacterSolvedQuest($currentQuest['id'], $character['id']); + + // Attach related Questgroups + $currentQuest['relatedQuestgroups'] = array(); + $relatedQuestgroups = $this->Questgroups->getRelatedQuestsgroupsOfQuest($currentQuest['id']); + foreach($relatedQuestgroups as &$relatedQuestgroup) + { + $firstQuest = $this->Quests->getFirstQuestOfQuestgroup($relatedQuestgroup['id']); + if(!empty($firstQuest) && $this->Quests->hasCharacterEnteredQuest($firstQuest['id'], $character['id'])) { + $currentQuest['relatedQuestgroups'][] = $relatedQuestgroup; + } + } + + // Add Quest to Quests + $quests[] = $currentQuest; + } + } + while(!is_null($currentQuest) && ($currentQuest['solved'] || count(array_intersect(array('admin','moderator'), SeminaryController::$character['characterroles'])) > 0)); + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('questgroup', $questgroup); + $this->set('childquestgroupshierarchy', $childQuestgroupshierarchy); + $this->set('texts', $questgroupTexts); + $this->set('picture', $picture); + $this->set('quests', $quests); + } + + + /** + * Action: create. + * + * Create a new Questgroup. + * + * @param string $seminaryUrl URL-Title of a Seminary + */ + public function create($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Create Questgroup + $validation = true; + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('create'))) + { + // TODO Validation + $title = $this->request->getPostParam('title'); + + // Create new Questgroup + if($validation === true) + { + $questgroupId = $this->Questgroups->createQuestgroup( + $this->Auth->getUserId(), + $seminary['id'], + $title + ); + + // Redirect + $this->redirect($this->linker->link(array('seminaries', 'seminary', $seminary['url']))); + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + } + + } + +?> diff --git a/controllers/QuestgroupshierarchypathController.inc b/controllers/QuestgroupshierarchypathController.inc new file mode 100644 index 00000000..fabf2bbb --- /dev/null +++ b/controllers/QuestgroupshierarchypathController.inc @@ -0,0 +1,90 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of QuestgroupshierarchypathAgent to display the + * Questgroups hierarchy path. + * + * @author Oliver Hanraths + */ + class QuestgroupshierarchypathController extends \hhu\z\Controller + { + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'questgroups', 'questgroupshierarchy', 'quests', 'questtexts'); + + + + + /** + * Action: index. + * + * Calculate and show the hierarchy path of a Questgroup. + * + * @param string $seminaryUrl URL-Title of a Seminary + * @param string $questgroupUrl URL-Title of a Questgroup + * @param boolean $showGroup Show the current group itself + */ + public function index($seminaryUrl, $questgroupUrl, $showGroup=false) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Questgroup + $questgroup = $this->Questgroups->getQuestgroupByUrl($seminary['id'], $questgroupUrl); + $questgroup['hierarchy'] = $this->Questgroupshierarchy->getHierarchyForQuestgroup($questgroup['id']); + + // Get parent Questgrouphierarchy + $currentQuestgroup = $questgroup; + $parentQuestgroupshierarchy = array(); + if($showGroup) { + array_unshift($parentQuestgroupshierarchy, $currentQuestgroup); + } + if(is_null($questgroup['hierarchy'])) + { + // Get related Questgroup + $questtext = $this->Questtexts->getRelatedQuesttextForQuestgroup($currentQuestgroup['id']); + $quest = $this->Quests->getQuestById($questtext['quest_id']); + $currentQuestgroup = $this->Questgroups->getQuestgroupById($quest['questgroup_id']); + $currentQuestgroup['hierarchy'] = $this->Questgroupshierarchy->getHierarchyForQuestgroup($currentQuestgroup['id']); + $quest['questgroup'] = $currentQuestgroup; + + // Use Hierarchy name for optional Questgroup + if(!empty($parentQuestgroupshierarchy)) { + $parentQuestgroupshierarchy[0]['hierarchy'] = $currentQuestgroup['hierarchy']; + unset($parentQuestgroupshierarchy[0]['hierarchy']['questgroup_pos']); + } + + array_unshift($parentQuestgroupshierarchy, $quest); + array_unshift($parentQuestgroupshierarchy, $currentQuestgroup); + } + while(!empty($currentQuestgroup['hierarchy']) && !is_null($currentQuestgroup['hierarchy']['parent_questgroup_id'])) + { + $currentQuestgroup = $this->Questgroups->GetQuestgroupById($currentQuestgroup['hierarchy']['parent_questgroup_id']); + $currentQuestgroup['hierarchy'] = $this->Questgroupshierarchy->getHierarchyForQuestgroup($currentQuestgroup['id']); + array_unshift($parentQuestgroupshierarchy, $currentQuestgroup); + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('parentquestgroupshierarchy', $parentQuestgroupshierarchy); + } + + } + +?> diff --git a/controllers/QuestsController.inc b/controllers/QuestsController.inc new file mode 100644 index 00000000..b0804a89 --- /dev/null +++ b/controllers/QuestsController.inc @@ -0,0 +1,817 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the QuestsAgent to display Quests. + * + * @author Oliver Hanraths + */ + class QuestsController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'questgroups', 'quests', 'questtexts', 'media', 'questtypes', 'questgroupshierarchy'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator', 'user'), + 'quest' => array('admin', 'moderator', 'user'), + 'submissions' => array('admin', 'moderator', 'user'), + 'submission' => array('admin', 'moderator', 'user'), + 'create' => array('admin', 'moderator', 'user') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'index' => array('admin', 'moderator', 'user'), + 'quest' => array('admin', 'moderator', 'user'), + 'submissions' => array('admin', 'moderator'), + 'submission' => array('admin', 'moderator'), + 'create' => array('admin', 'moderator') + ); + + + + + /** + * Action: index. + * + * List all Quests for a Seminary. + * + * @param string $seminaryUrl URL-Title of Seminary + */ + public function index($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Prepare filters + $filters = array( + 'questgroups' => array(), + 'questtypes' => array() + ); + + // Get selected filters + $selectedFilters = array( + 'questgroup' => "0", + 'questtype' => "" + ); + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('filters'))) { + $selectedFilters = $this->request->getPostParam('filters'); + } + + // Get Quests + $quests = array(); + foreach($this->Quests->getQuestsForSeminary($seminary['id']) as $quest) + { + // Get Questgroup + $quest['questgroup'] = $this->Questgroups->getQuestgroupById($quest['questgroup_id']); + if($selectedFilters['questgroup'] != "0" && $selectedFilters['questgroup'] != $quest['questgroup']['id']) { + continue; + } + + // Get Questtype + $quest['questtype'] = $this->Questtypes->getQuesttypeById($quest['questtype_id']); + if($selectedFilters['questtype'] != "" && $selectedFilters['questtype'] != $quest['questtype']['classname']) { + continue; + } + + // Add filter values + $filters['questgroups'][$quest['questgroup']['id']] = $quest['questgroup']; + $filters['questtypes'][$quest['questtype']['classname']] = $quest['questtype']; + + // Add open submissions count + $quest['opensubmissionscount'] = count($this->Characters->getCharactersSubmittedQuest($quest['id'])); + + $quests[] = $quest; + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('quests', $quests); + $this->set('filters', $filters); + $this->set('selectedFilters', $selectedFilters); + } + + + /** + * Action: quest. + * + * Show a quest and its task. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of Seminary + * @param string $questgroupUrl URL-Title of Questgroup + * @param string $questUrl URL-Title of Quest + * @param string $questtexttypeUrl URL-Title of Questtexttype + * @param int $questtextPos Position of Questtext + */ + public function quest($seminaryUrl, $questgroupUrl, $questUrl, $questtexttypeUrl=null, $questtextPos=1) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Questgroup + $questgroup = $this->Questgroups->getQuestgroupByUrl($seminary['id'], $questgroupUrl); + $questgroup['picture'] = null; + if(!is_null($questgroup['questgroupspicture_id'])) { + $questgroup['picture'] = $this->Media->getSeminaryMediaById($questgroup['questgroupspicture_id']); + } + + // Get Quest + $quest = $this->Quests->getQuestByUrl($seminary['id'], $questgroup['id'], $questUrl); + + // Get Character + $character = $this->Characters->getCharacterForUserAndSeminary($this->Auth->getUserId(), $seminary['id']); + + // Check permissions + if(count(array_intersect(array('admin','moderator'), SeminaryController::$character['characterroles'])) == 0) + { + $previousQuests = $this->Quests->getPreviousQuests($quest['id']); + if(count($previousQuests) == 0) + { + // Previous Questgroup + $previousQuestgroup = $this->Questgroups->getPreviousQuestgroup($questgroup['id']); + if(!is_null($previousQuestgroup) && !$this->Questgroups->hasCharacterSolvedQuestgroup($previousQuestgroup['id'], $character['id'])) { + throw new \nre\exceptions\AccessDeniedException(); + } + } + else + { + // Previous Quests + // One previous Quest has to be solved and no other + // following Quests of ones has to be tried + $solved = false; + $tried = false; + foreach($previousQuests as &$previousQuest) + { + // // Check previous Quest + if($this->Quests->hasCharacterSolvedQuest($previousQuest['id'], $character['id'])) + { + $solved = true; + + // Check following Quests + $followingQuests = $this->Quests->getNextQuests($previousQuest['id']); + foreach($followingQuests as $followingQuest) + { + // Check following Quest + if($followingQuest['id'] != $quest['id'] && $this->Quests->hasCharacterTriedQuest($followingQuest['id'], $character['id'])) + { + $tried = true; + break; + } + } + + break; + } + } + if(!$solved || $tried) { + throw new \nre\exceptions\AccessDeniedException(); + } + } + } + + // Set status “entered” + $this->Quests->setQuestEntered($quest['id'], $character['id']); + + // Get (related) Questtext + $relatedQuesttext = $this->Questtexts->getRelatedQuesttextForQuestgroup($questgroup['id']); + if(!empty($relatedQuesttext)) { + $relatedQuesttext['quest'] = $this->Quests->getQuestById($relatedQuesttext['quest_id']); + if(!empty($relatedQuesttext['quest'])) { + $relatedQuesttext['quest']['questgroup_url'] = $this->Questgroups->getQuestgroupById($relatedQuesttext['quest']['questgroup_id'])['url']; + } + } + + // Get Questtexts + if(is_null($questtexttypeUrl)) { + $questtexttypeUrl = 'Prolog'; + } + $questtexttypes = $this->Questtexts->getQuesttexttypes(); + if(($questtexttypeIndex = array_search($questtexttypeUrl, array_map(function($t) { return $t['url']; }, $questtexttypes))) === false) { + throw new ParamsNotValidException($questtexttypeUrl); + } + $questtexttype = $questtexttypes[$questtexttypeIndex]; + $questtexts = $this->Questtexts->getQuesttextsOfQuest($quest['id'], $questtexttypeUrl); + foreach($questtexts as &$questtext) + { + // Questtext media + if(!is_null($questtext['questsmedia_id'])) { + $questtext['media'] = $this->Media->getSeminaryMediaById($questtext['questsmedia_id']); + } + + // Related Questgroups + $questtext['relatedQuestsgroups'] = $this->Questgroups->getRelatedQuestsgroupsOfQuesttext($questtext['id']); + } + + // Has Epilog? + $hasEpilog = ($this->Questtexts->getQuesttextCountOfQuest($quest['id'], 'Epilog') > 0); + + // Quest status + $questStatus = $this->request->getGetParam('status'); + + // Quest media + $questmedia = null; + if(!is_null($quest['questsmedia_id'])) { + $questmedia = $this->Media->getSeminaryMediaById($quest['questsmedia_id']); + } + + // Task + $task = null; + if($questtexttypeUrl == 'Prolog') + { + // Questtype + $questtype = $this->Questtypes->getQuesttypeById($quest['questtype_id']); + + // Render task + if(!is_null($questtype['classname'])) { + $task = $this->renderTask($questtype['classname'], $seminary, $questgroup, $quest, $character); + } + else { + // Mark Quest as solved + $this->Quests->setQuestSolved($quest['id'], $character['id']); + } + } + + // Has Character solved quest? + $solved = $this->Quests->hasCharacterSolvedQuest($quest['id'], $character['id']); + + // Next Quest/Questgroup + $nextQuests = null; + $charactedHasChoosenNextQuest = false; + $nextQuestgroup = null; + if($questtexttypeUrl == 'Epilog' || (is_null($questtype['classname']) && !$hasEpilog)) + { + // Next Quest + $nextQuests = $this->Quests->getNextQuests($quest['id']); + foreach($nextQuests as &$nextQuest) + { + // Set entered status of Quest + $nextQuest['entered'] = $this->Quests->hasCharacterEnteredQuest($nextQuest['id'], $character['id']); + if($nextQuest['entered']) { + $charactedHasChoosenNextQuest = true; + } + } + + // Next Questgroup + if(empty($nextQuests)) + { + if(is_null($relatedQuesttext)) + { + $nextQuestgroup = $this->Questgroups->getNextQuestgroup($questgroup['id']); + $nextQuestgroup['hierarchy'] = $this->Questgroupshierarchy->getHierarchyForQuestgroup($nextQuestgroup['id']); + } + else + { + // Related (Main-) Quest + $nextQuest = $relatedQuesttext['quest']; + $nextQuest['entered'] = true; + $nextQuests = array($nextQuest); + } + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('questgroup', $questgroup); + $this->set('questtexttype', $questtexttype); + $this->set('questtexts', $questtexts); + $this->set('hasEpilog', $hasEpilog); + $this->set('quest', $quest); + $this->set('queststatus', $questStatus); + $this->set('relatedquesttext', $relatedQuesttext); + $this->set('nextquests', $nextQuests); + $this->set('charactedHasChoosenNextQuest', $charactedHasChoosenNextQuest); + $this->set('nextquestgroup', $nextQuestgroup); + $this->set('task', $task); + $this->set('media', $questmedia); + $this->set('solved', $solved); + } + + + /** + * List Character submissions for a Quest. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of Seminary + * @param string $questgroupUrl URL-Title of Questgroup + * @param string $questUrl URL-Title of Quest + */ + public function submissions($seminaryUrl, $questgroupUrl, $questUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Questgroup + $questgroup = $this->Questgroups->getQuestgroupByUrl($seminary['id'], $questgroupUrl); + $questgroup['picture'] = null; + if(!is_null($questgroup['questgroupspicture_id'])) { + $questgroup['picture'] = $this->Media->getSeminaryMediaById($questgroup['questgroupspicture_id']); + } + + // Get Quest + $quest = $this->Quests->getQuestByUrl($seminary['id'], $questgroup['id'], $questUrl); + + // Media + $questmedia = null; + if(!is_null($quest['questsmedia_id'])) { + $questmedia = $this->Media->getSeminaryMediaById($quest['questsmedia_id']); + } + + // Get submitted Character submissions waiting for approval + $submittedSubmissionCharacters = $this->Characters->getCharactersSubmittedQuest($quest['id']); + + // Get unsolved Character submissions + $unsolvedSubmissionCharacters = $this->Characters->getCharactersUnsolvedQuest($quest['id']); + + // Get solved Character submissions + $solvedSubmissionCharacters = $this->Characters->getCharactersSolvedQuest($quest['id']); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('questgroup', $questgroup); + $this->set('quest', $quest); + $this->set('media', $questmedia); + $this->set('submittedSubmissionCharacters', $submittedSubmissionCharacters); + $this->set('unsolvedSubmissionCharacters', $unsolvedSubmissionCharacters); + $this->set('solvedSubmissionCharacters', $solvedSubmissionCharacters); + } + + + /** + * Show and handle the submission of a Character for a Quest. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of Seminary + * @param string $questgroupUrl URL-Title of Questgroup + * @param string $questUrl URL-Title of Quest + * @param string $characterUrl URL-Title of Character + */ + public function submission($seminaryUrl, $questgroupUrl, $questUrl, $characterUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Questgroup + $questgroup = $this->Questgroups->getQuestgroupByUrl($seminary['id'], $questgroupUrl); + $questgroup['picture'] = null; + if(!is_null($questgroup['questgroupspicture_id'])) { + $questgroup['picture'] = $this->Media->getSeminaryMediaById($questgroup['questgroupspicture_id']); + } + + // Get Quest + $quest = $this->Quests->getQuestByUrl($seminary['id'], $questgroup['id'], $questUrl); + + // Character + $character = $this->Characters->getCharacterByUrl($seminary['id'], $characterUrl); + + // Media + $questmedia = null; + if(!is_null($quest['questsmedia_id'])) { + $questmedia = $this->Media->getSeminaryMediaById($quest['questsmedia_id']); + } + + // Questtype + $questtype = $this->Questtypes->getQuesttypeById($quest['questtype_id']); + + // Render Questtype output + $output = $this->renderTaskSubmission($questtype['classname'], $seminary, $questgroup, $quest, $character); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('questgroup', $questgroup); + $this->set('quest', $quest); + $this->set('character', $character); + $this->set('media', $questmedia); + $this->set('output', $output); + } + + + /** + * Action: create. + * + * Create a new Quest. + * + * @param string $seminaryUrl URL-Title of a Seminary + */ + public function create($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Quest groups + $questgroups = $this->Questgroups->getQuestgroupsForSeminary($seminary['id']); + + // Quest types + $questtypes = $this->Questtypes->getQuesttypes(); + + // Create Quest + $validation = true; + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('create'))) + { + // TODO Validation + $name = $this->request->getPostParam('name'); + $xps = $this->request->getPostParam('xps'); + $prolog = $this->request->getPostParam('prolog'); + $entrytext = $this->request->getPostParam('entrytext'); + $wrongtext = $this->request->getPostParam('wrongtext'); + $task = $this->request->getPostParam('task'); + + // Validate Questgroup + $questgroupIndex = null; + foreach($questgroups as $index => &$questgroup) + { + $questgroup['selected'] = ($questgroup['url'] == $this->request->getPostParam('questgroup')); + if($questgroup['selected']) { + $questgroupIndex = $index; + } + } + if(is_null($questgroupIndex)) { + throw new \nre\exceptions\ParamsNotValidException($questgroup); + } + + // Validate Questtype + $questtypeIndex = null; + foreach($questtypes as $index => &$questtype) + { + $questtype['selected'] = ($questtype['url'] == $this->request->getPostParam('questtype')); + if($questtype['selected']) { + $questtypeIndex = $index; + } + } + if(is_null($questtypeIndex)) { + throw new \nre\exceptions\ParamsNotValidException($questtype); + } + + // Questmedia + $questmedia = null; + if(array_key_exists('questmedia', $_FILES) && !empty($_FILES['questmedia']) && $_FILES['questmedia']['error'] == 0) { + $questmedia = $_FILES['questmedia']; + } + + // Process Prolog + if(!empty($prolog)) { + $prolog = preg_split('/\s*(_|=){5,}\s*/', $prolog, -1, PREG_SPLIT_NO_EMPTY); + } + + // Create new Quest + if($validation === true) + { + $questId = $this->Quests->createQuest( + $this->Auth->getUserId(), + $name, + $questgroups[$questgroupIndex]['id'], + $questtypes[$questtypeIndex]['id'], + $xps, + $entrytext, + $wrongtext, + $task + ); + + // Create Questmedia + if(!is_null($questmedia)) + { + $questsmediaId = $this->Media->createQuestMedia( + $this->Auth->getUserId(), + $seminary['id'], + $questmedia['name'], + $name, + $questmedia['type'], + $questmedia['tmp_name'] + ); + if($questsmediaId > 0) { + $this->Quests->setQuestmedia($questId, $questsmediaId); + } + } + + // Add Prolog-texts + if(!empty($prolog)) { + $this->Questtexts->addQuesttextsToQuest($this->Auth->getUserId(), $questId, 'Prolog', $prolog); + } + + + // Redirect + $this->redirect($this->linker->link(array('quests', 'index', $seminary['url']))); + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('questgroups', $questgroups); + $this->set('questtypes', $questtypes); + } + + + /** + * Action: createmedia. + * TODO only temporary for easier data import. + * + * Display a form for creating new Seminary media. + * + * @param string $seminaryUrl URL-title of Seminary + */ + public function createmedia($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Create media + $mediaId = null; + $error = null; + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('submit'))) + { + $file = $_FILES['file']; + $error = $file['error']; + if(empty($error)) + { + $mediaId = $this->Media->createQuestMedia( + $this->Auth->getUserId(), + $seminary['id'], + $file['name'], + $this->request->getPostParam('description'), + $file['type'], + $file['tmp_name'] + ); + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('mediaid', $mediaId); + } + + + + + /** + * Render and handle the task of a Quest. + * + * @param string $questtypeClassname Name of the class for the Questtype of a Quest + * @param array $seminary Seminary data + * @param array $questgroup Questgroup data + * @param array $quest Quest data + * @param array $character Character data + * @return string Rendered output + */ + private function renderTask($questtypeClassname, $seminary, $questgroup, $quest, $character) + { + $task = null; + try { + // Generate request and response + $request = clone $this->request; + $response = $this->createQuesttypeResponse('quest', $seminary, $questgroup, $quest, $character); + + // Load Questtype Agent + $questtypeAgent = $this->loadQuesttypeAgent($questtypeClassname, $request, $response); + + // Solve Quest + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('submit'))) + { + // Get user answers + $answers = $this->request->getPostParam('answers'); + + // Save answers in database + try { + if(!$this->Quests->hasCharacterSolvedQuest($quest['id'], $character['id'])) { + $questtypeAgent->saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers); + } + + // Match answers with correct ones + $status = $questtypeAgent->matchAnswersofCharacter($seminary, $questgroup, $quest, $character, $answers); + if($status === true) + { + // Mark Quest as solved + $this->Quests->setQuestSolved($quest['id'], $character['id']); + + // Redirect + $this->redirect($this->linker->link('Epilog', 5, true, array('status'=>'solved'))); + } + elseif($status === false) + { + // Mark Quest as unsolved + $this->Quests->setQuestUnsolved($quest['id'], $character['id']); + + // Redirect + $this->redirect($this->linker->link('Prolog', 5, true, array('status'=>'unsolved'))); + } + else { + // Mark Quest as submitted + $this->Quests->setQuestSubmitted($quest['id'], $character['id']); + + // Redirect + $this->redirect($this->linker->link('Prolog', 5, true)); + } + } + catch(\hhu\z\exceptions\SubmissionNotValidException $e) { + $response->addParam($e); + } + } + + // Render Task + $task = $this->runQuesttypeAgent($questtypeAgent, $request, $response); + } + catch(\nre\exceptions\ViewNotFoundException $e) { + $task = $e->getMessage(); + } + catch(\nre\exceptions\ActionNotFoundException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeModelNotValidException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeModelNotFoundException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeControllerNotValidException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeControllerNotFoundException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeAgentNotValidException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeAgentNotFoundException $e) { + $task = $e->getMessage(); + } + + + // Return rendered output + return $task; + } + + + /** + * Render and handle a Character submission for a Quest. + * + * @param string $questtypeClassname Name of the class for the Questtype of a Quest + * @param array $seminary Seminary data + * @param array $questgroup Questgroup data + * @param array $quest Quest data + * @param array $character Character data + * @return string Rendered output + */ + private function renderTaskSubmission($questtypeClassname, $seminary, $questgroup, $quest, $character) + { + $task = null; + try { + // Generate request and response + $request = clone $this->request; + $response = $this->createQuesttypeResponse('submission', $seminary, $questgroup, $quest, $character); + + // Load Questtype Agent + $questtypeAgent = $this->loadQuesttypeAgent($questtypeClassname, $request, $response); + + // Solve Quest + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('submit'))) + { + // Set status + if($this->request->getPostParam('submit') == _('solved')) + { + // Mark Quest as solved + $this->Quests->setQuestSolved($quest['id'], $character['id']); + } + else + { + // Mark Quest as unsolved + $this->Quests->setQuestUnsolved($quest['id'], $character['id']); + } + + // Save additional data for Character answers + $questtypeAgent->controller->saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $this->request->getPostParam('characterdata')); + + // Redirect + $this->redirect($this->linker->link(array('submissions', $seminary['url'], $questgroup['url'], $quest['url']), 1)); + } + + // Render task submissions + $task = $this->runQuesttypeAgent($questtypeAgent, $request, $response); + } + catch(\nre\exceptions\ViewNotFoundException $e) { + $task = $e->getMessage(); + } + catch(\nre\exceptions\ActionNotFoundException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeModelNotValidException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeModelNotFoundException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeControllerNotValidException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeControllerNotFoundException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeAgentNotValidException $e) { + $task = $e->getMessage(); + } + catch(\hhu\z\exceptions\QuesttypeAgentNotFoundException $e) { + $task = $e->getMessage(); + } + + + // Return rendered output + return $task; + } + + + /** + * Create a response for the Questtype rendering. + * + * @param string $action Action to run + * @param mixed $param Additional parameters to add to the response + * @return Response Generated response + */ + private function createQuesttypeResponse($action, $param1) + { + // Clone current response + $response = clone $this->response; + // Clear parameters + $response->clearParams(1); + + // Add Action + $response->addParams( + null, + $action + ); + + // Add additional parameters + foreach(array_slice(func_get_args(), 1) as $param) { + $response->addParam($param); + } + + + // Return response + return $response; + } + + + /** + * Load and construct the QuesttypeAgent for a Questtype. + * + * @param string $questtypeClassname Name of the class for the Questtype of a Quest + * @param Request $request Request + * @param Response $response Response + * @return QuesttypeAgent + */ + private function loadQuesttypeAgent($questtypeClassname, $request, $response) + { + // Load Agent + \hhu\z\QuesttypeAgent::load($questtypeClassname); + + + // Construct and return Agent + return \hhu\z\QuesttypeAgent::factory($questtypeClassname, $request, $response); + } + + + /** + * Run and render the Agent for a QuesttypeAgent and return ist output. + * + * @param Agent $questtypeAgent QuesttypeAgent to run and render + * @param Request $request Request + * @param Response $response Response + * @return string Rendered output + */ + private function runQuesttypeAgent($questtypeAgent, $request, $response) + { + // Run Agent + $questtypeAgent->run($request, $response); + + + // Render and return output + return $questtypeAgent->render(); + } + + } + +?> diff --git a/controllers/SeminariesController.inc b/controllers/SeminariesController.inc new file mode 100644 index 00000000..345d4085 --- /dev/null +++ b/controllers/SeminariesController.inc @@ -0,0 +1,254 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to list registered seminaries. + * + * @author Oliver Hanraths + */ + class SeminariesController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('seminaries', 'users', 'characterroles', 'questgroupshierarchy', 'questgroups', 'media'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator', 'user'), + 'seminary' => array('admin', 'moderator', 'user'), + 'create' => array('admin', 'moderator'), + 'edit' => array('admin', 'moderator', 'user'), + 'delete' => array('admin', 'moderator', 'user') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'seminary' => array('admin', 'moderator', 'user', 'guest'), + 'edit' => array('admin'), + 'delete' => array('admin') + ); + + + + + /** + * Action: index. + * + * List registered seminaries. + */ + public function index() + { + // Get seminaries + $seminaries = $this->Seminaries->getSeminaries(); + + // Get additional data + foreach($seminaries as &$seminary) + { + $seminary['description'] = \hhu\z\Utils::shortenString($seminary['description'], 100, 120).' …'; + + // Created user + $seminary['creator'] = $this->Users->getUserById($seminary['created_user_id']); + + // Character of currently logged-in user + try { + $seminary['usercharacter'] = $this->Characters->getCharacterForUserAndSeminary($this->Auth->getUserId(), $seminary['id']); + $seminary['usercharacter']['characterroles'] = $this->Characterroles->getCharacterrolesForCharacterById($seminary['usercharacter']['id']); + } + catch(\nre\exceptions\IdNotFoundException $e) { + } + + } + + + // Pass data to view + $this->set('seminaries', $seminaries); + } + + + /** + * Action: seminary. + * + * Show a seminary and its details. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a seminary + */ + public function seminary($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Created user + $seminary['creator'] = $this->Users->getUserById($seminary['created_user_id']); + + // Get Character + $character = $this->Characters->getCharacterForUserAndSeminary($this->Auth->getUserId(), $seminary['id']); + + // Questgrouphierarchy and Questgroups + $questgroupshierarchy = $this->Questgroupshierarchy->getHierarchyOfSeminary($seminary['id']); + foreach($questgroupshierarchy as &$hierarchy) + { + // Get Questgroups + $hierarchy['questgroups'] = $this->Questgroups->getQuestgroupsForHierarchy($hierarchy['id']); + + // Get additional data + foreach($hierarchy['questgroups'] as $i => &$questgroup) + { + // Check permission of Questgroups + if($i >= 1 && count(array_intersect(array('admin','moderator'), SeminaryController::$character['characterroles'])) == 0) + { + if(!$this->Questgroups->hasCharacterSolvedQuestgroup($hierarchy['questgroups'][$i-1]['id'], $character['id'])) + { + $hierarchy['questgroups'] = array_slice($hierarchy['questgroups'], 0, $i); + break; + } + } + + // Get first Questgroup text + $text = $this->Questgroups->getFirstQuestgroupText($questgroup['id']); + if(!empty($text)) + { + $text = \hhu\z\Utils::shortenString($text['text'], 100, 120).' …'; + $questgroup['text'] = $text; + } + + // Get cumulated data + $data = $this->Questgroups->getCumulatedDataForQuestgroup($questgroup['id'], $character['id']); + $questgroup['xps'] = $data['xps']; + $questgroup['character_xps'] = $data['character_xps']; + + // Get Media + $questgroup['picture'] = null; + try { + $questgroup['picture'] = $this->Media->getSeminaryMediaById($questgroup['questgroupspicture_id']); + } + catch(\nre\exceptions\IdNotFoundException $e) { + } + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('questgroupshierarchy', $questgroupshierarchy); + } + + + /** + * Action: create. + * + * Create a new seminary. + */ + public function create() + { + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('create'))) + { + // Create new seminary + $seminaryId = $this->Seminaries->createSeminary( + $this->request->getPostParam('title'), + $this->Auth->getUserId() + ); + + // Redirect to seminary + $user = $this->Seminaries->getSeminaryById($seminaryId); + $this->redirect($this->linker->link(array($seminary['url']), 1)); + } + } + + + /** + * Action: edit. + * + * Edit a seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a seminary + */ + public function edit($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Check request method + if($this->request->getRequestMethod() == 'POST') + { + // Save changes + if(!is_null($this->request->getPostParam('save'))) + { + // Edit seminary + $this->Seminaries->editSeminary( + $seminary['id'], + $this->request->getPostParam('title') + ); + $seminary = $this->Seminaries->getSeminaryById($seminary['id']); + } + + + // Redirect to entry + $this->redirect($this->linker->link(array($seminary['url']), 1)); + } + + + // Pass data to view + $this->set('seminary', $seminary); + } + + + /** + * Action: delete. + * + * Delete a seminary. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a seminary + */ + public function delete($seminaryUrl) + { + // Get seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Check request method + if($this->request->getRequestMethod() == 'POST') + { + // Check confirmation + if(!is_null($this->request->getPostParam('delete'))) + { + // Delete seminary + $this->Seminaries->deleteSeminary($seminary['id']); + + // Redirect to overview + $this->redirect($this->linker->link(null, 1)); + } + + // Redirect to entry + $this->redirect($this->linker->link(array('seminary', $seminary['url']), 1)); + } + + + // Show confirmation + $this->set('seminary', $seminary); + } + + } + +?> diff --git a/controllers/SeminarybarController.inc b/controllers/SeminarybarController.inc new file mode 100644 index 00000000..8b1e18bc --- /dev/null +++ b/controllers/SeminarybarController.inc @@ -0,0 +1,86 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to display a sidebar with Seminary related + * information. + * + * @author Oliver Hanraths + */ + class SeminarybarController extends \hhu\z\Controller + { + /** + * Required models + * + * @var array + */ + public $models = array('characters', 'quests', 'questgroups', 'achievements', 'charactergroups', 'avatars', 'media'); + + + + + /** + * Action: index. + */ + public function index() + { + if(is_null(SeminaryController::$seminary)) { + return; + } + + // Get Seminary + $seminary = SeminaryController::$seminary; + + // Get Character + $character = SeminaryController::$character; + if(is_null($character)) { + return; + } + $character['xplevel'] = $this->Characters->getXPLevelOfCharacters($character['id']); + $character['rank'] = $this->Characters->getXPRank($seminary['id'], $character['xps']); + + // Get “last” Quest + $lastQuest = $this->Quests->getLastQuestForCharacter($character['id']); + if(!is_null($lastQuest)) { + $lastQuest['questgroup'] = $this->Questgroups->getQuestgroupById($lastQuest['questgroup_id']); + } + + // Get last achieved Achievement + $achievements = $this->Achievements->getAchievedAchievementsForCharacter($character['id']); + $lastAchievement = array_shift($achievements); + + // Get Character group members + $characterGroups = array(); + foreach($this->Charactergroups->getGroupsForCharacter($character['id']) as $group) + { + $groupsgroup = $this->Charactergroups->getGroupsgroupById($group['charactergroupsgroup_id']); + if($groupsgroup['preferred']) + { + $group['members'] = $this->Characters->getCharactersForGroup($group['id']); + $characterGroups[] = $group; + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('character', $character); + $this->set('lastQuest', $lastQuest); + $this->set('lastAchievement', $lastAchievement); + $this->set('characterGroups', $characterGroups); + } + + } + +?> diff --git a/controllers/SeminarymenuController.inc b/controllers/SeminarymenuController.inc new file mode 100644 index 00000000..30311c93 --- /dev/null +++ b/controllers/SeminarymenuController.inc @@ -0,0 +1,52 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to display a menu with Seminary related + * links. + * + * @author Oliver Hanraths + */ + class SeminarymenuController extends \hhu\z\controllers\SeminaryController + { + + + + + /** + * Prefilter. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Set userdata + $this->set('loggedUser', self::$user); + $this->set('loggedSeminary', self::$seminary); + } + + + /** + * Action: index. + */ + public function index() + { + } + + } + +?> diff --git a/controllers/UploadsController.inc b/controllers/UploadsController.inc new file mode 100644 index 00000000..4d1da994 --- /dev/null +++ b/controllers/UploadsController.inc @@ -0,0 +1,171 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the UploadsAgent to process and show user uploads. + * + * @author Oliver Hanraths + */ + class UploadsController extends \hhu\z\controllers\SeminaryController + { + /** + * Required models + * + * @var array + */ + public $models = array('uploads', 'users', 'userroles', 'characterroles', 'seminaries'); + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator', 'user', 'guest') + ); + /** + * User seminary permissions + * + * @var array + */ + public $seminaryPermissions = array( + 'seminary' => array('admin', 'moderator', 'user', 'guest') + ); + + + + + /** + * Prefilter. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(\nre\core\Request $request, \nre\core\Response $response) + { + parent::preFilter($request, $response); + + // Set headers for caching control + $response->addHeader("Pragma: public"); + $response->addHeader("Cache-control: public, max-age=".(60*60*24)); + $response->addHeader("Expires: ".gmdate('r', time()+(60*60*24))); + $response->addHeader("Date: ".gmdate(\DateTime::RFC822)); + } + + + /** + * Action: seminary. + * + * Display a Seminary upload. + * + * @throws AccessDeniedException + * @throws IdNotFoundException + * @param string $seminaryUrl URL-title of Seminary + * @param string $uploadUrl URL-name of the upload + */ + public function seminary($seminaryUrl, $uploadUrl) + { + // Get Seminary + $seminary = $this->Seminaries->getSeminaryByUrl($seminaryUrl); + + // Get Upload + $upload = $this->Uploads->getSeminaryuploadByUrl($seminary['id'], $uploadUrl); + + // Check permissions + if(!$upload['public']) + { + $user = $this->Users->getUserById($this->Auth->getUserId()); + $user['roles'] = array_map(function($r) { return $r['name']; }, $this->Userroles->getUserrolesForUserById($user['id'])); + + // System roles + if(count(array_intersect(array('admin', 'moderator'), $user['roles'])) == 0) + { + // Owner of file + if($upload['created_user_id'] != $user['id']) + { + // Seminary permissions + $characterRoles = array_map(function($r) { return $r['name']; }, $this->Characterroles->getCharacterrolesForCharacterById($character['id'])); + if(count(array_intersect(array('admin', 'moderator'), $characterRoles)) == 0) { + throw new \nre\exceptions\AccessDeniedException(); + } + } + } + } + + // Set content-type + $this->response->addHeader("Content-type: ".$upload['mimetype'].""); + + // Set filename + $upload['filename'] = ROOT.DS.\nre\configs\AppConfig::$dirs['seminaryuploads'].DS.$upload['url']; + if(!file_exists($upload['filename'])) { + throw new \nre\exceptions\IdNotFoundException($uploadUrl); + } + + // Cache + if($this->setCacheHeaders($upload['filename'])) { + return; + } + + // Load file + $file = file_get_contents($upload['filename']); + + + + + // Pass data to view + $this->set('upload', $upload); + $this->set('file', $file); + } + + + + + /** + * Determine file information and set the HTTP-header for + * caching accordingly. + * + * @param string $fileName Filename + * @return boolean HTTP-status 304 was set (in cache) + */ + private function setCacheHeaders($fileName) + { + // Determine last change of file + $fileLastModified = gmdate('r', filemtime($fileName)); + + // Generate E-Tag + $fileEtag = hash('sha256', $fileLastModified.$fileName); + + + // Set header + $this->response->addHeader("Last-Modified: ".$fileLastModified); + $this->response->addHeader("Etag: ".$fileEtag); + // HTTP-status + $headerModifiedSince = $this->request->getServerParam('HTTP_IF_MODIFIED_SINCE'); + $headerNoneMatch = $this->request->getServerParam('HTTP_IF_NONE_MATCH'); + if( + !is_null($headerModifiedSince) && $fileLastModified < strtotime($headerModifiedSince) && + !is_null($headerNoneMatch) && $headerNoneMatch == $fileEtag + ) { + $this->response->setExit(true); + $this->response->addHeader(\nre\core\WebUtils::getHttpHeader(304)); + + return true; + } + + + return false; + } + + } + +?> diff --git a/controllers/UserrolesController.inc b/controllers/UserrolesController.inc new file mode 100644 index 00000000..80c00347 --- /dev/null +++ b/controllers/UserrolesController.inc @@ -0,0 +1,47 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to display and manage userroles. + * + * @author Oliver Hanraths + */ + class UserrolesController extends \hhu\z\Controller + { + + + + + /** + * Action: user. + * + * Show a user and its details. + * + * @throws IdNotFoundException + * @param string $userUrl URL-Username of an user + */ + public function user($userUrl) + { + // Get userroles + $roles = $this->Userroles->getUserrolesForUserByUrl($userUrl); + + + // Pass data to view + $this->set('roles', $roles); + } + + + } + +?> diff --git a/controllers/UsersController.inc b/controllers/UsersController.inc new file mode 100644 index 00000000..70ab8904 --- /dev/null +++ b/controllers/UsersController.inc @@ -0,0 +1,371 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers; + + + /** + * Controller of the Agent to list registered users and their data. + * + * @author Oliver Hanraths + */ + class UsersController extends \hhu\z\controllers\IntermediateController + { + /** + * User permissions + * + * @var array + */ + public $permissions = array( + 'index' => array('admin', 'moderator'), + 'user' => array('admin', 'moderator', 'user'), + 'create' => array('admin', 'moderator'), + 'edit' => array('admin', 'moderator'), + 'delete' => array('admin') + ); + /** + * Required models + * + * @var array + */ + public $models = array('users', 'characters', 'avatars', 'media', 'characterroles'); + /** + * Required components + * + * @var array + */ + public $components = array('validation'); + + + + + /** + * Action: index. + */ + public function index() + { + // Get registered users + $users = $this->Users->getUsers(); + + + // Pass data to view + $this->set('users', $users); + } + + + /** + * Action: user. + * + * Show a user and its details. + * + * @throws IdNotFoundException + * @throws AccessDeniedException + * @param string $userUrl URL-Username of an user + */ + public function user($userUrl) + { + // Get user + $user = $this->Users->getUserByUrl($userUrl); + + // Check permissions + if(count(array_intersect(array('admin','moderator'), \hhu\z\controllers\IntermediateController::$user['roles'])) == 0 && $user['id'] != IntermediateController::$user['id']) { + throw new \nre\exceptions\AccessDeniedException(); + } + + // Get Characters + $characters = $this->Characters->getCharactersForUser($user['id']); + + // Additional Character information + foreach($characters as &$character) + { + // Seminary roles + $character['characterroles'] = $this->Characterroles->getCharacterrolesForCharacterById($character['id']); + $character['characterroles'] = array_map(function($a) { return $a['name']; }, $character['characterroles']); + + // Level + $character['xplevel'] = $this->Characters->getXPLevelOfCharacters($character['id']); + + // Avatar + $avatar = $this->Avatars->getAvatarById($character['avatar_id']); + if(!is_null($avatar['small_avatarpicture_id'])) + { + //$character['seminary'] = + $character['small_avatar'] = $this->Media->getSeminaryMediaById($avatar['small_avatarpicture_id']); + } + } + + + // Pass data to view + $this->set('user', $user); + $this->set('characters', $characters); + } + + + /** + * Action: login. + * + * Log in a user. + */ + public function login() + { + $username = ''; + $referrer = null; + + // Log the user in + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('login'))) + { + $username = $this->request->getPostParam('username'); + $referrer = $this->request->getPostParam('referrer'); + $userId = $this->Users->login( + $username, + $this->request->getPostParam('password') + ); + + if(!is_null($userId)) + { + $this->Auth->setUserId($userId); + $user = $this->Users->getUserById($userId); + + if(!empty($referrer)) { + $this->redirect($referrer); + } + else { + $this->redirect($this->linker->link(array($user['url']), 1)); + } + } + } + + + // Pass data to view + $this->set('username', $username); + $this->set('referrer', $referrer); + $this->set('failed', ($this->request->getRequestMethod() == 'POST')); + } + + + /** + * Action: register. + * + * Register a new user. + */ + public function register() + { + $username = ''; + $prename = ''; + $surname = ''; + $email = ''; + + $fields = array('username', 'prename', 'surname', 'email', 'password'); + $validation = array(); + + // Register a new user + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('register'))) + { + // Get params and validate them + $validation = $this->Validation->validateParams($this->request->getPostParams(), $fields); + $username = $this->request->getPostParam('username'); + if($this->Users->usernameExists($username)) { + $validation = $this->Validation->addValidationResult($validation, 'username', 'exist', true); + } + $prename = $this->request->getPostParam('prename'); + $surname = $this->request->getPostParam('surname'); + $email = $this->request->getPostParam('email'); + if($this->Users->emailExists($email)) { + $validation = $this->Validation->addValidationResult($validation, 'email', 'exist', true); + } + + + // Register + if($validation === true) + { + $userId = $this->Users->createUser( + $username, + $prename, + $surname, + $email, + $this->request->getPostParam('password') + ); + + // Send mail + $this->sendRegistrationMail($username, $email); + + // Login + $this->Auth->setUserId($userId); + $user = $this->Users->getUserById($userId); + + // Redirect to user page + $this->redirect($this->linker->link(array($user['url']), 1)); + } + } + + // Get validation settings + $validationSettings = array(); + foreach($fields as &$field) { + $validationSettings[$field] = \nre\configs\AppConfig::$validation[$field]; + } + + + // Pass data to view + $this->set('username', $username); + $this->set('prename', $prename); + $this->set('surname', $surname); + $this->set('email', $email); + $this->set('validation', $validation); + $this->set('validationSettings', $validationSettings); + } + + + /** + * Action: logout. + * + * Log out a user. + */ + public function logout() + { + // Unset the currently logged in user + $this->Auth->setUserId(null); + + // Redirect + $this->redirect($this->linker->link(array())); + } + + + /** + * Action: create. + * + * Create a new user. + */ + public function create() + { + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('create'))) + { + // Create new user + $userId = $this->Users->createUser( + $this->request->getPostParam('username'), + $this->request->getPostParam('prename'), + $this->request->getPostParam('surname'), + $this->request->getPostParam('email'), + $this->request->getPostParam('password') + ); + + // Redirect to user + $user = $this->Users->getUserById($userId); + $this->redirect($this->linker->link(array($user['url']), 1)); + } + } + + + /** + * Action: edit. + * + * Edit a user. + * + * @throws IdNotFoundException + * @param string $userUrl URL-Username of an user + */ + public function edit($userUrl) + { + // User + $user = $this->Users->getUserByUrl($userUrl); + + // Check request method + if($this->request->getRequestMethod() == 'POST') + { + // Save changes + if(!is_null($this->request->getPostParam('save'))) + { + // Edit user + $this->Users->editUser( + $user['id'], + $this->request->getPostParam('username'), + $this->request->getPostParam('prename'), + $this->request->getPostParam('surname'), + $this->request->getPostParam('email'), + $this->request->getPostParam('password') + ); + $user = $this->Users->getUserById($user['id']); + } + + + // Redirect to entry + $this->redirect($this->linker->link(array($user['url']), 1)); + } + + + // Pass data to view + $this->set('user', $user); + } + + + /** + * Action: delete. + * + * Delete a user. + * + * @throws IdNotFoundException + * @param string $userUrl URL-Username of an user + */ + public function delete($userUrl) + { + // User + $user = $this->Users->getUserByUrl($userUrl); + + // Check request method + if($this->request->getRequestMethod() == 'POST') + { + // Check confirmation + if(!is_null($this->request->getPostParam('delete'))) + { + // Delete user + $this->Users->deleteUser($user['id']); + + // Redirect to overview + $this->redirect($this->linker->link(null, 1)); + } + + // Redirect to entry + $this->redirect($this->linker->link(array('user', $user['url']), 1)); + } + + + // Show confirmation + $this->set('user', $user); + } + + + + + /** + * Send mail for new user registration. + * + * @param string $username Name of newly registered user + * @param string $email E‑mail address of newly registered user + */ + private function sendRegistrationMail($username, $email) + { + $sender = \nre\configs\AppConfig::$app['mailsender']; + if(empty($sender)) { + return; + } + + // Send notification mail to system moderators + $subject = sprintf('new user registration: %s', $username); + $message = sprintf('User “%s” <%s> has registered themself to %s', $username, $email, \nre\configs\AppConfig::$app['name']); + $moderators = $this->Users->getUsersWithRole('moderator'); + foreach($moderators as &$moderator) + { + \hhu\z\Utils::sendMail($sender, $moderator['email'], $subject, $message); + } + } + + } + +?> diff --git a/controllers/components/AchievementComponent.inc b/controllers/components/AchievementComponent.inc new file mode 100644 index 00000000..70601543 --- /dev/null +++ b/controllers/components/AchievementComponent.inc @@ -0,0 +1,41 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers\components; + + + /** + * Component to handle achievements. + * + * @author Oliver Hanraths + */ + class AchievementComponent extends \nre\core\Component + { + /** + * Required models + * + * @var array + */ + public $models = array('achievements'); + + + + + /** + * Construct a new Achievements-component. + */ + public function __construct() + { + } + + } + +?> diff --git a/controllers/components/AuthComponent.inc b/controllers/components/AuthComponent.inc new file mode 100644 index 00000000..0db6c69d --- /dev/null +++ b/controllers/components/AuthComponent.inc @@ -0,0 +1,79 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers\components; + + + /** + * Component to handle authentication and authorization. + * + * @author Oliver Hanraths + */ + class AuthComponent extends \nre\core\Component + { + /** + * Key to save a user-ID as + * + * @var string + */ + const KEY_USER_ID = 'user_id'; + + + + + /** + * Construct a new Auth-component. + */ + public function __construct() + { + // Start session + if(session_id() === '') { + session_start(); + } + } + + + + + /** + * Set the ID of the user that is currently logged in. + * + * @param int $userId ID of the currently logged in user + */ + public function setUserId($userId) + { + if(is_null($userId)) { + unset($_SESSION[self::KEY_USER_ID]); + } + else { + $_SESSION[self::KEY_USER_ID] = $userId; + } + } + + + /** + * Get the ID of the user that is currently logged in. + * + * @return int ID of the currently logged in user + */ + public function getUserId() + { + if(array_key_exists(self::KEY_USER_ID, $_SESSION)) { + return $_SESSION[self::KEY_USER_ID]; + } + + + return null; + } + + } + +?> diff --git a/controllers/components/ValidationComponent.inc b/controllers/components/ValidationComponent.inc new file mode 100644 index 00000000..950d45ed --- /dev/null +++ b/controllers/components/ValidationComponent.inc @@ -0,0 +1,140 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\controllers\components; + + + /** + * Component to validate user input. + * + * @author Oliver Hanraths + */ + class ValidationComponent extends \nre\core\Component + { + /** + * Validation settings + * + * @var array + */ + private $config; + + + + + /** + * Construct a new Validation-component. + */ + public function __construct() + { + // Get validation settings from configuration + $this->config = \nre\configs\AppConfig::$validation; + } + + + + + /** + * Validate an user input. + * + * @param mixed $input User input to validate + * @param array $settings Validation setting + * @return mixed True or the settings the validation fails on + */ + public function validate($input, $settings) + { + $validation = array(); + + // Min string length + if(array_key_exists('minlength', $settings) && strlen($input) < $settings['minlength']) { + $validation['minlength'] = $settings['minlength']; + } + // Max string length + if(array_key_exists('maxlength', $settings) && strlen($input) > $settings['maxlength']) { + $validation['maxlength'] = $settings['maxlength']; + } + + // Regex + if(array_key_exists('regex', $settings) && !preg_match($settings['regex'], $input)) { + $validation['regex'] = $settings['regex']; + } + + + // Return true or the failed fields + if(empty($validation)) { + return true; + } + return $validation; + } + + + /** + * Validate user input parameters. + * + * @param array $params User input parameters + * @param array $indices Names of parameters to validate and to validate against + * @return mixed True or the parameters with settings the validation failed on + */ + public function validateParams($params, $indices) + { + $validation = array(); + foreach($indices as $index) + { + if(!array_key_exists($index, $params)) { + throw new \nre\exceptions\ParamsNotValidException($index); + } + + // Check parameter + if(array_key_exists($index, $this->config)) + { + $param = $params[$index]; + $check = $this->validate($param, $this->config[$index]); + if($check !== true) { + $validation[$index] = $check; + } + } + } + + + // Return true or the failed parameters with failed settings + if(empty($validation)) { + return true; + } + return $validation; + } + + + /** + * Add a custom determined validation result to a validation + * store. + * + * @param mixed $validation Validation store to add result to + * @param string $param Name of parameter of the custom validation result + * @param string $setting Name of setting of the custom validation result + * @param mixed $result Validation result + * @return mixed The altered validation store + */ + public function addValidationResult($validation, $param, $setting, $result) + { + if(!is_array($validation)) { + $validation = array(); + } + if(!array_key_exists($param, $validation)) { + $validation[$param] = array(); + } + $validation[$param][$setting] = $result; + + + return $validation; + } + + } + +?> diff --git a/core/Agent.inc b/core/Agent.inc new file mode 100644 index 00000000..00ec1a90 --- /dev/null +++ b/core/Agent.inc @@ -0,0 +1,607 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Abstract class for the implementation af an Agent. + * + * @author coderkun + */ + abstract class Agent + { + /** + * Name of BottomlevelAgent for showing inline errors + * + * @var string + */ + const INLINEERROR_AGENT = 'inlineerror'; + + /** + * Current request + * + * @var Request + */ + private $request; + /** + * Current response + * + * @var Response + */ + private $response; + /** + * Log-system + * + * @var Logger + */ + protected $log; + /** + * SubAgents + * + * @var array + */ + protected $subAgents = array(); + /** + * Controller of this Agent + * + * @var Controller + */ + public $controller = null; + + + + + /** + * Load the class of an Agent. + * + * @static + * @throws AgentNotFoundException + * @throws AgentNotValidException + * @param string $agentName Name of the Agent to load + */ + public static function load($agentName) + { + // Determine full classname + $agentType = self::getAgentType(); + $className = self::getClassName($agentName, $agentType); + + try { + // Load class + ClassLoader::load($className); + + // Validate class + $parentAgentClassName = ClassLoader::concatClassNames($agentType, 'agent'); + $parentAgentClassName = "\\nre\\agents\\$parentAgentClassName"; + ClassLoader::check($className, $parentAgentClassName); + } + catch(\nre\exceptions\ClassNotValidException $e) { + throw new \nre\exceptions\AgentNotValidException($e->getClassName()); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + throw new \nre\exceptions\AgentNotFoundException($e->getClassName()); + } + } + + + + /** + * Instantiate an Agent (Factory Pattern). + * + * @static + * @throws DatamodelException + * @throws DriverNotValidException + * @throws DriverNotFoundException + * @throws ViewNotFoundException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ControllerNotValidException + * @throws ControllerNotFoundException + * @param string $agentName Name of the Agent to instantiate + * @param Request $request Current request + * @param Response $respone Current respone + * @param Logger $log Log-system + */ + public static function factory($agentName, Request $request, Response $response, Logger $log=null) + { + // Determine full classname + $agentType = self::getAgentType(); + $className = self::getClassName($agentName, $agentType); + + + // Construct and return Agent + return new $className($request, $response, $log); + } + + + /** + * Determine the type of an Agent. + * + * @static + * @return string Agent type + */ + private static function getAgentType() + { + return strtolower(ClassLoader::getClassName(get_called_class())); + } + + + /** + * Determine the classname for the given Agent name. + * + * @static + * @param string $agentName Agent name to get classname of + * @param string $agentType Agent type of given Agent name + * @return string Classname for the Agent name + */ + private static function getClassName($agentName, $agentType) + { + $className = ClassLoader::concatClassNames($agentName, 'agent'); + + + return \nre\configs\AppConfig::$app['namespace']."agents\\$agentType\\$className"; + } + + + + + /** + * Construct a new Agent. + * + * @throws DatamodelException + * @throws DriverNotValidException + * @throws DriverNotFoundException + * @throws ViewNotFoundException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ControllerNotValidException + * @throws ControllerNotFoundException + * @param Request $request Current request + * @param Response $respone Current response + * @param Logger $log Log-system + */ + protected function __construct(Request $request, Response $response, Logger $log=null) + { + // Store values + $this->request = $request; + $this->response = $response; + $this->log = $log; + + // Construct SubAgent + $this->actionConstruct(); + + // Load corresponding Controller + $this->loadController(); + } + + + + + /** + * Run the Controller of this Agent and its SubAgents. + * + * @throws ParamsNotValidException + * @throws IdNotFoundException + * @throws DatamodelException + * @throws ActionNotFoundException + * @param Request $request Current request + * @param Response $response Current response + * @return Exception Last occurred exception of SubAgents + */ + public function run(Request $request, Response $response) + { + // Check Controller + if(!is_null($this->controller)) + { + // Call prefilter + $this->controller->preFilter($request, $response); + + // Run controller + $this->controller->run($request, $response); + + // Call postfilter + $this->controller->postFilter($request, $response); + } + + + // Run SubAgents + $exception = null; + foreach($this->subAgents as &$subAgent) + { + try { + $subAgent['object']->run( + $request, + $subAgent['response'] + ); + } + catch(ParamsNotValidException $e) { + $subAgent = $this->errorInline($subAgent, $request, $e); + } + catch(IdNotFoundException $e) { + $subAgent = $this->errorInline($subAgent, $request, $e); + } + catch(DatamodelException $e) { + $exception = $e; + $subAgent = $this->errorInline($subAgent, $request, $e); + } + catch(ActionNotFoundException $e) { + $subAgent = $this->errorInline($subAgent, $request, $e); + } + } + + + // Return last occurred exception + return $exception; + } + + + /** + * Generate output of the Controller of this Agent and its + * SubAgents. + * + * @param array $data View data + * @return string Generated output + */ + public function render($data=array()) + { + // Check Controller + if(!is_null($this->controller)) + { + // Render SubAgents + foreach($this->subAgents as $subAgent) + { + $label = array_key_exists('label', $subAgent) ? $subAgent['label'] : $subAgent['name']; + $data[$label] = $this->renderSubAgent($subAgent); + } + + // Render the Controller of this agent + return $this->controller->render($data); + } + } + + + + + /** + * Construct SubAgents (per Action). + */ + protected function actionConstruct() + { + // Action ermitteln + $action = $this->response->getParam(2); + if(is_null($action)) { + $action = $this->request->getParam(2, 'action'); + $this->response->addParam($action); + } + + // Initialisierungsmethode für diese Action ausführen + if(method_exists($this, $action)) + { + call_user_func_array( + array( + $this, + $action + ), + array( + $this->request, + $this->response + ) + ); + } + } + + + /** + * Load the Controller of this Agent. + * + * @throws DatamodelException + * @throws DriverNotValidException + * @throws DriverNotFoundException + * @throws ViewNotFoundException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ControllerNotValidException + * @throws ControllerNotFoundException + */ + protected function loadController() + { + // Determine Controller name + $controllerName = ClassLoader::getClassName(get_class($this)); + + // Determine ToplevelAgent + $toplevelAgentName = $this->response->getParam(0); + if(is_null($toplevelAgentName)) { + $toplevelAgentName = $this->request->getParam(0, 'toplevel'); + $this->response->addParam($toplevelAgentName); + } + + // Determine Action + $action = $this->response->getParam(2); + if(is_null($action)) { + $action = $this->request->getParam(2, 'action'); + $this->response->addParam($action); + } + + + // Load Controller + Controller::load($controllerName); + + // Construct Controller + $this->controller = Controller::factory($controllerName, $toplevelAgentName, $action, $this); + } + + + /** + * Log an error. + * + * @param Exception $exception Occurred exception + * @param int $logMode Log mode + */ + protected function log($exception, $logMode) + { + if(is_null($this->log)) { + return; + } + + $this->log->log( + $exception->getMessage(), + $logMode + ); + } + + + /** + * Load a SubAgent and add it. + * + * @throws ServiceUnavailableException + * @throws AgentNotFoundException + * @throws AgentNotValidException + * @param string $agentName Name of the Agent to load + * @param mixed … Additional parameters for the agent + */ + protected function addSubAgent($agentName) + { + try { + call_user_func_array( + array( + $this, + '_addSubAgent' + ), + func_get_args() + ); + } + catch(DatamodelException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + } + + + /** + * Load a SubAgent and add it. + * + * @throws ServiceUnavailableException + * @throws DatamodelException + * @throws AgentNotFoundException + * @throws AgentNotValidException + * @param string $agentName Name of the Agent to load + * @param mixed … Additional parameters for the agent + */ + protected function _addSubAgent($agentName) + { + try { + // Load Agent + \nre\agents\BottomlevelAgent::load($agentName); + + // Construct Agent + $this->subAgents[] = call_user_func_array( + array( + $this, + 'newSubAgent' + ), + func_get_args() + ); + } + catch(ViewNotFoundException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + catch(DriverNotValidException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + catch(DriverNotFoundException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + catch(ModelNotValidException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + catch(ModelNotFoundException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + catch(ControllerNotValidException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + catch(ControllerNotFoundException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + catch(AgentNotValidException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + catch(AgentNotFoundException $e) { + $this->subAgents[] = $this->newInlineError($agentName, $e); + } + } + + + + + /** + * Create a new SubAgent. + * + * @throws DatamodelException + * @param string $agentName Agent name + * @return array SubAgent + */ + private function newSubAgent($agentName) + { + // Response + $response = clone $this->response; + $response->clearParams(1); + $params = func_get_args(); + if(count($params) < 2 || empty($params[1])) { + $params[1] = \nre\configs\CoreConfig::$defaults['action']; + } + call_user_func_array( + array( + $response, + 'addParams' + ), + $params + ); + + return array( + 'name' => strtolower($agentName), + 'response' => $response, + 'object' => \nre\agents\BottomlevelAgent::factory( + $agentName, + $this->request, + $response, + $this->log + ) + ); + } + + + /** + * Render a SubAgent. + * + * @param array $subAgent SubAgent to render + * @return string Generated output + */ + private function renderSubAgent(&$subAgent) + { + // Check for InlineError + if(array_key_exists('inlineerror', $subAgent) && !empty($subAgent['inlineerror'])) { + return file_get_contents($subAgent['inlineerror']); + } + + + // Rendern SubAgent and return its output + return $subAgent['object']->render(); + } + + + /** + * Handle the exception of a SubAgent. + * + * @param string $label Name of the original Agent + * @param Excepiton $exception Occurred exception + * @return array InlineError-SubAgent + */ + private function errorInline($subAgent, $request, $exception) + { + // Create the SubAgent for the exception + $subAgent = $this->newInlineError($subAgent['name'], $exception); + + + // Run the InlineError-SubAgent + try { + $subAgent['object']->run( + $request, + $subAgent['response'] + ); + } + catch(ActionNotFoundException $e) { + $this->log($e, Logger::LOGMODE_AUTO); + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + + + // Return the InlineError-SubAgent + return $subAgent; + } + + + /** + * Create a new InlineError. + * + * @param string $label Name of the original Agent + * @param Exception $exception Occurred exception + */ + private function newInlineError($label, $exception) + { + // Log error + $this->log($exception, Logger::LOGMODE_AUTO); + + // Determine Agent name + $agentName = self::INLINEERROR_AGENT; + + // Create SugAgent + $subAgent = array(); + + + try { + // Load Agenten + \nre\agents\BottomlevelAgent::load($agentName); + + // Construct Agent + $subAgent = $this->newSubAgent($agentName); + $subAgent['label'] = $label; + $subAgent['response']->addParam($exception); + } + catch(ViewNotFoundException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(DatamodelException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(DriverNotValidException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(DriverNotFoundException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(ModelNotValidException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(ModelNotFoundException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(ControllerNotValidException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(ControllerNotFoundException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(AgentNotValidException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + catch(AgentNotFoundException $e) { + $subAgent['inlineerror'] = $this->newInlineErrorService(); + } + + + // Return SubAgent + return $subAgent; + } + + + /** + * Handle a hardcore error that could not be handled by the + * system. + */ + private function newInlineErrorService() + { + // Read and return static error file + return ROOT.DS.\nre\configs\CoreConfig::getClassDir('views').DS.\nre\configs\CoreConfig::$defaults['inlineErrorFile'].\nre\configs\Config::getFileExt('views'); + } + + } + +?> diff --git a/core/Api.inc b/core/Api.inc new file mode 100644 index 00000000..b89b12e7 --- /dev/null +++ b/core/Api.inc @@ -0,0 +1,163 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Abstract class to implement an API. + * + * The API is the center of each application and specifies how and what + * to run and render. + * + * @author coderkun + */ + abstract class Api + { + /** + * Die aktuelle Anfrage + * + * @var Request + */ + protected $request; + /** + * Der Toplevelagent + * + * @var ToplevelAgent + */ + private $toplevelAgent = null; + /** + * Die aktuelle Antwort + * + * @var Response + */ + protected $response; + /** + * Log-System + * + * @var Logger + */ + protected $log; + + + + + /** + * Construct a new API. + * + * @param Request $request Current request + * @param Response $respone Current response + */ + public function __construct(Request $request, Response $response) + { + // Store request + $this->request = $request; + + // Store response + $this->response = $response; + + // Init logging + $this->log = new \nre\core\Logger(); + } + + + + + /** + * Run the application. + * + * @throws DatamodelException + * @throws DriverNotValidException + * @throws DriverNotFoundException + * @throws ViewNotFoundException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ControllerNotValidException + * @throws ControllerNotFoundException + * @throws AgentNotValidException + * @throws AgentNotFoundException + * @return Exception Last occurred exception of an subagent + */ + public function run() + { + // Load ToplevelAgent + $this->loadToplevelAgent(); + + // Run ToplevelAgent + return $this->toplevelAgent->run($this->request, $this->response); + } + + + /** + * Render the output. + */ + public function render() + { + // Check exit-status + if($this->response->getExit()) { + return; + } + + // Render ToplevelAgent + $this->response->setOutput($this->toplevelAgent->render()); + } + + + + + /** + * Log an exception + * + * @param Exception $exception Occurred exception + * @param int $logMode Log-mode + */ + protected function log($exception, $logMode) + { + $this->log->log( + $exception->getMessage(), + $logMode + ); + } + + + + + /** + * Load the ToplevelAgent specified by the request. + * + * @throws ServiceUnavailableException + * @throws AgentNotValidException + * @throws AgentNotFoundException + */ + private function loadToplevelAgent() + { + // Determine agent + $agentName = $this->response->getParam(0); + if(is_null($agentName)) { + $agentName = $this->request->getParam(0, 'toplevel'); + $this->response->addParam($agentName); + } + + // Load agent + \nre\agents\ToplevelAgent::load($agentName); + + // Construct agent + $this->toplevelAgent = \nre\agents\ToplevelAgent::factory( + $agentName, + $this->request, + $this->response, + $this->log + ); + } + + } + +?> diff --git a/core/Autoloader.inc b/core/Autoloader.inc new file mode 100644 index 00000000..020b61f7 --- /dev/null +++ b/core/Autoloader.inc @@ -0,0 +1,98 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Autoloader. + * + * This class tries to load not yet used classes. + * + * @author coderkun + */ + class Autoloader + { + /** + * Private construct(). + */ + private function __construct() {} + + /** + * Private clone(). + */ + private function __clone() {} + + + + + /** + * Register load-method. + */ + public static function register() + { + spl_autoload_register( + array( + get_class(), + 'load' + ) + ); + } + + + /** + * Look for the given class and try to load it. + * + * @param string $fullClassName Die zu ladende Klasse + */ + public static function load($fullClassName) + { + $fullClassNameA = explode('\\', $fullClassName); + + if(strpos($fullClassName, \nre\configs\CoreConfig::$core['namespace']) !== 0) + { + // App + $className = array_slice($fullClassNameA, substr_count(\nre\configs\AppConfig::$app['namespace'], '\\')); + array_unshift($className, \nre\configs\CoreConfig::getClassDir('app')); + $filename = ROOT.DS.implode(DS, $className).\nre\configs\CoreConfig::getFileExt('includes'); + if(file_exists($filename)) { + require_once($filename); + } + } + else + { + // Core + $className = array_slice($fullClassNameA, substr_count(\nre\configs\CoreConfig::$core['namespace'], '\\')); + $filename = ROOT.DS.implode(DS, $className).\nre\configs\CoreConfig::getFileExt('includes'); + if(file_exists($filename)) { + require_once($filename); + } + } + + + } + + + /** + * Determine classtype of a class. + * + * @param string $className Name of the class to determine the classtype of + * @return string Classtype of the given class + */ + public static function getClassType($className) + { + // CamelCase + return strtolower(preg_replace('/^.*([A-Z][^A-Z]+)$/', '$1', $className)); + } + + } + +?> diff --git a/core/ClassLoader.inc b/core/ClassLoader.inc new file mode 100644 index 00000000..81cf537a --- /dev/null +++ b/core/ClassLoader.inc @@ -0,0 +1,129 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Class for safely loading classes. + * + * @author coderkun + */ + class ClassLoader + { + + + + + /** + * Load a class. + * + * @throws ClassNotFoundException + * @param string $className Name of the class to load + */ + public static function load($fullClassName) + { + // Determine folder to look in + $className = explode('\\', $fullClassName); + $className = array_slice($className, substr_count(\nre\configs\AppConfig::$app['namespace'], '\\')); + + // Determine filename + $fileName = ROOT.DS.implode(DS, $className). \nre\configs\CoreConfig::getFileExt('includes'); + + + // Check file + if(!file_exists($fileName)) + { + throw new \nre\exceptions\ClassNotFoundException( + $fullClassName + ); + } + + // Include file + include_once($fileName); + } + + + /** + * Check inheritance of a class. + * + * @throws ClassNotValidException + * @param string $className Name of the class to check + * @param string $parentClassName Name of the parent class + */ + public static function check($className, $parentClassName) + { + // Check if class is subclass of parent class + if(!is_subclass_of($className, $parentClassName)) { + throw new \nre\exceptions\ClassNotValidException( + $className + ); + } + } + + + /** + * Strip the namespace from a class name. + * + * @param string $class Name of a class including its namespace + * @return Name of the given class without its namespace + */ + public static function stripNamespace($class) + { + return array_slice(explode('\\', $class), -1)[0]; + } + + + /** + * Strip the class type from a class name. + * + * @param string $className Name of a class + * @return Name of the given class without its class type + */ + public static function stripClassType($className) + { + return preg_replace('/^(.*)[A-Z][^A-Z]+$/', '$1', $className); + } + + + /** + * Strip the namespace and the class type of a full class name + * to get only its name. + * + * @param string $class Full name of a class + * @return Only the name of the given class + */ + public static function getClassName($class) + { + return self::stripClassType(self::stripNamespace($class)); + } + + + /** + * Concatenate strings to a class name following the CamelCase + * pattern. + * + * @param string $className1 Arbitrary number of strings to concat + * @return string Class name as CamelCase + */ + public static function concatClassNames($className1) + { + return implode('', array_map( + function($arg) { + return ucfirst(strtolower($arg)); + }, + func_get_args() + )); + } + + } + +?> diff --git a/core/Component.inc b/core/Component.inc new file mode 100644 index 00000000..a93363dc --- /dev/null +++ b/core/Component.inc @@ -0,0 +1,85 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Abstract class to implement a (Controller) Component. + * + * @author coderkun + */ + abstract class Component + { + + + + + /** + * Load the class of a Component. + * + * @throws ComponentNotFoundException + * @throws ComponentNotValidException + * @param string $componentName Name of the Component to load + */ + public static function load($componentName) + { + // Determine full classname + $className = self::getClassName($componentName); + + try { + // Load class + ClassLoader::load($className); + + // Validate class + ClassLoader::check($className, get_class()); + } + catch(\nre\exceptions\ClassNotValidException $e) { + throw new \nre\exceptions\ComponentNotValidException($e->getClassName()); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + throw new \nre\exceptions\ComponentNotFoundException($e->getClassName()); + } + } + + + /** + * Instantiate a Component (Factory Pattern). + * + * @param string $componentName Name of the Component to instantiate + */ + public static function factory($componentName) + { + // Determine full classname + $className = self::getClassName($componentName); + + // Construct and return Controller + return new $className(); + } + + + /** + * Determine the classname for the given Component name. + * + * @param string $componentName Component name to get classname of + * @return string Classname for the Component name + */ + private static function getClassName($componentName) + { + $className = \nre\core\ClassLoader::concatClassNames($componentName, \nre\core\ClassLoader::stripNamespace(get_class())); + + + return \nre\configs\AppConfig::$app['namespace']."controllers\\components\\$className"; + } + + } + +?> diff --git a/core/Config.inc b/core/Config.inc new file mode 100644 index 00000000..b51f1e47 --- /dev/null +++ b/core/Config.inc @@ -0,0 +1,49 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Configuration. + * + * This class does not hold any configuration value but helps to + * determine values that can be hold by AppConfig or CoreConfig. + * + * @author coderkun + */ + final class Config + { + + + + + /** + * Get a default value. + * + * @param string $index Index of value to get + */ + public static function getDefault($index) + { + if(array_key_exists($index, \nre\configs\AppConfig::$defaults)) { + return \nre\configs\AppConfig::$defaults[$index]; + } + if(array_key_exists($index, \nre\configs\CoreConfig::$defaults)) { + return \nre\configs\CoreConfig::$defaults[$index]; + } + + + return null; + } + + } + +?> diff --git a/core/Controller.inc b/core/Controller.inc new file mode 100644 index 00000000..941e7523 --- /dev/null +++ b/core/Controller.inc @@ -0,0 +1,433 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Abstract class for implementing a Controller. + * + * @author coderkun + */ + abstract class Controller + { + /** + * Corresponding Agent + * + * @var Agent + */ + protected $agent; + /** + * View of the Controller + * + * @var View + */ + protected $view = null; + /** + * Data to pass to the View + * + * @var array + */ + protected $viewData = array(); + /** + * Current request + * + * @var Request + */ + protected $request = null; + /** + * Current response + * + * @var Response + */ + protected $response = null; + + + + + /** + * Load the class of a Controller. + * + * @throws ControllerNotFoundException + * @throws ControllerNotValidException + * @param string $controllerName Name of the Controller to load + */ + public static function load($controllerName) + { + // Determine full classname + $className = self::getClassName($controllerName); + + try { + // Load class + ClassLoader::load($className); + + // Validate class + ClassLoader::check($className, get_class()); + } + catch(\nre\exceptions\ClassNotValidException $e) { + throw new \nre\exceptions\ControllerNotValidException($e->getClassName()); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + throw new \nre\exceptions\ControllerNotFoundException($e->getClassName()); + } + } + + + /** + * Instantiate a Controller (Factory Pattern). + * + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ViewNotFoundException + * @param string $controllerName Name of the Controller to instantiate + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + */ + public static function factory($controllerName, $layoutName, $action, $agent) + { + // Determine full classname + $className = self::getClassName($controllerName); + + // Construct and return Controller + return new $className($layoutName, $action, $agent); + } + + + /** + * Determine the classname for the given Controller name. + * + * @param string $controllerName Controller name to get classname of + * @return string Classname for the Controller name + */ + private static function getClassName($controllerName) + { + $className = \nre\core\ClassLoader::concatClassNames($controllerName, \nre\core\ClassLoader::stripNamespace(get_class())); + + + return \nre\configs\AppConfig::$app['namespace']."controllers\\$className"; + } + + + + + /** + * Construct a new Controller. + * + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + * @throws ViewNotFoundException + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + * @param Agent $agent Corresponding Agent + */ + protected function __construct($layoutName, $action, $agent) + { + // Store values + $this->agent = $agent; + + // Load Components + $this->loadComponents(); + + // Load Models + $this->loadModels(); + + // Load View + $this->loadView($layoutName, $action); + } + + + + + /** + * Prefilter that is executed before running the Controller. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function preFilter(Request $request, Response $response) + { + // Request speichern + $this->request = $request; + + // Response speichern + $this->response = $response; + + + // Linker erstellen + $this->set('linker', new \nre\core\Linker($request)); + + } + + + /** + * Prefilter that is executed after running the Controller. + * + * @param Request $request Current request + * @param Response $response Current response + */ + public function postFilter(Request $request, Response $response) + { + } + + + /** + * Run the Controller. + * + * This method executes the Action of the Controller defined by + * the current Request. + * + * @throws ParamsNotValidException + * @throws IdNotFoundException + * @throws DatamodelException + * @throws ActionNotFoundException + * @param Request $request Current request + * @param Response $response Current response + */ + public function run(Request $request, Response $response) + { + // Determine Action + $action = $response->getParam(2, 'action'); + if(!method_exists($this, $action)) { + throw new \nre\exceptions\ActionNotFoundException($action); + } + + // Determine parameters + $params = $response->getParams(3); + if(empty($params)) { + $params = $request->getParams(3); + } + + // Fill missing parameters + $rc = new \ReflectionClass($this); + $nullParamsCount = $rc->getMethod($action)->getNumberOfParameters() - count($params); + $nullParams = ($nullParamsCount > 0 ? array_fill(0, $nullParamsCount, NULL) : array()); + + + // Call Action + call_user_func_array( + array( + $this, + $action + ), + array_merge( + $params, + $nullParams + ) + ); + } + + + /** + * Generate the output. + * + * @param array $viewData Data to pass to the View + * @return string Generated output + */ + public function render($viewData=null) + { + // Combine given data and data of this Controller + $data = $this->viewData; + if(!is_null($viewData)) { + $data = array_merge($viewData, $data); + } + + // Rendern and return output + return $this->view->render($data); + } + + + + + /** + * Set data for the View. + * + * @param string $name Key + * @param mixed $data Value + */ + protected function set($name, $data) + { + $this->viewData[$name] = $data; + } + + + /** + * Redirect to the given URL. + * + * @param string $url Relative URL + */ + protected function redirect($url) + { + $url = 'http://'.$_SERVER['HTTP_HOST'].$url; + header('Location: '.$url); + exit; + } + + + /** + * Check if Models of this Controller are loaded and available. + * + * @param string $modelName Arbitrary number of Models to check + * @return bool All given Models are loaded and available + */ + protected function checkModels($modelName) + { + foreach(func_get_args() as $modelName) + { + if(!isset($this->$modelName) || !is_subclass_of($this->$modelName, 'Model')) { + return false; + } + } + + + return true; + } + + + /** + * Get the View of the Controller + * + * @return View View of the Controller + */ + protected function getView() + { + return $this->view; + } + + + + + /** + * Load the Components of this Controller. + * + * @throws ComponentNotValidException + * @throws ComponentNotFoundException + */ + private function loadComponents() + { + // Determine components + $components = array(); + if(property_exists($this, 'components')) { + $components = $this->components; + } + if(!is_array($components)) { + $components = array($components); + } + // Components of parent classes + $parent = $this; + while($parent = get_parent_class($parent)) + { + $properties = get_class_vars($parent); + if(array_key_exists('components', $properties)) { + $components = array_merge($components, $properties['components']); + } + } + + // Load components + foreach($components as &$component) + { + // Load class + Component::load($component); + + // Construct component + $componentName = ucfirst(strtolower($component)); + $this->$componentName = Component::factory($component); + } + } + + + /** + * Load the Models of this Controller. + * + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + */ + protected function loadModels() + { + // Determine Models + $explicit = false; + $models = \nre\core\ClassLoader::stripClassType(\nre\core\ClassLoader::stripNamespace(get_class($this))); + if(property_exists($this, 'models')) + { + $models = $this->models; + $explicit = true; + } + if(!is_array($models)) { + $models = array($models); + } + // Models of parent classes + $parent = $this; + while($parent = get_parent_class($parent)) + { + $properties = get_class_vars($parent); + if(array_key_exists('models', $properties)) { + $models = array_merge($models, $properties['models']); + } + } + + // Load Models + foreach($models as &$model) + { + try { + // Load class + Model::load($model); + + // Construct Model + $modelName = ucfirst(strtolower($model)); + $this->$modelName = Model::factory($model); + } + catch(\nre\exceptions\ModelNotValidException $e) { + if($explicit) { + throw $e; + } + } + catch(\nre\exceptions\ModelNotFoundException $e) { + if($explicit) { + throw $e; + } + } + } + } + + + /** + * Load the View of this Controller. + * + * @throws ViewNotFoundException + * @param string $layoutName Name of the current Layout + * @param string $action Current Action + */ + protected function loadView($layoutName, $action) + { + // Check Layout name + if(is_null($layoutName)) { + return; + } + + // Determine controller name + $controllerName = \nre\core\ClassLoader::getClassName(get_class($this)); + + + // Load view + $isToplevel = is_subclass_of($this->agent, '\nre\agents\ToplevelAgent'); + $this->view = View::loadAndFactory($layoutName, $controllerName, $action, $isToplevel); + } + + } + +?> diff --git a/core/Driver.inc b/core/Driver.inc new file mode 100644 index 00000000..eec59143 --- /dev/null +++ b/core/Driver.inc @@ -0,0 +1,96 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Abstract class for implementing a Driver. + * + * @author coderkun + */ + abstract class Driver + { + + + + + /** + * Load the class of a Driver. + * + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @param string $driverName Name of the Driver to load + */ + public static function load($driverName) + { + // Determine full classname + $className = self::getClassName($driverName); + + try { + // Load class + ClassLoader::load($className); + + // Validate class + ClassLoader::check($className, get_class()); + } + catch(\nre\exceptions\ClassNotValidException $e) { + throw new \nre\exceptions\DriverNotValidException($e->getClassName()); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + throw new \nre\exceptions\DriverNotFoundException($e->getClassName()); + } + } + + + /** + * Instantiate a Driver (Factory Pattern). + * + * @param string $driverName Name of the Driver to instantiate + */ + public static function factory($driverName, $config) + { + // Determine full classname + $className = self::getClassName($driverName); + + + // Construct and return Driver + return $className::singleton($config); + } + + + /** + * Determine the classname for the given Driver name. + * + * @param string $driverName Driver name to get classname of + * @return string Classname fore the Driver name + */ + private static function getClassName($driverName) + { + $className = ClassLoader::concatClassNames($driverName, ClassLoader::stripNamespace(get_class())); + + + return "\\nre\\drivers\\$className"; + } + + + + + /** + * Construct a new Driver. + */ + protected function __construct() + { + } + + } + +?> diff --git a/core/Exception.inc b/core/Exception.inc new file mode 100644 index 00000000..a17a700f --- /dev/null +++ b/core/Exception.inc @@ -0,0 +1,65 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Exception class. + * + * @author coderkun + */ + class Exception extends \Exception + { + + + + + /** + * Construct a new exception. + * + * @param string $message Error message + * @param int $code Error code + * @param string $name Name to insert + */ + function __construct($message, $code, $name=null) + { + parent::__construct( + $this->concat( + $message, + $name + ), + $code + ); + } + + + + + /** + * Insert the name in a message + * + * @param string $message Error message + * @param string $name Name to insert + */ + private function concat($message, $name) + { + if(is_null($name)) { + return $message; + } + + + return "$message: $name"; + } + + } + +?> diff --git a/core/Linker.inc b/core/Linker.inc new file mode 100644 index 00000000..e8c4fa0f --- /dev/null +++ b/core/Linker.inc @@ -0,0 +1,311 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Class to create web links based on the current request. + * + * @author coderkun + */ + class Linker + { + /** + * Current request + * + * @var Request + */ + private $request; + + + + + /** + * Construct a new linker. + * + * @param Request $request Current request + */ + function __construct(\nre\requests\WebRequest $request) + { + $this->request = $request; + } + + + + + /** + * Mask parameters to be used in an URL. + * + * @param string $param1 First parameter + * @return string Masked parameters as string + */ + public static function createLinkParam($param1) + { + return implode( + \nre\configs\CoreConfig::$classes['linker']['url']['delimiter'], + call_user_func_array( + '\nre\core\Linker::createLinkParams', + func_get_args() + ) + ); + } + + + /** + * Mask parameters to be used in an URL. + * + * @param string $param1 First parameter + * @return string Masked parameters as array + */ + public static function createLinkParams($param1) + { + // Parameters + $linkParams = array(); + $params = func_get_args(); + + foreach($params as $param) + { + // Delete critical signs + $specials = array('/', '?', '&'); + foreach($specials as &$special) { + $param = str_replace($special, '', $param); + } + + // Process parameter + $param = str_replace( + ' ', + \nre\configs\CoreConfig::$classes['linker']['url']['delimiter'], + substr( + $param, + 0, + \nre\configs\CoreConfig::$classes['linker']['url']['length'] + ) + ); + + // Encode parameter + $linkParams[] = $param; + } + + + // Return link parameters + return $linkParams; + } + + + + + /** + * Create a web link. + * + * @param array $params Parameters to use + * @param int $offset Ignore first parameters + * @param bool $exclusiveParams Use only the given parameters + * @param array $getParams GET-parameter to use + * @param bool $exclusiveGetParams Use only the given GET-parameters + * @param string $anchor Target anchor + * @param bool $absolute Include hostname etc. for an absolute URL + * @return string Created link + */ + public function link($params=null, $offset=0, $exclusiveParams=true, $getParams=null, $exclusiveGetParams=true, $anchor=null, $absolute=false) + { + // Make current request to response + $response = new \nre\responses\WebResponse(); + + + // Check parameters + if(is_null($params)) { + $params = array(); + } + elseif(!is_array($params)) { + $params = array($params); + } + + // Set parameters from request + $reqParams = array_slice($this->request->getParams(), 1, $offset); + if(count($reqParams) < $offset && $offset > 0) { + $reqParams[] = $this->request->getParam(1, 'intermediate'); + } + if(count($reqParams) < $offset && $offset > 1) { + $reqParams[] = $this->request->getParam(2, 'action'); + } + $params = array_map('rawurlencode', $params); + $params = array_merge($reqParams, $params); + + // Use Layout + $layout = \nre\configs\AppConfig::$defaults['toplevel']; + if(!empty($getParams) && array_key_exists('layout', $getParams)) { + $layout = $getParams['layout']; + } + array_unshift($params, $layout); + + // Use parameters from request only + if(!$exclusiveParams) + { + $params = array_merge( + $params, + array_slice( + $this->request->getParams(), + count($params) + ) + ); + } + + // Set parameters + call_user_func_array( + array( + $response, + 'addParams' + ), + $params + ); + + + // Check GET-parameters + if(is_null($getParams)) { + $getParams = array(); + } + elseif(!is_array($params)) { + $params = array($params); + } + if(!$exclusiveGetParams) + { + $getParams = array_merge( + $this->request->getGetParams(), + $getParams + ); + } + + // Set GET-parameters + $response->addGetParams($getParams); + + + // Create and return link + return self::createLink($this->request, $response, $anchor, $absolute); + } + + + /** + * Create a link from an URL. + * + * @param string $url URL to create link from + * @param bool $absolute Create absolute URL + * @return string Created link + */ + public function hardlink($url, $absolute=false) + { + return $this->createUrl($url, $this->request, $absolute); + } + + + + + /** + * Create a link. + * + * @param Request $request Current request + * @param Response $response Current response + * @param bool $absolute Create absolute link + * @param string $anchor Anchor on target + * @return string Created link + */ + private static function createLink(Request $request, Response $response, $anchor=null, $absolute=false) + { + // Check response + if(is_null($response)) { + return null; + } + + + // Get parameters + $params = $response->getParams(1); + + // Check Action + if(count($params) == 2 && $params[1] == \nre\configs\CoreConfig::$defaults['action']) { + array_pop($params); + } + + // Set parameters + $link = implode('/', $params); + + // Apply reverse-routes + $link = $request->applyReverseRoutes($link); + + + // Get GET-parameters + $getParams = $response->getGetParams(); + + // Layout überprüfen + if(array_key_exists('layout', $getParams) && $getParams['layout'] == \nre\configs\AppConfig::$defaults['toplevel']) { + unset($getParams['layout']); + } + + // Set GET-parameters + if(array_key_exists('url', $getParams)) { + unset($getParams['url']); + } + if(count($getParams) > 0) { + $link .= '?'.http_build_query($getParams); + } + + // Add anchor + if(!is_null($anchor)) { + $link .= "#$anchor"; + } + + + // Create URL + $url = self::createUrl($link, $request, $absolute); + + + return $url; + } + + + /** + * Adapt a link to the environment. + * + * @param string $url URL + * @param Request $request Current request + * @param bool $absolute Create absolute URL + * @return string Adapted URL + */ + private static function createUrl($url, Request $request, $absolute=false) + { + // Variables + $server = $_SERVER['SERVER_NAME']; + $uri = $_SERVER['REQUEST_URI']; + $prefix = ''; + + + // Determine prefix + $ppos = array(strlen($uri)); + if(($p = strpos($uri, '/'.$request->getParam(1, 'intermediate'))) !== false) { + $ppos[] = $p; + } + $prefix = substr($uri, 0, min($ppos)); + + // Create absolute URL + if($absolute) { + $prefix = "http://$server/".trim($prefix, '/'); + } + + // Put URL together + $url = rtrim($prefix, '/').'/'.ltrim($url, '/'); + + + // Return URL + return $url; + } + + } + +?> diff --git a/core/Logger.inc b/core/Logger.inc new file mode 100644 index 00000000..b5ac7dc9 --- /dev/null +++ b/core/Logger.inc @@ -0,0 +1,132 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Class to log messages to different targets. + * + * @author coderkun + */ + class Logger + { + /** + * Log mode: Detect automatic + * + * @var int + */ + const LOGMODE_AUTO = 0; + /** + * Log mode: Print to screen + * + * @var int + */ + const LOGMODE_SCREEN = 1; + /** + * Log mode: Use PHP-logging mechanism + * + * @var int + */ + const LOGMODE_PHP = 2; + + /** + * Do not auto-log to screen + * + * @var boolean + */ + private $autoLogToScreenDisabled = false; + + + + + /** + * Construct a new logger. + */ + public function __construct() + { + } + + + + + /** + * Log a message. + * + * @param string $message Message to log + * @param int $logMode Log mode to use + */ + public function log($message, $logMode=self::LOGMODE_SCREEN) + { + // Choose log mode automatically + if($logMode == self::LOGMODE_AUTO) { + $logMode = $this->getAutoLogMode(); + } + + // Print message to screen + if($logMode & self::LOGMODE_SCREEN) { + $this->logToScreen($message); + } + + // Use PHP-logging mechanism + if($logMode & self::LOGMODE_PHP) { + $this->logToPhp($message); + } + } + + + /** + * Disable logging to screen when the log mode is automatically + * detected. + */ + public function disableAutoLogToScreen() + { + $this->autoLogToScreenDisabled = true; + } + + + + /** + * Print a message to screen. + * + * @param string $message Message to print + */ + private function logToScreen($message) + { + echo "$message
\n"; + } + + + /** + * Log a message by using PHP-logging mechanism. + * + * @param string $message Message to log + */ + private function logToPhp($message) + { + error_log($message, 0); + } + + + /** + * Detect log mode automatically by distinguishing between + * production and test environment. + * + * @return int Automatically detected log mode + */ + private function getAutoLogMode() + { + return ($_SERVER['SERVER_ADDR'] == '127.0.0.1' && !$this->autoLogToScreenDisabled) ? self::LOGMODE_SCREEN : self::LOGMODE_PHP; + } + + } + +?> diff --git a/core/Model.inc b/core/Model.inc new file mode 100644 index 00000000..c0475b4a --- /dev/null +++ b/core/Model.inc @@ -0,0 +1,141 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Abstract class for implementing a Model. + * + * @author coderkun + */ + abstract class Model + { + + + + + /** + * Load the class of a Model. + * + * @throws ModelNotFoundException + * @throws ModelNotValidException + * @param string $modelName Name of the Model to load + */ + public static function load($modelName) + { + // Determine full classname + $className = self::getClassName($modelName); + + try { + // Load class + ClassLoader::load($className); + + // Validate class + ClassLoader::check($className, get_class()); + } + catch(\nre\exceptions\ClassNotValidException $e) { + throw new \nre\exceptions\ModelNotValidException($e->getClassName()); + } + catch(\nre\exceptions\ClassNotFoundException $e) { + throw new \nre\exceptions\ModelNotFoundException($e->getClassName()); + } + } + + + /** + * Instantiate a Model (Factory Pattern). + * + * @param string $modelName Name of the Model to instantiate + */ + public static function factory($modelName) + { + // Determine full classname + $className = self::getClassName($modelName); + + // Construct and return Model + return new $className(); + } + + + /** + * Determine the classname for the given Model name. + * + * @param string $modelName Model name to get classname of + * @return string Classname fore the Model name + */ + private static function getClassName($modelName) + { + $className = ClassLoader::concatClassNames($modelName, ClassLoader::stripNamespace(get_class())); + + + return \nre\configs\AppConfig::$app['namespace']."models\\$className"; + } + + + + + /** + * Construct a new Model. + * + * TODO Catch exception + * + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + */ + protected function __construct() + { + // Load Models + $this->loadModels(); + } + + + + + /** + * Load the Models of this Model. + * + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @throws ModelNotValidException + * @throws ModelNotFoundException + */ + private function loadModels() + { + // Determine Models + $models = array(); + if(property_exists($this, 'models')) { + $models = $this->models; + } + if(!is_array($models)) { + $models = array($models); + } + + + // Load Models + foreach($models as $model) + { + // Load class + Model::load($model); + + // Construct Model + $modelName = ucfirst(strtolower($model)); + $this->$modelName = Model::factory($model); + } + } + + } + +?> diff --git a/core/Request.inc b/core/Request.inc new file mode 100644 index 00000000..5fda187e --- /dev/null +++ b/core/Request.inc @@ -0,0 +1,64 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Base class to represent a request. + * + * @author coderkun + */ + class Request + { + /** + * Passed parameters + * + * @var array + */ + protected $params = array(); + + + + + /** + * Get a parameter. + * + * @param int $index Index of parameter + * @param string $defaultIndex Index of default configuration value for this parameter + * @return string Value of parameter + */ + public function getParam($index, $defaultIndex=null) + { + // Return parameter + if(count($this->params) > $index) { + return $this->params[$index]; + } + + // Return default value + return \nre\core\Config::getDefault($defaultIndex); + } + + + /** + * Get all parameters from index on. + * + * @param int $offset Offset-index + * @return array Parameter values + */ + public function getParams($offset=0) + { + return array_slice($this->params, $offset); + } + + } + +?> diff --git a/core/Response.inc b/core/Response.inc new file mode 100644 index 00000000..4781b2ab --- /dev/null +++ b/core/Response.inc @@ -0,0 +1,158 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Base class to represent a response. + * + * @author coderkun + */ + class Response + { + /** + * Applied parameters + * + * @var array + */ + protected $params = array(); + /** + * Generated output + * + * @var string + */ + private $output = ''; + /** + * Abort futher execution + * + * @var bool + */ + private $exit = false; + + + + + /** + * Add a parameter. + * + * @param mixed $value Value of parameter + */ + public function addParam($value) + { + $this->params[] = $value; + } + + + /** + * Add multiple parameters. + * + * @param mixed $value1 Value of first parameter + * @param mixed … Values of further parameters + */ + public function addParams($value1) + { + $this->params = array_merge( + $this->params, + func_get_args() + ); + } + + + /** + * Delete all stored parameters (from offset on). + * + * @param int $offset Offset-index + */ + public function clearParams($offset=0) + { + $this->params = array_slice($this->params, 0, $offset); + } + + + /** + * Get a parameter. + * + * @param int $index Index of parameter + * @param string $defaultIndex Index of default configuration value for this parameter + * @return string Value of parameter + */ + public function getParam($index, $defaultIndex=null) + { + // Return parameter + if(count($this->params) > $index) { + return $this->params[$index]; + } + + + // Return default value + return \nre\core\Config::getDefault($defaultIndex); + } + + + /** + * Get all parameters from index on. + * + * @param int $offset Offset-index + * @return array Parameter values + */ + public function getParams($offset=0) + { + return array_slice($this->params, $offset); + } + + + /** + * Set output. + * + * @param string $output Generated output + */ + public function setOutput($output) + { + $this->output = $output; + } + + + /** + * Get generated output. + * + * @return string Generated output + */ + public function getOutput() + { + return $this->output; + } + + + /** + * Set exit-state. + * + * @param bool $exit Abort further execution + */ + public function setExit($exit=true) + { + $this->exit = $exit; + } + + + /** + * Get exit-state. + * + * @return bool Abort further execution + */ + public function getExit() + { + return $this->exit; + } + + } + +?> diff --git a/core/View.inc b/core/View.inc new file mode 100644 index 00000000..546ddb6e --- /dev/null +++ b/core/View.inc @@ -0,0 +1,124 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * View. + * + * Class to encapsulate a template file and to provide rendering methods. + * + * @author coderkun + */ + class View + { + /** + * Template filename + * + * @var string + */ + protected $templateFilename; + + + + + /** + * Load and instantiate the View of an Agent. + * + * @throws ViewNotFoundException + * @param string $layoutName Name of Layout in use + * @param string $agentName Name of the Agent + * @param string $action Current Action + * @param bool $isToplevel Agent is a ToplevelAgent + */ + public static function loadAndFactory($layoutName, $agentName=null, $action=null, $isToplevel=false) + { + return new View($layoutName, $agentName, $action, $isToplevel); + } + + + + + /** + * Construct a new View. + * + * @throws ViewNotFoundException + * @param string $layoutName Name of Layout in use + * @param string $agentName Name of the Agent + * @param string $action Current Action + * @param bool $isToplevel Agent is a ToplevelAgent + */ + protected function __construct($layoutName, $agentName=null, $action=null, $isToplevel=false) + { + // Create template filename + // LayoutName + $fileName = ROOT.DS. \nre\configs\CoreConfig::getClassDir('views').DS. strtolower($layoutName).DS; + // AgentName and Action + if(strtolower($agentName) != $layoutName || !$isToplevel) { + $fileName .= strtolower($agentName).DS.strtolower($action); + } + else { + $fileName .= strtolower($layoutName); + } + // File extension + $fileName .= \nre\configs\CoreConfig::getFileExt('views'); + + + // Check template file + if(!file_exists($fileName)) { + throw new \nre\exceptions\ViewNotFoundException($fileName); + } + + // Save filename + $this->templateFilename = $fileName; + } + + + + + /** + * Generate output + * + * @param array $data Data to have available in the template file + */ + public function render($data) + { + // Extract data + if(!is_null($data)) { + extract($data, EXTR_SKIP); + } + + // Buffer output + ob_start(); + + // Include template + include($this->templateFilename); + + + // Return buffered output + return ob_get_clean(); + } + + + /** + * Get template filename. + * + * @return string Template filename + */ + public function getTemplateFilename() + { + return $this->templateFilename; + } + + } + +?> diff --git a/core/WebUtils.inc b/core/WebUtils.inc new file mode 100644 index 00000000..5a4bb6f0 --- /dev/null +++ b/core/WebUtils.inc @@ -0,0 +1,75 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\core; + + + /** + * Class that holds several web-specific methods and properties. + * + * @author coderkun + */ + class WebUtils + { + /** + * HTTP-statuscode 403: Forbidden + * + * @var int + */ + const HTTP_FORBIDDEN = 403; + /** + * HTTP-statuscode 404: Not Found + * + * @var int + */ + const HTTP_NOT_FOUND = 404; + /** + * HTTP-statuscode 503: Service Unavailable + * + * @var int + */ + const HTTP_SERVICE_UNAVAILABLE = 503; + + /** + * HTTP-header strings + * + * @var array + */ + public static $httpStrings = array( + 200 => 'OK', + 304 => 'Not Modified', + 403 => 'Forbidden', + 404 => 'Not Found', + 503 => 'Service Unavailable' + ); + + + + + /** + * Get the HTTP-header of a HTTP-statuscode + * + * @param int $httpStatusCode HTTP-statuscode + * @return string HTTP-header of HTTP-statuscode + */ + public static function getHttpHeader($httpStatusCode) + { + if(!array_key_exists($httpStatusCode, self::$httpStrings)) { + $httpStatusCode = 200; + } + + + return sprintf("HTTP/1.1 %d %s\n", $httpStatusCode, self::$httpStrings[$httpStatusCode]); + } + + } + +?> diff --git a/drivers/DatabaseDriver.inc b/drivers/DatabaseDriver.inc new file mode 100644 index 00000000..dfd5c0fd --- /dev/null +++ b/drivers/DatabaseDriver.inc @@ -0,0 +1,87 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\drivers; + + + /** + * Abstract class for implementing a database driver. + * + * @author coderkun + */ + abstract class DatabaseDriver extends \nre\core\Driver + { + /** + * Driver class instance + * + * @static + * @var DatabaseDriver + */ + protected static $driver = null; + /** + * Connection resource + * + * @var resource + */ + protected $connection = null; + + + + + /** + * Singleton-pattern. + * + * @param array $config Database driver configuration + * @return DatabaseDriver Singleton-instance of database driver class + */ + public static function singleton($config) + { + // Singleton + if(self::$driver !== null) { + return self::$driver; + } + + // Construct + $className = get_called_class(); + self::$driver = new $className($config); + + + return self::$driver; + } + + + /** + * Construct a new database driver. + * + * @param array $config Connection and login settings + */ + protected function __construct($config) + { + parent::__construct(); + + // Establish connection + $this->connect($config); + } + + + + + /** + * Establish a connect to a MqSQL-database. + * + * @throws DatamodelException + * @param array $config Connection and login settings + */ + protected abstract function connect($config); + + } + +?> diff --git a/drivers/MysqliDriver.inc b/drivers/MysqliDriver.inc new file mode 100644 index 00000000..fe4fa81a --- /dev/null +++ b/drivers/MysqliDriver.inc @@ -0,0 +1,169 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\drivers; + + + /** + * Implementation of a database driver for MySQL-databases. + * + * @author coderkun + */ + class MysqliDriver extends \nre\drivers\DatabaseDriver + { + + + + + /** + * Construct a MySQL-driver. + * + * @throws DatamodelException + * @param array $config Connection and login settings + */ + function __construct($config) + { + parent::__construct($config); + } + + + + + /** + * Execute a SQL-query. + * + * @throws DatamodelException + * @param string $query Query to run + * @param mixed … Params + * @return array Result + */ + public function query($query) + { + // Prepare statement + if(!($stmt = $this->connection->prepare($query))) { + throw new \nre\exceptions\DatamodelException($this->connection->error, $this->connection->errno); + } + + try { + // Prepare data + $data = array(); + + // Bind parameters + $p = array_slice(func_get_args(), 1); + $params = array(); + foreach($p as $key => $value) { + $params[$key] = &$p[$key]; + } + if(count($params) > 0) + { + if(!(call_user_func_array(array($stmt, 'bind_param'), $params))) { + throw new \nre\exceptions\DatamodelException($this->connection->error, $this->connection->errno); + } + } + + // Execute query + if(!$stmt->execute()) { + throw new \nre\exceptions\DatamodelException($this->connection->error, $this->connection->errno); + } + + // Fetch result + if($result = $stmt->get_result()) { + while($row = $result->fetch_assoc()) { + $data[] = $row; + } + } + + + $stmt->close(); + return $data; + } + catch(Exception $e) { + $stmt->close(); + throw $e; + } + } + + + /** + * Return the last insert id (of the last insert-query). + * + * @return int Last insert id + */ + public function getInsertId() + { + return $this->connection->insert_id; + } + + + /** + * Enable/disable autocommit feature. + * + * @param boolean $autocommit Enable/disable autocommit + */ + public function setAutocommit($autocommit) + { + $this->connection->autocommit($autocommit); + } + + + /** + * Rollback the current transaction. + */ + public function rollback() + { + $this->connection->rollback(); + } + + + /** + * Commit the current transaction. + */ + public function commit() + { + $this->connection->commit(); + } + + + + + /** + * Establish a connect to a MqSQL-database. + * + * @throws DatamodelException + * @param array $config Connection and login settings + */ + protected function connect($config) + { + // Connect + $con = @new \mysqli( + $config['host'], + $config['user'], + $config['password'], + $config['db'] + ); + + // Check connection + if($con->connect_error) { + throw new \nre\exceptions\DatamodelException($con->connect_error, $con->connect_errno); + } + + // Set character encoding + if(!$con->set_charset('utf8')) { + throw new \nre\exceptions\DatamodelException($con->connect_error, $con->connect_errno); + } + + // Save connection + $this->connection = $con; + } + + } + +?> diff --git a/exceptions/AccessDeniedException.inc b/exceptions/AccessDeniedException.inc new file mode 100644 index 00000000..31bba929 --- /dev/null +++ b/exceptions/AccessDeniedException.inc @@ -0,0 +1,51 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Access denied. + * + * @author coderkun + */ + class AccessDeniedException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 85; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'access denied'; + + + + + /** + * Consturct a new exception. + */ + function __construct() + { + parent::__construct( + self::MESSAGE, + self::CODE + ); + } + + } + +?> diff --git a/exceptions/ActionNotFoundException.inc b/exceptions/ActionNotFoundException.inc new file mode 100644 index 00000000..418dd49c --- /dev/null +++ b/exceptions/ActionNotFoundException.inc @@ -0,0 +1,77 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Action not found. + * + * @author coderkun + */ + class ActionNotFoundException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 70; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'action not found'; + + /** + * Name of the action that was not found + * + * @var string + */ + private $action; + + + + + /** + * Construct a new exception. + * + * @param string $action Name of the action that was not found + */ + function __construct($action) + { + parent::__construct( + self::MESSAGE, + self::CODE, + $action + ); + + // Store values + $this->action = $action; + } + + + + + /** + * Get the name of the action that was not found. + * + * @return string Name of the action that was not found + */ + public function getAction() + { + return $this->action; + } + + } + +?> diff --git a/exceptions/AgentNotFoundException.inc b/exceptions/AgentNotFoundException.inc new file mode 100644 index 00000000..f0b3a2a7 --- /dev/null +++ b/exceptions/AgentNotFoundException.inc @@ -0,0 +1,67 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Agent not found. + * + * @author coderkun + */ + class AgentNotFoundException extends \nre\exceptions\ClassNotFoundException + { + /** + * Error code + * + * @var int + */ + const CODE = 66; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'agent not found'; + + + + + /** + * Construct a new exception. + * + * @param string $agentName Name of the Agent that was not found + */ + function __construct($agentName) + { + parent::__construct( + $agentName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the Agent that was not found. + * + * @return string Name of the Agent that was not found + */ + public function getAgentName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/AgentNotValidException.inc b/exceptions/AgentNotValidException.inc new file mode 100644 index 00000000..1c752051 --- /dev/null +++ b/exceptions/AgentNotValidException.inc @@ -0,0 +1,67 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Agent not valid. + * + * @author coderkun + */ + class AgentNotValidException extends \nre\exceptions\ClassNotValidException + { + /** + * Error code + * + * @var int + */ + const CODE = 76; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'agent not valid'; + + + + + /** + * Construct a new exception. + * + * @param string $agentName Name of the invalid Agent + */ + function __construct($agentName) + { + parent::__construct( + $agentName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the invalid Agent. + * + * @return string Name of the invalid Agent + */ + public function getAgentName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/ClassNotFoundException.inc b/exceptions/ClassNotFoundException.inc new file mode 100644 index 00000000..070f383f --- /dev/null +++ b/exceptions/ClassNotFoundException.inc @@ -0,0 +1,77 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Class not found. + * + * @author coderkun + */ + class ClassNotFoundException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 64; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'class not found'; + + /** + * Name of the class that was not found + * + * @var string + */ + private $className; + + + + + /** + * Construct a new exception + * + * @param string $className Name of the class that was not found + */ + function __construct($className, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $className + ); + + // Store values + $this->className = $className; + } + + + + + /** + * Get the name of the class that was not found. + * + * @return string Name of the class that was not found + */ + public function getClassName() + { + return $this->className; + } + + } + +?> diff --git a/exceptions/ClassNotValidException.inc b/exceptions/ClassNotValidException.inc new file mode 100644 index 00000000..bdd36d5e --- /dev/null +++ b/exceptions/ClassNotValidException.inc @@ -0,0 +1,77 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Class not valid. + * + * @author coderkun + */ + class ClassNotValidException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 74; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'class not valid'; + + /** + * Name of the invalid class + * + * @var string + */ + private $className; + + + + + /** + * Construct a new exception. + * + * @param string $className Name of the invalid class + */ + function __construct($className, $message=self::MESSAGE, $code=self::CODE) + { + parent::__construct( + $message, + $code, + $className + ); + + // Store value + $this->className = $className; + } + + + + + /** + * Get the name of the invalid class. + * + * @return string Name of the invalid class + */ + public function getClassName() + { + return $this->className; + } + + } + +?> diff --git a/exceptions/ComponentNotFoundException.inc b/exceptions/ComponentNotFoundException.inc new file mode 100644 index 00000000..5e75de44 --- /dev/null +++ b/exceptions/ComponentNotFoundException.inc @@ -0,0 +1,67 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Component not found. + * + * @author coderkun + */ + class ComponentNotFoundException extends \nre\exceptions\ClassNotFoundException + { + /** + * Error code + * + * @var int + */ + const CODE = 67; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'component not found'; + + + + + /** + * Construct a new exception. + * + * @param string $componentName Name of the Component that was not found + */ + function __construct($componentName) + { + parent::__construct( + $componentName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the Component that was not found. + * + * @return string Name of the Component that was not found + */ + public function getComponentName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/ComponentNotValidException.inc b/exceptions/ComponentNotValidException.inc new file mode 100644 index 00000000..a03b0c0d --- /dev/null +++ b/exceptions/ComponentNotValidException.inc @@ -0,0 +1,67 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Component not valid. + * + * @author coderkun + */ + class ComponentNotValidException extends \nre\exceptions\ClassNotValidException + { + /** + * Error code + * + * @var int + */ + const CODE = 77; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'component not valid'; + + + + + /** + * Construct a new exception. + * + * @param string $componentName Name of the invalid Component + */ + function __construct($componentName) + { + parent::__construct( + $componentName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the invalid Component. + * + * @return string Name of the invalid Component + */ + public function getComponentName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/ControllerNotFoundException.inc b/exceptions/ControllerNotFoundException.inc new file mode 100644 index 00000000..90859c49 --- /dev/null +++ b/exceptions/ControllerNotFoundException.inc @@ -0,0 +1,67 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Controller not found. + * + * @author coderkun + */ + class ControllerNotFoundException extends \nre\exceptions\ClassNotFoundException + { + /** + * Error code + * + * @var int + */ + const CODE = 67; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'controller not found'; + + + + + /** + * Construct a new exception. + * + * @param string $controllerName Name of the Controller that was not found + */ + function __construct($controllerName) + { + parent::__construct( + $controllerName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the Controller that was not found. + * + * @return string Name of the Controller that was not found + */ + public function getControllerName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/ControllerNotValidException.inc b/exceptions/ControllerNotValidException.inc new file mode 100644 index 00000000..0c945bcb --- /dev/null +++ b/exceptions/ControllerNotValidException.inc @@ -0,0 +1,67 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Controller not valid. + * + * @author coderkun + */ + class ControllerNotValidException extends \nre\exceptions\ClassNotValidException + { + /** + * Error code + * + * @var int + */ + const CODE = 77; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'controller not valid'; + + + + + /** + * Construct a new exception. + * + * @param string $controllerName Name of the invalid Controller + */ + function __construct($controllerName) + { + parent::__construct( + $controllerName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the invalid Controller. + * + * @return string Name of the invalid Controller + */ + public function getControllerName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/DatamodelException.inc b/exceptions/DatamodelException.inc new file mode 100644 index 00000000..7785cd21 --- /dev/null +++ b/exceptions/DatamodelException.inc @@ -0,0 +1,99 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Datamodel. + * + * This exception is thrown when an error occurred during the execution + * of a datamodel. + * + * @author coderkun + */ + class DatamodelException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 84; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'datamodel error'; + + /** + * Error message of datamodel + * + * @var string + */ + private $datamodelMessage; + /** + * Error code of datamodel + * + * @var int + */ + private $datamodelErrorNumber; + + + + + /** + * Consturct a new exception. + * + * @param string $datamodelMessage Error message of datamodel + * @param int $datamodelErrorNumber Error code of datamodel + */ + function __construct($datamodelMessage, $datamodelErrorNumber) + { + parent::__construct( + self::MESSAGE, + self::CODE, + $datamodelMessage." ($datamodelErrorNumber)" + ); + + // Store values + $this->datamodelMessage = $datamodelMessage; + $this->datamodelErrorNumber = $datamodelErrorNumber; + } + + + + + /** + * Get the error message of datamodel. + * + * @return string Error message of datamodel + */ + public function getDatamodelMessage() + { + return $this->datamodelMessage; + } + + + /** + * Get the error code of datamodel. + * + * @return string Error code of datamodel + */ + public function getDatamodelErrorNumber() + { + return $this->datamodelErrorNumber; + } + + } + +?> diff --git a/exceptions/DriverNotFoundException.inc b/exceptions/DriverNotFoundException.inc new file mode 100644 index 00000000..9b218f29 --- /dev/null +++ b/exceptions/DriverNotFoundException.inc @@ -0,0 +1,68 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Driver not found. + * + * @author coderkun + */ + class DriverNotFoundException extends \nre\exceptions\ClassNotFoundException + { + /** + * Error code + * + * @var int + */ + const CODE = 71; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'driver not found'; + + + + + /** + * Construct a new exception. + * + * @param string $driverName Name of the driver that was not found + */ + function __construct($driverName) + { + // Elternkonstruktor aufrufen + parent::__construct( + $driverName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the driver that was not found. + * + * @return string Name of the driver that was not found + */ + public function getDriverName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/DriverNotValidException.inc b/exceptions/DriverNotValidException.inc new file mode 100644 index 00000000..fa9022e8 --- /dev/null +++ b/exceptions/DriverNotValidException.inc @@ -0,0 +1,68 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Driver not valid. + * + * @author coderkun + */ + class DriverNotValidException extends \nre\exceptions\ClassNotValidException + { + /** + * Error code + * + * @var int + */ + const CODE = 81; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'driver not valid'; + + + + + /** + * Konstruktor. + * + * @param string $driverName Name of the invalid driver + */ + function __construct($driverName) + { + // Elternkonstruktor aufrufen + parent::__construct( + $driverName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the invalid driver. + * + * @return string Name of the invalid driver + */ + public function getDriverName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/FatalDatamodelException.inc b/exceptions/FatalDatamodelException.inc new file mode 100644 index 00000000..afd80b86 --- /dev/null +++ b/exceptions/FatalDatamodelException.inc @@ -0,0 +1,96 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Datamodel exception that is fatal for the application. + * + * @author coderkun + */ + class FatalDatamodelException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 85; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'fatal datamodel error'; + + /** + * Error message of datamodel + * + * @var string + */ + private $datamodelMessage; + /** + * Error code of datamodel + * + * @var int + */ + private $datamodelErrorNumber; + + + + + /** + * Consturct a new exception. + * + * @param string $datamodelMessage Error message of datamodel + * @param int $datamodelErrorNumber Error code of datamodel + */ + function __construct($datamodelMessage, $datamodelErrorNumber) + { + parent::__construct( + self::MESSAGE, + self::CODE, + $datamodelMessage." ($datamodelErrorNumber)" + ); + + // Store values + $this->datamodelMessage = $datamodelMessage; + $this->datamodelErrorNumber = $datamodelErrorNumber; + } + + + + + /** + * Get the error message of datamodel. + * + * @return string Error message of datamodel + */ + public function getDatamodelMessage() + { + return $this->datamodelMessage; + } + + + /** + * Get the error code of datamodel. + * + * @return string Error code of datamodel + */ + public function getDatamodelErrorNumber() + { + return $this->datamodelErrorNumber; + } + + } + +?> diff --git a/exceptions/IdNotFoundException.inc b/exceptions/IdNotFoundException.inc new file mode 100644 index 00000000..fc3315c3 --- /dev/null +++ b/exceptions/IdNotFoundException.inc @@ -0,0 +1,77 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: ID not found. + * + * @author coderkun + */ + class IdNotFoundException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 85; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'id not found'; + + /** + * ID that was not found + * + * @var mixed + */ + private $id; + + + + + /** + * Consturct a new exception. + * + * @param mixed $id ID that was not found + */ + function __construct($id) + { + parent::__construct( + self::MESSAGE, + self::CODE, + $id + ); + + // Store values + $this->id = $id; + } + + + + + /** + * Get the ID that was not found. + * + * @return mixed ID that was not found + */ + public function getId() + { + return $this->id; + } + + } + +?> diff --git a/exceptions/LayoutNotFoundException.inc b/exceptions/LayoutNotFoundException.inc new file mode 100644 index 00000000..0eb8c89c --- /dev/null +++ b/exceptions/LayoutNotFoundException.inc @@ -0,0 +1,68 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Layout not found. + * + * @author coderkun + */ + class LayoutNotFoundException extends \nre\exceptions\AgentNotFoundException + { + /** + * Error code + * + * @var int + */ + const CODE = 65; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'layout not found'; + + + + + /** + * Construct a new exception. + * + * @param string $layoutName Name of the Layout that was not found + */ + function __construct($layoutName) + { + // Elternkonstruktor aufrufen + parent::__construct( + $layoutName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the Layout that was not found. + * + * @return string Name of the Layout that was not found + */ + public function getLayoutName() + { + return $this->getAgentName(); + } + + } + +?> diff --git a/exceptions/LayoutNotValidException.inc b/exceptions/LayoutNotValidException.inc new file mode 100644 index 00000000..b3af184f --- /dev/null +++ b/exceptions/LayoutNotValidException.inc @@ -0,0 +1,67 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Layout not valid. + * + * @author coderkun + */ + class LayoutNotValidException extends \nre\exceptions\AgentNotFoundException + { + /** + * Error code + * + * @var int + */ + const CODE = 75; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'layout not valid'; + + + + + /** + * Construct a new exception. + * + * @param string $layoutName Name of the invalid Layout + */ + function __construct($layoutName) + { + parent::__construct( + $layoutName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the invalid Layout. + * + * @return string Name of the invalid Layout + */ + public function getLayoutName() + { + return $this->getAgentName(); + } + + } + +?> diff --git a/exceptions/ModelNotFoundException.inc b/exceptions/ModelNotFoundException.inc new file mode 100644 index 00000000..48e8c0df --- /dev/null +++ b/exceptions/ModelNotFoundException.inc @@ -0,0 +1,67 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Action not found. + * + * @author coderkun + */ + class ModelNotFoundException extends \nre\exceptions\ClassNotFoundException + { + /** + * Error code + * + * @var int + */ + const CODE = 68; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'model not found'; + + + + + /** + * Construct a new exception. + * + * @param string $modelName Name of the Model that was not found + */ + function __construct($modelName) + { + parent::__construct( + $modelName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the Model that was not found + * + * @return string Name of the Model that was not found + */ + public function getModelName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/ModelNotValidException.inc b/exceptions/ModelNotValidException.inc new file mode 100644 index 00000000..267e460e --- /dev/null +++ b/exceptions/ModelNotValidException.inc @@ -0,0 +1,68 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Action not found. + * + * @author coderkun + */ + class ModelNotValidException extends \nre\exceptions\ClassNotValidException + { + /** + * Error code + * + * @var int + */ + const CODE = 78; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'model not valid'; + + + + + /** + * Construct a new exception. + * + * @param string $modelName Name of the invalid Model + */ + function __construct($modelName) + { + // Elternkonstruktor aufrufen + parent::__construct( + $modelName, + self::MESSAGE, + self::CODE + ); + } + + + + + /** + * Get the name of the invalid Model + * + * @return string Name of the invalid Model + */ + public function getModelName() + { + return $this->getClassName(); + } + + } + +?> diff --git a/exceptions/ParamsNotValidException.inc b/exceptions/ParamsNotValidException.inc new file mode 100644 index 00000000..be650ce2 --- /dev/null +++ b/exceptions/ParamsNotValidException.inc @@ -0,0 +1,77 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception Parameters not valid. + * + * @author coderkun + */ + class ParamsNotValidException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 86; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'parameters not valid'; + + /** + * Invalid parameters. + * + * @var array + */ + private $params; + + + + + /** + * Construct a new exception. + * + * @param mixed $param1 Invalid parameters as argument list + */ + function __construct($param1) + { + parent::__construct( + self::MESSAGE, + self::CODE, + implode(', ', func_get_args()) + ); + + // Store values + $this->params = func_get_args(); + } + + + + + /** + * Get invalid parameters. + * + * @return array Invalid parameters + */ + public function getParams() + { + return $this->params; + } + + } + +?> diff --git a/exceptions/ServiceUnavailableException.inc b/exceptions/ServiceUnavailableException.inc new file mode 100644 index 00000000..bafc297f --- /dev/null +++ b/exceptions/ServiceUnavailableException.inc @@ -0,0 +1,77 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: Service is unavailable. + * + * @author coderkun + */ + class ServiceUnavailableException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 84; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'service unavailable'; + + /** + * Throws exception + * + * @var Exception + */ + private $exception; + + + + + /** + * Construct a new exception. + * + * @param Exception $exception Exception that has occurred + */ + function __construct($exception) + { + parent::__construct( + self::MESSAGE, + self::CODE, + $exception->getMessage() + ); + + // Store values + $this->exception = $exception; + } + + + + + /** + * Get the exception that hat occurred + * + * @return Exception Exception that has occurred + */ + public function getException() + { + return $this->exception; + } + + } + +?> diff --git a/exceptions/ViewNotFoundException.inc b/exceptions/ViewNotFoundException.inc new file mode 100644 index 00000000..30366201 --- /dev/null +++ b/exceptions/ViewNotFoundException.inc @@ -0,0 +1,77 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\exceptions; + + + /** + * Exception: View not found. + * + * @author coderkun + */ + class ViewNotFoundException extends \nre\core\Exception + { + /** + * Error code + * + * @var int + */ + const CODE = 69; + /** + * Error message + * + * @var string + */ + const MESSAGE = 'view not found'; + + /** + * Filename of the view that was not found + * + * @var string + */ + private $fileName; + + + + + /** + * Construct a new exception. + * + * @param string $fileName Filename of the view that was not found + */ + function __construct($fileName) + { + parent::__construct( + self::MESSAGE, + self::CODE, + $fileName + ); + + // Save values + $this->fileName = $fileName; + } + + + + + /** + * Get the filename of the view that was not found. + * + * @return string Filename of the view that was not found + */ + public function getFileName() + { + return $this->fileName; + } + + } + +?> diff --git a/locale/de_DE/LC_MESSAGES/The Legend of Z.mo b/locale/de_DE/LC_MESSAGES/The Legend of Z.mo new file mode 100644 index 0000000000000000000000000000000000000000..55ce9cc15849ae427033cdb41394c67eb00df3b2 GIT binary patch literal 9727 zcmai&ZIETfS;w335?C=2Ouz(88pyydaEICDZJlLVc9~h;c9vmhmRCtQb8pYRhdKA0 z%Q@%HWd}7&5)(16ghEpoiBOcWQmLS&!Bn}i)FH%DKVWG-sL>BwYFWSsQyQqUQ2hP- z^nIDx*~zJS?(cM;K7G2Mex9eFo_{&_w5JW%Q^SM_s-*I3mk)IzzO(ncs;xT?ty&DJOFpV7F4_6f*Q~BP~-eV__Od8 z_+I#rfv>@zqWnLBXYi0Am~)|2W1+r(In=zbh0@#5 z3%9~msBymmrQf$8f96ycL-Tq+RQ-ie1U)+d7cUKZGe z{F%r2(K>$ts^90K#_>(4asM&A3Vt6>z_&vER$fy7I|HYn-k*i2#M}=xt|cfv6j1&B zW+;ChN)LYxHIM&<7XBDs3eUr+FN3?G#_b>`XO;q2p#0_usP~_Pn*Z;H`WK=4|31`s z-U#LYg<7|ZSzPsdE7Z6TLe2YLcq**JZLkS9HFA3E5KOOi*_-ChJ zS5V)*59fLnHlXbMT3*t3W}&_tLD@wC8LD|4`t^Zo_jM@!eg|6kDpdO)L9ORIum&%o zbNTaKPs>LQbstoCz&F7s{TlhS$SA;du{Aua85;i>Dz= zXucK7t5DO4{9EN2eppJ zq563%@c*F3d-_F{{w{>-=LV>Lr=aw8JKPQr!%6rVNN6&D0X4oKK&|V)L9N%DP~SZp zEH3R?{)mjet!Wed<`}OYsmSCY^Hzx3OCa6FC#4^Mo_bvK@K68 zAX*#w#ePKgqiZXoHN+JL*8+F4%QKOcQ2SAM1JXbqLq3IUL+(Iy-G<12R=M5Ym4 z*CP)hvO!(43E79P%aGHNMHRRv5XFGU5!o7{(_gaZUquch_aRRqvdIM5ha5m;`?Bi@ z(RDxKkP`VN6%5JM6Oa{tf(!6-dhRM-XQQX5r`8#YUmBX+D|%ecL^4?1i#Y@Q{Kn|4e;Ns{$k!|H3cJkn9~PKURt zC|zEdn_E#H)w#8|xy!;UgH@Ig;9*mVYFEqwcSYfD%vQ?UDP;Y-J|i^ zKK;{rmX=YR7B)^2*NhTd?+{bVnPoP~(x%9+X)puJ`X13=T!ozFChQWep@R=x!4g0c_JIY5nR^xO8Er3v8@Am#R%|=T3q72oHk)U1b#>6QVZZSA zQCjrS!`xEbv1L>&nYn=td95v|6 zB1@L>^Lg~RR>eaN;bQ$!&HKnboF|WZQB2S$9%i|XI`}Y~mzj@CR5mY58n`vZ5~u2W z`MToJ=?^P*>&NS^e=`_;*j@+qoB83OQGfiyicQqIzR|{dRjb0>gBMj!c~2FRwOinO zMlKokbDF-Nt+8`dPn6BoG9T`2L$xc;r&-xgkIHzN&9PyRxY7(dj!M7x)T6XcjF&3d zf1P1x)ka#OVHZfK!cJ`}hcILY@roi?ViE$jQ9faa-g$6udxTLHE?FGZ@M*u-n}R@N zyxCO0)%@kI!Ipe1Eg7pB+*Jue)d8WsWx8o-ysQMI&M@$^hjIJbZH?O9XqNsat*r8Z zYr53Osr$Fg-Z@|M*#S>$Q%rHv?wHtqO>N?u+K#L3j_s2Z6IV=ZpO~Ow?T}lJ)yu~1 zwpZ=6P1fdfTshI^HEFjTh|*@44X!rtqV`C6l2G!osp%~FSZ+U3~wijh}`54TU+N0dMN)O(8| zR%%INmnu=}rYu`Qdb)^m$YNN+z1?EP#r0O|pV`ILV>wEVQ)j31#Vlz$8@DZMpG4h7 z9_03riwiey=VIbG-RhK(6qXRgQpNPG7vmJ2RUJmDopFTfl>Lcbuf%iM$LYw~c?On} zem00?Y+fW85wuELY|<^tn2%5AE^ekI0ldlQ$nz{?aSKH`ZhGCk!S0%qDd5X(!dTNS z(%lAgHTzeO(Hr|7SzIelSGv`4g;7@t(zGVHz}LOD2%ByBDyf|gDpA_Q+UeG3ze`j# z^KPfpuh3y8RMf&=)Kc!1Aouom3a+wi(m&Wygpx4Q(tk5-)Gd5`x;rC26m&ZLwnB`8M~9P(cjS;VGUm2D7}Z-W zK*IJ?1*3anM+9n-B6C8YV(qOrHP$FDOG4GKU;anJ90*k!4R0TAFUos(+YSePr7bG! zBur-Hv`v?^S6^8DTHFlw>LXwC(or2@7FV+FcIdm3$39(>iOnSsSknO$Pxw+*{I$O~Y=#-8yGM52H^unaPPQPcjj2 z9wr#cLX-dIwa6PWaqL!SPt1}{h{Ts=tRmjv^r`Kudb7Wk?a%Q5wfFIMaQdvgt7@vC zJxfluvhLuSyiHqrzwi@{aM18XD_rwvb2D7u^dxo|=3)9qr&!ycTS#9<14&SP<@s70t=CY~ z>qe}NeCVi(awY%CP&TT~T>8_&mV=fom4AHT1ZzW1l_^x=8yhR4c1eC$(oO+=7?ApF zKG>oMTJJwm#YS5t-&@<{Cq#%fjn_?geP4RK`68Xr+sPwezbR_yW#s6CEn%KaqS|s6 z*{0AS`%1|_`Kn`ce_a=1PGsyfAxMyh5OYxHNm5``cO_fR#+vj+Hmx0X&6lz IFLBKNfBmRFb^rhX literal 0 HcmV?d00001 diff --git a/locale/de_DE/LC_MESSAGES/The Legend of Z.po b/locale/de_DE/LC_MESSAGES/The Legend of Z.po new file mode 100644 index 00000000..af4cd3bb --- /dev/null +++ b/locale/de_DE/LC_MESSAGES/The Legend of Z.po @@ -0,0 +1,749 @@ +msgid "" +msgstr "" +"Project-Id-Version: The Legend of Z\n" +"POT-Creation-Date: 2014-04-23 21:00+0100\n" +"PO-Revision-Date: 2014-04-23 21:01+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.6.4\n" +"X-Poedit-Basepath: ../../../\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-SearchPath-0: views\n" +"X-Poedit-SearchPath-1: questtypes\n" + +#: questtypes/bossfight/html/quest.tpl:11 +#: questtypes/bossfight/html/quest.tpl:24 +#: questtypes/bossfight/html/submission.tpl:21 +#: questtypes/bossfight/html/submission.tpl:33 +msgid "lost" +msgstr "verloren" + +#: questtypes/bossfight/html/quest.tpl:41 +msgid "Choose" +msgstr "Wählen" + +#: questtypes/bossfight/html/quest.tpl:47 +#: questtypes/choiceinput/html/quest.tpl:14 +#: questtypes/crossword/html/quest.tpl:30 +#: questtypes/dragndrop/html/quest.tpl:16 +#: questtypes/multiplechoice/html/quest.tpl:19 +#: questtypes/submit/html/quest.tpl:23 questtypes/textinput/html/quest.tpl:10 +msgid "solve" +msgstr "lösen" + +#: questtypes/crossword/html/quest.tpl:22 +#: questtypes/crossword/html/submission.tpl:22 +msgid "vertical" +msgstr "vertikal" + +#: questtypes/crossword/html/quest.tpl:24 +#: questtypes/crossword/html/submission.tpl:24 +msgid "horizontal" +msgstr "horizontal" + +#: questtypes/multiplechoice/html/quest.tpl:3 +#, php-format +msgid "Question %d of %d" +msgstr "Frage %d von %d" + +#: questtypes/multiplechoice/html/quest.tpl:17 +msgid "solve Question" +msgstr "Frage lösen" + +#: questtypes/submit/html/quest.tpl:4 +#, php-format +msgid "File has wrong type “%s”" +msgstr "Der Dateityp „%s“ ist nicht erlaubt" + +#: questtypes/submit/html/quest.tpl:6 +msgid "File exceeds size maximum" +msgstr "Die Datei ist zu groß" + +#: questtypes/submit/html/quest.tpl:8 +#, php-format +msgid "Error during file upload: %s" +msgstr "Fehler beim Dateiupload: %s" + +#: questtypes/submit/html/quest.tpl:17 +msgid "Allowed file types" +msgstr "Erlaubte Dateiformate" + +#: questtypes/submit/html/quest.tpl:20 +#, php-format +msgid "%s-files" +msgstr "%s-Dateien" + +#: questtypes/submit/html/quest.tpl:20 +msgid "max." +msgstr "max." + +#: questtypes/submit/html/quest.tpl:28 +msgid "Past submissions" +msgstr "Vorherige Lösungen" + +#: questtypes/submit/html/quest.tpl:33 questtypes/submit/html/submission.tpl:6 +#, php-format +msgid "submitted at %s on %s h" +msgstr "eingereicht am %s um %s Uhr" + +#: questtypes/submit/html/quest.tpl:35 +msgid "This submission is waiting for approval" +msgstr "Diese Lösungen wartet auf freigabe" + +#: questtypes/submit/html/quest.tpl:42 +#: questtypes/submit/html/submission.tpl:12 +#, php-format +msgid "Comment from %s on %s at %s" +msgstr "Kommentar von %s am %s um %s Uhr" + +#: questtypes/submit/html/submission.tpl:27 +msgid "Comment" +msgstr "Kommentar" + +#: questtypes/submit/html/submission.tpl:30 views/html/quests/quest.tpl:43 +#: views/html/quests/submissions.tpl:29 +msgid "solved" +msgstr "gelöst" + +#: questtypes/submit/html/submission.tpl:31 views/html/quests/quest.tpl:47 +#: views/html/quests/submissions.tpl:20 +msgid "unsolved" +msgstr "ungelöst" + +#: views/binary/error/index.tpl:1 views/html/error/index.tpl:1 +msgid "Error" +msgstr "Fehler" + +#: views/html/achievements/index.tpl:9 views/html/seminarymenu/index.tpl:4 +msgid "Achievements" +msgstr "Errungenschaften" + +#: views/html/achievements/index.tpl:10 +msgid "Achievement description" +msgstr "" +"Errungenschaften sind Auszeichnungen für deine Erfolge im Verlauf der Reise. " +"Sie dienen als historische Erinnerungen an Meilensteine, besondere Taten und " +"interessante oder lustige Ereignisse, die du erlebst." + +#: views/html/achievements/index.tpl:13 +msgid "Seldom Achievements" +msgstr "Die seltensten Errungenschaften" + +#: views/html/achievements/index.tpl:27 +#, php-format +msgid "Achievement has been achieved only %d times" +msgstr "wurde erst %d mal gefunden" + +#: views/html/achievements/index.tpl:33 +msgid "Most successful collectors" +msgstr "Die erfolgreichsten Sammler" + +#: views/html/achievements/index.tpl:39 +#, php-format +msgid "Character has achieved %d Achievements" +msgstr "hat %d Errungenschaften erhalten" + +#: views/html/achievements/index.tpl:45 +msgid "Personal Achievements" +msgstr "Deine Errungenschaften" + +#: views/html/achievements/index.tpl:47 +#, php-format +msgid "Own progress: %d %%" +msgstr "Persönlicher Fortschritt: %d %%" + +#: views/html/achievements/index.tpl:52 +#: views/html/charactergroups/group.tpl:17 +#: views/html/characters/character.tpl:28 views/html/seminarybar/index.tpl:7 +msgid "Rank" +msgstr "Platz" + +#: views/html/achievements/index.tpl:52 +#, php-format +msgid "You achieved %d of %d Achievements so far" +msgstr "Du hast bislang %d von insgesamt %d Errungenschaften erreicht" + +#: views/html/achievements/index.tpl:59 views/html/seminarybar/index.tpl:28 +#, php-format +msgid "achieved at: %s" +msgstr "erhalten am: %s" + +#: views/html/achievements/index.tpl:68 +msgid "Secret Achievement" +msgstr "Geheime Errungenschaft" + +#: views/html/achievements/index.tpl:72 +msgid "Continue playing to unlock this secret Achievement" +msgstr "Spiele weiter, um diesen geheimen Erfolg freizuschalten" + +#: views/html/charactergroups/group.tpl:8 +#: views/html/charactergroups/groupsgroup.tpl:8 +#: views/html/charactergroups/index.tpl:9 +#: views/html/characters/character.tpl:46 views/html/seminarymenu/index.tpl:3 +msgid "Character Groups" +msgstr "Charaktergruppen" + +#: views/html/charactergroups/group.tpl:19 +msgid "Members" +msgstr "Mitglieder" + +#: views/html/charactergroups/group.tpl:19 +msgid "Member" +msgstr "Mitglied" + +#: views/html/charactergroups/group.tpl:23 views/html/characters/index.tpl:7 +#: views/html/characters/manage.tpl:7 views/html/seminarymenu/index.tpl:2 +#: views/html/users/user.tpl:15 +msgid "Characters" +msgstr "Charaktere" + +#: views/html/charactergroups/group.tpl:38 +#: views/html/questgroups/questgroup.tpl:57 views/html/quests/create.tpl:7 +#: views/html/quests/index.tpl:7 +msgid "Quests" +msgstr "Quests" + +#: views/html/charactergroups/groupsgroup.tpl:20 +#: views/html/charactergroupsquests/quest.tpl:9 +msgid "Character Groups Quests" +msgstr "Charactergruppen-Quests" + +#: views/html/charactergroupsquests/quest.tpl:16 +msgid "Description" +msgstr "Beschreibung" + +#: views/html/charactergroupsquests/quest.tpl:24 +msgid "Rules" +msgstr "Regeln" + +#: views/html/charactergroupsquests/quest.tpl:31 +msgid "Won Quest" +msgstr "Gewonnene Quest" + +#: views/html/charactergroupsquests/quest.tpl:37 +msgid "Lost Quest" +msgstr "Verlorene Quest" + +#: views/html/characters/character.tpl:16 +msgid "Total progress" +msgstr "Fortschritt" + +#: views/html/characters/character.tpl:20 +#: views/html/characters/character.tpl:63 +#: views/html/characters/character.tpl:69 +#: views/html/characters/character.tpl:75 views/html/seminarybar/index.tpl:42 +#: views/html/users/user.tpl:29 +msgid "Level" +msgstr "Level" + +#: views/html/characters/character.tpl:57 +msgid "Ranking" +msgstr "Ranking" + +#: views/html/characters/character.tpl:83 +msgid "Topic progress" +msgstr "Thematischer Fortschritt" + +#: views/html/characters/index.tpl:11 views/html/characters/manage.tpl:8 +msgid "Manage" +msgstr "Verwalten" + +#: views/html/characters/manage.tpl:12 +msgid "Selection" +msgstr "Auswahl" + +#: views/html/characters/manage.tpl:26 +msgid "Add role" +msgstr "Füge Rolle hinzu" + +#: views/html/characters/manage.tpl:28 views/html/characters/manage.tpl:36 +msgid "Admin" +msgstr "Administrator" + +#: views/html/characters/manage.tpl:29 views/html/characters/manage.tpl:37 +msgid "Moderator" +msgstr "Moderator" + +#: views/html/characters/manage.tpl:31 views/html/characters/manage.tpl:39 +msgid "User" +msgstr "Benutzer" + +#: views/html/characters/manage.tpl:34 +msgid "Remove role" +msgstr "Entferne Rolle" + +#: views/html/characters/register.tpl:7 +msgid "Create Character" +msgstr "Charakter erstellen" + +#: views/html/characters/register.tpl:20 +#, php-format +msgid "Character name is too short (min. %d chars)" +msgstr "Der Charaktername ist zu kurz (min. %d Zeichen)" + +#: views/html/characters/register.tpl:22 +#, php-format +msgid "Character name is too long (max. %d chars)" +msgstr "Der Charaktername ist zu lang (max. %d Zeichen)" + +#: views/html/characters/register.tpl:24 +msgid "Character name contains illegal characters" +msgstr "Der Charaktername enthält ungültige Zeichen" + +#: views/html/characters/register.tpl:26 +msgid "Character name already exists" +msgstr "Der Charaktername existiert bereits" + +#: views/html/characters/register.tpl:28 +msgid "Character name invalid" +msgstr "Der Charaktername ist ungültig" + +#: views/html/characters/register.tpl:40 +msgid "Character properties" +msgstr "Charaktereigenschaften" + +#: views/html/characters/register.tpl:41 views/html/characters/register.tpl:42 +msgid "Character name" +msgstr "Charaktername" + +#: views/html/characters/register.tpl:43 +msgid "Character type" +msgstr "Charaktertyp" + +#: views/html/characters/register.tpl:54 +#, php-format +msgid "The Seminary field “%s” is invalid" +msgstr "Das Kursfeld „%s“ ist ungültig" + +#: views/html/characters/register.tpl:59 +msgid "Seminary fields" +msgstr "Kursfelder" + +#: views/html/characters/register.tpl:81 views/html/seminaries/create.tpl:14 +#: views/html/users/create.tpl:17 +msgid "create" +msgstr "erstellen" + +#: views/html/error/index.tpl:5 views/html/error/index.tpl:14 +#: views/html/introduction/index.tpl:3 views/html/introduction/index.tpl:11 +#: views/html/menu/index.tpl:6 views/html/users/login.tpl:3 +#: views/html/users/login.tpl:17 +msgid "Login" +msgstr "Login" + +#: views/html/error/index.tpl:8 views/html/error/index.tpl:9 +#: views/html/introduction/index.tpl:6 views/html/introduction/index.tpl:7 +#: views/html/users/create.tpl:6 views/html/users/create.tpl:7 +#: views/html/users/edit.tpl:6 views/html/users/edit.tpl:7 +#: views/html/users/login.tpl:9 views/html/users/login.tpl:10 +#: views/html/users/register.tpl:78 views/html/users/register.tpl:79 +msgid "Username" +msgstr "Benutzername" + +#: views/html/error/index.tpl:10 views/html/error/index.tpl:11 +#: views/html/introduction/index.tpl:8 views/html/introduction/index.tpl:9 +#: views/html/users/create.tpl:14 views/html/users/create.tpl:15 +#: views/html/users/edit.tpl:14 views/html/users/edit.tpl:15 +#: views/html/users/login.tpl:11 views/html/users/login.tpl:12 +#: views/html/users/register.tpl:86 views/html/users/register.tpl:87 +msgid "Password" +msgstr "Passwort" + +#: views/html/error/index.tpl:15 views/html/introduction/index.tpl:12 +msgid "or" +msgstr "oder" + +#: views/html/error/index.tpl:15 views/html/introduction/index.tpl:12 +msgid "register yourself" +msgstr "registriere dich" + +#: views/html/introduction/index.tpl:1 +msgid "Introduction" +msgstr "Einführung" + +#: views/html/library/index.tpl:9 views/html/library/topic.tpl:8 +msgid "Questtopics" +msgstr "Themen" + +#: views/html/library/index.tpl:10 +#, php-format +msgid "Library description, %s, %s" +msgstr "" +"Hier findest du alle Themen aus der Vorlesung „%s“ und die passenden Quests " +"zum Nachschlagen und Wiederholen. Dein Fortschritt in „%s“ beeinflusst den " +"Umfang der Bibliothek, spiele also regelmäßig weiter und schalte so Quest " +"für Quest alle Inhalte frei." + +#: views/html/library/index.tpl:12 +#, php-format +msgid "Total progress: %d %%" +msgstr "Gesamtfortschritt: %d %%" + +#: views/html/menu/index.tpl:2 views/html/users/create.tpl:1 +#: views/html/users/delete.tpl:1 views/html/users/edit.tpl:1 +#: views/html/users/index.tpl:1 views/html/users/login.tpl:1 +#: views/html/users/register.tpl:1 views/html/users/user.tpl:1 +msgid "Users" +msgstr "Benutzer" + +#: views/html/menu/index.tpl:3 views/html/seminaries/create.tpl:6 +#: views/html/seminaries/delete.tpl:6 views/html/seminaries/edit.tpl:6 +#: views/html/seminaries/index.tpl:1 +msgid "Seminaries" +msgstr "Kurse" + +#: views/html/menu/index.tpl:8 +msgid "Logout" +msgstr "Logout" + +#: views/html/questgroups/create.tpl:7 +msgid "Questgroups" +msgstr "Questgruppen" + +#: views/html/questgroups/create.tpl:8 views/html/questgroups/create.tpl:15 +#: views/html/quests/create.tpl:8 views/html/quests/create.tpl:40 +msgid "Create" +msgstr "Erstellen" + +#: views/html/questgroups/create.tpl:12 views/html/questgroups/create.tpl:13 +#: views/html/seminaries/create.tpl:11 views/html/seminaries/create.tpl:12 +#: views/html/seminaries/edit.tpl:11 views/html/seminaries/edit.tpl:12 +msgid "Title" +msgstr "Titel" + +#: views/html/quests/create.tpl:12 views/html/quests/create.tpl:13 +#: views/html/users/user.tpl:11 +msgid "Name" +msgstr "Name" + +#: views/html/quests/create.tpl:14 views/html/quests/index.tpl:12 +msgid "Questgroup" +msgstr "Questgruppe" + +#: views/html/quests/create.tpl:27 +msgid "XPs" +msgstr "XPs" + +#: views/html/quests/create.tpl:34 +msgid "Entry text" +msgstr "Einstiegstext" + +#: views/html/quests/create.tpl:36 +msgid "Wrong text" +msgstr "Text für falsche Antwort" + +#: views/html/quests/create.tpl:37 views/html/quests/quest.tpl:56 +msgid "Task" +msgstr "Aufgabe" + +#: views/html/quests/index.tpl:19 +msgid "Questtype" +msgstr "Questtyp" + +#: views/html/quests/index.tpl:27 +msgid "Apply filters" +msgstr "Filter anwenden" + +#: views/html/quests/index.tpl:28 +msgid "Reset filters" +msgstr "Filter zurücksetzen" + +#: views/html/quests/quest.tpl:44 +#, php-format +msgid "Quest completed. You have earned %d XPs." +msgstr "Quest abgeschlossen. Du hast %d XPs erhalten." + +#: views/html/quests/quest.tpl:62 +msgid "Task already successfully solved" +msgstr "Du hast die Aufgabe bereits erfolgreich gelöst" + +#: views/html/quests/quest.tpl:65 +msgid "Show answer" +msgstr "Lösung anzeigen" + +#: views/html/quests/quest.tpl:66 +msgid "Skip task" +msgstr "Aufgabe überspringen" + +#: views/html/quests/quest.tpl:72 +msgid "continue" +msgstr "fortfahren" + +#: views/html/quests/quest.tpl:79 +msgid "Continuation" +msgstr "Fortsetzung" + +#: views/html/quests/quest.tpl:85 views/html/quests/quest.tpl:98 +msgid "Quest" +msgstr "Quest" + +#: views/html/quests/submission.tpl:10 +#, php-format +msgid "Submission of %s" +msgstr "Lösung von %s" + +#: views/html/quests/submissions.tpl:11 +msgid "submitted" +msgstr "eingereicht" + +#: views/html/seminaries/create.tpl:7 +msgid "New seminary" +msgstr "Neuer Kurs" + +#: views/html/seminaries/delete.tpl:7 views/html/seminaries/seminary.tpl:10 +msgid "Delete seminary" +msgstr "Kurs löschen" + +#: views/html/seminaries/delete.tpl:9 +#, php-format +msgid "Should the seminary “%s” really be deleted?" +msgstr "Soll der Kurs „%s“ wirklich gelöscht werden?" + +#: views/html/seminaries/delete.tpl:11 views/html/users/delete.tpl:6 +msgid "delete" +msgstr "löschen" + +#: views/html/seminaries/delete.tpl:12 views/html/users/delete.tpl:7 +msgid "cancel" +msgstr "abbrechen" + +#: views/html/seminaries/edit.tpl:7 views/html/seminaries/seminary.tpl:9 +msgid "Edit seminary" +msgstr "Kurs bearbeiten" + +#: views/html/seminaries/edit.tpl:14 views/html/users/edit.tpl:17 +msgid "save" +msgstr "speichern" + +#: views/html/seminaries/index.tpl:4 +msgid "Create new seminary" +msgstr "Neuen Kurs erstellen" + +#: views/html/seminaries/index.tpl:21 +#, php-format +msgid "created by %s on %s" +msgstr "erstellt von %s am %s" + +#: views/html/seminaries/index.tpl:24 +msgid "Create a Character" +msgstr "Erstelle einen Charakter" + +#: views/html/seminaries/index.tpl:26 +#, php-format +msgid "Your Character “%s” has not been activated yet" +msgstr "Dein Charakter „%s“ wurde noch nicht aktiviert" + +#: views/html/seminaries/seminary.tpl:11 +msgid "Show Quests" +msgstr "Quests anzeigen" + +#: views/html/seminarybar/index.tpl:14 +msgid "Last Quest" +msgstr "Letzter Speicherpunkt" + +#: views/html/seminarybar/index.tpl:46 +#, php-format +msgid "Show %s-Profile" +msgstr "%s-Profil anzeigen" + +#: views/html/seminarymenu/index.tpl:5 +msgid "Library" +msgstr "Bibliothek" + +#: views/html/users/create.tpl:2 +msgid "New user" +msgstr "Neuer Benutzer" + +#: views/html/users/create.tpl:8 views/html/users/create.tpl:9 +#: views/html/users/edit.tpl:8 views/html/users/edit.tpl:9 +#: views/html/users/register.tpl:80 views/html/users/register.tpl:81 +msgid "Prename" +msgstr "Vorname" + +#: views/html/users/create.tpl:10 views/html/users/create.tpl:11 +#: views/html/users/edit.tpl:10 views/html/users/edit.tpl:11 +#: views/html/users/register.tpl:82 views/html/users/register.tpl:83 +msgid "Surname" +msgstr "Nachname" + +#: views/html/users/create.tpl:12 views/html/users/create.tpl:13 +#: views/html/users/edit.tpl:12 views/html/users/edit.tpl:13 +#: views/html/users/register.tpl:84 views/html/users/register.tpl:85 +#: views/html/users/user.tpl:12 +msgid "E‑mail address" +msgstr "E‑Mail-Adresse" + +#: views/html/users/delete.tpl:2 views/html/users/user.tpl:5 +msgid "Delete user" +msgstr "Benutzer löschen" + +#: views/html/users/delete.tpl:4 +#, php-format +msgid "Should the user “%s” (%s) really be deleted?" +msgstr "Soll der Benutzer „%s“ (%s) wirklich gelöscht werden?" + +#: views/html/users/edit.tpl:2 views/html/users/user.tpl:4 +msgid "Edit user" +msgstr "Benutzer bearbeiten" + +#: views/html/users/index.tpl:3 +msgid "Create new user" +msgstr "Neuen Benutzer erstellen" + +#: views/html/users/index.tpl:7 views/html/users/user.tpl:10 +#, php-format +msgid "registered on %s" +msgstr "registriert am %s" + +#: views/html/users/login.tpl:5 +msgid "Login failed" +msgstr "Die Anmeldung war nicht korrekt" + +#: views/html/users/register.tpl:3 +msgid "Registration" +msgstr "Registrierung" + +#: views/html/users/register.tpl:14 +#, php-format +msgid "Username is too short (min. %d chars)" +msgstr "Der Benutzername ist zu kurz (min. %d Zeichen)" + +#: views/html/users/register.tpl:16 +#, php-format +msgid "Username is too long (max. %d chars)" +msgstr "Der Benutzername ist zu lang (max. %d Zeichen)" + +#: views/html/users/register.tpl:18 +msgid "Username contains illegal characters" +msgstr "Der Benutzername enthält ungültige Zeichen" + +#: views/html/users/register.tpl:20 +msgid "Username already exists" +msgstr "Der Benutzername existiert bereits" + +#: views/html/users/register.tpl:22 +msgid "Username invalid" +msgstr "Der Benutzername ist ungültig" + +#: views/html/users/register.tpl:27 +#, php-format +msgid "Prename is too short (min. %d chars)" +msgstr "Der Vorname ist zu kurz (min. %d Zeichen)" + +#: views/html/users/register.tpl:29 +#, php-format +msgid "Prename is too long (max. %d chars)" +msgstr "Der Vorname ist zu lang (max. %d Zeichen)" + +#: views/html/users/register.tpl:31 +#, php-format +msgid "Prename contains illegal characters" +msgstr "Der Vorname enthält ungültige Zeichen" + +#: views/html/users/register.tpl:33 +msgid "Prename invalid" +msgstr "Der Vorname ist ungültig" + +#: views/html/users/register.tpl:38 +#, php-format +msgid "Surname is too short (min. %d chars)" +msgstr "Der Nachname ist zu kurz (min. %d Zeichen)" + +#: views/html/users/register.tpl:40 +#, php-format +msgid "Surname is too long (max. %d chars)" +msgstr "Der Nachname ist zu lang (max. %d Zeichen)" + +#: views/html/users/register.tpl:42 +#, php-format +msgid "Surname contains illegal characters" +msgstr "Der Nachname enthält ungültige Zeichen" + +#: views/html/users/register.tpl:44 +msgid "Surname invalid" +msgstr "Der Nachname ist ungültig" + +#: views/html/users/register.tpl:49 views/html/users/register.tpl:53 +msgid "E‑mail address invalid" +msgstr "Die E‑Mail-Adresse ist ungültig" + +#: views/html/users/register.tpl:51 +msgid "E‑mail address already exists" +msgstr "E‑Mail-Adresse existiert bereits" + +#: views/html/users/register.tpl:58 +#, php-format +msgid "Password is too short (min. %d chars)" +msgstr "Das Passwort ist zu kurz (min. %d Zeichen)" + +#: views/html/users/register.tpl:60 +#, php-format +msgid "Password is too long (max. %d chars)" +msgstr "Das Passwort ist zu lang (max. %d Zeichen)" + +#: views/html/users/register.tpl:62 +msgid "Password invalid" +msgstr "Das Passwort ist ungültig" + +#: views/html/users/register.tpl:89 +msgid "Register" +msgstr "Registrieren" + +#: views/html/users/user.tpl:34 +msgid "Roles" +msgstr "Rollen" + +#~ msgid "Questname" +#~ msgstr "Questname" + +#, fuzzy +#~ msgid "achieved at %s" +#~ msgstr "erhalten am: %s" + +#~ msgid "Usergroups" +#~ msgstr "Benutzergruppen" + +#~ msgid "E‑Mail" +#~ msgstr "E‑Mail" + +#~ msgid "E‑Mail-Address" +#~ msgstr "E‑Mail-Adresse" + +#~ msgid "E‑mail address not valid" +#~ msgstr "Die E‑Mail-Adresse ist nicht gültig" + +#~ msgid "Username is too long" +#~ msgstr "Der Benutzername ist zu lang" + +#~ msgid "Registration failed: %s" +#~ msgstr "Registrierung fehlgeschlagen: %s" + +#~ msgid "Words" +#~ msgstr "Wörter" + +#~ msgid "Go on" +#~ msgstr "Hier geht es weiter" + +#, fuzzy +#~ msgid "Character groups" +#~ msgstr "Charaktergruppen" + +#~ msgid "locked" +#~ msgstr "gesperrt" + +#~ msgid "Group Leader" +#~ msgstr "Gruppenleiter" + +#~ msgid "as" +#~ msgstr "als" + +#~ msgid "containing optional Quests" +#~ msgstr "Enthaltene optionale Quests" + +#~ msgid "This Quest is optional" +#~ msgstr "Diese Quest ist optional" diff --git a/logs/empty b/logs/empty new file mode 100644 index 00000000..e69de29b diff --git a/media/empty b/media/empty new file mode 100644 index 00000000..e69de29b diff --git a/models/AchievementsModel.inc b/models/AchievementsModel.inc new file mode 100644 index 00000000..898a100f --- /dev/null +++ b/models/AchievementsModel.inc @@ -0,0 +1,571 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Achievements-tables. + * + * @author Oliver Hanraths + */ + class AchievementsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new AchievementsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get an Achievement by its URL. + * + * @param int $seminaryId ID of Seminary + * @param string $achievementUrl URL-title of Achievement + * @return array Achievement data + */ + public function getAchievementByUrl($seminaryId, $achievementUrl) + { + $data = $this->db->query( + 'SELECT achievements.id, achievementconditions.condition, title, url, description, progress, unachieved_achievementsmedia_id, achieved_achievementsmedia_id '. + 'FROM achievements '. + 'LEFT JOIN achievementconditions ON achievementconditions.id = achievements.achievementcondition_id '. + 'WHERE seminary_id = ? AND url = ?', + 'is', + $seminaryId, $achievementUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($achievementUrl); + } + + + return $data[0]; + } + + + /** + * Get all not yet achieved Achievements for a Seminary that can + * only be achieved once (only by one Character). + * + * @param int $seminaryId ID of Seminary + * @return array Achievements data + */ + public function getUnachievedOnlyOnceAchievementsForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT achievements.id, achievementconditions.condition, title, url, description, progress, hidden, only_once, all_conditions, deadline, unachieved_achievementsmedia_id, achieved_achievementsmedia_id '. + 'FROM achievements '. + 'LEFT JOIN achievementconditions ON achievementconditions.id = achievements.achievementcondition_id '. + 'WHERE achievements.seminary_id = ? AND only_once = 1 AND NOT EXISTS ('. + 'SELECT character_id '. + 'FROM achievements_characters '. + 'WHERE achievements_characters.achievement_id = achievements.id'. + ')', + 'i', + $seminaryId + ); + } + + + /** + * Get all Achievements that have a deadline. + * + * @param int $seminaryId ID of Seminary + * @return array Achievements data + */ + public function getDeadlineAchievements($seminaryId) + { + return $this->db->query( + 'SELECT achievements.id, achievementconditions.condition, title, url, description, progress, hidden, only_once, all_conditions, deadline, unachieved_achievementsmedia_id, achieved_achievementsmedia_id '. + 'FROM achievements '. + 'LEFT JOIN achievementconditions ON achievementconditions.id = achievements.achievementcondition_id '. + 'WHERE achievements.seminary_id = ? AND deadline IS NOT NULL', + 'i', + $seminaryId + ); + } + + + /** + * Get seldom Achievements. + * + * @param int $seminaryId ID of Seminary + * @param int $count Number of Achievements to retrieve + * @return array List of seldom Achievements + */ + public function getSeldomAchievements($seminaryId, $count, $alsoWithDeadline=true) + { + return $this->db->query( + 'SELECT id, title, url, description, progress, hidden, unachieved_achievementsmedia_id, achieved_achievementsmedia_id, count(DISTINCT character_id) AS c '. + 'FROM achievements_characters '. + 'LEFT JOIN achievements ON achievements.id = achievements_characters.achievement_id '. + 'WHERE achievements.seminary_id = ? AND only_once = 0 '. + (!$alsoWithDeadline ? 'AND achievements.deadline IS NULL ' : null). + 'GROUP BY achievement_id '. + 'ORDER BY count(DISTINCT character_id) ASC '. + 'LIMIT ?', + 'ii', + $seminaryId, + $count + ); + } + + + /** + * Get all achieved Achievements for a Character. + * + * @param int $characterId ID of Character + * @return array Achievements data + */ + public function getAchievedAchievementsForCharacter($characterId, $alsoWithDeadline=true) + { + return $this->db->query( + 'SELECT achievements.id, achievements_characters.created, achievements.title, achievements.url, achievements.description, achievements.progress, unachieved_achievementsmedia_id, achieved_achievementsmedia_id '. + 'FROM achievements '. + 'INNER JOIN achievements_characters ON achievements_characters.achievement_id = achievements.id '. + 'WHERE achievements_characters.character_id = ? '. + (!$alsoWithDeadline ? 'AND achievements.deadline IS NULL ' : null). + 'ORDER BY achievements_characters.created DESC', + 'i', + $characterId + ); + } + + + /** + * Get all not yet achieved Achievements for a Character. + * + * @param int $seminaryId ID of Seminary + * @param int $characterId ID of Character + * @return array Achievements data + */ + public function getUnachhievedAchievementsForCharacter($seminaryId, $characterId, $includeOnlyOnce=false, $alsoWithDeadline=true) + { + return $this->db->query( + 'SELECT achievements.id, achievementconditions.condition, title, url, description, progress, hidden, only_once, all_conditions, deadline, unachieved_achievementsmedia_id, achieved_achievementsmedia_id '. + 'FROM achievements '. + 'LEFT JOIN achievementconditions ON achievementconditions.id = achievements.achievementcondition_id '. + 'WHERE achievements.seminary_id = ? AND only_once <= ? AND NOT EXISTS ('. + 'SELECT character_id '. + 'FROM achievements_characters '. + 'WHERE '. + 'achievements_characters.achievement_id = achievements.id AND '. + 'achievements_characters.character_id = ?'. + ') '. + (!$alsoWithDeadline ? 'AND achievements.deadline IS NULL ' : null). + 'ORDER BY achievements.pos ASC', + 'iii', + $seminaryId, + $includeOnlyOnce, + $characterId + ); + } + + + /** + * Get the rank for the number of achieved Achievements. + * + * @param int $seminaryId ID of Seminary + * @param int $xps Amount of achieved Achievements + * @return int Rank of Achievements count + */ + public function getCountRank($seminaryId, $count) + { + $data = $this->db->query( + 'SELECT count(*) AS c '. + 'FROM ('. + 'SELECT count(DISTINCT achievement_id) '. + 'FROM achievements_characters '. + 'LEFT JOIN achievements ON achievements.id = achievements_characters.achievement_id '. + 'WHERE achievements.seminary_id = ? '. + 'GROUP BY character_id '. + 'HAVING count(DISTINCT achievement_id) > ?'. + ') AS ranking', + 'ii', + $seminaryId, + $count + ); + if(!empty($data)) { + return $data[0]['c'] + 1; + } + + + return 1; + } + + + /** + * Get all date conditions for an Achievement. + * + * @param int $achievementId ID of Achievement + * @return array Date conditions + */ + public function getAchievementConditionsDate($achievementId) + { + return $this->db->query( + 'SELECT `select` '. + 'FROM achievementconditions_date '. + 'WHERE achievement_id = ?', + 'i', + $achievementId + ); + } + + + /** + * Check a date condition. + * + * @param string $select SELECT-string with date-functions + * @return boolean Result + */ + public function checkAchievementConditionDate($select) + { + $data = $this->db->query( + 'SELECT ('.$select.') AS got ' + ); + if(!empty($data)) { + return ($data[0]['got'] == 1); + } + + + return false; + } + + + /** + * Get all Character conditions for an Achievement. + * + * @param int $achievementId ID of Achievement + * @return array Character conditions + */ + public function getAchievementConditionsCharacter($achievementId) + { + return $this->db->query( + 'SELECT field, value '. + 'FROM achievementconditions_character '. + 'WHERE achievement_id = ?', + 'i', + $achievementId + ); + } + + + /** + * Check a Character condition. + * + * @param string $field Field to check + * @param int $value The value the field has to match + * @param int $characterId ID of Character + * @return boolean Result + */ + public function checkAchievementConditionCharacter($field, $value, $characterId) + { + $data = $this->db->query( + "SELECT ($field >= $value) AS got ". + 'FROM v_characters '. + 'WHERE user_id = ?', + 'i', + $characterId + ); + if(!empty($data)) { + return ($data[0]['got'] == 1); + } + + + return false; + } + + + /** + * Get the progress for a Character condition. + * + * @param string $field Field to check + * @param int $value The value the field has to match + * @param int $characterId ID of Character + * @return float Percentage progress + */ + public function getAchievementConditionCharacterProgress($field, $value, $characterId) + { + $data = $this->db->query( + "SELECT $field AS field ". + 'FROM v_characters '. + 'WHERE user_id = ?', + 'i', + $characterId + ); + if(!empty($data)) { + return $data[0]['field'] / $value; + } + + + return 0; + } + + + /** + * Get all Quest conditions for an Achievement. + * + * @param int $achievementId ID of Achievement + * @return array Quest conditions + */ + public function getAchievementConditionsQuest($achievementId) + { + return $this->db->query( + 'SELECT field, `count`, value, quest_id, status, groupby '. + 'FROM achievementconditions_quest '. + 'WHERE achievement_id = ?', + 'i', + $achievementId + ); + } + + + /** + * Check a Quest condition. + * + * @param string $field Field to check + * @param boolean $count Conut field-value + * @param int $value The value the field has to match + * @param int $status Status of Quest or NULL + * @param string $groupby Field to group or NULL + * @param int $questId ID of related Quest or NULL + * @param int $characterId ID of Character + * @return boolean Result + */ + public function checkAchievementConditionQuest($field, $count, $value, $status, $groupby, $questId, $characterId) + { + $data = $this->db->query( + 'SELECT ('.( + $count + ? "count($field) >= $value" + : "$field = $value" + ). ') AS got '. + 'FROM quests_characters '. + 'WHERE '. + 'character_id = ?'. + (!is_null($questId) ? " AND quest_id = $questId" : ''). + (!is_null($status) ? " AND status = $status" : ''). + (!is_null($groupby) ? " GROUP BY $groupby" : ''), + 'i', + $characterId + ); + if(!empty($data)) { + foreach($data as &$datum) { + if($datum['got'] == 1) { + return true; + } + } + } + + + return false; + } + + + /** + * Get the progress for a Quest condition. + * + * @param string $field Field to check + * @param boolean $count Conut field-value + * @param int $value The value the field has to match + * @param int $status Status of Quest or NULL + * @param string $groupby Field to group or NULL + * @param int $questId ID of related Quest or NULL + * @param int $characterId ID of Character + * @return float Percentage progress + */ + public function getAchievementConditionQuestProgress($field, $count, $value, $status, $groupby, $questId, $characterId) + { + $data = $this->db->query( + 'SELECT '.( + $count + ? "count($field)" + : "$field" + ). ' AS field '. + 'FROM quests_characters '. + 'WHERE '. + 'character_id = ?'. + (!is_null($questId) ? " AND quest_id = $questId" : ''). + (!is_null($status) ? " AND status = $status" : ''). + (!is_null($groupby) ? " GROUP BY $groupby" : ''), + 'i', + $characterId + ); + if(!empty($data)) + { + $maxField = 0; + foreach($data as &$datum) { + $maxField = max($maxField, intval($datum['field'])); + } + + return $maxField / $value; + } + + + return 0; + } + + + /** + * Get all Metaachievement conditions for an Achievement. + * + * @param int $achievementId ID of Achievement + * @return array Metaachievement conditions + */ + public function getAchievementConditionsAchievement($achievementId) + { + return $this->db->query( + 'SELECT field, `count`, value, meta_achievement_id, groupby '. + 'FROM achievementconditions_achievement '. + 'WHERE achievement_id = ?', + 'i', + $achievementId + ); + } + + + /** + * Check a Metaachievement condition. + * + * @param string $field Field to check + * @param boolean $count Conut field-value + * @param int $value The value the field has to match + * @param string $groupby Field to group or NULL + * @param int $metaAchievementId ID of related Achievement or NULL + * @param int $characterId ID of Character + * @return boolean Result + */ + public function checkAchievementConditionAchievement($field, $count, $value, $groupby, $metaAchievementId, $characterId) + { + $data = $this->db->query( + 'SELECT ('.( + $count + ? "count($field) >= $value" + : "$field = $value" + ). ') AS got '. + 'FROM achievements_characters '. + 'WHERE '. + 'character_id = ?'. + (!is_null($metaAchievementId) ? " AND achievement_id = $metaAchievementId" : ''). + (!is_null($groupby) ? " GROUP BY $groupby" : ''), + 'i', + $characterId + ); + if(!empty($data)) { + foreach($data as &$datum) { + if($datum['got'] == 1) { + return true; + } + } + } + + + return false; + } + + + /** + * Get the progress for a Metaachievement condition. + * + * @param string $field Field to check + * @param boolean $count Conut field-value + * @param int $value The value the field has to match + * @param string $groupby Field to group or NULL + * @param int $metaAchievementId ID of related Achievement or NULL + * @param int $characterId ID of Character + * @return float Percentage progress + */ + public function getAchievementConditionAchievementProgress($field, $count, $value, $groupby, $metaAchievementId, $characterId) + { + $data = $this->db->query( + 'SELECT '.( + $count + ? "count($field)" + : "$field" + ). ' AS field '. + 'FROM achievements_characters '. + 'WHERE '. + 'character_id = ?'. + (!is_null($metaAchievementId) ? " AND achievement_id = $metaAchievementId" : ''). + (!is_null($groupby) ? " GROUP BY $groupby" : ''), + 'i', + $characterId + ); + if(!empty($data)) + { + $maxField = 0; + foreach($data as &$datum) { + $maxField = max($maxField, intval($datum['field'])); + } + + return $maxField / $value; + } + + + return 0; + } + + + /** + * Set an Achievement as achieved for a Character. + * + * @param int $achievementId ID of Achievement + * @param int $characterId ID of Character + */ + public function setAchievementAchieved($achievementId, $characterId) + { + $this->db->query( + 'INSERT INTO achievements_characters '. + '(achievement_id, character_id) '. + 'VALUES '. + '(?, ?)', + 'ii', + $achievementId, $characterId + ); + } + + + /** + * Check if a Character has achieved an Achievement. + * + * @param int $achievementId ID of Achievement + * @param int $characterId ID of Character + * @return boolean Whether Character has achieved the Achievement or not + */ + public function hasCharacterAchievedAchievement($achievementId, $characterId) + { + $data = $this->db->query( + 'SELECT achievement_id, character_id, created '. + 'FROM achievements_characters '. + 'WHERE achievement_id = ? AND character_id = ?', + 'ii', + $achievementId, $characterId + ); + if(!empty($data)) { + return $data[0]; + } + + + return false; + } + + } + +?> diff --git a/models/AvatarsModel.inc b/models/AvatarsModel.inc new file mode 100644 index 00000000..9c3d3701 --- /dev/null +++ b/models/AvatarsModel.inc @@ -0,0 +1,92 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Avatars-tables. + * + * @author Oliver Hanraths + */ + class AvatarsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new AvatarsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get an Avatar by its ID + * + * @param int $avatarId ID of Avatar + * @return array Avatar data + */ + public function getAvatarById($avatarId) + { + $data = $this->db->query( + 'SELECT id, charactertype_id, xplevel_id, avatarpicture_id, small_avatarpicture_id '. + 'FROM avatars '. + 'WHERE id = ?', + 'i', + $avatarId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get an Avatar by its Character type and XP-level. + * + * @param int $seminaryId ID of Seminary + * @param string $charactertypeUrl URL-title of Character type + * @param int $xplevel XP-level + * @return array Avatar data + */ + public function getAvatarByTypeAndLevel($seminaryId, $charactertypeUrl, $xplevel) + { + $data = $this->db->query( + 'SELECT avatars.id, charactertype_id, xplevel_id, avatarpicture_id, small_avatarpicture_id '. + 'FROM avatars '. + 'INNER JOIN charactertypes ON charactertypes.id = avatars.charactertype_id '. + 'INNER JOIN xplevels ON xplevels.id = avatars.xplevel_id AND xplevels.seminary_id = charactertypes.seminary_id '. + 'WHERE charactertypes.seminary_id = ? AND charactertypes.url = ? AND xplevels.level = ?', + 'isi', + $seminaryId, + $charactertypeUrl, + $xplevel + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($charactertypeUrl); + } + + + return $data[0]; + } + + } + +?> diff --git a/models/CharactergroupsModel.inc b/models/CharactergroupsModel.inc new file mode 100644 index 00000000..e042fd10 --- /dev/null +++ b/models/CharactergroupsModel.inc @@ -0,0 +1,197 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model of the CharactergroupsAgent to interact with + * Charactergroups-table. + * + * @author Oliver Hanraths + */ + class CharactergroupsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new CharactergroupsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get Character groups-groups of a Seminary. + * + * @param int $seminaryId ID of the corresponding Seminary + * @return array Character groups-groups data + */ + public function getGroupsroupsForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT id, name, url, preferred '. + 'FROM charactergroupsgroups '. + 'WHERE seminary_id = ?', + 'i', + $seminaryId + ); + } + + + /** + * Get a Character groups-group by its URL. + * + * @throws IdNotFoundException + * @param int $seminaryId ID of the corresponding Seminary + * @param string $groupsgroupUrl URL-name of the Character groups-group + * @return array Character groups-group data + */ + public function getGroupsgroupByUrl($seminaryId, $groupsgroupUrl) + { + $data = $this->db->query( + 'SELECT id, name, url, preferred '. + 'FROM charactergroupsgroups '. + 'WHERE seminary_id = ? AND url = ?', + 'is', + $seminaryId, $groupsgroupUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($groupsgroupUrl); + } + + + return $data[0]; + } + + + /** + * Get a Character groups-group by its ID. + * + * @throws IdNotFoundException + * @param string $groupsgroupId ID of the Character groups-group + * @return array Character groups-group data + */ + public function getGroupsgroupById($groupsgroupId) + { + $data = $this->db->query( + 'SELECT id, name, url, preferred '. + 'FROM charactergroupsgroups '. + 'WHERE id = ?', + 'i', + $groupsgroupId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($groupsgroupId); + } + + + return $data[0]; + } + + + /** + * Get Character groups for a Character groups-group. + * + * @param int $groupsgroupId ID of the Character groups-group + * @return array Character groups + */ + public function getGroupsForGroupsgroup($groupsgroupId) + { + return $this->db->query( + 'SELECT id, name, url, xps, motto '. + 'FROM v_charactergroups '. + 'WHERE charactergroupsgroup_id = ?', + 'i', + $groupsgroupId + ); + } + + + /** + * Get Character groups for a Character. + * + * @param int $characterId ID of the Character + * @return array Character groups + */ + public function getGroupsForCharacter($characterId) + { + return $this->db->query( + 'SELECT charactergroups.id, charactergroups.charactergroupsgroup_id, charactergroups.name, charactergroups.url, charactergroups.xps, charactergroupsgroups.id AS charactergroupsgroup_id, charactergroupsgroups.name AS charactergroupsgroup_name, charactergroupsgroups.url AS charactergroupsgroup_url '. + 'FROM characters_charactergroups '. + 'LEFT JOIN v_charactergroups AS charactergroups ON charactergroups.id = characters_charactergroups.charactergroup_id '. + 'LEFT JOIN charactergroupsgroups ON charactergroupsgroups.id = charactergroups.charactergroupsgroup_id '. + 'WHERE characters_charactergroups.character_id = ?', + 'i', + $characterId + ); + } + + + /** + * Get a Character group by its URL. + * + * @throws IdNotFoundException + * @param int $groupsgroupId ID of the Character groups-group + * @param string $groupUrl URL-name of the Character group + * @return array Character group data + */ + public function getGroupByUrl($groupsgroupId, $groupUrl) + { + $data = $this->db->query( + 'SELECT id, name, url, xps, motto '. + 'FROM v_charactergroups '. + 'WHERE charactergroupsgroup_id = ? AND url = ?', + 'is', + $groupsgroupId, $groupUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($groupUrl); + } + + + return $data[0]; + } + + + /** + * Get the rank of a XP-value of a Character. + * + * @param int $seminaryId ID of Seminary + * @param int $xps XP-value to get rank for + * @return int Rank of XP-value + */ + public function getXPRank($groupsgroupId, $xps) + { + $data = $this->db->query( + 'SELECT count(id) AS c '. + 'FROM v_charactergroups '. + 'WHERE charactergroupsgroup_id = ? AND xps > ?', + 'id', + $groupsgroupId, $xps + ); + if(!empty($data)) { + return $data[0]['c'] + 1; + } + + + return 1; + } + + } + +?> diff --git a/models/CharactergroupsquestsModel.inc b/models/CharactergroupsquestsModel.inc new file mode 100644 index 00000000..4490168d --- /dev/null +++ b/models/CharactergroupsquestsModel.inc @@ -0,0 +1,136 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model of the CharactergroupsquestsAgent to interact with + * Charactergroupsquests-table. + * + * @author Oliver Hanraths + */ + class CharactergroupsquestsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new CharactergroupsquestsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get Character groups Quests of a Character groups-groups. + * + * @param int $groupsgroupId ID of the Character groups-group + * @return array Character groups Quest data + */ + public function getQuestsForCharactergroupsgroup($groupsgroupId) + { + return $this->db->query( + 'SELECT id, questgroups_id, title, url, xps '. + 'FROM charactergroupsquests '. + 'WHERE charactergroupsgroup_id = ?', + 'i', + $groupsgroupId + ); + } + + + /** + * Get a Character groups Quest by its URL. + * + * @throws IdNotFoundException + * @param int $groupsgroupId ID of the Character groups-group + * @param string $questUrl URL-title of the Character groups Quest + * @return array Character groups Quest data + */ + public function getQuestByUrl($groupsgroupId, $questUrl) + { + $data = $this->db->query( + 'SELECT id, questgroups_id, title, url, description, xps, rules, won_text, lost_text, questsmedia_id '. + 'FROM charactergroupsquests '. + 'WHERE charactergroupsgroup_id = ? AND url = ?', + 'is', + $groupsgroupId, + $questUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questUrl); + } + + + return $data[0]; + } + + + /** + * Get the Character groups for a Quest. + * + * @param int $questId ID of the Character groups Quest + * @return array Character groups + */ + public function getGroupsForQuest($questId) + { + $groups = $this->db->query( + 'SELECT charactergroups.id, charactergroups.name, charactergroups.url, charactergroupsquests_groups.created, charactergroupsquests_groups.xps_factor, charactergroupsquests.xps '. + 'FROM charactergroupsquests_groups '. + 'LEFT JOIN charactergroups ON charactergroups.id = charactergroupsquests_groups.charactergroup_id '. + 'LEFT JOIN charactergroupsquests ON charactergroupsquests.id = charactergroupsquests_groups.charactergroupsquest_id '. + 'WHERE charactergroupsquests_groups.charactergroupsquest_id = ?', + 'i', + $questId + ); + foreach($groups as &$group) { + $group['xps'] = round($group['xps'] * $group['xps_factor'], 1); + } + + + return $groups; + } + + + /** + * Get Character groups Quests for a Character group. + * + * @param int $groupId ID of the Character group + * @return array Character groups Quests + */ + public function getQuestsForGroup($groupId) + { + $quests = $this->db->query( + 'SELECT charactergroupsquests.id, charactergroupsquests_groups.created, charactergroupsquests.title, charactergroupsquests.url, charactergroupsquests.xps, charactergroupsquests_groups.xps_factor '. + 'FROM charactergroupsquests_groups '. + 'LEFT JOIN charactergroupsquests ON charactergroupsquests.id = charactergroupsquests_groups.charactergroupsquest_id '. + 'WHERE charactergroupsquests_groups.charactergroup_id = ?', + 'i', + $groupId + ); + foreach($quests as &$quest) { + $quest['group_xps'] = round($quest['xps'] * $quest['xps_factor'], 1); + } + + + return $quests; + } + + + } + +?> diff --git a/models/CharacterrolesModel.inc b/models/CharacterrolesModel.inc new file mode 100644 index 00000000..752d4a0a --- /dev/null +++ b/models/CharacterrolesModel.inc @@ -0,0 +1,100 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with characterroles-table. + * + * @author Oliver Hanraths + */ + class CharacterrolesModel extends \hhu\z\Model + { + + + + + /** + * Construct a new CharacterrolesModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get all characterroles for a Character referenced by its ID. + * + * @param int $userId ID of an user + * @return array Characterroles for a Character + */ + public function getCharacterrolesForCharacterById($characterId) + { + return $this->db->query( + 'SELECT characterroles.id, characterroles.created, characterroles.name '. + 'FROM characters_characterroles '. + 'LEFT JOIN characterroles ON characterroles.id = characters_characterroles.characterrole_id '. + 'WHERE characters_characterroles.character_id = ?', + 'i', + $characterId + ); + } + + + /** + * Add a role to a Character. + * + * @param int $characterId ID of Character to add role to + * @param string $characterrole Role to add + */ + public function addCharacterroleToCharacter($characterId, $characterrole) + { + $this->db->query( + 'INSERT IGNORE INTO characters_characterroles '. + '(character_id, characterrole_id) '. + 'SELECT ?, id '. + 'FROM characterroles '. + 'WHERE name = ?', + 'is', + $characterId, + $characterrole + ); + } + + + /** + * Remove a role from a Character. + * + * @param int $characterId ID of Character to remove role from + * @param string $characterrole Role to remove + */ + public function removeCharacterroleFromCharacter($characterId, $characterrole) + { + $this->db->query( + 'DELETE FROM characters_characterroles '. + 'WHERE character_id = ? AND characterrole_id = ('. + 'SELECT id '. + 'FROM characterroles '. + 'WHERE name = ?'. + ')', + 'is', + $characterId, + $characterrole + ); + } + + } + +?> diff --git a/models/CharactersModel.inc b/models/CharactersModel.inc new file mode 100644 index 00000000..eb7c1531 --- /dev/null +++ b/models/CharactersModel.inc @@ -0,0 +1,519 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Characters-table. + * + * @author Oliver Hanraths + */ + class CharactersModel extends \hhu\z\Model + { + + + + + /** + * Construct a new CharactersModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get all characters for an user. + * + * @param int $userId ID of the user + * @return array Characters + */ + public function getCharactersForUser($userId) + { + return $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url, seminaries.id AS seminary_id, seminaries.url AS seminary_url, seminaries.title AS seminary_title, seminaries.url AS seminary_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'LEFT JOIN seminaries ON seminaries.id = charactertypes.seminary_id '. + 'WHERE user_id = ?', + 'i', + $userId + ); + } + + + /** + * Get Characters for a Seminary. + * + * @param int $seminaryId ID of the Seminary + * @return array Characters + */ + public function getCharactersForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url, seminaries.id AS seminary_url, seminaries.title AS seminary_title, seminaries.url AS seminary_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'LEFT JOIN seminaries ON seminaries.id = charactertypes.seminary_id '. + 'WHERE seminaries.id = ?', + 'i', + $seminaryId + ); + } + + + /** + * Get Characters for a Character group. + * + * @param int $groupId ID of the Character group + * @return array Characters + */ + public function getCharactersForGroup($groupId) + { + return $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN characters_charactergroups ON characters_charactergroups.character_id = characters.id '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'WHERE characters_charactergroups.charactergroup_id = ?', + 'i', + $groupId + ); + } + + + /** + * Get the character of a user for a Seminary. + * + * @throws IdNotFoundException + * @param int $userId ID of the user + * @param int $seminaryId ID of the Seminary + * @return array Character data + */ + public function getCharacterForUserAndSeminary($userId, $seminaryId) + { + $data = $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'WHERE characters.user_id = ? AND charactertypes.seminary_id = ?', + 'ii', + $userId, $seminaryId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($userId); + } + + + return $data[0]; + } + + + /** + * Get a Character by its Url. + * + * @throws IdNotFoundException + * @param int $seminaryId ID of the Seminary + * @param string $characterUrl URL-name of the Character + * @return array Character data + */ + public function getCharacterByUrl($seminaryId, $characterUrl) + { + $data = $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'WHERE charactertypes.seminary_id = ? AND characters.url = ?', + 'is', + $seminaryId, $characterUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($characterUrl); + } + + + return $data[0]; + } + + + /** + * Get a Character by its Id. + * + * @throws IdNotFoundException + * @param string $characterId ID of the Character + * @return array Character data + */ + public function getCharacterById($characterId) + { + $data = $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'WHERE characters.id = ?', + 'i', + $characterId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($characterUrl); + } + + + return $data[0]; + } + + + /** + * Get Characters with the most amount of Achievements. + * + * @param int $seminaryId ID of Seminary + * @param int $conut Amount of Characters to retrieve + * @return array List of Characters + */ + public function getCharactersWithMostAchievements($seminaryId, $count, $alsoWithDeadline=true) + { + return $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url, count(DISTINCT achievement_id) AS c '. + 'FROM achievements_characters '. + 'LEFT JOIN achievements ON achievements.id = achievements_characters.achievement_id '. + 'LEFT JOIN v_characters AS characters ON characters.id = achievements_characters.character_id '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'WHERE achievements.seminary_id = ? AND deadline IS NULL '. + (!$alsoWithDeadline ? 'AND achievements.deadline IS NULL ' : null). + 'GROUP BY character_id '. + 'ORDER BY count(DISTINCT achievement_id) DESC '. + 'LIMIT ?', + 'ii', + $seminaryId, + $count + ); + } + + + /** + * Calculate only XPs for a Character achieved through Quests. + * + * @param int $characterId ID of Character + * @return int Quest-XPs for Character + */ + public function getQuestXPsOfCharacter($characterId) + { + $data = $this->db->query( + 'SELECT quest_xps '. + 'FROM v_charactersxps '. + 'WHERE character_id = ?', + 'i', + $characterId + ); + if(!empty($data)) { + return $data[0]['quest_xps']; + } + + + return 0; + } + + + /** + * Get the XP-level of a Character. + * + * @param string $characterId ID of the Character + * @return array XP-level of Character + */ + public function getXPLevelOfCharacters($characterId) + { + $data = $this->db->query( + 'SELECT xplevels.xps, xplevels.level, xplevels.name '. + 'FROM v_charactersxplevels '. + 'INNER JOIN xplevels ON xplevels.id = v_charactersxplevels.xplevel_id '. + 'WHERE v_charactersxplevels.character_id = ?', + 'i', + $characterId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get the rank of a XP-value of a Character. + * + * @param int $seminaryId ID of Seminary + * @param int $xps XP-value to get rank for + * @return int Rank of XP-value + */ + public function getXPRank($seminaryId, $xps) + { + $data = $this->db->query( + 'SELECT count(characters.id) AS c '. + 'FROM charactertypes '. + 'INNER JOIN v_characters AS characters ON characters.charactertype_id = charactertypes.id '. + 'INNER JOIN characters_characterroles ON characters_characterroles.character_id = characters.id '. + 'INNER JOIN characterroles ON characterroles.id = characters_characterroles.characterrole_id AND characterroles.name = ? '. + 'WHERE seminary_id = ? AND characters.xps > ?', + 'sid', + 'user', + $seminaryId, $xps + ); + if(!empty($data)) { + return $data[0]['c'] + 1; + } + + + return 1; + } + + + /** + * Get the superior $count Characters in the ranking. + * + * @param int $seminaryId ID of Seminary + * @param int $xps XP-value of Character + * @param int $count Count of Characters to determine + * @return array List of superior Characters + */ + public function getSuperiorCharacters($seminaryId, $xps, $count) + { + return $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'INNER JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'INNER JOIN characters_characterroles ON characters_characterroles.character_id = characters.id '. + 'INNER JOIN characterroles ON characterroles.id = characters_characterroles.characterrole_id AND characterroles.name = ? '. + 'WHERE charactertypes.seminary_id = ? AND characters.xps > ? '. + 'ORDER BY characters.xps ASC '. + 'LIMIT ?', + 'sidd', + 'user', + $seminaryId, $xps, $count + ); + } + + + /** + * Get the inferior $count Characters in the ranking. + * + * @param int $seminaryId ID of Seminary + * @param int $xps XP-value of Character + * @param int $count Count of Characters to determine + * @return array List of inferior Characters + */ + public function getInferiorCharacters($seminaryId, $xps, $count) + { + return $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'INNER JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'INNER JOIN characters_characterroles ON characters_characterroles.character_id = characters.id '. + 'INNER JOIN characterroles ON characterroles.id = characters_characterroles.characterrole_id AND characterroles.name = ? '. + 'WHERE charactertypes.seminary_id = ? AND characters.xps < ? '. + 'ORDER BY characters.xps DESC '. + 'LIMIT ?', + 'sidd', + 'user', + $seminaryId, $xps, $count + ); + } + + + /** + * Get Characters that solved a Quest. + * + * @param int $questId ID of Quest to get Characters for + * @return array Characters data + */ + public function getCharactersSolvedQuest($questId) + { + return $data = $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'WHERE EXISTS ('. + 'SELECT character_id FROM quests_characters WHERE character_id = characters.id AND quest_id = ? AND status = ?'. + ')', + 'ii', + $questId, + QuestsModel::QUEST_STATUS_SOLVED + ); + } + + + /** + * Get Characters that did not solv a Quest. + * + * @param int $questId ID of Quest to get Characters for + * @return array Characters data + */ + public function getCharactersUnsolvedQuest($questId) + { + return $data = $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'WHERE EXISTS ('. + 'SELECT character_id FROM quests_characters WHERE character_id = characters.id AND quest_id = ? AND status = ?'. + ') AND NOT EXISTS ('. + 'SELECT character_id FROM quests_characters WHERE character_id = characters.id AND quest_id = ? AND status = ?'. + ')', + 'iiii', + $questId, QuestsModel::QUEST_STATUS_UNSOLVED, + $questId, QuestsModel::QUEST_STATUS_SOLVED + ); + } + + + /** + * Get Characters that sent a submission for a Quest. + * + * @param int $questId ID of Quest to get Characters for + * @return array Characters data + */ + public function getCharactersSubmittedQuest($questId) + { + return $data = $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'WHERE ('. + 'SELECT status '. + 'FROM quests_characters '. + 'WHERE quest_id = ? AND character_id = characters.id '. + 'ORDER BY created DESC '. + 'LIMIT 1'. + ') = ?', + 'ii', + $questId, QuestsModel::QUEST_STATUS_SUBMITTED + ); + } + + + public function getXPLevelsForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT id, xps, level, name '. + 'FROM xplevels '. + 'WHERE seminary_id = ? '. + 'ORDER BY level ASC', + 'i', + $seminaryId + ); + } + + + /** + * Check if a Character name already exists. + * + * @param string $name Character name to check + * @return boolean Whether Character name exists or not + */ + public function characterNameExists($name) + { + $data = $this->db->query( + 'SELECT count(id) AS c '. + 'FROM characters '. + 'WHERE name = ? OR url = ?', + 'ss', + $name, + \nre\core\Linker::createLinkParam($name) + ); + if(!empty($data)) { + return ($data[0]['c'] > 0); + } + + + return false; + } + + + /** + * Create a new Character. + * + * @param int $userId User-ID that creates the new character + * @param int $charactertypeId ID of type of new Character + * @param string $characterName Name for the new Character + * @return int ID of Character + */ + public function createCharacter($userId, $charactertypeId, $characterName) + { + $this->db->query( + 'INSERT INTO characters '. + '(user_id, charactertype_id, name, url) '. + 'VALUES '. + '(?, ?, ?, ?)', + 'iiss', + $userId, + $charactertypeId, + $characterName, + \nre\core\Linker::createLinkParam($characterName) + ); + + + return $this->db->getInsertId(); + } + + + /** + * Set the value of a Seminary field for a Character. + * + * @param int $characterId ID of Character + * @param int $seminarycharacterfieldId ID of seminarycharacterfield to set value of + * @param string $value Value to set + */ + public function setSeminaryFieldOfCharacter($characterId, $seminarycharacterfieldId, $value) + { + $this->db->query( + 'INSERT INTO characters_seminarycharacterfields '. + '(character_id, seminarycharacterfield_id, value) '. + 'VALUES '. + '(?, ?, ?) '. + 'ON DUPLICATE KEY UPDATE '. + 'value = ?', + 'iiss', + $characterId, + $seminarycharacterfieldId, + $value, + $value + ); + } + + + /** + * Get Characters with the given Character role. + * + * @param int $seminaryId ID of Seminary + * @param string $characterrole Character role + * @return array List of users + */ + public function getCharactersWithCharacterRole($seminaryId, $characterrole) + { + return $this->db->query( + 'SELECT characters.id, characters.created, characters.charactertype_id, characters.name, characters.url, characters.user_id, characters.xps, characters.xplevel, characters.avatar_id, charactertypes.name AS charactertype_name, charactertypes.url AS charactertype_url, seminaries.id AS seminary_url, seminaries.title AS seminary_title, seminaries.url AS seminary_url '. + 'FROM v_characters AS characters '. + 'LEFT JOIN charactertypes ON charactertypes.id = characters.charactertype_id '. + 'LEFT JOIN seminaries ON seminaries.id = charactertypes.seminary_id '. + 'LEFT JOIN characters_characterroles ON characters_characterroles.character_id = characters.id '. + 'LEFT JOIN characterroles ON characterroles.id = characters_characterroles.characterrole_id '. + 'WHERE seminaries.id = ? AND characterroles.name = ?', + 'is', + $seminaryId, $characterrole + ); + } + + } + +?> diff --git a/models/CharactertypesModel.inc b/models/CharactertypesModel.inc new file mode 100644 index 00000000..07c8d5e9 --- /dev/null +++ b/models/CharactertypesModel.inc @@ -0,0 +1,57 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Charactertypes-table. + * + * @author Oliver Hanraths + */ + class CharactertypesModel extends \hhu\z\Model + { + + + + + /** + * Construct a new CharactertypesModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get all Character types of a Seminary. + * + * @param int $seminaryId ID of Seminary to get types of + * @return array Character types + */ + public function getCharacterTypesForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT id, name, url '. + 'FROM charactertypes '. + 'WHERE seminary_id = ? '. + 'ORDER BY name ASC', + 'i', + $seminaryId + ); + } + + } + +?> diff --git a/models/DatabaseModel.inc b/models/DatabaseModel.inc new file mode 100644 index 00000000..51259f4f --- /dev/null +++ b/models/DatabaseModel.inc @@ -0,0 +1,83 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\models; + + + /** + * Default implementation of a database model. + * + * @author coderkun + */ + class DatabaseModel extends \nre\core\Model + { + /** + * Database connection + * + * @static + * @var DatabaseDriver + */ + protected $db = NULL; + + + + + /** + * Construct a new datamase model. + * + * @throws DatamodelException + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @param string $type Database type + * @param array $config Connection settings + */ + function __construct($type, $config) + { + parent::__construct(); + + // Load database driver + $this->loadDriver($type); + + // Establish database connection + $this->connect($type, $config); + } + + + + + /** + * Load the database driver. + * + * @throws DriverNotFoundException + * @throws DriverNotValidException + * @param string $driverName Name of the database driver + */ + private function loadDriver($driverName) + { + \nre\core\Driver::load($driverName); + } + + + /** + * Establish a connection to the database. + * + * @throws DatamodelException + * @param string $driverName Name of the database driver + * @param array $config Connection settings + */ + private function connect($driverName, $config) + { + $this->db = \nre\core\Driver::factory($driverName, $config); + } + + } + +?> diff --git a/models/MediaModel.inc b/models/MediaModel.inc new file mode 100644 index 00000000..031df753 --- /dev/null +++ b/models/MediaModel.inc @@ -0,0 +1,195 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with the Media-tables. + * + * @author Oliver Hanraths + */ + class MediaModel extends \hhu\z\Model + { + + + + + /** + * Construct a new MediaModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get a medium by its URL. + * + * @throws IdNotFoundException + * @param string $mediaURL URL-name of the Medium + * @return array Medium data + */ + public function getMediaByUrl($mediaUrl) + { + $data = $this->db->query( + 'SELECT id, name, url, description, mimetype '. + 'FROM media '. + 'WHERE url = ?', + 's', + $mediaUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($mediaUrl); + } + + + return $data[0]; + } + + + /** + * Get a medium by its ID. + * + * @throws IdNotFoundException + * @param int $mediaId ID of the Medium + * @return array Medium data + */ + public function getMediaById($mediaId) + { + $data = $this->db->query( + 'SELECT id, name, url, description, mimetype '. + 'FROM media '. + 'WHERE id = ?', + 'i', + $mediaId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($mediaId); + } + + + return $data[0]; + } + + + /** + * Get a Seminary medium by its URL. + * + * @throws IdNotFoundException + * @param int $seminaryId ID of the seminary + * @param string $seminaryMediaUrl URL-name of the Seminary medium + * @return array Seminary medium data + */ + public function getSeminaryMediaByUrl($seminaryId, $seminaryMediaUrl) + { + $data = $this->db->query( + 'SELECT id, name, url, description, mimetype '. + 'FROM seminarymedia '. + 'WHERE url = ?', + 's', + $seminaryMediaUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($seminaryMediaUrl); + } + + + return $data[0]; + } + + + /** + * Get a Seminary medium by its ID. + * + * @throws IdNotFoundException + * @param int $seminaryMediaId ID of the Seminary medium + * @return array Seminary medium data + */ + public function getSeminaryMediaById($mediaId) + { + $data = $this->db->query( + 'SELECT id, name, url, description, mimetype '. + 'FROM seminarymedia '. + 'WHERE id = ?', + 'i', + $mediaId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($mediaId); + } + + + return $data[0]; + } + + + /** + * Create a new Questsmedia by creating a new Seminarymedia and + * adding it to the list of Questsmedia. + * TODO Currently only temporary for easier data import. + */ + public function createQuestMedia($userId, $seminaryId, $filename, $description, $mimetype, $tmpFilename) + { + $uploadId = false; + $this->db->setAutocommit(false); + + try { + // Create database record + $this->db->query( + 'INSERT INTO seminarymedia '. + '(created_user_id, seminary_id, name, url, description, mimetype) '. + 'VALUES '. + '(?, ? ,? ,?, ?, ?)', + 'iissss', + $userId, + $seminaryId, + $filename, + \nre\core\Linker::createLinkParam($filename), + $description, + $mimetype + ); + $uploadId = $this->db->getInsertId(); + + $this->db->query( + 'INSERT INTO questsmedia '. + '(media_id, created_user_id) '. + 'VALUES '. + '(?, ?)', + 'ii', + $uploadId, + $userId + ); + + // Create filename + $filename = ROOT.DS.'seminarymedia'.DS.$uploadId; + if(!move_uploaded_file($tmpFilename, $filename)) + { + $this->db->rollback(); + $uploadId = false; + } + } + catch(\nre\exceptions\DatamodelException $e) { + $this->db->rollback(); + $this->db->setAutocommit(true); + } + + + $this->db->setAutocommit(true); + return $uploadId; + } + + } + +?> diff --git a/models/QuestgroupsModel.inc b/models/QuestgroupsModel.inc new file mode 100644 index 00000000..f3cd0949 --- /dev/null +++ b/models/QuestgroupsModel.inc @@ -0,0 +1,662 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Questgroups-table. + * + * @author Oliver Hanraths + */ + class QuestgroupsModel extends \hhu\z\Model + { + /** + * Required models + * + * @var array + */ + public $models = array('questgroupshierarchy', 'quests', 'questtexts'); + + + + + /** + * Construct a new QuestgroupsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get all Questgroups for a Questgroup hierarchy. + * + * @param int $hierarchyId ID of the Questgroup hierarchy to get Questgroups for + * @param int $parentQuestgroupId ID of the parent Questgroup hierarchy + * @return array Questgroups for the given hierarchy + */ + public function getQuestgroupsForHierarchy($hierarchyId, $parentQuestgroupId=null) + { + // Get Questgroups + $questgroups = array(); + if(is_null($parentQuestgroupId)) + { + $questgroups = $this->db->query( + 'SELECT questgroups.id, questgroups_questgroupshierarchy.questgroupshierarchy_id, questgroups_questgroupshierarchy.pos, questgroups.title, questgroups.url, questgroups.questgroupspicture_id '. + 'FROM questgroups_questgroupshierarchy '. + 'INNER JOIN questgroups ON questgroups.id = questgroups_questgroupshierarchy.questgroup_id '. + 'WHERE questgroups_questgroupshierarchy.questgroupshierarchy_id = ? AND questgroups_questgroupshierarchy.parent_questgroup_id IS NULL '. + 'ORDER BY questgroups_questgroupshierarchy.pos ASC', + 'i', + $hierarchyId + ); + } + else + { + $questgroups = $this->db->query( + 'SELECT questgroups.id, questgroups_questgroupshierarchy.questgroupshierarchy_id, questgroups_questgroupshierarchy.pos, questgroups.title, questgroups.url, questgroups.questgroupspicture_id '. + 'FROM questgroups_questgroupshierarchy '. + 'INNER JOIN questgroups ON questgroups.id = questgroups_questgroupshierarchy.questgroup_id '. + 'WHERE questgroups_questgroupshierarchy.questgroupshierarchy_id = ? AND questgroups_questgroupshierarchy.parent_questgroup_id = ? '. + 'ORDER BY questgroups_questgroupshierarchy.pos ASC', + 'ii', + $hierarchyId, $parentQuestgroupId + ); + } + + + // Return Questgroups + return $questgroups; + } + + + /** + * Get all Questgroups for a Seminary. + * + * @param int $seminaryId ID of Seminary + * @return array List of Questgroups + */ + public function getQuestgroupsForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT id, title, url '. + 'FROM questgroups '. + 'WHERE seminary_id = ? '. + 'ORDER BY title ASC', + 'i', + $seminaryId + ); + } + + + /** + * Get a Questgroup by its ID. + * + * @throws IdNotFoundException + * @param int $questgroupId ID of a Questgroup + * @return array Questgroup data + */ + public function getQuestgroupById($questgroupId) + { + $data = $this->db->query( + 'SELECT id, title, url, questgroupspicture_id '. + 'FROM questgroups '. + 'WHERE questgroups.id = ?', + 'i', + $questgroupId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questgroupId); + } + + + return $data[0]; + } + + + /** + * Get a Questgroup by its URL. + * + * @throws IdNotFoundException + * @param int $seminaryId ID of the corresponding seminary + * @param string $questgroupURL URL-title of a Questgroup + * @return array Questgroup data + */ + public function getQuestgroupByUrl($seminaryId, $questgroupUrl) + { + $data = $this->db->query( + 'SELECT id, title, url, questgroupspicture_id '. + 'FROM questgroups '. + 'WHERE seminary_id = ? AND url = ?', + 'is', + $seminaryId, $questgroupUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questgroupUrl); + } + + + return $data[0]; + } + + + /** + * Get texts of a Questgroup. + * + * @param int $questgroupId ID of a Questgroup + * @return array Texts of this Questgroup + */ + public function getQuestgroupTexts($questgroupId) + { + return $this->db->query( + 'SELECT id, pos, text '. + 'FROM questgrouptexts '. + 'WHERE questgroup_id = ? '. + 'ORDER BY pos ASC', + 'i', + $questgroupId + ); + } + + + /** + * Get first texts of a Questgroup. + * + * @param int $questgroupId ID of a Questgroup + * @return array First Text of this Questgroup + */ + public function getFirstQuestgroupText($questgroupId) + { + $data = $this->db->query( + 'SELECT id, pos, text '. + 'FROM questgrouptexts '. + 'WHERE questgroup_id = ? '. + 'ORDER BY pos ASC '. + 'LIMIT 1', + 'i', + $questgroupId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get the next Questgroup. + * + * Determine the next Questgroup. If there is no next Questgroup + * on the same level as the given Quest then the followed-up + * Questgroup from a higher hierarchy level is returned. + * + * @param int $questgroupId ID of Questgroup to get next Questgroup of + * @return array Questgroup data + */ + public function getNextQuestgroup($questgroupId) + { + $currentQuestgroup = $this->getQuestgroupById($questgroupId); + $currentQuestgroup['hierarchy'] = $this->Questgroupshierarchy->getHierarchyForQuestgroup($currentQuestgroup['id']); + if(empty($currentQuestgroup['hierarchy'])) { + return null; + } + + $nextQuestgroup = $this->_getNextQuestgroup($currentQuestgroup['hierarchy']['parent_questgroup_id'], $currentQuestgroup['hierarchy']['questgroup_pos']); + if(is_null($nextQuestgroup) && !is_null($currentQuestgroup['hierarchy']['parent_questgroup_id'])) { + $nextQuestgroup = $this->getNextQuestgroup($currentQuestgroup['hierarchy']['parent_questgroup_id']); + } + + + return $nextQuestgroup; + } + + + /** + * Get the previous Questgroup. + * + * Determine the previous Questgroup. If there is no previous + * Questgroup on the same level as the given Quest then the + * followed-up Questgroup from a higher hierarchy level is + * returned. + * + * @param int $questgroupId ID of Questgroup to get previous Questgroup of + * @return array Questgroup data + */ + public function getPreviousQuestgroup($questgroupId) + { + $currentQuestgroup = $this->getQuestgroupById($questgroupId); + $currentQuestgroup['hierarchy'] = $this->Questgroupshierarchy->getHierarchyForQuestgroup($currentQuestgroup['id']); + if(empty($currentQuestgroup['hierarchy'])) { + return null; + } + + $previousQuestgroup = $this->_getPreviousQuestgroup($currentQuestgroup['hierarchy']['parent_questgroup_id'], $currentQuestgroup['hierarchy']['questgroup_pos']); + if(is_null($previousQuestgroup) && !is_null($currentQuestgroup['hierarchy']['parent_questgroup_id'])) { + $previousQuestgroup = $this->getPreviousQuestgroup($currentQuestgroup['hierarchy']['parent_questgroup_id']); + } + + + return $previousQuestgroup; + } + + + /** + * Determine if the given Character has solved the Quests form + * this Questgroup. + * + * @param int $questgroupId ID of Questgroup to check + * @param int $characterId ID of Character to check + * @result boolean Whether Character has solved the Questgroup or not + */ + public function hasCharacterSolvedQuestgroup($questgroupId, $characterId) + { + // Get data of Questgroup + $questgroup = $this->getQuestgroupById($questgroupId); + + // Chack all Quests + $currentQuest = $this->Quests->getFirstQuestOfQuestgroup($questgroup['id']); + if(!is_null($currentQuest)) + { + if(!$this->Quests->hasCharacterSolvedQuest($currentQuest['id'], $characterId)) { + return false; + } + + // Get next Quests + $nextQuests = !is_null($currentQuest) ? $this->Quests->getNextQuests($currentQuest['id']) : null; + while(!is_null($currentQuest) && !empty($nextQuests)) + { + // Get choosed Quest + $currentQuest = null; + foreach($nextQuests as &$nextQuest) { + if($this->Quests->hasCharacterEnteredQuest($nextQuest['id'], $characterId)) { + $currentQuest = $nextQuest; + } + } + + // Check Quest + if(is_null($currentQuest)) { + return false; + } + + // Check status + if(!$this->Quests->hasCharacterSolvedQuest($currentQuest['id'], $characterId)) { + return false; + } + + $nextQuests = !is_null($currentQuest) ? $this->Quests->getNextQuests($currentQuest['id']) : null; + } + } + + // Check all child Questgroups + $questgroup['hierarchy'] = $this->Questgroupshierarchy->getHierarchyForQuestgroup($questgroup['id']); + if(!empty($questgroup['hierarchy'])) + { + $childQuestgroupshierarchy = $this->Questgroupshierarchy->getChildQuestgroupshierarchy($questgroup['hierarchy']['id']); + foreach($childQuestgroupshierarchy as &$hierarchy) + { + // Get Questgroups + $questgroups = $this->getQuestgroupsForHierarchy($hierarchy['id'], $questgroup['id']); + foreach($questgroups as &$group) { + if(!$this->hasCharacterSolvedQuestgroup($group['id'], $characterId)) { + return false; + } + } + } + } + + + return true; + } + + + /** + * Get all related Questgroups of a Questtext. + * + * @param int $questtextId ID of the questtext + * @return array Sidequests for the questtext + */ + public function getRelatedQuestsgroupsOfQuesttext($questtextId) + { + return $this->db->query( + 'SELECT questgroups.id, questgroups_questtexts.questtext_id, questgroups.title, questgroups.url, questgroups_questtexts.entry_text '. + 'FROM questgroups_questtexts '. + 'INNER JOIN questgroups ON questgroups.id = questgroups_questtexts.questgroup_id '. + 'WHERE questgroups_questtexts.questtext_id = ?', + 'i', + $questtextId + ); + } + + + /** + * Get all related Questgroups of a Quest. + * + * @param int $questId ID of the quest + * @return array Sidequests for the quest + */ + public function getRelatedQuestsgroupsOfQuest($questId) + { + return $this->db->query( + 'SELECT questgroups_questtexts.questgroup_id AS id '. + 'FROM quests '. + 'INNER JOIN questtexts ON questtexts.quest_id = quests.id '. + 'INNER JOIN questgroups_questtexts ON questgroups_questtexts.questtext_id = questtexts.id '. + 'WHERE quests.id = ?', + 'i', + $questId + ); + + } + + + /** + * Calculate cumulated data for a Questgroup, its + * sub-Questgroups and all its Quests. + * + * @param int $questgroupId ID of Questgroup + * @param int $characterId ID of Character + * @param array $calculatedQuests IDs of already calculated Quests + * @return array Cumulated data for Questgroup + */ + public function getCumulatedDataForQuestgroup($questgroupId, $characterId=null, &$calculatedQuests=array()) + { + // Cumulated data + $data = array( + 'xps' => 0, + 'character_xps' => 0 + ); + + // Current Questgroup + $questgroup = $this->getQuestgroupById($questgroupId); + + // Quests of current Questgroup + $quest = $this->Quests->getFirstQuestOfQuestgroup($questgroup['id']); + if(!is_null($quest)) + { + $questData = $this->getCumulatedDataForQuest($quest, $characterId, $calculatedQuests); + $data['xps'] += $questData['xps']; + $data['character_xps'] += $questData['character_xps']; + } + + // XPs of child Questgroups + $questgroupHierarchy = $this->Questgroupshierarchy->getHierarchyForQuestgroup($questgroup['id']); + if(!empty($questgroupHierarchy)) + { + $childQuestgroupshierarchy = $this->Questgroupshierarchy->getChildQuestgroupshierarchy($questgroupHierarchy['id']); + foreach($childQuestgroupshierarchy as &$hierarchy) + { + $questgroups = $this->getQuestgroupsForHierarchy($hierarchy['id'], $questgroup['id']); + foreach($questgroups as &$questgroup) + { + $childData = $this->getCumulatedDataForQuestgroup($questgroup['id'], $characterId, $calculatedQuests); + $data['xps'] += $childData['xps']; + $data['character_xps'] += $childData['character_xps']; + } + } + } + + + // Return cumulated data + return $data; + } + + + /** + * Calculate cumulated data of the given Quest, its following + * Quests and its related Questgroups. + * + * @param array $quest Quest data + * @param int $characterId ID of Character + * @param array $calculatedQuests IDs of already calculated Quests + * @return array Cumulated data for Quest + */ + public function getCumulatedDataForQuest($quest, $characterId=null, &$calculatedQuests=array()) + { + // Cumulated data + $data = array( + 'xps' => $quest['xps'], + 'character_xps' => (!is_null($characterId) && $this->Quests->hasCharacterSolvedQuest($quest['id'], $characterId)) ? $quest['xps'] : 0 + ); + + // Related Questgroups + $relatedQuestgroups = $this->getRelatedQuestsgroupsOfQuest($quest['id']); + foreach($relatedQuestgroups as &$relatedQuestgroup) + { + $relatedData = $this->getCumulatedDataForQuestgroup($relatedQuestgroup['id'], $characterId, $calculatedQuests); + $data['xps'] += $relatedData['xps']; + $data['character_xps'] += $relatedData['character_xps']; + } + + // Next Quests + $nextQuests = $this->Quests->getNextQuests($quest['id']); + $allNextData = array( + 'xps' => array(0), + 'character_xps' => array(0), + ); + foreach($nextQuests as &$nextQuest) + { + if(!in_array($nextQuest['id'], $calculatedQuests)) + { + $nextData = $this->getCumulatedDataForQuest($nextQuest, $characterId, $calculatedQuests); + $allNextData['xps'][] = $nextData['xps']; + $allNextData['character_xps'][] = $nextData['character_xps']; + $calculatedQuests[] = $nextQuest['id']; + } + } + $data['xps'] += max($allNextData['xps']); + $data['character_xps'] += max($allNextData['character_xps']); + + + // Return cumulated data + return $data; + } + + + /** + * Summarize XPs of all Quests for a Questgroup and its + * sub-Questgroups solved by a Character. + * + * @param int $questgroupId ID of Questgroup + * @param int $characterId ID of Character + * @return int Sum of XPs + */ + public function getAchievedXPsForQuestgroup($questgroupId, $characterId) + { + // Sum of XPs + $xps = 0; + + // Current Questgroup + $questgroup = $this->getQuestgroupById($questgroupId); + + // Quests of current Questgroup + $quest = $this->Quests->getFirstQuestOfQuestgroup($questgroup['id']); + if(!is_null($quest)) { + $xps += $this->getAchievedXPsForQuest($quest, $characterId); + } + + // XPs of child Questgroups + $questgroupHierarchy = $this->Questgroupshierarchy->getHierarchyForQuestgroup($questgroup['id']); + if(empty($questgroupHierarchy)) { + return $xps; + } + $childQuestgroupshierarchy = $this->Questgroupshierarchy->getChildQuestgroupshierarchy($questgroupHierarchy['id']); + foreach($childQuestgroupshierarchy as &$hierarchy) + { + $questgroups = $this->getQuestgroupsForHierarchy($hierarchy['id'], $questgroup['id']); + foreach($questgroups as &$questgroup) { + $xps += $this->getAchievedXPsForQuestgroup($questgroup['id'], $characterId); + } + } + + + // Return summarized XPs + return $xps; + } + + + /** + * Summarize XPs of the given Quest, its following Quests and + * its related Questgroups solved by a Character. + * + * @param int $quest Quest to summarize XPs for + * @param int $characterId ID of Character + * @return int Sum of XPs + */ + public function getAchievedXPsForQuest($quest, $characterId) + { + $xps = 0; + + // XPs for the given Quest + if($this->Quests->hasCharacterSolvedQuest($quest['id'], $characterId)) + { + $xps += $quest['xps']; + + // Next Quests + $nextQuests = $this->Quests->getNextQuests($quest['id']); + foreach($nextQuests as &$nextQuest) + { + if($this->Quests->hasCharacterEnteredQuest($nextQuest['id'], $characterId)) + { + $xps += $this->getAchievedXPsForQuest($nextQuest, $characterId); + break; + } + } + } + + // Related Questgroups + $relatedQuestgroups = $this->getRelatedQuestsgroupsOfQuest($quest['id']); + foreach($relatedQuestgroups as &$relatedQuestgroup) { + $xps += $this->getAchievedXPsForQuestgroup($relatedQuestgroup['id'], $characterId); + } + + + // Return summarized XPs + return $xps; + } + + + /** + * Create a new Questgroup. + * + * @param int $userId User-ID that creates the new character + * @param int $seminaryId ID of Seminary + * @param string $title Title for new Questgroup + * @return int ID of new Questgroup + */ + public function createQuestgroup($userId, $seminaryId, $title) + { + $this->db->query( + 'INSERT INTO questgroups '. + '(created_user_id, seminary_id, title, url) '. + 'VALUES '. + '(?, ?, ?, ?)', + 'iiss', + $userId, + $seminaryId, + $title, + \nre\core\Linker::createLinkParam($title) + ); + + + return $this->db->getInsertId(); + } + + + + + /** + * Get the next (direct) Questgroup. + * + * @param int $parentQuestgroupId ID of parent Questgroup to get next Questgroup of + * @param int $questgroupPos Position of Questgroup to get next Questgroup of + * @return array Data of next Questgroup or NULL + */ + private function _getNextQuestgroup($parentQuestgroupId, $questgroupPos) + { + if(!is_null($parentQuestgroupId)) + { + $data = $this->db->query( + 'SELECT * '. + 'FROM questgroups_questgroupshierarchy '. + 'INNER JOIN questgroups ON questgroups.id = questgroups_questgroupshierarchy.questgroup_id '. + 'WHERE parent_questgroup_id = ? AND pos = ? + 1', + 'ii', + $parentQuestgroupId, $questgroupPos + ); + } + else + { + $data = $this->db->query( + 'SELECT * '. + 'FROM questgroups_questgroupshierarchy '. + 'INNER JOIN questgroups ON questgroups.id = questgroups_questgroupshierarchy.questgroup_id '. + 'WHERE parent_questgroup_id IS NULL AND pos = ? + 1', + 'i', + $questgroupPos + ); + } + if(empty($data)) { + return null; + } + + + return $data[0]; + } + + + /** + * Get the previous (direct) Questgroup. + * + * @param int $parentQuestgroupId ID of parent Questgroup to get previous Questgroup of + * @param int $questgroupPos Position of Questgroup to get previous Questgroup of + * @return array Data of previous Questgroup or NULL + */ + private function _getPreviousQuestgroup($parentQuestgroupId, $questgroupPos) + { + if(!is_null($parentQuestgroupId)) + { + $data = $this->db->query( + 'SELECT * '. + 'FROM questgroups_questgroupshierarchy '. + 'INNER JOIN questgroups ON questgroups.id = questgroups_questgroupshierarchy.questgroup_id '. + 'WHERE parent_questgroup_id = ? AND pos = ? - 1', + 'ii', + $parentQuestgroupId, $questgroupPos + ); + } + else + { + $data = $this->db->query( + 'SELECT * '. + 'FROM questgroups_questgroupshierarchy '. + 'INNER JOIN questgroups ON questgroups.id = questgroups_questgroupshierarchy.questgroup_id '. + 'WHERE parent_questgroup_id IS NULL AND pos = ? - 1', + 'i', + $questgroupPos + ); + } + if(empty($data)) { + return null; + } + + + return $data[0]; + } + + } + +?> diff --git a/models/QuestgroupshierarchyModel.inc b/models/QuestgroupshierarchyModel.inc new file mode 100644 index 00000000..c1dae116 --- /dev/null +++ b/models/QuestgroupshierarchyModel.inc @@ -0,0 +1,128 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Questgroupshierarchy-table. + * + * @author Oliver Hanraths + */ + class QuestgroupshierarchyModel extends \hhu\z\Model + { + + + + + /** + * Construct a new QuestgroupshierarchyModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get a Questgroup hierarchy by its ID. + * + * throws IdNotFoundException + * @param int $questgroupshierarchyId ID of a Questgroup hierarchy + * @return array Questgroup hierarchy + */ + public function getHierarchyById($questgroupshierarchyId) + { + $data = $this->db->query( + 'SELECT id, seminary_id, parent_questgroupshierarchy_id, pos, title_singular, title_plural, url '. + 'FROM questgroupshierarchy '. + 'WHERE questgroupshierarchy.id = ?', + 'i', + $questgroupshierarchyId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questgroupshierarchyId); + } + + + return $data[0]; + } + + + /** + * Get the toplevel hierarchy entries of a Seminary. + * + * @param int $seminaryId ID of the seminary to get hierarchy for + * @return array Toplevel hierarchy + */ + public function getHierarchyOfSeminary($seminaryId) + { + return $this->db->query( + 'SELECT id, seminary_id, parent_questgroupshierarchy_id, pos, title_singular, title_plural, url '. + 'FROM questgroupshierarchy '. + 'WHERE '. + 'questgroupshierarchy.seminary_id = ? AND '. + 'questgroupshierarchy.parent_questgroupshierarchy_id IS NULL '. + 'ORDER BY questgroupshierarchy.pos ASC', + 'i', + $seminaryId + ); + } + + + /** + * Get the Questgroup-Hierarchy for a Questgroup. + * + * @param int $questgroupId ID of Questgroup + * @return array Hierarchy for Questgroup + */ + public function getHierarchyForQuestgroup($questgroupId) + { + $data = $this->db->query( + 'SELECT questgroups_questgroupshierarchy.parent_questgroup_id, questgroups_questgroupshierarchy.pos AS questgroup_pos, questgroupshierarchy.id, questgroupshierarchy.seminary_id, questgroupshierarchy.parent_questgroupshierarchy_id, questgroupshierarchy.pos, questgroupshierarchy.title_singular, questgroupshierarchy.title_plural, questgroupshierarchy.url '. + 'FROM questgroups_questgroupshierarchy '. + 'INNER JOIN questgroupshierarchy ON questgroupshierarchy.id = questgroups_questgroupshierarchy.questgroupshierarchy_id '. + 'WHERE questgroups_questgroupshierarchy.questgroup_id = ?', + 'i', + $questgroupId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get the child hierarchy entries of a Questgroup hierarchy. + * + * @param int $questgroupshierarchyId ID of a Questgroup hierarchy + * @return array Child Questgroup hierarchy entries + */ + public function getChildQuestgroupshierarchy($questgroupshierarchyId) + { + return $this->db->query( + 'SELECT id, seminary_id, parent_questgroupshierarchy_id, pos, title_singular, title_plural, url '. + 'FROM questgroupshierarchy '. + 'WHERE questgroupshierarchy.parent_questgroupshierarchy_id = ? '. + 'ORDER BY questgroupshierarchy.pos ASC', + 'i', + $questgroupshierarchyId + ); + } + + } + +?> diff --git a/models/QuestsModel.inc b/models/QuestsModel.inc new file mode 100644 index 00000000..d4175545 --- /dev/null +++ b/models/QuestsModel.inc @@ -0,0 +1,487 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Quests-table. + * + * @author Oliver Hanraths + */ + class QuestsModel extends \hhu\z\Model + { + /** + * Quest-status: Entered + * + * @var int; + */ + const QUEST_STATUS_ENTERED = 0; + /** + * Quest-status: submitted + * + * @var int; + */ + const QUEST_STATUS_SUBMITTED = 1; + /** + * Quest-status: Unsolved + * + * @var int; + */ + const QUEST_STATUS_UNSOLVED = 2; + /** + * Quest-status: Solved + * + * @var int; + */ + const QUEST_STATUS_SOLVED = 3; + + + + + /** + * Construct a new QuestsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get a Quest and its data by its URL. + * + * @throws IdNotFoundException + * @param int $seminaryId ID of the corresponding Seminary + * @param int $questgroupId ID of the corresponding Questgroup + * @param string $questURL URL-title of a Quest + * @return array Quest data + */ + public function getQuestByUrl($seminaryId, $questgroupId, $questUrl) + { + $data = $this->db->query( + 'SELECT quests.id, quests.questgroup_id, quests.questtype_id, quests.title, quests.url, quests.xps, quests.entry_text, quests.task, quests.wrong_text, quests.questsmedia_id '. + 'FROM quests '. + 'LEFT JOIN questgroups ON questgroups.id = quests.questgroup_id '. + 'WHERE questgroups.seminary_id = ? AND questgroups.id = ? AND quests.url = ?', + 'iis', + $seminaryId, $questgroupId, $questUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questUrl); + } + + + return $data[0]; + } + + + /** + * Get a Quest and its data by its ID. + * + * @throws IdNotFoundException + * @param string $questId ID of a Quest + * @return array Quest data + */ + public function getQuestById($questId) + { + $data = $this->db->query( + 'SELECT quests.id, quests.questgroup_id, quests.questtype_id, quests.title, quests.url, quests.xps, quests.entry_text, quests.task, quests.wrong_text, quests.questsmedia_id '. + 'FROM quests '. + 'LEFT JOIN questgroups ON questgroups.id = quests.questgroup_id '. + 'WHERE quests.id = ?', + 'i', + $questId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questId); + } + + + return $data[0]; + } + + + /** + * Get the first Quest of a Qusetgroup. + * + * @param int $questId ID of Questgroup + * @return array Data of first Quest + */ + public function getFirstQuestOfQuestgroup($questgroupId) + { + $data = $this->db->query( + 'SELECT id, questtype_id, title, url, xps, task '. + 'FROM quests '. + 'LEFT JOIN quests_previousquests ON quests_previousquests.quest_id = quests.id '. + 'WHERE questgroup_id = ? AND quests_previousquests.previous_quest_id IS NULL', + 'i', + $questgroupId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get Quests that follow-up a Quest. + * + * @param int $questId ID of Quest to get next Quests of + * @return array Quests data + */ + public function getNextQuests($questId) + { + return $this->db->query( + 'SELECT quests.id, quests.questtype_id, quests.title, quests.url, quests.xps, quests.entry_text, quests.task, questgroups.title AS questgroup_title, questgroups.url AS questgroup_url '. + 'FROM quests_previousquests '. + 'INNER JOIN quests ON quests.id = quests_previousquests.quest_id '. + 'INNER JOIN questgroups ON questgroups.id = quests.questgroup_id '. + 'WHERE quests_previousquests.previous_quest_id = ?', + 'i', + $questId + ); + } + + + /** + * Get Quests that the given Quests follows-up to. + * + * @param int $questId ID of Quest to get previous Quests of + * @return array Quests data + */ + public function getPreviousQuests($questId) + { + return $this->db->query( + 'SELECT quests.id, quests.title, quests.url, quests.entry_text, questgroups.title AS questgroup_title, questgroups.url AS questgroup_url '. + 'FROM quests_previousquests '. + 'INNER JOIN quests ON quests.id = quests_previousquests.previous_quest_id '. + 'INNER JOIN questgroups ON questgroups.id = quests.questgroup_id '. + 'WHERE quests_previousquests.quest_id = ?', + 'i', + $questId + ); + } + + + /** + * Mark a Quest as entered for a Character. + * + * @param int $questId ID of Quest to mark as entered + * @param int $characterId ID of Character that entered the Quest + */ + public function setQuestEntered($questId, $characterId) + { + $this->setQuestStatus($questId, $characterId, self::QUEST_STATUS_ENTERED, false); + } + + + /** + * Mark a Quest as submitted for a Character. + * + * @param int $questId ID of Quest to mark as unsolved + * @param int $characterId ID of Character that unsolved the Quest + */ + public function setQuestSubmitted($questId, $characterId) + { + $this->setQuestStatus($questId, $characterId, self::QUEST_STATUS_SUBMITTED); + } + + + /** + * Mark a Quest as unsolved for a Character. + * + * @param int $questId ID of Quest to mark as unsolved + * @param int $characterId ID of Character that unsolved the Quest + */ + public function setQuestUnsolved($questId, $characterId) + { + $this->setQuestStatus($questId, $characterId, self::QUEST_STATUS_UNSOLVED); + } + + + /** + * Mark a Quest as solved for a Character. + * + * @param int $questId ID of Quest to mark as solved + * @param int $characterId ID of Character that solved the Quest + */ + public function setQuestSolved($questId, $characterId) + { + $this->setQuestStatus($questId, $characterId, self::QUEST_STATUS_SOLVED, false); + } + + + /** + * Determine if the given Character has entered a Quest. + * + * @param int $questId ID of Quest to check + * @param int $characterId ID of Character to check + * @result boolean Whether Character has entered the Quest or not + */ + public function hasCharacterEnteredQuest($questId, $characterId) + { + $count = $this->db->query( + 'SELECT count(id) AS c '. + 'FROM quests_characters '. + 'WHERE quest_id = ? AND character_id = ? AND status IN (?,?,?)', + 'iiiii', + $questId, + $characterId, + self::QUEST_STATUS_ENTERED, self::QUEST_STATUS_SOLVED, self::QUEST_STATUS_UNSOLVED + ); + + + return (!empty($count) && intval($count[0]['c']) > 0); + } + + + /** + * Determine if the given Character has tried to solve a Quest. + * + * @param int $questId ID of Quest to check + * @param int $characterId ID of Character to check + * @result boolean Whether Character has tried to solved the Quest or not + */ + public function hasCharacterTriedQuest($questId, $characterId) + { + $count = $this->db->query( + 'SELECT count(id) AS c '. + 'FROM quests_characters '. + 'WHERE quest_id = ? AND character_id = ? AND status IN (?,?)', + 'iiii', + $questId, + $characterId, + self::QUEST_STATUS_SOLVED, self::QUEST_STATUS_UNSOLVED + ); + + + return (!empty($count) && intval($count[0]['c']) > 0); + } + + + /** + * Determine if the given Character has solved the given Quest. + * + * @param int $questId ID of Quest to check + * @param int $characterId ID of Character to check + * @result boolean Whether Character has solved the Quest or not + */ + public function hasCharacterSolvedQuest($questId, $characterId) + { + $count = $this->db->query( + 'SELECT count(id) AS c '. + 'FROM quests_characters '. + 'WHERE quest_id = ? AND character_id = ? AND status = ?', + 'iii', + $questId, + $characterId, + self::QUEST_STATUS_SOLVED + ); + + + return (!empty($count) && intval($count[0]['c']) > 0); + } + + + /** + * Get the last Quests for a Character. + * + * @param int $characterId ID of Character + * @retrun array Quest data + */ + public function getLastQuestForCharacter($characterId) + { + $data = $this->db->query( + 'SELECT quests.id, quests.questgroup_id, quests.questtype_id, quests.title, quests.url, quests.xps, quests.task, quests.wrong_text, quests.questsmedia_id '. + 'FROM quests_characters '. + 'LEFT JOIN quests ON quests.id = quests_characters.quest_id '. + 'WHERE quests_characters.character_id = ? AND quests_characters.status IN (?, ?, ?) '. + 'ORDER BY quests_characters.created desc '. + 'LIMIT 1', + 'iiii', + $characterId, + self::QUEST_STATUS_ENTERED, self::QUEST_STATUS_SUBMITTED, self::QUEST_STATUS_SOLVED + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get all Quests for a Seminary. + * + * @param int $seminaryId ID of Seminary + * @return array Quests for this Seminary + */ + public function getQuestsForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT DISTINCT quests.id, quests.questgroup_id, quests.questtype_id, quests.title, quests.url, quests.xps, quests.task, quests.wrong_text, quests.questsmedia_id '. + 'FROM questgroups '. + 'INNER JOIN quests ON quests.questgroup_id = questgroups.id '. + 'WHERE questgroups.seminary_id = ?', + 'i', + $seminaryId + ); + } + + + /** + * Get all Quests that are linked to a Questtopic. + * + * @param int $questtopicId ID of Questtopic + * @return array Quests for this Questtopic + */ + public function getQuestsForQuesttopic($questtopicId) + { + return $this->db->query( + 'SELECT DISTINCT quests.id, quests.questgroup_id, quests.questtype_id, quests.title, quests.url, quests.xps, quests.task, quests.wrong_text, quests.questsmedia_id '. + 'FROM quests_questsubtopics '. + 'INNER JOIN quests ON quests.id = quests_questsubtopics.quest_id '. + 'WHERE quests_questsubtopics.questsubtopic_id = ?', + 'i', + $questtopicId + ); + } + + + + + /** + * Mark a Quest for a Character. + * + * @param int $questId ID of Quest to mark + * @param int $characterId ID of Character to mark the Quest for + * @param int $status Quest status to mark + * @param boolean $repeated Insert although status is already set for this Quest and Character + */ + private function setQuestStatus($questId, $characterId, $status, $repeated=true) + { + // Check if status is already set + if(!$repeated) + { + $count = $this->db->query( + 'SELECT count(*) AS c '. + 'FROM quests_characters '. + 'WHERE quest_id = ? AND character_id = ? AND status = ?', + 'iii', + $questId, + $characterId, + $status + ); + if(!empty($count) && intval($count[0]['c']) > 0) { + return; + } + } + + // Set status + $this->db->query( + 'INSERT INTO quests_characters '. + '(quest_id, character_id, status) '. + 'VALUES '. + '(?, ?, ?) ', + 'iii', + $questId, + $characterId, + $status + ); + } + + + /** + * Get the last status of a Quest for a Character. + * + * @param int $questId ID of Quest + * @param int $characterId ID of Character to get status for + * @return int Last status + */ + public function getLastQuestStatus($questId, $characterId) + { + $data = $this->db->query( + 'SELECT id, created, status '. + 'FROM quests_characters '. + 'WHERE quest_id = ? AND character_id = ? '. + 'ORDER BY created DESC '. + 'LIMIT 1', + 'ii', + $questId, $characterId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Create a new Quest. + * + * @param int $userId User-ID that creates the new character + * @param string $name Name for new Quest + * @param int $questgroupId ID of Questgroup + * @param int $questtypeId ID of Questtype + * @param int $xps XPs for new Quest + * @param string $entrytext Entrytext for new Quest + * @param string $wrongtext Wrongtext for new Quest + * @param string $task Task for new Quest + * @return int ID of new Quest + */ + public function createQuest($userId, $name, $questgroupId, $questtypeId, $xps, $entrytext, $wrongtext, $task) + { + $this->db->query( + 'INSERT INTO quests '. + '(created_user_id, questgroup_id, questtype_id, title, url, xps, entry_text, wrong_text, task) '. + 'VALUES '. + '(?, ?, ?, ?, ?, ?, ?, ?, ?)', + 'iiississs', + $userId, $questgroupId, $questtypeId, + $name, \nre\core\Linker::createLinkParam($name), + $xps, $entrytext, $wrongtext, $task + ); + + + return $this->db->getInsertId(); + } + + + /** + * Set the media for a Quest. + * + * @param int $questId ID of Quest to set media for + * @param int $questmediaId ID of Questsmedia to set + */ + public function setQuestmedia($questId, $questsmediaId) + { + $this->db->query( + 'UPDATE quests '. + 'SET questsmedia_id = ? '. + 'WHERE id = ?', + 'ii', + $questsmediaId, + $questId + ); + } + + } + +?> diff --git a/models/QuesttextsModel.inc b/models/QuesttextsModel.inc new file mode 100644 index 00000000..7aca28b9 --- /dev/null +++ b/models/QuesttextsModel.inc @@ -0,0 +1,220 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Questtexts-table. + * + * @author Oliver Hanraths + */ + class QuesttextsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new QuesttextsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get all Questtexts for a Quest. + * + * @param int $questId ID of the Quest + * @param string $questtexttypeUrl URL of the Questtexttype + * @return array All Questtexts for a Quest + */ + public function getQuesttextsOfQuest($questId, $questtexttypeUrl=null) + { + if(is_null($questtexttypeUrl)) + { + return $this->db->query( + 'SELECT questtexts.id, questtexts.text, questtexts.pos, questtexts.out_text, questtexts.abort_text, questtexts.questsmedia_id, questtexttypes.id AS type_id, questtexttypes.type, questtexttypes.url AS type_url '. + 'FROM questtexts '. + 'LEFT JOIN questtexttypes ON questtexttypes.id = questtexts.questtexttype_id '. + 'WHERE questtexts.quest_id = ?', + 'i', + $questId + ); + } + else + { + return $this->db->query( + 'SELECT questtexts.id, questtexts.text, questtexts.pos, questtexts.out_text, questtexts.abort_text, questtexts.questsmedia_id, questtexttypes.id AS type_id, questtexttypes.type, questtexttypes.url AS type_url '. + 'FROM questtexts '. + 'LEFT JOIN questtexttypes ON questtexttypes.id = questtexts.questtexttype_id '. + 'WHERE questtexts.quest_id = ? and questtexttypes.url = ?', + 'is', + $questId, + $questtexttypeUrl + ); + } + } + + + /** + * Get count of Questtexts for a Quest. + * + * @param int $questId ID of the Quest + * @param string $questtexttypeUrl URL of the Questtexttype + * @return int Amount of Questtexts for a Quest + */ + public function getQuesttextCountOfQuest($questId, $questtexttypeUrl=null) + { + if(is_null($questtexttypeUrl)) + { + $data = $this->db->query( + 'SELECT count(questtexts.id) AS c '. + 'FROM questtexts '. + 'LEFT JOIN questtexttypes ON questtexttypes.id = questtexts.questtexttype_id '. + 'WHERE questtexts.quest_id = ?', + 'i', + $questId + ); + } + else + { + $data = $this->db->query( + 'SELECT count(questtexts.id) AS c '. + 'FROM questtexts '. + 'LEFT JOIN questtexttypes ON questtexttypes.id = questtexts.questtexttype_id '. + 'WHERE questtexts.quest_id = ? and questtexttypes.url = ?', + 'is', + $questId, + $questtexttypeUrl + ); + } + if(!empty($data)) { + return $data[0]['c']; + } + + + return 0; + } + + + /** + * Get corresponding Questtext for a Sidequest. + * + * @throws IdNotFoundException + * @param int $sidequestId ID of the Sidequest to get the Questtext for + * @param array Questtext data + */ + public function getRelatedQuesttextForQuestgroup($questgroupId) + { + $data = $this->db->query( + 'SELECT questtexts.id, questtexts.text, questtexts.pos, questtexts.quest_id, questtexttypes.id AS type_id, questtexttypes.type, questtexttypes.url AS type_url '. + 'FROM questgroups_questtexts '. + 'LEFT JOIN questtexts ON questtexts.id = questgroups_questtexts.questtext_id '. + 'LEFT JOIN questtexttypes ON questtexttypes.id = questtexts.questtexttype_id '. + 'WHERE questgroups_questtexts.questgroup_id = ?', + 'i', + $questgroupId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get all registered Questtexttypes. + * + * @return array Registered Questtexttypes + */ + public function getQuesttexttypes() + { + return $this->db->query( + 'SELECT id, type, url '. + 'FROM questtexttypes' + ); + } + + + /** + * Get a Questtexttype by its URL. + * + * @param string $questtexttypeUrl URL-type of Questtexttype + * @return array Questtexttype data + */ + public function getQuesttexttypeByUrl($questtexttypeUrl) + { + $data = $this->db->query( + 'SELECT id, type, url '. + 'FROM questtexttypes '. + 'WHERE url = ?', + 's', + $questtexttypeUrl + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Add a list of Questtexts to a Quest. + * + * @param int $userId ID of user + * @param int $questId ID of Quest to add texts to + * @param string $questtexttypeUrl URL-type of Questtexttype of texts + * @param array $texts List of texts to add. + */ + public function addQuesttextsToQuest($userId, $questId, $questtexttypeUrl, $texts) + { + $questtexttype = $this->getQuesttexttypeByUrl($questtexttypeUrl); + if(is_null($questtexttype)) { + return; + } + foreach($texts as &$text) + { + $pos = $this->db->query( + 'SELECT COALESCE(MAX(pos),0)+1 AS pos '. + 'FROM questtexts '. + 'WHERE quest_id = ?', + 'i', + $questId + ); + $pos = $pos[0]['pos']; + + $this->db->query( + 'INSERT INTO questtexts '. + '(created_user_id, quest_id, questtexttype_id, pos, text) '. + 'VALUES '. + '(?, ?, ?, ?, ?)', + 'iiiis', + $userId, $questId, $questtexttype['id'], $pos, + $text + ); + } + } + + + + + } + +?> diff --git a/models/QuesttopicsModel.inc b/models/QuesttopicsModel.inc new file mode 100644 index 00000000..abd246f7 --- /dev/null +++ b/models/QuesttopicsModel.inc @@ -0,0 +1,154 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Questtopics-table. + * + * @author Oliver Hanraths + */ + class QuesttopicsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new QuesttopicsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get a Questtopic by its URL. + * + * @param int $seminaryId ID of Seminary + * @param string $questtopicUrl URL-Title of Questtopic + * @return array Questtopic data + */ + public function getQuesttopicByUrl($seminaryId, $questtopicUrl) + { + $data = $this->db->query( + 'SELECT id, title, url '. + 'FROM questtopics '. + 'WHERE seminary_id = ? AND url = ?', + 'is', + $seminaryId, $questtopicUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questtopicUrl); + } + + + return $data[0]; + } + + + /** + * Get all Questtopics for a Seminary. + * + * @param int $seminaryId ID of Seminary + * @return array List of Questtopics + */ + public function getQuesttopicsForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT id, title, url '. + 'FROM questtopics '. + 'WHERE seminary_id = ?', + 'i', + $seminaryId + ); + } + + + /** + * Get count of Quests that are linked to a Questtopic. + * + * @param int $questtopicId ID of Questtopic + * @return int Count of Quests + */ + public function getQuestCountForQuesttopic($questtopicId) + { + $data = $this->db->query( + 'SELECT count(DISTINCT quests_questsubtopics.quest_id) AS c ' . + 'FROM questsubtopics '. + 'LEFT JOIN quests_questsubtopics ON quests_questsubtopics.questsubtopic_id = questsubtopics.id '. + 'WHERE questsubtopics.questtopic_id = ?', + 'i', + $questtopicId + ); + if(!empty($data)) { + return $data[0]['c']; + } + + + return 0; + } + + + /** + * Get count of Quests that are linked to a Questtopic and are + * unlocked by a Character. + * + * @param int $questtopicId ID of Questtopic + * @param int $characterId ID of Character + * @return int Count of Quests + */ + public function getCharacterQuestCountForQuesttopic($questtopicId, $characterId) + { + $data = $this->db->query( + 'SELECT count(DISTINCT quests_characters.quest_id) AS c '. + 'FROM questsubtopics '. + 'LEFT JOIN quests_questsubtopics ON quests_questsubtopics.questsubtopic_id = questsubtopics.id '. + 'INNER JOIN quests_characters ON quests_characters.quest_id = quests_questsubtopics.quest_id AND quests_characters.character_id = ? AND quests_characters.status = 3 '. + 'WHERE questsubtopics.questtopic_id = ?', + 'ii', + $characterId, + $questtopicId + ); + if(!empty($data)) { + return $data[0]['c']; + } + + + return 0; + } + + + /** + * Get all Questsubtopics for a Quest. + * + * @param int $questId ID of Quest + * @return array List of Questsubtopics + */ + public function getQuestsubtopicsForQuest($questId) + { + return $this->db->query( + 'SELECT DISTINCT id, questtopic_id, title, url '. + 'FROM quests_questsubtopics '. + 'INNER JOIN questsubtopics ON questsubtopics.id = quests_questsubtopics.questsubtopic_id '. + 'WHERE quests_questsubtopics.quest_id = ?', + 'i', + $questId + ); + } + + } + +?> diff --git a/models/QuesttypesModel.inc b/models/QuesttypesModel.inc new file mode 100644 index 00000000..58b36648 --- /dev/null +++ b/models/QuesttypesModel.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with Questtypes-table. + * + * @author Oliver Hanraths + */ + class QuesttypesModel extends \hhu\z\Model + { + + + + + /** + * Construct a new QuesttypesModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get all registered Questtypes. + * + * @return array List of registered Questtypes + */ + public function getQuesttypes() + { + return $this->db->query( + 'SELECT id, title, url, classname '. + 'FROM questtypes '. + 'ORDER BY title ASC' + ); + } + + + /** + * Get a Questtype by its ID + * + * @param int $questtypeId ID of Questtype + * @return array Questtype data + */ + public function getQuesttypeById($questtypeId) + { + $data = $this->db->query( + 'SELECT title, classname '. + 'FROM questtypes '. + 'WHERE id = ?', + 'i', + $questtypeId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questtypeId); + } + + + return $data = $data[0]; + } + + } + +?> diff --git a/models/SeminariesModel.inc b/models/SeminariesModel.inc new file mode 100644 index 00000000..207411df --- /dev/null +++ b/models/SeminariesModel.inc @@ -0,0 +1,195 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model of the SeminariesAgent to list registered seminaries. + * + * @author Oliver Hanraths + */ + class SeminariesModel extends \hhu\z\Model + { + /** + * Required models + * + * @var array + */ + public $models = array('questgroupshierarchy', 'questgroups'); + + + + + /** + * Construct a new SeminariesModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get registered seminaries. + * + * @return array Seminaries + */ + public function getSeminaries() + { + // Get seminaries + return $this->db->query( + 'SELECT id, created, created_user_id, title, url, course, description, seminarymedia_id '. + 'FROM seminaries '. + 'ORDER BY created DESC' + ); + } + + + /** + * Get a seminary and its data by its ID. + * + * @throws IdNotFoundException + * @param string $seminaryId ID of a seminary + * @return array Seminary + */ + public function getSeminaryById($seminaryId) + { + $seminary = $this->db->query( + 'SELECT id, created, created_user_id, title, url, course, description, seminarymedia_id '. + 'FROM seminaries '. + 'WHERE id = ?', + 'i', + $seminaryId + ); + if(empty($seminary)) { + throw new \nre\exceptions\IdNotFoundException($seminaryId); + } + + + return $seminary[0]; + } + + + /** + * Get a seminary and its data by its URL-title. + * + * @throws IdNotFoundException + * @param string $seminaryUrl URL-Title of a seminary + * @return array Seminary + */ + public function getSeminaryByUrl($seminaryUrl) + { + $seminary = $this->db->query( + 'SELECT id, created, created_user_id, title, url, course, description, seminarymedia_id '. + 'FROM seminaries '. + 'WHERE url = ?', + 's', + $seminaryUrl + ); + if(empty($seminary)) { + throw new \nre\exceptions\IdNotFoundException($seminaryUrl); + } + + + return $seminary[0]; + } + + + /* + * Calculate sum of XPs for a Seminary. + * + * @param int $seminaryId ID of Seminary + * @return int Total sum of XPs + */ + public function getTotalXPs($seminaryId) + { + $xps = 0; + + // Questgroups + $questgroupshierarchy = $this->Questgroupshierarchy->getHierarchyOfSeminary($seminaryId); + foreach($questgroupshierarchy as &$hierarchy) + { + // Get Questgroups + $questgroups = $this->Questgroups->getQuestgroupsForHierarchy($hierarchy['id']); + foreach($questgroups as &$questgroup) + { + $data = $this->Questgroups->getCumulatedDataForQuestgroup($questgroup['id']); + $xps += $data['xps']; + } + } + + + return $xps; + } + + + /** + * Create a new seminary. + * + * @param string $title Title of seminary to create + * @param int $userId ID of creating user + * @return int ID of the newly created seminary + */ + public function createSeminary($title, $userId) + { + $this->db->query( + 'INSERT INTO seminaries '. + '(created_user_id, title, url) '. + 'VALUES '. + '(?, ?, ?)', + 'iss', + $userId, + $title, + \nre\core\Linker::createLinkParam($title) + ); + + + return $this->db->getInsertId(); + } + + + /** + * Edit a seminary. + * + * @throws DatamodelException + * @param int $seminaryId ID of the seminary to delete + * @param string $title New title of seminary + */ + public function editSeminary($seminaryId, $title) + { + $this->db->query( + 'UPDATE seminaries '. + 'SET title = ?, url = ? '. + 'WHERE id = ?', + 'ssi', + $title, + \nre\core\Linker::createLinkParam($title), + $seminaryId + ); + } + + + /** + * Delete a seminary. + * + * @param int $seminaryId ID of the seminary to delete + */ + public function deleteSeminary($seminaryId) + { + $this->db->query('DELETE FROM seminaries WHERE id = ?', 'i', $seminaryId); + } + + } + +?> diff --git a/models/SeminarycharacterfieldsModel.inc b/models/SeminarycharacterfieldsModel.inc new file mode 100644 index 00000000..efad3bde --- /dev/null +++ b/models/SeminarycharacterfieldsModel.inc @@ -0,0 +1,78 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with the Seminarycharacterfields-tables. + * + * @author Oliver Hanraths + */ + class SeminarycharacterfieldsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new SeminarycharacterfieldsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get all Character fields of a Seminary. + * + * @param int $seminaryId ID of Seminary to get fields of + * @param array Seminary Character fields + */ + public function getFieldsForSeminary($seminaryId) + { + return $this->db->query( + 'SELECT seminarycharacterfields.id, seminarycharacterfields.title, seminarycharacterfields.url, seminarycharacterfields.regex, seminarycharacterfields.required, seminarycharacterfieldtypes.id AS type_id, seminarycharacterfieldtypes.title AS type_title, seminarycharacterfieldtypes.url AS type_url '. + 'FROM seminarycharacterfields '. + 'LEFT JOIN seminarycharacterfieldtypes ON seminarycharacterfieldtypes.id = seminarycharacterfields.seminarycharacterfieldtype_id '. + 'WHERE seminarycharacterfields.seminary_id = ? '. + 'ORDER BY pos ASC', + 'i', + $seminaryId + ); + } + + + /** + * Get Seminary Character fields of a Character. + * + * @param int $characterId ID of the Character + * @return array Seminary Character fields + */ + public function getFieldsForCharacter($characterId) + { + return $this->db->query( + 'SELECT seminarycharacterfields.title, characters_seminarycharacterfields.value '. + 'FROM characters_seminarycharacterfields '. + 'LEFT JOIN seminarycharacterfields ON seminarycharacterfields.id = characters_seminarycharacterfields.seminarycharacterfield_id '. + 'LEFT JOIN seminarycharacterfieldtypes ON seminarycharacterfieldtypes.id = seminarycharacterfields.seminarycharacterfieldtype_id '. + 'WHERE characters_seminarycharacterfields.character_id = ?', + 'i', + $characterId + ); + } + + } + +?> diff --git a/models/UploadsModel.inc b/models/UploadsModel.inc new file mode 100644 index 00000000..fc4ec2fd --- /dev/null +++ b/models/UploadsModel.inc @@ -0,0 +1,141 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to handle files to upload. + * + * @author Oliver Hanraths + */ + class UploadsModel extends \hhu\z\Model + { + + + + + /** + * Construct a new UploadsModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Upload a file and create a database record. + * + * @param int $userId ID of user that uploads the file + * @param int $seminaryId ID of Seminary + * @param string $name Name of file to upload + * @param string $filename Filename of file to upload + * @param string $tmpFilename Name of temporary uploaded file + * @param string $mimetype Mimetype of file to upload + * @return mixed ID of database record or false + */ + public function uploadSeminaryFile($userId, $seminaryId, $name, $filename, $tmpFilename, $mimetype) + { + $uploadId = false; + $this->db->setAutocommit(false); + + try { + // Create database record + $this->db->query( + 'INSERT INTO seminaryuploads '. + '(created_user_id, seminary_id, name, url, mimetype) '. + 'VALUES '. + '(?, ? ,? ,?, ?)', + 'iisss', + $userId, + $seminaryId, + $name, + \nre\core\Linker::createLinkParam($filename), + $mimetype + ); + $uploadId = $this->db->getInsertId(); + + // Create filename + $filename = ROOT.DS.\nre\configs\AppConfig::$dirs['seminaryuploads'].DS.$filename; + if(!move_uploaded_file($tmpFilename, $filename)) + { + $this->db->rollback(); + $uploadId = false; + } + } + catch(\nre\exceptions\DatamodelException $e) { + $this->db->rollback(); + $this->db->setAutocommit(true); + } + + + $this->db->setAutocommit(true); + return $uploadId; + } + + + /** + * Get an upload by its ID. + * + * @throws IdNotFoundException + * @param int $uploadId ID of the uploaded file + * @return array Upload data + */ + public function getSeminaryuploadById($seminaryuploadId) + { + $data = $this->db->query( + 'SELECT id, created, created_user_id, seminary_id, name, url, mimetype, public '. + 'FROM seminaryuploads '. + 'WHERE id = ?', + 'i', + $seminaryuploadId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($seminaryuploadId); + } + + + return $data[0]; + } + + + /** + * Get an upload by its URL. + * + * @throws IdNotFoundException + * @param int $seminaryId ID of Seminary + * @param int $uploadId ID of the uploaded file + * @return array Upload data + */ + public function getSeminaryuploadByUrl($seminaryId, $seminaryuploadUrl) + { + $data = $this->db->query( + 'SELECT id, created, created_user_id, seminary_id, name, url, mimetype, public '. + 'FROM seminaryuploads '. + 'WHERE seminary_id = ? AND url = ?', + 'is', + $seminaryId, + $seminaryuploadUrl + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($seminaryuploadUrl); + } + + + return $data[0]; + } + + } + +?> diff --git a/models/UserrolesModel.inc b/models/UserrolesModel.inc new file mode 100644 index 00000000..33121a21 --- /dev/null +++ b/models/UserrolesModel.inc @@ -0,0 +1,77 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model to interact with userroles-table. + * + * @author Oliver Hanraths + */ + class UserrolesModel extends \hhu\z\Model + { + + + + + /** + * Construct a new UserrolesModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get all userroles for an user referenced by its ID. + * + * @param int $userId ID of an user + * @return array Userroles for an user + */ + public function getUserrolesForUserById($userId) + { + return $this->db->query( + 'SELECT userroles.id, userroles.created, userroles.name '. + 'FROM users_userroles '. + 'LEFT JOIN userroles ON userroles.id = users_userroles.userrole_id '. + 'WHERE users_userroles.user_id = ?', + 'i', + $userId + ); + } + + + /** + * Get all userroles for an user referenced by its URL-username. + * + * @param string $userUrl URL-Username of an user + * @return array Userroles for an user + */ + public function getUserrolesForUserByUrl($userUrl) + { + return $this->db->query( + 'SELECT userroles.id, userroles.created, userroles.name '. + 'FROM users '. + 'LEFT JOIN users_userroles ON users_userroles.user_id = users.id '. + 'LEFT JOIN userroles ON userroles.id = users_userroles.userrole_id '. + 'WHERE users.url = ?', + 's', + $userUrl + ); + } + + } + +?> diff --git a/models/UsersModel.inc b/models/UsersModel.inc new file mode 100644 index 00000000..d2a69003 --- /dev/null +++ b/models/UsersModel.inc @@ -0,0 +1,348 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\models; + + + /** + * Model of the UsersAgent to list users and get their data. + * + * @author Oliver Hanraths + */ + class UsersModel extends \hhu\z\Model + { + + + + + /** + * Construct a new UsersModel. + */ + public function __construct() + { + parent::__construct(); + } + + + + + /** + * Get registered users. + * + * @return array Users + */ + public function getUsers() + { + return $this->db->query( + 'SELECT id, created, username, url, surname, prename, email '. + 'FROM users '. + 'ORDER BY username ASC' + ); + } + + + /** + * Get users with the given user role. + * + * @param string $userrole User role + * @return array List of users + */ + public function getUsersWithRole($userrole) + { + return $this->db->query( + 'SELECT users.id, users.created, users.username, users.url, users.surname, users.prename, users.email '. + 'FROM users '. + 'LEFT JOIN users_userroles ON users_userroles.user_id = users.id '. + 'LEFT JOIN userroles ON userroles.id = users_userroles.userrole_id '. + 'WHERE userroles.name = ? '. + 'ORDER BY username ASC', + 's', + $userrole + ); + } + + + /** + * Get a user and its data by its ID. + * + * @throws IdNotFoundException + * @param int $userId ID of an user + * @return array Userdata + */ + public function getUserById($userId) + { + // Get user + $user = $this->db->query( + 'SELECT id, created, username, url, surname, prename, email '. + 'FROM users '. + 'WHERE id = ?', + 'i', + $userId + ); + if(empty($user)) { + throw new \nre\exceptions\IdNotFoundException($userId); + } + + + return $user[0]; + } + + + /** + * Get a user and its data by its URL-username. + * + * @throws IdNotFoundException + * @param string $userUrl URL-Username of an user + * @return array Userdata + */ + public function getUserByUrl($userUrl) + { + // Get user + $user = $this->db->query( + 'SELECT id, created, username, url, surname, prename, email '. + 'FROM users '. + 'WHERE url = ?', + 's', + $userUrl + ); + if(empty($user)) { + throw new \nre\exceptions\IdNotFoundException($userUrl); + } + + + return $user[0]; + } + + + /** + * Log a user in if its credentials are valid. + * + * @throws DatamodelException + * @param string $username The name of the user to log in + * @param string $password Plaintext password of the user to log in + */ + public function login($username, $password) + { + $data = $this->db->query('SELECT id, password FROM users WHERE username = ?', 's', $username); + if(!empty($data)) + { + $data = $data[0]; + if($this->verify($password, $data['password'])) { + return $data['id']; + } + } + + + return null; + } + + + /** + * Check if an username already exists. + * + * @param string $username Username to check + * @return boolean Whether username exists or not + */ + public function usernameExists($username) + { + $data = $this->db->query( + 'SELECT count(id) AS c '. + 'FROM users '. + 'WHERE username = ? OR url = ?', + 'ss', + $username, + \nre\core\Linker::createLinkParam($username) + ); + if(!empty($data)) { + return ($data[0]['c'] > 0); + } + + + return false; + } + + + /** + * Check if an e‑mail address already exists. + * + * @param string $email E‑mail address to check + * @return boolean Whether e‑mail address exists or not + */ + public function emailExists($email) + { + $data = $this->db->query( + 'SELECT count(id) AS c '. + 'FROM users '. + 'WHERE email = ?', + 's', + $email + ); + if(!empty($data)) { + return ($data[0]['c'] > 0); + } + + + return false; + } + + + /** + * Create a new user. + * + * @param string $username Username of the user to create + * @param string $email E‑Mail-Address of the user to create + * @param string $password Password of the user to create + * @return int ID of the newly created user + */ + public function createUser($username, $prename, $surname, $email, $password) + { + $userId = null; + $this->db->setAutocommit(false); + try { + // Create user + $this->db->query( + 'INSERT INTO users '. + '(username, url, surname, prename, email, password) '. + 'VALUES '. + '(?, ?, ?, ?, ?, ?)', + 'ssssss', + $username, + \nre\core\Linker::createLinkParam($username), + $surname, + $prename, + $email, + $this->hash($password) + ); + $userId = $this->db->getInsertId(); + + // Add role “user” + $this->db->query( + 'INSERT INTO users_userroles '. + '(user_id, userrole_id) '. + 'SELECT ?, userroles.id '. + 'FROM userroles '. + 'WHERE userroles.name = ?', + 'is', + $userId, + 'user' + ); + } + catch(Exception $e) { + $this->db->rollback(); + $this->db->setAutocommit(true); + throw $e; + } + $this->db->setAutocommit(true); + + + return $userId; + } + + + /** + * Edit a user. + * + * @throws DatamodelException + * @param int $userId ID of the user to delete + * @param string $username New name of user + * @param string $email Changed e‑mail-address of user + * @param string $password Changed plaintext password of user + */ + public function editUser($userId, $username, $prename, $surname, $email, $password) + { + $this->db->setAutocommit(false); + try { + // Update user data + $this->db->query( + 'UPDATE users '. + 'SET username = ?, url = ?, prename = ?, surname = ?, email = ? '. + 'WHERE id = ?', + 'sssssi', + $username, + \nre\core\Linker::createLinkParam($username), + $prename, + $surname, + $email, + $userId + ); + + // Set new password + if(!empty($password)) + { + $this->db->query( + 'UPDATE users '. + 'SET password = ? '. + 'WHERE id = ?', + 'si', + $this->hash($password), + $userId + ); + } + } + catch(Exception $e) { + $this->db->rollback(); + $this->db->setAutocommit(true); + throw $e; + } + $this->db->setAutocommit(true); + } + + + /** + * Delete a user. + * + * @param int $userId ID of the user to delete + */ + public function deleteUser($userId) + { + $this->db->query('DELETE FROM users WHERE id = ?', 'i', $userId); + } + + + + + /** + * Hash a password. + * + * @param string $password Plaintext password + * @return string Hashed password + */ + public function hash($password) + { + if(!function_exists('password_hash')) { + \hhu\z\lib\Password::load(); + } + + + return password_hash($password, PASSWORD_DEFAULT); + } + + + /** + * Verify a password. + * + * @param string $password Plaintext password to verify + * @param string $hash Hashed password to match with + * @return boolean Verified + */ + private function verify($password, $hash) + { + if(!function_exists('password_verify')) { + \hhu\z\lib\Password::load(); + } + + + return password_verify($password, $hash); + } + + } + +?> diff --git a/questtypes/bossfight/BossfightQuesttypeAgent.inc b/questtypes/bossfight/BossfightQuesttypeAgent.inc new file mode 100644 index 00000000..6d99ec44 --- /dev/null +++ b/questtypes/bossfight/BossfightQuesttypeAgent.inc @@ -0,0 +1,24 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * QuesttypeAgent for a boss-fight. + * + * @author Oliver Hanraths + */ + class BossfightQuesttypeAgent extends \hhu\z\QuesttypeAgent + { + } + +?> diff --git a/questtypes/bossfight/BossfightQuesttypeController.inc b/questtypes/bossfight/BossfightQuesttypeController.inc new file mode 100644 index 00000000..4f8001e7 --- /dev/null +++ b/questtypes/bossfight/BossfightQuesttypeController.inc @@ -0,0 +1,258 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Controller of the BossfightQuesttypeAgent for a boss-fight. + * + * @author Oliver Hanraths + */ + class BossfightQuesttypeController extends \hhu\z\QuesttypeController + { + /** + * Required models + * + * @var array + */ + public $models = array('media'); + + + + + /** + * Save the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Prepare session + $this->prepareSession($quest['id']); + + // Remove previous answers + $this->Bossfight->clearCharacterSubmissions($quest['id'], $character['id']); + + // Save answers + foreach($_SESSION['quests'][$quest['id']]['stages'] as &$stage) { + $this->Bossfight->setCharacterSubmission($stage['id'], $character['id']); + } + } + + + /** + * Save additional data for the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $data Additional (POST-) data + */ + public function saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $data) + { + } + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @return boolean True/false for a right/wrong answer or null for moderator evaluation + */ + public function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get Boss-Fight + $fight = $this->Bossfight->getBossFight($quest['id']); + + // Prepare session + $this->prepareSession($quest['id']); + + // Calculate lives + $lives = array( + 'character' => $fight['lives_character'], + 'boss' => $fight['lives_boss'] + ); + foreach($_SESSION['quests'][$quest['id']]['stages'] as &$stage) + { + $lives['character'] += $stage['livedrain_character']; + $lives['boss'] += $stage['livedrain_boss']; + } + + + return ($lives['boss'] == 0 && $lives['character'] > 0); + } + + + /** + * Action: quest. + * + * Display a stage with a text and the answers for the following + * stages. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param Exception $exception Character submission exception + */ + public function quest($seminary, $questgroup, $quest, $character, $exception) + { + // Get Boss-Fight + $fight = $this->Bossfight->getBossFight($quest['id']); + if(!is_null($fight['boss_seminarymedia_id'])) { + $fight['bossmedia'] = $this->Media->getSeminaryMediaById($fight['boss_seminarymedia_id']); + } + + // Prepare session + $this->prepareSession($quest['id']); + + // Get Stage + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('submit_stages'))) + { + $stages = $this->request->getPostParam('submit_stages'); + $stageId = array_keys($stages)[0]; + $stage = $this->Bossfight->getStageById($stageId); + } + else + { + $_SESSION['quests'][$quest['id']]['stages'] = array(); + $stage = $this->Bossfight->getFirstStage($quest['id']); + } + + // Store Stage in session + if(count($_SESSION['quests'][$quest['id']]['stages']) == 0 || $_SESSION['quests'][$quest['id']]['stages'][count($_SESSION['quests'][$quest['id']]['stages'])-1]['id'] != $stage['id']) { + $_SESSION['quests'][$quest['id']]['stages'][] = $stage; + } + + // Calculate lives + $lives = array( + 'character' => $fight['lives_character'], + 'boss' => $fight['lives_boss'] + ); + foreach($_SESSION['quests'][$quest['id']]['stages'] as &$stage) + { + $lives['character'] += $stage['livedrain_character']; + $lives['boss'] += $stage['livedrain_boss']; + } + + // Get Child-Stages + $childStages = $this->Bossfight->getChildStages($stage['id']); + + // Get answer of Character + if($this->request->getGetParam('show-answer') == 'true') { + foreach($childStages as &$childStage) { + $childStage['answer'] = $this->Bossfight->getCharacterSubmission($childStage['id'], $character['id']); + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('character', $character); + $this->set('fight', $fight); + $this->set('stage', $stage); + $this->set('lives', $lives); + $this->set('childStages', $childStages); + } + + + /** + * Action: submission. + * + * Display all stages with the answers the character has + * choosen. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + */ + public function submission($seminary, $questgroup, $quest, $character) + { + // Get Boss-Fight + $fight = $this->Bossfight->getBossFight($quest['id']); + if(!is_null($fight['boss_seminarymedia_id'])) { + $fight['bossmedia'] = $this->Media->getSeminaryMediaById($fight['boss_seminarymedia_id']); + } + + // Get stages + $stages = array(); + $stage = $this->Bossfight->getFirstStage($quest['id']); + while(!is_null($stage)) + { + $stages[] = $stage; + + $childStages = $this->Bossfight->getChildStages($stage['id']); + $stage = null; + foreach($childStages as &$childStage) + { + if($this->Bossfight->getCharacterSubmission($childStage['id'], $character['id'])) + { + $stage = $childStage; + break; + } + } + } + + // Calculate lives + $stages[0]['lives'] = array( + 'character' => $fight['lives_character'], + 'boss' => $fight['lives_boss'] + ); + for($i=1; $i $stages[$i-1]['lives']['character'] + $stages[$i]['livedrain_character'], + 'boss' => $stages[$i-1]['lives']['boss'] + $stages[$i]['livedrain_boss'], + ); + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('character', $character); + $this->set('fight', $fight); + $this->set('stages', $stages); + } + + + + + /** + * Prepare the session to store stage information in + * + * @param int $questId ID of Quest + */ + private function prepareSession($questId) + { + if(!array_key_exists('quests', $_SESSION)) { + $_SESSION['quests'] = array(); + } + if(!array_key_exists($questId, $_SESSION['quests'])) { + $_SESSION['quests'][$questId] = array(); + } + if(!array_key_exists('stages', $_SESSION['quests'][$questId])) { + $_SESSION['quests'][$questId]['stages'] = array(); + } + } + + } + +?> diff --git a/questtypes/bossfight/BossfightQuesttypeModel.inc b/questtypes/bossfight/BossfightQuesttypeModel.inc new file mode 100644 index 00000000..a05927f0 --- /dev/null +++ b/questtypes/bossfight/BossfightQuesttypeModel.inc @@ -0,0 +1,183 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Model of the BossfightQuesttypeAgent for a boss-fight. + * + * @author Oliver Hanraths + */ + class BossfightQuesttypeModel extends \hhu\z\QuesttypeModel + { + + + + + /** + * Get a Boss-Fight. + * + * @throws IdNotFoundException + * @param int $questId ID of Quest + * @return array Boss-Fight data + */ + public function getBossFight($questId) + { + $data = $this->db->query( + 'SELECT bossname, boss_seminarymedia_id, lives_character, lives_boss '. + 'FROM questtypes_bossfight '. + 'WHERE quest_id = ?', + 'i', + $questId + ); + if(empty($data)) { + throw new \nre\exceptions\IdNotFoundException($questId); + } + + + return $data[0]; + } + + + /** + * Get the first Stage to begin the Boss-Fight with. + * + * @param int $questId ID of Quest + * @return array Data of first Stage + */ + public function getFirstStage($questId) + { + $data = $this->db->query( + 'SELECT id, text, question, livedrain_character, livedrain_boss '. + 'FROM questtypes_bossfight_stages '. + 'WHERE questtypes_bossfight_quest_id = ? AND parent_stage_id IS NULL', + 'i', + $questId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get a Stage by its ID. + * + * @param int $stageId ID of Stage + * @return array Stage data or null + */ + public function getStageById($stageId) + { + $data = $this->db->query( + 'SELECT id, text, question, livedrain_character, livedrain_boss '. + 'FROM questtypes_bossfight_stages '. + 'WHERE id = ?', + 'i', + $stageId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get the follow-up Stages for a Stage. + * + * @param int $parentStageId ID of Stage to get follow-up Stages for + * @return array List of follow-up Stages + */ + public function getChildStages($parentStageId) + { + return $this->db->query( + 'SELECT id, text, question, livedrain_character, livedrain_boss '. + 'FROM questtypes_bossfight_stages '. + 'WHERE parent_stage_id = ?', + 'i', + $parentStageId + ); + } + + + /** + * Reset all Character submissions of a Boss-Fight. + * + * @param int $questId ID of Quest + * @param int $characterId ID of Character + */ + public function clearCharacterSubmissions($questId, $characterId) + { + $this->db->query( + 'DELETE FROM questtypes_bossfight_stages_characters '. + 'WHERE questtypes_bossfight_stage_id IN ('. + 'SELECT id '. + 'FROM questtypes_bossfight_stages '. + 'WHERE questtypes_bossfight_quest_id = ?'. + ') AND character_id = ?', + 'ii', + $questId, + $characterId + ); + } + + + /** + * Save Character’s submitted answer for one Boss-Fight-Stage. + * + * @param int $regexId ID of list + * @param int $characterId ID of Character + */ + public function setCharacterSubmission($stageId, $characterId) + { + $this->db->query( + 'INSERT INTO questtypes_bossfight_stages_characters '. + '(questtypes_bossfight_stage_id, character_id) '. + 'VALUES '. + '(?, ?) '. + 'ON DUPLICATE KEY UPDATE '. + 'questtypes_bossfight_stage_id = ?', + 'iii', + $stageId, $characterId, $stageId + ); + } + + + /** + * Get answer of one Boss-Fight-Stage submitted by Character. + * + * @param int $regexId ID of list + * @param int $characterId ID of Character + * @return boolean Stage taken + */ + public function getCharacterSubmission($stageId, $characterId) + { + $data = $this->db->query( + 'SELECT questtypes_bossfight_stage_id '. + 'FROM questtypes_bossfight_stages_characters '. + 'WHERE questtypes_bossfight_stage_id = ? AND character_id = ? ', + 'ii', + $stageId, $characterId + ); + + + return (!empty($data)); + } + + } + +?> diff --git a/questtypes/bossfight/html/quest.tpl b/questtypes/bossfight/html/quest.tpl new file mode 100644 index 00000000..e97569c2 --- /dev/null +++ b/questtypes/bossfight/html/quest.tpl @@ -0,0 +1,51 @@ +

+
+

+

+

+ 0) : ?> + + + + + + +

+
+
+

+

+

+ 0) : ?> + + + + + + +

+
+
+ +

+ +
+ +
    + +
  • +

    + + +

    +

    +
  • + + +
  • + + +
  • + +
+
diff --git a/questtypes/bossfight/html/submission.tpl b/questtypes/bossfight/html/submission.tpl new file mode 100644 index 00000000..3696964e --- /dev/null +++ b/questtypes/bossfight/html/submission.tpl @@ -0,0 +1,38 @@ +
+
+

+
+
+

+
+
+ + +

+
+
+

+

+ 0) : ?> + + + + + + +

+
+
+

+

+ 0) : ?> + + + + + + +

+
+
+ diff --git a/questtypes/choiceinput/ChoiceinputQuesttypeAgent.inc b/questtypes/choiceinput/ChoiceinputQuesttypeAgent.inc new file mode 100644 index 00000000..b418f01e --- /dev/null +++ b/questtypes/choiceinput/ChoiceinputQuesttypeAgent.inc @@ -0,0 +1,24 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * QuesttypeAgent for choosing between predefined input values. + * + * @author Oliver Hanraths + */ + class ChoiceinputQuesttypeAgent extends \hhu\z\QuesttypeAgent + { + } + +?> diff --git a/questtypes/choiceinput/ChoiceinputQuesttypeController.inc b/questtypes/choiceinput/ChoiceinputQuesttypeController.inc new file mode 100644 index 00000000..be75b852 --- /dev/null +++ b/questtypes/choiceinput/ChoiceinputQuesttypeController.inc @@ -0,0 +1,171 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Controller of the ChoiceinputQuesttypeAgent for choosing between + * predefined input values. + * + * @author Oliver Hanraths + */ + class ChoiceinputQuesttypeController extends \hhu\z\QuesttypeController + { + + + + + /** + * Save the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get lists + $choiceLists = $this->Choiceinput->getChoiceinputLists($quest['id']); + + // Save answers + foreach($choiceLists as &$list) + { + $pos = intval($list['number']) - 1; + $answer = (array_key_exists($pos, $answers)) ? $answers[$pos] : null; + $this->Choiceinput->setCharacterSubmission($list['id'], $character['id'], $answer); + } + } + + + /** + * Save additional data for the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $data Additional (POST-) data + */ + public function saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $data) + { + } + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @return boolean True/false for a right/wrong answer or null for moderator evaluation + */ + public function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get lists + $choiceLists = $this->Choiceinput->getChoiceinputLists($quest['id']); + + // Match lists with user answers + foreach($choiceLists as $i => &$list) + { + if(!array_key_exists($i, $answers)) { + return false; + } + if($list['questtypes_choiceinput_choice_id'] != $answers[$i]) { + return false; + } + } + + + // All answer right + return true; + } + + + /** + * Action: quest. + * + * Display a text with lists with predefined values. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param Exception $exception Character submission exception + */ + public function quest($seminary, $questgroup, $quest, $character, $exception) + { + // Get Task + $task = $this->Choiceinput->getChoiceinputQuest($quest['id']); + + // Process text + $textParts = preg_split('/(\$\$)/', ' '.$task['text'].' '); + + // Get lists + $choiceLists = $this->Choiceinput->getChoiceinputLists($quest['id']); + foreach($choiceLists as &$list) { + $list['values'] = $this->Choiceinput->getChoiceinputChoices($list['id']); + } + + // Get Character answers + if($this->request->getGetParam('show-answer') == 'true') + { + foreach($choiceLists as &$list) { + $list['answer'] = $this->Choiceinput->getCharacterSubmission($list['id'], $character['id']); + } + } + + + // Pass data to view + $this->set('texts', $textParts); + $this->set('choiceLists', $choiceLists); + } + + + /** + * Action: submission. + * + * Show the submission of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + */ + public function submission($seminary, $questgroup, $quest, $character) + { + // Get Task + $task = $this->Choiceinput->getChoiceinputQuest($quest['id']); + + // Process text + $textParts = preg_split('/(\$\$)/', ' '.$task['text'].' '); + + // Get lists + $choiceLists = $this->Choiceinput->getChoiceinputLists($quest['id']); + foreach($choiceLists as &$list) + { + $list['values'] = $this->Choiceinput->getChoiceinputChoices($list['id']); + $list['answer'] = $this->Choiceinput->getCharacterSubmission($list['id'], $character['id']); + $list['right'] = ($list['questtypes_choiceinput_choice_id'] == $list['answer']); + } + + + // Pass data to view + $this->set('texts', $textParts); + $this->set('choiceLists', $choiceLists); + } + + } + +?> diff --git a/questtypes/choiceinput/ChoiceinputQuesttypeModel.inc b/questtypes/choiceinput/ChoiceinputQuesttypeModel.inc new file mode 100644 index 00000000..e908600f --- /dev/null +++ b/questtypes/choiceinput/ChoiceinputQuesttypeModel.inc @@ -0,0 +1,153 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Model of the ChoiceinputQuesttypeAgent for choosing between + * predefined input values. + * + * @author Oliver Hanraths + */ + class ChoiceinputQuesttypeModel extends \hhu\z\QuesttypeModel + { + + + + + /** + * Get choiceinput-text for a Quest. + * + * @param int $questId ID of Quest + * @return array Choiceinput-text + */ + public function getChoiceinputQuest($questId) + { + $data = $this->db->query( + 'SELECT text '. + 'FROM questtypes_choiceinput '. + 'WHERE quest_id = ?', + 'i', + $questId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get all lists of input values for a choiceinput-text. + * + * @param int $questId ID of Quest + * @return array List + */ + public function getChoiceinputLists($questId) + { + return $this->db->query( + 'SELECT id, number, questtypes_choiceinput_choice_id '. + 'FROM questtypes_choiceinput_lists '. + 'WHERE questtypes_choiceinput_quest_id = ? '. + 'ORDER BY number ASC', + 'i', + $questId + ); + } + + + /** + * Get the list of values for a choiceinput-list. + * + * @param int $listId ID of list + * @return array Input values + */ + public function getChoiceinputChoices($listId) + { + return $this->db->query( + 'SELECT id, pos, text '. + 'FROM questtypes_choiceinput_choices '. + 'WHERE questtypes_choiceinput_list_id = ? '. + 'ORDER BY pos ASC', + 'i', + $listId + ); + } + + + /** + * Save Character’s submitted answer for one choiceinput-list. + * + * @param int $regexId ID of list + * @param int $characterId ID of Character + * @param string $answer Submitted answer for this list + */ + public function setCharacterSubmission($listId, $characterId, $answer) + { + if(is_null($answer)) + { + $this->db->query( + 'INSERT INTO questtypes_choiceinput_lists_characters '. + '(questtypes_choiceinput_list_id, character_id, questtypes_choiceinput_choice_id) '. + 'VALUES '. + '(?, ?, NULL) '. + 'ON DUPLICATE KEY UPDATE '. + 'questtypes_choiceinput_choice_id = NULL', + 'ii', + $listId, $characterId + ); + } + else + { + $this->db->query( + 'INSERT INTO questtypes_choiceinput_lists_characters '. + '(questtypes_choiceinput_list_id, character_id, questtypes_choiceinput_choice_id) '. + 'VALUES '. + '(?, ?, ?) '. + 'ON DUPLICATE KEY UPDATE '. + 'questtypes_choiceinput_choice_id = ?', + 'iiii', + $listId, $characterId, $answer, $answer + ); + } + } + + + /** + * Get answer of one choiceinput-list submitted by Character. + * + * @param int $regexId ID of list + * @param int $characterId ID of Character + * @return int Submitted answer for this list or null + */ + public function getCharacterSubmission($listId, $characterId) + { + $data = $this->db->query( + 'SELECT questtypes_choiceinput_choice_id '. + 'FROM questtypes_choiceinput_lists_characters '. + 'WHERE questtypes_choiceinput_list_id = ? AND character_id = ? ', + 'ii', + $listId, $characterId + ); + if(!empty($data)) { + return $data[0]['questtypes_choiceinput_choice_id']; + } + + + return null; + } + + } + +?> diff --git a/questtypes/choiceinput/html/quest.tpl b/questtypes/choiceinput/html/quest.tpl new file mode 100644 index 00000000..489cbc2f --- /dev/null +++ b/questtypes/choiceinput/html/quest.tpl @@ -0,0 +1,15 @@ +
+ &$text) : ?> + 0) : ?> + + + t($text)?> + + +

+ +
diff --git a/questtypes/choiceinput/html/submission.tpl b/questtypes/choiceinput/html/submission.tpl new file mode 100644 index 00000000..40f60505 --- /dev/null +++ b/questtypes/choiceinput/html/submission.tpl @@ -0,0 +1,12 @@ +
+ &$text) : ?> + 0) : ?> + + + t($text)?> + +
diff --git a/questtypes/crossword/CrosswordQuesttypeAgent.inc b/questtypes/crossword/CrosswordQuesttypeAgent.inc new file mode 100644 index 00000000..9b137fe9 --- /dev/null +++ b/questtypes/crossword/CrosswordQuesttypeAgent.inc @@ -0,0 +1,24 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * QuesttypeAgent for solving a crossword. + * + * @author Oliver Hanraths + */ + class CrosswordQuesttypeAgent extends \hhu\z\QuesttypeAgent + { + } + +?> diff --git a/questtypes/crossword/CrosswordQuesttypeController.inc b/questtypes/crossword/CrosswordQuesttypeController.inc new file mode 100644 index 00000000..318271f9 --- /dev/null +++ b/questtypes/crossword/CrosswordQuesttypeController.inc @@ -0,0 +1,365 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Controller of the CrosswordQuesttypeAgent for solving a crossword. + * + * @author Oliver Hanraths + */ + class CrosswordQuesttypeController extends \hhu\z\QuesttypeController + { + + + + + /** + * Save the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get words + $words = $this->Crossword->getWordsForQuest($quest['id']); + + // Iterate words + foreach($words as &$word) + { + // Assemble answer for word + $answer = ''; + if($word['vertical']) + { + $x = $word['pos_x']; + $startY = $word['pos_y']; + $endY = $startY + mb_strlen($word['word'], 'UTF-8') - 1; + + foreach(range($startY, $endY) as $y) + { + if(array_key_exists($x, $answers) && array_key_exists($y, $answers[$x]) && !empty($answers[$x][$y])) { + $answer .= $answers[$x][$y]; + } + else { + $answer .= ' '; + } + } + } + else + { + $startX = $word['pos_x']; + $endX = $startX + mb_strlen($word['word'], 'UTF-8') - 1; + $y = $word['pos_y']; + + foreach(range($startX, $endX) as $x) + { + if(array_key_exists($x, $answers) && array_key_exists($y, $answers[$x]) && !empty($answers[$x][$y])) { + $answer .= $answers[$x][$y]; + } + else { + $answer .= ' '; + } + } + } + + // Save answer + $this->Crossword->setCharacterSubmission($word['id'], $character['id'], $answer); + } + } + + + /** + * Save additional data for the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $data Additional (POST-) data + */ + public function saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $data) + { + } + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @return boolean True/false for a right/wrong answer or null for moderator evaluation + */ + public function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get words + $words = $this->Crossword->getWordsForQuest($quest['id']); + + // Iterate words + foreach($words as &$word) + { + // Assemble answer for word + $answer = ''; + if($word['vertical']) + { + $x = $word['pos_x']; + $startY = $word['pos_y']; + $endY = $startY + mb_strlen($word['word'], 'UTF-8') - 1; + + foreach(range($startY, $endY) as $y) + { + if(array_key_exists($x, $answers) && array_key_exists($y, $answers[$x]) && !empty($answers[$x][$y])) { + $answer .= $answers[$x][$y]; + } + else { + $answer .= ' '; + } + } + } + else + { + $startX = $word['pos_x']; + $endX = $startX + mb_strlen($word['word'], 'UTF-8') - 1; + $y = $word['pos_y']; + + foreach(range($startX, $endX) as $x) + { + if(array_key_exists($x, $answers) && array_key_exists($y, $answers[$x]) && !empty($answers[$x][$y])) { + $answer .= $answers[$x][$y]; + } + else { + $answer .= ' '; + } + } + } + + // Check answer + if(mb_strtolower($word['word'], 'UTF-8') != mb_strtolower($answer, 'UTF-8')) { + return false; + } + } + + + // All answer right + return true; + } + + + /** + * Action: quest. + * + * Display a text with lists with predefined values. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param Exception $exception Character submission exception + */ + public function quest($seminary, $questgroup, $quest, $character, $exception) + { + // Get words + $words = $this->Crossword->getWordsForQuest($quest['id']); + + // Create 2D-matrix + $matrix = array(); + $maxX = 0; + $maxY = 0; + foreach($words as $index => &$word) + { + if($this->request->getGetParam('show-answer') == 'true') { + $word['answer'] = $this->Crossword->getCharacterSubmission($word['id'], $character['id']); + } + // Insert word + if($word['vertical']) + { + $x = $word['pos_x']; + $startY = $word['pos_y']; + $endY = $startY + mb_strlen($word['word'], 'UTF-8') - 1; + + $matrix = array_pad($matrix, $x+1, array()); + $matrix[$x] = array_pad($matrix[$x], $endY+1, null); + $maxX = max($maxX, $x); + $maxY = max($maxY, $endY); + + foreach(range($startY, $endY) as $y) + { + $matrix[$x][$y] = array( + 'char' => mb_substr($word['word'], $y-$startY, 1, 'UTF-8'), + 'indices' => (array_key_exists($x, $matrix) && array_key_exists($y, $matrix[$x]) && !is_null($matrix[$x][$y]) && array_key_exists('indices', $matrix[$x][$y])) ? $matrix[$x][$y]['indices'] : array(), + 'answer' => null + ); + if($y == $startY) { + $matrix[$x][$y]['indices'][] = $index; + } + if(array_key_exists('answer', $word)) + { + $answer = mb_substr($word['answer'], $y-$startY, 1, 'UTF-8'); + if($answer != ' ') { + $matrix[$x][$y]['answer'] = $answer; + } + } + } + } + else + { + $startX = $word['pos_x']; + $endX = $startX + mb_strlen($word['word'], 'UTF-8') - 1; + $y = $word['pos_y']; + + $matrix = array_pad($matrix, $endX+1, array()); + $maxX = max($maxX, $endX); + $maxY = max($maxY, $y); + + foreach(range($startX, $endX) as $x) + { + $matrix[$x] = array_pad($matrix[$x], $y+1, null); + + $matrix[$x][$y] = array( + 'char' => mb_substr($word['word'], $x-$startX, 1, 'UTF-8'), + 'indices' => (array_key_exists($x, $matrix) && array_key_exists($y, $matrix[$x]) && !is_null($matrix[$x][$y]) && array_key_exists('indices', $matrix[$x][$y])) ? $matrix[$x][$y]['indices'] : array(), + 'answer' => null + ); + if($x == $startX) { + $matrix[$x][$y]['indices'][] = $index; + } + if(array_key_exists('answer', $word)) + { + $answer = mb_substr($word['answer'], $x-$startX, 1, 'UTF-8'); + if($answer != ' ') { + $matrix[$x][$y]['answer'] = $answer; + } + } + } + } + } + + + // Pass data to view + $this->set('words', $words); + $this->set('maxX', $maxX); + $this->set('maxY', $maxY); + $this->set('matrix', $matrix); + } + + + /** + * Action: submission. + * + * Show the submission of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + */ + public function submission($seminary, $questgroup, $quest, $character) + { + // Get words + $words = $this->Crossword->getWordsForQuest($quest['id']); + + // Create 2D-matrix + $matrix = array(); + $maxX = 0; + $maxY = 0; + foreach($words as $index => &$word) + { + // Character answer + $word['answer'] = $this->Crossword->getCharacterSubmission($word['id'], $character['id']); + + // Insert word + if($word['vertical']) + { + $x = $word['pos_x']; + $startY = $word['pos_y']; + $endY = $startY + mb_strlen($word['word'], 'UTF-8') - 1; + + $matrix = array_pad($matrix, $x+1, array()); + $matrix[$x] = array_pad($matrix[$x], $endY+1, null); + $maxX = max($maxX, $x); + $maxY = max($maxY, $endY); + + foreach(range($startY, $endY) as $y) + { + $matrix[$x][$y] = array( + 'char' => mb_substr($word['word'], $y-$startY, 1, 'UTF-8'), + 'indices' => (array_key_exists($x, $matrix) && array_key_exists($y, $matrix[$x]) && !is_null($matrix[$x][$y]) && array_key_exists('indices', $matrix[$x][$y])) ? $matrix[$x][$y]['indices'] : array(), + 'answer' => null, + 'right' => false + ); + if($y == $startY) { + $matrix[$x][$y]['indices'][] = $index; + } + + if(!is_null($word['answer'])) + { + $answer = mb_substr($word['answer'], $y-$startY, 1, 'UTF-8'); + if($answer != ' ') + { + $matrix[$x][$y]['answer'] = $answer; + $matrix[$x][$y]['right'] = (mb_strtolower($matrix[$x][$y]['char'], 'UTF-8') == mb_strtolower($answer, 'UTF-8')); + } + } + } + } + else + { + $startX = $word['pos_x']; + $endX = $startX + mb_strlen($word['word'], 'UTF-8') - 1; + $y = $word['pos_y']; + + $matrix = array_pad($matrix, $endX+1, array()); + $maxX = max($maxX, $endX); + $maxY = max($maxY, $y); + + foreach(range($startX, $endX) as $x) + { + $matrix[$x] = array_pad($matrix[$x], $y+1, null); + + $matrix[$x][$y] = array( + 'char' => mb_substr($word['word'], $x-$startX, 1, 'UTF-8'), + 'indices' => (array_key_exists($x, $matrix) && array_key_exists($y, $matrix[$x]) && !is_null($matrix[$x][$y]) && array_key_exists('indices', $matrix[$x][$y])) ? $matrix[$x][$y]['indices'] : array(), + 'answer' => null, + 'right' => false + ); + if($x == $startX) { + $matrix[$x][$y]['indices'][] = $index; + } + if(!is_null($word['answer'])) + { + $answer = mb_substr($word['answer'], $x-$startX, 1, 'UTF-8'); + if($answer != ' ') + { + $matrix[$x][$y]['answer'] = $answer; + $matrix[$x][$y]['right'] = (mb_strtolower($matrix[$x][$y]['char'], 'UTF-8') == mb_strtolower($answer, 'UTF-8')); + } + } + } + } + } + + + // Pass data to view + $this->set('words', $words); + $this->set('maxX', $maxX); + $this->set('maxY', $maxY); + $this->set('matrix', $matrix); + } + + } + +?> diff --git a/questtypes/crossword/CrosswordQuesttypeModel.inc b/questtypes/crossword/CrosswordQuesttypeModel.inc new file mode 100644 index 00000000..78a42821 --- /dev/null +++ b/questtypes/crossword/CrosswordQuesttypeModel.inc @@ -0,0 +1,93 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Model of the CrosswordQuesttypeAgent for solving a crossword. + * + * @author Oliver Hanraths + */ + class CrosswordQuesttypeModel extends \hhu\z\QuesttypeModel + { + + + + + /** + * Get all words for a crossword-Quest. + * + * @param int $questId ID of Quest + * @return array Words + */ + public function getWordsForQuest($questId) + { + return $this->db->query( + 'SELECT id, question, word, vertical, pos_x, pos_y '. + 'FROM questtypes_crossword_words '. + 'WHERE quest_id = ? ', + 'i', + $questId + ); + } + + + /** + * Save Character’s submitted answer for one crossword-word. + * + * @param int $regexId ID of word + * @param int $characterId ID of Character + * @param string $answer Submitted answer for this word + */ + public function setCharacterSubmission($wordId, $characterId, $answer) + { + $this->db->query( + 'INSERT INTO questtypes_crossword_words_characters '. + '(questtypes_crossword_word_id, character_id, answer) '. + 'VALUES '. + '(?, ?, ?) '. + 'ON DUPLICATE KEY UPDATE '. + 'answer = ?', + 'iiss', + $wordId, $characterId, $answer, + $answer + ); + } + + + /** + * Get answer of one crossword-word submitted by Character. + * + * @param int $regexId ID of lisword + * @param int $characterId ID of Character + * @return int Submitted answer for this word or null + */ + public function getCharacterSubmission($wordId, $characterId) + { + $data = $this->db->query( + 'SELECT answer '. + 'FROM questtypes_crossword_words_characters '. + 'WHERE questtypes_crossword_word_id = ? AND character_id = ? ', + 'ii', + $wordId, $characterId + ); + if(!empty($data)) { + return $data[0]['answer']; + } + + + return null; + } + + } + +?> diff --git a/questtypes/crossword/html/quest.tpl b/questtypes/crossword/html/quest.tpl new file mode 100644 index 00000000..d202eb8f --- /dev/null +++ b/questtypes/crossword/html/quest.tpl @@ -0,0 +1,49 @@ +
+ + + + + + + + + + +
+ + 0) : ?> + + +
+
    + +
  1. + + : + + : + + +
  2. + +
+ +
+ diff --git a/questtypes/crossword/html/submission.tpl b/questtypes/crossword/html/submission.tpl new file mode 100644 index 00000000..c47e414d --- /dev/null +++ b/questtypes/crossword/html/submission.tpl @@ -0,0 +1,48 @@ +
+ + + + + + + + + + +
+ + 0) : ?> + + +
+
    + +
  1. + + : + + : + + +
  2. + +
+
+ diff --git a/questtypes/dragndrop/DragndropQuesttypeAgent.inc b/questtypes/dragndrop/DragndropQuesttypeAgent.inc new file mode 100644 index 00000000..c7d0abdf --- /dev/null +++ b/questtypes/dragndrop/DragndropQuesttypeAgent.inc @@ -0,0 +1,24 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * QuesttypeAgent for Drag&Drop. + * + * @author Oliver Hanraths + */ + class DragndropQuesttypeAgent extends \hhu\z\QuesttypeAgent + { + } + +?> diff --git a/questtypes/dragndrop/DragndropQuesttypeController.inc b/questtypes/dragndrop/DragndropQuesttypeController.inc new file mode 100644 index 00000000..9797f8fd --- /dev/null +++ b/questtypes/dragndrop/DragndropQuesttypeController.inc @@ -0,0 +1,217 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Controller of the DragndropQuesttypeAgent for Drag&Drop. + * + * @author Oliver Hanraths + */ + class DragndropQuesttypeController extends \hhu\z\QuesttypeController + { + /** + * Required models + * + * @var array + */ + public $models = array('media'); + + + + + /** + * Save the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get Drag&Drop field + $dndField = $this->Dragndrop->getDragndrop($quest['id']); + + // Get Drops + $drops = $this->Dragndrop->getDrops($dndField['quest_id']); + + // Save user answers + foreach($drops as &$drop) + { + // Determine user answer + $answer = null; + if(array_key_exists($drop['id'], $answers) && !empty($answers[$drop['id']])) + { + $a = intval(substr($answers[$drop['id']], 4)); + if($a !== false && $a > 0) { + $answer = $a; + } + } + + // Update database record + $this->Dragndrop->setCharacterSubmission($drop['id'], $character['id'], $answer); + } + } + + + /** + * Save additional data for the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $data Additional (POST-) data + */ + public function saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $data) + { + } + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @return boolean True/false for a right/wrong answer or null for moderator evaluation + */ + public function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get Drag&Drop field + $dndField = $this->Dragndrop->getDragndrop($quest['id']); + + // Get Drags + $drags = $this->Dragndrop->getDrags($dndField['quest_id'], true); + + // Match drags with user answers + foreach($drags as &$drag) + { + $founds = array_keys($answers, 'drag'.$drag['id']); + if(count($founds) != 1) { + return false; + } + if(!$this->Dragndrop->dragMatchesDrop($drag['id'], $founds[0])) { + return false; + } + } + + + // Set status + return true; + } + + + /** + * Action: quest. + * + * Display a text with input fields and evaluate if user input + * matches with stored regular expressions. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param Exception $exception Character submission exception + */ + public function quest($seminary, $questgroup, $quest, $character, $exception) + { + // Get Drag&Drop field + $dndField = $this->Dragndrop->getDragndrop($quest['id']); + $dndField['media'] = $this->Media->getSeminaryMediaById($dndField['questmedia_id']); + + // Get Drags + $drags = array(); + $dragsByIndex = $this->Dragndrop->getDrags($dndField['quest_id']); + foreach($dragsByIndex as &$drag) { + $drag['media'] = $this->Media->getSeminaryMediaById($drag['questmedia_id']); + $drags[$drag['id']] = $drag; + } + + // Get Drops + $drops = $this->Dragndrop->getDrops($dndField['quest_id']); + + // Get Character answers + if($this->request->getGetParam('show-answer') == 'true') + { + foreach($drops as &$drop) + { + $drop['answer'] = $this->Dragndrop->getCharacterSubmission($drop['id'], $character['id']); + if(!is_null($drop['answer'])) + { + $drop['answer'] = $drags[$drop['answer']]; + unset($drags[$drop['answer']['id']]); + } + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('field', $dndField); + $this->set('drops', $drops); + $this->set('drags', $drags); + } + + + /** + * Action: submission. + * + * Show the submission of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + */ + public function submission($seminary, $questgroup, $quest, $character) + { + // Get Drag&Drop field + $dndField = $this->Dragndrop->getDragndrop($quest['id']); + $dndField['media'] = $this->Media->getSeminaryMediaById($dndField['questmedia_id']); + + // Get Drags + $drags = array(); + $dragsByIndex = $this->Dragndrop->getDrags($dndField['quest_id']); + foreach($dragsByIndex as &$drag) { + $drag['media'] = $this->Media->getSeminaryMediaById($drag['questmedia_id']); + $drags[$drag['id']] = $drag; + } + + // Get Drops + $drops = $this->Dragndrop->getDrops($dndField['quest_id']); + + // Get Character answers + foreach($drops as &$drop) + { + $drop['answer'] = $this->Dragndrop->getCharacterSubmission($drop['id'], $character['id']); + if(!is_null($drop['answer'])) + { + $drop['answer'] = $drags[$drop['answer']]; + unset($drags[$drop['answer']['id']]); + } + } + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('field', $dndField); + $this->set('drops', $drops); + $this->set('drags', $drags); + } + + } + +?> diff --git a/questtypes/dragndrop/DragndropQuesttypeModel.inc b/questtypes/dragndrop/DragndropQuesttypeModel.inc new file mode 100644 index 00000000..2aadb335 --- /dev/null +++ b/questtypes/dragndrop/DragndropQuesttypeModel.inc @@ -0,0 +1,180 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Model of the DragndropQuesttypeAgent for Drag&Drop. + * + * @author Oliver Hanraths + */ + class DragndropQuesttypeModel extends \hhu\z\QuesttypeModel + { + + + + + /** + * Get Drag&Drop-field. + * + * @param int $questId ID of Quest + * @return array Drag&Drop-field + */ + public function getDragndrop($questId) + { + $data = $this->db->query( + 'SELECT quest_id, questmedia_id, width, height '. + 'FROM questtypes_dragndrop '. + 'WHERE quest_id = ?', + 'i', + $questId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get Drop-items. + * + * @param int $dragndropId ID of Drag&Drop-field + * @return array Drop-items + */ + public function getDrops($dragndropId) + { + return $this->db->query( + 'SELECT id, top, `left`, width, height '. //, questtypes_dragndrop_drag_id '. + 'FROM questtypes_dragndrop_drops '. + 'WHERE questtypes_dragndrop_id = ?', + 'i', + $dragndropId + ); + } + + + /** + * Get Drag-items. + * + * @param int $dragndropId ID of Drag&Drop-field + * @param boolean $onlyUsed Only Drag-items that are used for a Drop-item + * @return array Drag-items + */ + public function getDrags($dragndropId, $onlyUsed=false) + { + return $this->db->query( + 'SELECT id, questmedia_id '. + 'FROM questtypes_dragndrop_drags '. + 'WHERE questtypes_dragndrop_id = ?'. + ($onlyUsed + ? ' AND EXISTS ('. + 'SELECT questtypes_dragndrop_drag_id '. + 'FROM questtypes_dragndrop_drops_drags '. + 'WHERE questtypes_dragndrop_drag_id = questtypes_dragndrop_drags.id'. + ')' + : null + ), + 'i', + $dragndropId + ); + } + + + /** + * Check if a Drag-item mathes a Drop-item. + * + * @param int $dragId ID of Drag-field + * @param int $dropId ID of Drop-field + * @return boolean Drag-item is valid for Drop-item + */ + public function dragMatchesDrop($dragId, $dropId) + { + $data = $this->db->query( + 'SELECT count(*) AS c '. + 'FROM questtypes_dragndrop_drops_drags '. + 'WHERE questtypes_dragndrop_drop_id = ? AND questtypes_dragndrop_drag_id = ?', + 'ii', + $dropId, $dragId + ); + if(!empty($data)) { + return ($data[0]['c'] > 0); + } + + + return false; + } + + + /** + * Save Character’s submitted answer for one Drop-field. + * + * @param int $dropId ID of Drop-field + * @param int $characterId ID of Character + * @param string $answer Submitted Drag-field-ID for this field + */ + public function setCharacterSubmission($dropId, $characterId, $answer) + { + if(is_null($answer)) + { + $this->db->query( + 'DELETE FROM questtypes_dragndrop_drops_characters '. + 'WHERE questtypes_dragndrop_drop_id = ? AND character_id = ?', + 'ii', + $dropId, $characterId + ); + } + else + { + $this->db->query( + 'INSERT INTO questtypes_dragndrop_drops_characters '. + '(questtypes_dragndrop_drop_id, character_id, questtypes_dragndrop_drag_id) '. + 'VALUES '. + '(?, ?, ?) '. + 'ON DUPLICATE KEY UPDATE '. + 'questtypes_dragndrop_drag_id = ?', + 'iiii', + $dropId, $characterId, $answer, $answer + ); + } + } + + + /** + * Get Character’s saved answer for one Drop-field. + * + * @param int $dropId ID of Drop-field + * @param int $characterId ID of Character + * @return int ID of Drag-field or null + */ + public function getCharacterSubmission($dropId, $characterId) + { + $data = $this->db->query( + 'SELECT questtypes_dragndrop_drag_id '. + 'FROM questtypes_dragndrop_drops_characters '. + 'WHERE questtypes_dragndrop_drop_id = ? AND character_id = ?', + 'ii', + $dropId, $characterId + ); + if(!empty($data)) { + return $data[0]['questtypes_dragndrop_drag_id']; + } + + + return null; + } + + } + +?> diff --git a/questtypes/dragndrop/html/quest.tpl b/questtypes/dragndrop/html/quest.tpl new file mode 100644 index 00000000..4cb10b60 --- /dev/null +++ b/questtypes/dragndrop/html/quest.tpl @@ -0,0 +1,17 @@ +
+
+ +
+ + +
+ +
+ + + +
+ +
+ +
diff --git a/questtypes/dragndrop/html/submission.tpl b/questtypes/dragndrop/html/submission.tpl new file mode 100644 index 00000000..c136fe1e --- /dev/null +++ b/questtypes/dragndrop/html/submission.tpl @@ -0,0 +1,15 @@ +
+ +
+ + + +
+ +
+ +
+ + + +
diff --git a/questtypes/multiplechoice/MultiplechoiceQuesttypeAgent.inc b/questtypes/multiplechoice/MultiplechoiceQuesttypeAgent.inc new file mode 100644 index 00000000..2789c5f6 --- /dev/null +++ b/questtypes/multiplechoice/MultiplechoiceQuesttypeAgent.inc @@ -0,0 +1,24 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * QuesttypeAgent for multiple choice. + * + * @author Oliver Hanraths + */ + class MultiplechoiceQuesttypeAgent extends \hhu\z\QuesttypeAgent + { + } + +?> diff --git a/questtypes/multiplechoice/MultiplechoiceQuesttypeController.inc b/questtypes/multiplechoice/MultiplechoiceQuesttypeController.inc new file mode 100644 index 00000000..b025f758 --- /dev/null +++ b/questtypes/multiplechoice/MultiplechoiceQuesttypeController.inc @@ -0,0 +1,276 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Controller of the MultiplechoiceQuesttypeAgent multiple choice. + * + * @author Oliver Hanraths + */ + class MultiplechoiceQuesttypeController extends \hhu\z\QuesttypeController + { + + + + + /** + * Save the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Save temporary user answer of last question + $answers = (!is_array($answers)) ? array() : $answers; + $pos = $this->Multiplechoice->getQuestionsCountOfQuest($quest['id']); + $question = $this->Multiplechoice->getQuestionOfQuest($quest['id'], $pos); + $this->saveUserAnswers($quest['id'], $question['id'], $answers); + + // Save answers + $questions = $this->Multiplechoice->getQuestionsOfQuest($quest['id']); + foreach($questions as &$question) + { + $userAnswers = $this->getUserAnswers($quest['id'], $question['id']); + $answers = $this->Multiplechoice->getAnswersOfQuestion($question['id']); + foreach($answers as &$answer) + { + $userAnswer = (array_key_exists($answer['pos']-1, $userAnswers)) ? true : false; + $this->Multiplechoice->setCharacterSubmission($answer['id'], $character['id'], $userAnswer); + } + } + } + + + /** + * Save additional data for the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $data Additional (POST-) data + */ + public function saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $data) + { + } + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @return boolean True/false for a right/wrong answer or null for moderator evaluation + */ + public function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Save temporary user answer of last question + $answers = (!is_array($answers)) ? array() : $answers; + $pos = $this->Multiplechoice->getQuestionsCountOfQuest($quest['id']); + $question = $this->Multiplechoice->getQuestionOfQuest($quest['id'], $pos); + $this->saveUserAnswers($quest['id'], $question['id'], $answers); + + // Get questions + $questions = $this->Multiplechoice->getQuestionsOfQuest($quest['id']); + + // Iterate questions + foreach($questions as &$question) + { + // Get answers + $userAnswers = $this->getUserAnswers($quest['id'], $question['id']); + $answers = $this->Multiplechoice->getAnswersOfQuestion($question['id']); + + // Match answers with user answers + foreach($answers as &$answer) + { + if(is_null($answer['tick'])) { + continue; + } + if($answer['tick']) { + if(!array_key_exists($answer['pos']-1, $userAnswers)) { + return false; + } + } + else { + if(array_key_exists($answer['pos']-1, $userAnswers)) { + return false; + } + } + } + } + + + // All questions correct answerd + return true; + } + + + /** + * Action: quest. + * + * Display questions with a checkbox to let the user choose the + * right ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param Exception $exception Character submission exception + */ + public function quest($seminary, $questgroup, $quest, $character, $exception) + { + // Get count of questions + $count = $this->Multiplechoice->getQuestionsCountOfQuest($quest['id']); + + // Get position + $pos = 1; + if($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('submit-answer'))) + { + if(!is_null($this->request->getPostParam('question'))) + { + // Get current position + $pos = intval($this->request->getPostParam('question')); + if($pos < 0 || $pos > $count) { + throw new \nre\exceptions\ParamsNotValidException($pos); + } + + // Save temporary answer of user + $question = $this->Multiplechoice->getQuestionOfQuest($quest['id'], $pos); + $answers = ($this->request->getRequestMethod() == 'POST' && !is_null($this->request->getPostParam('answers'))) ? $this->request->getPostParam('answers') : array(); + $this->saveUserAnswers($quest['id'], $question['id'], $answers); + + // Go to next position + $pos++; + } + else { + throw new \nre\exceptions\ParamsNotValidException('pos'); + } + } + + // Get current question + $question = $this->Multiplechoice->getQuestionOfQuest($quest['id'], $pos); + + // Get answers + $question['answers'] = $this->Multiplechoice->getAnswersOfQuestion($question['id']); + + + // Get previous user answers + if($this->request->getGetParam('show-answer') == 'true') + { + foreach($question['answers'] as &$answer) { + $answer['useranswer'] = $this->Multiplechoice->getCharacterSubmission($answer['id'], $character['id']); + } + } + + + // Pass data to view + $this->set('question', $question); + $this->set('pos', $pos); + $this->set('count', $count); + } + + + /** + * Action: submission. + * + * Show the submission of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + */ + public function submission($seminary, $questgroup, $quest, $character) + { + // Get questions + $questions = $this->Multiplechoice->getQuestionsOfQuest($quest['id']); + + // Get answers + foreach($questions as &$question) + { + $question['answers'] = $this->Multiplechoice->getAnswersOfQuestion($question['id']); + + // Get user answers + foreach($question['answers'] as &$answer) { + $answer['useranswer'] = $this->Multiplechoice->getCharacterSubmission($answer['id'], $character['id']); + } + } + + + // Pass data to view + $this->set('questions', $questions); + } + + + + + /** + * Save the answers of a user for a question temporary in the + * session. + * + * @param int $questId ID of Quest + * @param int $questionId ID of multiple choice question + * @param array $userAnswers Answers of user for the question + */ + private function saveUserAnswers($questId, $questionId, $userAnswers) + { + // Ensure session structure + if(!array_key_exists('answers', $_SESSION)) { + $_SESSION['answers'] = array(); + } + if(!array_key_exists($questId, $_SESSION['answers'])) { + $_SESSION['answers'][$questId] = array(); + } + $_SESSION['answers'][$questId][$questionId] = array(); + + // Save answres + foreach($userAnswers as $pos => &$answer) { + $_SESSION['answers'][$questId][$questionId][$pos] = $answer; + } + } + + + /** + * Get the temporary saved answers of a user for a question. + * + * @param int $questId ID of Quest + * @param int $questionId ID of multiple choice question + * @return array Answers of user for the question + */ + private function getUserAnswers($questId, $questionId) + { + // Ensure session structure + if(!array_key_exists('answers', $_SESSION)) { + $_SESSION['answers'] = array(); + } + if(!array_key_exists($questId, $_SESSION['answers'])) { + $_SESSION['answers'][$questId] = array(); + } + if(!array_key_exists($questionId, $_SESSION['answers'][$questId])) { + $_SESSION['answers'][$questId][$questionId] = array(); + } + + + // Return answers + return $_SESSION['answers'][$questId][$questionId]; + } + + } + +?> diff --git a/questtypes/multiplechoice/MultiplechoiceQuesttypeModel.inc b/questtypes/multiplechoice/MultiplechoiceQuesttypeModel.inc new file mode 100644 index 00000000..6c4931b7 --- /dev/null +++ b/questtypes/multiplechoice/MultiplechoiceQuesttypeModel.inc @@ -0,0 +1,159 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Model of the MultiplechoiceQuesttypeAgent for multiple choice. + * + * @author Oliver Hanraths + */ + class MultiplechoiceQuesttypeModel extends \hhu\z\QuesttypeModel + { + + + + + /** + * Get the count of multiple choice questions for a Quest. + * + * @param int $questId ID of Quest to get count for + * @return int Conut of questions + */ + public function getQuestionsCountOfQuest($questId) + { + $data = $this->db->query( + 'SELECT count(id) AS c '. + 'FROM questtypes_multiplechoice '. + 'WHERE quest_id = ?', + 'i', + $questId + ); + if(!empty($data)) { + return $data[0]['c']; + } + + + return 0; + } + + + /** + * Get all multiple choice questions of a Quest. + * + * @param int $questId ID of Quest + * @return array Multiple choice questions + */ + public function getQuestionsOfQuest($questId) + { + return $this->db->query( + 'SELECT id, pos, question '. + 'FROM questtypes_multiplechoice '. + 'WHERE quest_id = ?', + 'i', + $questId + ); + } + + + /** + * Get one multiple choice question of a Quest. + * + * @param int $questId ID of Quest + * @param int $pos Position of question + * @return array Question data + */ + public function getQuestionOfQuest($questId, $pos) + { + $data = $this->db->query( + 'SELECT id, pos, question '. + 'FROM questtypes_multiplechoice '. + 'WHERE quest_id = ? AND pos = ?', + 'ii', + $questId, $pos + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get all answers of a multiple choice question. + * + * @param int $questionId ID of multiple choice question + * @return array Answers of question + */ + public function getAnswersOfQuestion($questionId) + { + return $this->db->query( + 'SELECT id, pos, answer, tick '. + 'FROM questtypes_multiplechoice_answers '. + 'WHERE questtypes_multiplechoice_id = ?', + 'i', + $questionId + ); + } + + + /** + * Save Character’s submitted answer for one option. + * + * @param int $answerId ID of multiple choice answer + * @param int $characterId ID of Character + * @param boolean $answer Submitted answer for this option + */ + public function setCharacterSubmission($answerId, $characterId, $answer) + { + $this->db->query( + 'INSERT INTO questtypes_multiplechoice_characters '. + '(questtypes_multiplechoice_answer_id, character_id, ticked) '. + 'VALUES '. + '(?, ?, ?) '. + 'ON DUPLICATE KEY UPDATE '. + 'ticked = ?', + 'iiii', + $answerId, $characterId, $answer, $answer + ); + } + + + /** + * Get answer of one option submitted by Character. + * + * @param int $answerId ID of multiple choice answer + * @param int $characterId ID of Character + * @return boolean Submitted answer of Character or false + */ + public function getCharacterSubmission($answerId, $characterId) + { + $data = $this->db->query( + 'SELECT ticked '. + 'FROM questtypes_multiplechoice_characters '. + 'WHERE questtypes_multiplechoice_answer_id = ? AND character_id = ? ', + 'ii', + $answerId, $characterId + ); + if(!empty($data)) { + return $data[0]['ticked']; + } + + + return false; + } + + } + +?> diff --git a/questtypes/multiplechoice/html/quest.tpl b/questtypes/multiplechoice/html/quest.tpl new file mode 100644 index 00000000..eba80286 --- /dev/null +++ b/questtypes/multiplechoice/html/quest.tpl @@ -0,0 +1,21 @@ +
+
+ : +

+
    + &$answer) : ?> +
  1. + /> + +
  2. + +
+
+ + + + + + + +
diff --git a/questtypes/multiplechoice/html/submission.tpl b/questtypes/multiplechoice/html/submission.tpl new file mode 100644 index 00000000..ebbbd493 --- /dev/null +++ b/questtypes/multiplechoice/html/submission.tpl @@ -0,0 +1,16 @@ +
    + &$question) : ?> +
  1. +

    t($question['question'])?>

    +
      + +
    1. + + × + +
    2. + +
    +
  2. + +
diff --git a/questtypes/submit/SubmitQuesttypeAgent.inc b/questtypes/submit/SubmitQuesttypeAgent.inc new file mode 100644 index 00000000..68ca9ae5 --- /dev/null +++ b/questtypes/submit/SubmitQuesttypeAgent.inc @@ -0,0 +1,24 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * QuesttypeAgent for submitting. + * + * @author Oliver Hanraths + */ + class SubmitQuesttypeAgent extends \hhu\z\QuesttypeAgent + { + } + +?> diff --git a/questtypes/submit/SubmitQuesttypeController.inc b/questtypes/submit/SubmitQuesttypeController.inc new file mode 100644 index 00000000..e064c542 --- /dev/null +++ b/questtypes/submit/SubmitQuesttypeController.inc @@ -0,0 +1,221 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Controller of the SubmitQuesttypeAgent for a submit task. + * + * @author Oliver Hanraths + */ + class SubmitQuesttypeController extends \hhu\z\QuesttypeController + { + /** + * Required models + * + * @var array + */ + public $models = array('quests', 'uploads', 'users'); + + + + + /** + * Save the answers of a Character for a Quest. + * + * @throws SubmissionNotValidException + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Save answer + if(array_key_exists('answers', $_FILES)) + { + $answer = $_FILES['answers']; + + // Check error + if($answer['error'] !== 0) { + throw new \hhu\z\exceptions\SubmissionNotValidException( + new \hhu\z\exceptions\FileUploadException($answer['error']) + ); + } + + // Check mimetype + $mimetypes = $this->Submit->getAllowedMimetypes($seminary['id']); + $answerMimetype = null; + foreach($mimetypes as &$mimetype) { + if($mimetype['mimetype'] == $answer['type']) { + $answerMimetype = $mimetype; + break; + } + } + if(is_null($answerMimetype)) { + throw new \hhu\z\exceptions\SubmissionNotValidException( + new \hhu\z\exceptions\WrongFiletypeException($answer['type']) + ); + } + + // Check file size + if($answer['size'] > $answerMimetype['size']) { + throw new \hhu\z\exceptions\SubmissionNotValidException( + new \hhu\z\exceptions\MaxFilesizeException() + ); + } + + // Create filename + $filename = sprintf( + '%s,%s,%s.%s', + $character['url'], + mb_substr($quest['url'], 0, 32), + date('Ymd-His'), + mb_substr(mb_substr($answer['name'], strrpos($answer['name'], '.')+1), 0, 4) + ); + + // Save file + if(!$this->Submit->setCharacterSubmission($seminary['id'], $quest['id'], $this->Auth->getUserId(), $character['id'], $answer, $filename)) { + throw new \hhu\z\exceptions\SubmissionNotValidException( + new \hhu\z\exceptions\FileUploadException(error_get_last()['message']) + ); + } + } + } + + + /** + * Save additional data for the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $data Additional (POST-) data + */ + public function saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $data) + { + $this->Submit->addCommentForCharacterAnswer($this->Auth->getUserId(), $data['submission_id'], $data['comment']); + } + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + * @return boolean True/false for a right/wrong answer or null for moderator evaluation + */ + public function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // A moderator has to evaluate the answer + return null; + } + + + /** + * Action: quest. + * + * Display a big textbox to let the user enter a text that has + * to be evaluated by a moderator. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param Exception $exception Character submission exception + */ + public function quest($seminary, $questgroup, $quest, $character, $exception) + { + // Answers (Submissions) + $characterSubmissions = $this->Submit->getCharacterSubmissions($quest['id'], $character['id']); + foreach($characterSubmissions as &$submission) + { + $submission['upload'] = $this->Uploads->getSeminaryuploadById($submission['upload_id']); + $submission['comments'] = $this->Submit->getCharacterSubmissionComments($submission['id']); + foreach($submission['comments'] as &$comment) + { + try { + $comment['user'] = $this->Users->getUserById($comment['created_user_id']); + $comment['user']['character'] = $this->Characters->getCharacterForUserAndSeminary($comment['user']['id'], $seminary['id']); + } + catch(\nre\exceptions\IdNotFoundException $e) { + } + } + } + + // Has Character already solved Quest? + $solved = $this->Quests->hasCharacterSolvedQuest($quest['id'], $character['id']); + + // Last Quest status for Character + $lastStatus = $this->Quests->getLastQuestStatus($quest['id'], $character['id']); + + // Get allowed mimetypes + $mimetypes = $this->Submit->getAllowedMimetypes($seminary['id']); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('submissions', $characterSubmissions); + $this->set('solved', $solved); + $this->set('lastStatus', $lastStatus); + $this->set('mimetypes', $mimetypes); + $this->set('exception', $exception); + } + + + /** + * Action: submission. + * + * Show the submission of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + */ + public function submission($seminary, $questgroup, $quest, $character) + { + // Get Character submissions + $submissions = $this->Submit->getCharacterSubmissions($quest['id'], $character['id']); + foreach($submissions as &$submission) + { + $submission['upload'] = $this->Uploads->getSeminaryuploadById($submission['upload_id']); + $submission['comments'] = $this->Submit->getCharacterSubmissionComments($submission['id']); + foreach($submission['comments'] as &$comment) + { + try { + $comment['user'] = $this->Users->getUserById($comment['created_user_id']); + $comment['user']['character'] = $this->Characters->getCharacterForUserAndSeminary($comment['user']['id'], $seminary['id']); + } + catch(\nre\exceptions\IdNotFoundException $e) { + } + } + } + + // Status + $solved = $this->Quests->hasCharacterSolvedQuest($quest['id'], $character['id']); + + + // Pass data to view + $this->set('seminary', $seminary); + $this->set('submissions', $submissions); + $this->set('solved', $solved); + } + + } + +?> diff --git a/questtypes/submit/SubmitQuesttypeModel.inc b/questtypes/submit/SubmitQuesttypeModel.inc new file mode 100644 index 00000000..5d821655 --- /dev/null +++ b/questtypes/submit/SubmitQuesttypeModel.inc @@ -0,0 +1,142 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Model of the SubmitQuesttypeAgent for a submit task. + * + * @author Oliver Hanraths + */ + class SubmitQuesttypeModel extends \hhu\z\QuesttypeModel + { + /** + * Required models + * + * @var array + */ + public $models = array('uploads'); + + + + + /** + * Save Character’s submitted upload. + * + * @param int $seminaryId ID of Seminary + * @param int $questId ID of Quest + * @param int $characterId ID of Character + * @param array $file Submitted upload + */ + public function setCharacterSubmission($seminaryId, $questId, $userId, $characterId, $file, $filename) + { + // Save file on harddrive + $uploadId = $this->Uploads->uploadSeminaryFile($userId, $seminaryId, $file['name'], $filename, $file['tmp_name'], $file['type']); + if($uploadId === false) { + return false; + } + + // Create database record + $this->db->query( + 'INSERT INTO questtypes_submit_characters '. + '(quest_id, character_id, upload_id) '. + 'VALUES '. + '(?, ?, ?) ', + 'iii', + $questId, $characterId, $uploadId + ); + + + return true; + } + + + /** + * Add a comment for the answer of a Character. + * + * @param int $userId ID of user that comments + * @param int $submissionId ID of Character answer to comment + * @param string $comment Comment text + */ + public function addCommentForCharacterAnswer($userId, $submissionId, $comment) + { + $this->db->query( + 'INSERT INTO questtypes_submit_characters_comments '. + '(created_user_id, questtypes_submit_character_id, comment) '. + 'VALUES '. + '(?, ?, ?)', + 'iis', + $userId, + $submissionId, + $comment + ); + } + + + /** + * Get all uploads submitted by Character. + * + * @param int $questId ID of Quest + * @param int $characterId ID of Character + * @return array Text submitted by Character or NULL + */ + public function getCharacterSubmissions($questId, $characterId) + { + return $this->db->query( + 'SELECT id, created, upload_id '. + 'FROM questtypes_submit_characters '. + 'WHERE quest_id = ? AND character_id = ? '. + 'ORDER BY created ASC', + 'ii', + $questId, $characterId + ); + } + + + /** + * Get allowed mimetypes for uploading a file. + * + * @param int $seminaryId ID of Seminary + * @return array Allowed mimetypes + */ + public function getAllowedMimetypes($seminaryId) + { + return $this->db->query( + 'SELECT id, mimetype, size '. + 'FROM questtypes_submit_mimetypes '. + 'WHERE seminary_id = ?', + 'i', + $seminaryId + ); + } + + + /** + * Get all comments for a Character submission. + * + * @param int $characterSubmissionId ID of Character submission + * @return array Comments for this submission + */ + public function getCharacterSubmissionComments($characterSubmissionId) + { + return $this->db->query( + 'SELECT id, created, created_user_id, comment '. + 'FROM questtypes_submit_characters_comments '. + 'WHERE questtypes_submit_character_id = ?', + 'i', + $characterSubmissionId + ); + } + + } + +?> diff --git a/questtypes/submit/html/quest.tpl b/questtypes/submit/html/quest.tpl new file mode 100644 index 00000000..4581c40d --- /dev/null +++ b/questtypes/submit/html/quest.tpl @@ -0,0 +1,52 @@ + +

+ getNestedException() instanceof \hhu\z\exceptions\WrongFiletypeException) : ?> + getNestedException()->getType())?> + getNestedException() instanceof \hhu\z\exceptions\WrongFiletypeException) : ?> + + getNestedException() instanceof \hhu\z\exceptions\FileUploadException) : ?> + getNestedException()->getNestedMessage())?> + + getNestedException()->getMessage()?> + +

+ + $submissions[count($submissions)-1]['created'])) : ?> +
+ +

:

+
    + +
  • 0) : ?>( format(round($mimetype['size']/(1024*1024),2))?> MiB)
  • + +
+ +
+ + + 0) : ?> +

+
    + +
  1. +
    + format(new \DateTime($submission['created'])), $timeFormatter->format(new \DateTime($submission['created'])))?>
    + + + + 0) : ?> +
      + +
    1. + + format(new \DateTime($comment['created'])), $timeFormatter->format(new \DateTime($comment['created'])))?>:
      + + +
    2. + +
    + +
  2. + +
+ diff --git a/questtypes/submit/html/submission.tpl b/questtypes/submit/html/submission.tpl new file mode 100644 index 00000000..f3c748ef --- /dev/null +++ b/questtypes/submit/html/submission.tpl @@ -0,0 +1,33 @@ + 0) : ?> +
    + +
  1. +
    + format(new \DateTime($submission['created'])), $timeFormatter->format(new \DateTime($submission['created'])))?>
    + 0) : ?> +
      + +
    1. + + format(new \DateTime($comment['created'])), $timeFormatter->format(new \DateTime($comment['created'])))?>:
      + + +
    2. + +
    + +
  2. + +
+ + +
+ + +
+
+ + + + +
diff --git a/questtypes/textinput/TextinputQuesttypeAgent.inc b/questtypes/textinput/TextinputQuesttypeAgent.inc new file mode 100644 index 00000000..8144c148 --- /dev/null +++ b/questtypes/textinput/TextinputQuesttypeAgent.inc @@ -0,0 +1,24 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * QuesttypeAgent for inserting text. + * + * @author Oliver Hanraths + */ + class TextinputQuesttypeAgent extends \hhu\z\QuesttypeAgent + { + } + +?> diff --git a/questtypes/textinput/TextinputQuesttypeController.inc b/questtypes/textinput/TextinputQuesttypeController.inc new file mode 100644 index 00000000..02db2d8b --- /dev/null +++ b/questtypes/textinput/TextinputQuesttypeController.inc @@ -0,0 +1,185 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Controller of the TextinputQuesttypeAgent for for inserting text. + * + * @author Oliver Hanraths + */ + class TextinputQuesttypeController extends \hhu\z\QuesttypeController + { + + + + + /** + * Save the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $answers Character answers for the Quest + */ + public function saveAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get regexs + $regexs = $this->Textinput->getTextinputRegexs($quest['id']); + + // Save answers + foreach($regexs as &$regex) + { + $pos = intval($regex['number']) - 1; + $answer = (array_key_exists($pos, $answers)) ? $answers[$pos] : ''; + $this->Textinput->setCharacterSubmission($regex['id'], $character['id'], $answer); + } + } + + + /** + * Save additional data for the answers of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param array $data Additional (POST-) data + */ + public function saveDataForCharacterAnswers($seminary, $questgroup, $quest, $character, $data) + { + } + + + /** + * Check if answers of a Character for a Quest match the correct ones. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @return boolean True/false for a right/wrong answer or null for moderator evaluation + */ + public function matchAnswersOfCharacter($seminary, $questgroup, $quest, $character, $answers) + { + // Get right answers + $regexs = $this->Textinput->getTextinputRegexs($quest['id']); + + // Match regexs with user answers + $allSolved = true; + foreach($regexs as $i => &$regex) + { + if(!array_key_exists($i, $answers)) + { + $allSolved = false; + break; + } + + if(!$this->isMatching($regex['regex'], $answers[$i])) + { + $allSolved = false; + break; + } + } + + + // Set status + return $allSolved; + } + + + /** + * Action: quest. + * + * Display a text with input fields and evaluate if user input + * matches with stored regular expressions. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + * @param Exception $exception Character submission exception + */ + public function quest($seminary, $questgroup, $quest, $character, $exception) + { + // Get Task + $task = $this->Textinput->getTextinputQuest($quest['id']); + + // Process text + $textParts = preg_split('/(\$\$)/', ' '.$task['text'].' ', -1, PREG_SPLIT_NO_EMPTY); + + // Get Character answers + $regexs = null; + if($this->request->getGetParam('show-answer') == 'true') + { + $regexs = $this->Textinput->getTextinputRegexs($quest['id']); + foreach($regexs as &$regex) { + $regex['answer'] = $this->Textinput->getCharacterSubmission($regex['id'], $character['id']); + } + } + + // Has Character already solved Quest? + $solved = $this->Quests->hasCharacterSolvedQuest($quest['id'], $character['id']); + + + // Pass data to view + $this->set('texts', $textParts); + $this->set('regexs', $regexs); + } + + + /** + * Action: submission. + * + * Show the submission of a Character for a Quest. + * + * @param array $seminary Current Seminary data + * @param array $questgroup Current Questgroup data + * @param array $quest Current Quest data + * @param array $character Current Character data + */ + public function submission($seminary, $questgroup, $quest, $character) + { + // Get Task + $task = $this->Textinput->getTextinputQuest($quest['id']); + + // Process text + $textParts = preg_split('/(\$\$)/', $task['text'], -1, PREG_SPLIT_NO_EMPTY); + + // Get Character answers + $regexs = $this->Textinput->getTextinputRegexs($quest['id']); + foreach($regexs as &$regex) { + $regex['answer'] = $this->Textinput->getCharacterSubmission($regex['id'], $character['id']); + $regex['right'] = $this->isMatching($regex['regex'], $regex['answer']); + } + + + // Pass data to view + $this->set('texts', $textParts); + $this->set('regexs', $regexs); + } + + + + + private function isMatching($regex, $answer) + { + $score = preg_match($regex, $answer); + + + return ($score !== false && $score > 0); + } + + } + +?> diff --git a/questtypes/textinput/TextinputQuesttypeModel.inc b/questtypes/textinput/TextinputQuesttypeModel.inc new file mode 100644 index 00000000..3d00d038 --- /dev/null +++ b/questtypes/textinput/TextinputQuesttypeModel.inc @@ -0,0 +1,117 @@ + + * @copyright 2014 Heinrich-Heine-Universität Düsseldorf + * @license http://www.gnu.org/licenses/gpl.html + * @link https://bitbucket.org/coderkun/the-legend-of-z + */ + + namespace hhu\z\questtypes; + + + /** + * Model of the TextinputQuesttypeAgent for inserting text. + * + * @author Oliver Hanraths + */ + class TextinputQuesttypeModel extends \hhu\z\QuesttypeModel + { + + + + + /** + * Get textinput-text for a Quest. + * + * @param int $questId ID of Quest + * @return array Textinput-text + */ + public function getTextinputQuest($questId) + { + $data = $this->db->query( + 'SELECT text '. + 'FROM questtypes_textinput '. + 'WHERE quest_id = ?', + 'i', + $questId + ); + if(!empty($data)) { + return $data[0]; + } + + + return null; + } + + + /** + * Get regular expressions for a textinput-text. + * + * @param int $questId ID of Quest + * @return array Regexs + */ + public function getTextinputRegexs($questId) + { + return $this->db->query( + 'SELECT id, number, regex '. + 'FROM questtypes_textinput_regexs '. + 'WHERE questtypes_textinput_quest_id = ? '. + 'ORDER BY number ASC', + 'i', + $questId + ); + } + + + /** + * Save Character’s submitted answer for one textinput field. + * + * @param int $regexId ID of regex + * @param int $characterId ID of Character + * @param string $answer Submitted answer for this field + */ + public function setCharacterSubmission($regexId, $characterId, $answer) + { + $this->db->query( + 'INSERT INTO questtypes_textinput_regexs_characters '. + '(questtypes_textinput_regex_id, character_id, value) '. + 'VALUES '. + '(?, ?, ?) '. + 'ON DUPLICATE KEY UPDATE '. + 'value = ?', + 'iiss', + $regexId, $characterId, $answer, $answer + ); + } + + + /** + * Get answer of one regex input field submitted by Character. + * + * @param int $regexId ID of regex + * @param int $characterId ID of Character + * @return string Submitted answer for this field or empty string + */ + public function getCharacterSubmission($regexId, $characterId) + { + $data = $this->db->query( + 'SELECT value '. + 'FROM questtypes_textinput_regexs_characters '. + 'WHERE questtypes_textinput_regex_id = ? AND character_id = ? ', + 'ii', + $regexId, $characterId + ); + if(!empty($data)) { + return $data[0]['value']; + } + + + return ''; + } + + } + +?> diff --git a/questtypes/textinput/html/quest.tpl b/questtypes/textinput/html/quest.tpl new file mode 100644 index 00000000..6616ba4d --- /dev/null +++ b/questtypes/textinput/html/quest.tpl @@ -0,0 +1,11 @@ +
+

+ &$text) : ?> + 0) : ?> + + + t($text)?> + +

+ +
diff --git a/questtypes/textinput/html/submission.tpl b/questtypes/textinput/html/submission.tpl new file mode 100644 index 00000000..b3d606f4 --- /dev/null +++ b/questtypes/textinput/html/submission.tpl @@ -0,0 +1,7 @@ + &$text) : ?> + 0) : ?> + + + +t($text)?> + diff --git a/requests/WebRequest.inc b/requests/WebRequest.inc new file mode 100644 index 00000000..2e0f19b9 --- /dev/null +++ b/requests/WebRequest.inc @@ -0,0 +1,401 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\requests; + + + /** + * Representation of a web-request. + * + * @author coderkun + */ + class WebRequest extends \nre\core\Request + { + /** + * Passed GET-parameters + * + * @var array + */ + private $getParams = array(); + /** + * Passed POST-parameters + * + * @var array + */ + private $postParams = array(); + /** + * Stored routes + * + * @var array + */ + private $routes = array(); + /** + * Stored reverse-routes + * + * @var array + */ + private $reverseRoutes = array(); + + + + + /** + * Construct a new web-request. + */ + public function __construct() + { + // Detect current request + $this->detectRequest(); + + // Load GET-parameters + $this->loadGetParams(); + + // Load POST-parameters + $this->loadPostParams(); + + // Detect AJAX + $this->detectAJAX(); + } + + + + + /** + * Get a parameter. + * + * @param int $index Index of parameter + * @param string $defaultIndex Index of default configuration value for this parameter + * @return string Value of parameter + */ + public function getParam($index, $defaultIndex=null) + { + if($index == 0) { + return $this->getGetParam('layout', $defaultIndex); + } + else { + return parent::getParam($index-1, $defaultIndex); + } + } + + + /** + * Get all parameters from index on. + * + * @param int $offset Offset-index + * @return array Parameters + */ + public function getParams($offset=0) + { + if($offset == 0) + { + return array_merge( + array( + $this->getGetParam('layout', 'toplevel') + ), + parent::getParams() + ); + } + + + return array_slice($this->params, $offset-1); + } + + + /** + * Get a GET-parameter. + * + * @param string $key Key of GET-parameter + * @param string $defaultIndex Index of default configuration value for this parameter + * @return string Value of GET-parameter + */ + public function getGetParam($key, $defaultIndex=null) + { + // Check key + if(array_key_exists($key, $this->getParams)) + { + // Return value + return $this->getParams[$key]; + } + + + // Return default value + return \nre\core\Config::getDefault($defaultIndex); + } + + + /** + * Get all GET-parameters. + * + * @return array GET-Parameters + */ + public function getGetParams() + { + return $this->getParams; + } + + + /** + * Get a POST-parameter. + * + * @param string $key Key of POST-parameter + * @param string $defaultValue Default value for this parameter + * @return string Value of POST-parameter + */ + public function getPostParam($key, $defaultValue=null) + { + // Check key + if(array_key_exists($key, $this->postParams)) + { + // Return value + return $this->postParams[$key]; + } + + + // Return default value + return $defaultValue; + } + + + /** + * Get all POST-parameters. + * + * @return array POST-parameters + */ + public function getPostParams() + { + return $this->postParams; + } + + + /** + * Get the method of the current request. + * + * @return string Current request method + */ + public function getRequestMethod() + { + return $_SERVER['REQUEST_METHOD']; + } + + + /** + * Add a URL-route. + * + * @param string $pattern Regex-Pattern that defines the routing + * @param string $replacement Regex-Pattern for replacement + * @param bool $isLast Stop after that rule + */ + public function addRoute($pattern, $replacement, $isLast=false) + { + // Store route + $this->routes[] = $this->newRoute($pattern, $replacement, $isLast); + } + + + /** + * Add a reverse URL-route. + * + * @param string $pattern Regex-Pattern that defines the reverse routing + * @param string $replacement Regex-Pattern for replacement + * @param bool $isLast Stop after that rule + */ + public function addReverseRoute($pattern, $replacement, $isLast=false) + { + // Store reverse route + $this->reverseRoutes[] = $this->newRoute($pattern, $replacement, $isLast); + } + + + /** + * Apply stored reverse-routes to an URL + * + * @param string $url URL to apply reverse-routes to + * @return string Reverse-routed URL + */ + public function applyReverseRoutes($url) + { + return $this->applyRoutes($url, $this->reverseRoutes); + } + + + /** + * Revalidate the current request + */ + public function revalidate() + { + $this->detectRequest(); + } + + + /** + * Get a SERVER-parameter. + * + * @param string $key Key of SERVER-parameter + * @return string Value of SERVER-parameter + */ + public function getServerParam($key) + { + if(array_key_exists($key, $_SERVER)) { + return $_SERVER[$key]; + } + + + return null; + } + + + + + /** + * Detect the current HTTP-request. + */ + private function detectRequest() + { + // URL ermitteln + $url = isset($_GET) && array_key_exists('url', $_GET) ? $_GET['url'] : ''; + $url = trim($url, '/'); + + // Routes anwenden + $url = $this->applyRoutes($url, $this->routes); + + // URL splitten + $params = explode('/', $url); + if(empty($params[0])) { + $params = array(); + } + + + // Parameter speichern + $this->params = $params; + } + + + /** + * Determine parameters passed by GET. + */ + private function loadGetParams() + { + if(isset($_GET)) { + $this->getParams = $_GET; + } + } + + + /** + * Determine parameters passed by POST. + */ + private function loadPostParams() + { + if(isset($_POST)) { + $this->postParams = $_POST; + } + } + + + /** + * Detect an AJAX-request by checking the X-Requested-With + * header and set the layout to 'ajax' in this case. + */ + private function detectAjax() + { + // Get request headers + $headers = apache_request_headers(); + + // Check X-Requested-With header and set layout + if(array_key_exists('X-Requested-With', $headers) && $headers['X-Requested-With'] == 'XMLHttpRequest') { + if(!array_key_exists('layout', $this->getParams)) { + $this->getParams['layout'] = 'ajax'; + } + } + } + + + /** + * Create a new URL-route. + * + * @param string $pattern Regex-Pattern that defines the reverse routing + * @param string $replacement Regex-Pattern for replacement + * @param bool $isLast Stop after that rule + * @return array New URL-route + */ + private function newRoute($pattern, $replacement, $isLast=false) + { + return array( + 'pattern' => $pattern, + 'replacement' => $replacement, + 'isLast' => $isLast + ); + } + + + /** + * Apply given routes to an URL + * + * @param string $url URL to apply routes to + * @param array $routes Routes to apply + * @return string Routed URL + */ + private function applyRoutes($url, $routes) + { + // Traverse given routes + foreach($routes as &$route) + { + // Create and apply Regex + $urlR = preg_replace( + '>'.$route['pattern'].'>i', + $route['replacement'], + $url + ); + + // Split URL + $get = ''; + if(($gpos = strrpos($urlR, '?')) !== false) { + $get = substr($urlR, $gpos+1); + $urlR = substr($urlR, 0, $gpos); + } + + // Has current route changed anything? + if($urlR != $url || !empty($get)) + { + // Extract GET-parameters + if(strlen($get) > 0) + { + $gets = explode('&', $get); + foreach($gets as $get) + { + $get = explode('=', $get); + if(!array_key_exists($get[0], $this->getParams)) { + $this->getParams[$get[0]] = $get[1]; + } + } + } + + + // Stop when route “isLast” + if($route['isLast']) { + $url = $urlR; + break; + } + } + + + // Set new URL + $url = $urlR; + } + + + // Return routed URL + return $url; + } + + } + +?> diff --git a/responses/WebResponse.inc b/responses/WebResponse.inc new file mode 100644 index 00000000..2ca25079 --- /dev/null +++ b/responses/WebResponse.inc @@ -0,0 +1,250 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + namespace nre\responses; + + + /** + * Representation of a web-response. + * + * @author coderkun + */ + class WebResponse extends \nre\core\Response + { + /** + * Applied GET-parameters + * + * @var array + */ + private $getParams = array(); + /** + * Changed header lines + * + * @var array + */ + private $headers = array(); + + + + + /** + * Add a parameter. + * + * @param mixed $value Value of parameter + */ + public function addParam($value) + { + if(array_key_exists('layout', $this->getParams)) { + parent::addParam($value); + } + else { + $this->addGetParam('layout', $value); + } + } + + + /** + * Add multiple parameters. + * + * @param mixed $value1 Value of first parameter + * @param mixed … Values of further parameters + */ + public function addParams($value1) + { + $this->addParam($value1); + + $this->params = array_merge( + $this->params, + array_slice( + func_get_args(), + 1 + ) + ); + } + + + /** + * Delete all stored parameters (from offset on). + * + * @param int $offset Offset-index + */ + public function clearParams($offset=0) + { + if($offset == 0) { + unset($this->getParams['layout']); + } + + parent::clearParams(max(0, $offset-1)); + } + + + /** + * Get a parameter. + * + * @param int $index Index of parameter + * @param string $defaultIndex Index of default configuration value for this parameter + * @return string Value of parameter + */ + public function getParam($index, $defaultIndex=null) + { + if($index == 0) { + return $this->getGetParam('layout', $defaultIndex); + } + else { + return parent::getParam($index-1, $defaultIndex); + } + } + + + /** + * Get all parameters from index on. + * + * @param int $offset Offset-index + * @return array Parameter values + */ + public function getParams($offset=0) + { + if($offset == 0) + { + if(!array_key_exists('layout', $this->getParams)) { + return array(); + } + + return array_merge( + array( + $this->getParams['layout'] + ), + $this->params + ); + } + + + return array_slice($this->params, $offset-1); + } + + + /** + * Add a GET-parameter. + * + * @param string $key Key of GET-parameter + * @param mixed $value Value of GET-parameter + */ + public function addGetParam($key, $value) + { + $this->getParams[$key] = $value; + } + + + /** + * Add multiple GET-parameters. + * + * @param array $params Associative arary with key-value GET-parameters + */ + public function addGetParams($params) + { + $this->getParams = array_merge( + $this->getParams, + $params + ); + } + + + /** + * Get a GET-parameter. + * + * @param int $index Index of GET-parameter + * @param string $defaultIndex Index of default configuration value for this parameter + * @return string Value of GET-parameter + */ + public function getGetParam($key, $defaultIndex=null) + { + // Check key + if(array_key_exists($key, $this->getParams)) + { + // Return value + return $this->getParams[$key]; + } + + // Return default value + return \nre\core\Config::getDefault($defaultIndex); + } + + + /** + * Get all GET-parameters. + * + * @return array All GET-parameters + */ + public function getGetParams() + { + return $this->getParams; + } + + + /** + * Add a line to the response header. + * + * @param string $headerLine Header line + * @param bool $replace Replace existing header line + * @param int $http_response_code HTTP-response code + */ + public function addHeader($headerLine, $replace=true, $http_response_code=null) + { + $this->headers[] = $this->newHeader($headerLine, $replace, $http_response_code); + } + + + /** + * Clear all stored headers. + */ + public function clearHeaders() + { + $this->headers = array(); + } + + + /** + * Send stored headers. + */ + public function header() + { + foreach($this->headers as $header) + { + header( + $header['string'], + $header['replace'], + $header['responseCode'] + ); + } + } + + + + + /** + * Create a new header line. + * + * @param string $headerLine Header line + * @param bool $replace Replace existing header line + * @param int $http_response_code HTTP-response code + */ + private function newHeader($headerLine, $replace=true, $http_response_code=null) + { + return array( + 'string' => $headerLine, + 'replace' => $replace, + 'responseCode' => $http_response_code + ); + } + + } + +?> diff --git a/seminarymedia/empty b/seminarymedia/empty new file mode 100644 index 00000000..e69de29b diff --git a/seminaryuploads/empty b/seminaryuploads/empty new file mode 100644 index 00000000..e69de29b diff --git a/tmp/empty b/tmp/empty new file mode 100644 index 00000000..e69de29b diff --git a/uploads/empty b/uploads/empty new file mode 100644 index 00000000..e69de29b diff --git a/views/binary/binary.tpl b/views/binary/binary.tpl new file mode 100644 index 00000000..43a06a51 --- /dev/null +++ b/views/binary/binary.tpl @@ -0,0 +1 @@ + diff --git a/views/binary/error/index.tpl b/views/binary/error/index.tpl new file mode 100644 index 00000000..2d8440b4 --- /dev/null +++ b/views/binary/error/index.tpl @@ -0,0 +1 @@ +: : diff --git a/views/binary/media/achievement.tpl b/views/binary/media/achievement.tpl new file mode 100644 index 00000000..0d6fb0df --- /dev/null +++ b/views/binary/media/achievement.tpl @@ -0,0 +1 @@ + diff --git a/views/binary/media/avatar.tpl b/views/binary/media/avatar.tpl new file mode 100644 index 00000000..0d6fb0df --- /dev/null +++ b/views/binary/media/avatar.tpl @@ -0,0 +1 @@ + diff --git a/views/binary/media/index.tpl b/views/binary/media/index.tpl new file mode 100644 index 00000000..0d6fb0df --- /dev/null +++ b/views/binary/media/index.tpl @@ -0,0 +1 @@ + diff --git a/views/binary/media/seminary.tpl b/views/binary/media/seminary.tpl new file mode 100644 index 00000000..0d6fb0df --- /dev/null +++ b/views/binary/media/seminary.tpl @@ -0,0 +1 @@ + diff --git a/views/binary/media/seminaryheader.tpl b/views/binary/media/seminaryheader.tpl new file mode 100644 index 00000000..0d6fb0df --- /dev/null +++ b/views/binary/media/seminaryheader.tpl @@ -0,0 +1 @@ + diff --git a/views/binary/uploads/seminary.tpl b/views/binary/uploads/seminary.tpl new file mode 100644 index 00000000..0d6fb0df --- /dev/null +++ b/views/binary/uploads/seminary.tpl @@ -0,0 +1 @@ + diff --git a/views/error.tpl b/views/error.tpl new file mode 100644 index 00000000..f45b01a0 --- /dev/null +++ b/views/error.tpl @@ -0,0 +1,13 @@ + + + + + + Service Unavailable + + + +

Die Anwendung steht zur Zeit leider nicht zur Verfügung.

+ + + diff --git a/views/fault/error/index.tpl b/views/fault/error/index.tpl new file mode 100644 index 00000000..f7e42500 --- /dev/null +++ b/views/fault/error/index.tpl @@ -0,0 +1,2 @@ +

Fehler

+

:

diff --git a/views/fault/fault.tpl b/views/fault/fault.tpl new file mode 100644 index 00000000..307b772d --- /dev/null +++ b/views/fault/fault.tpl @@ -0,0 +1,16 @@ + + + + + + The Legend of Z + + + +

The Legend of Z

+
+ +
+ + + diff --git a/views/html/achievements/index.tpl b/views/html/achievements/index.tpl new file mode 100644 index 00000000..2d63d0e2 --- /dev/null +++ b/views/html/achievements/index.tpl @@ -0,0 +1,84 @@ + +
+ +
+ + +

+

+
+
+

+
    + +
  1. + + + + + + + + + +

    +

    +
  2. + +
+
+
+

+
    + +
  1. + +

    +

    +
  2. + +
+
+
+

+
+

+
+ +
+
+

. : .

+
    + +
  • + + + +

    format(new \DateTime($achievement['created'])))?>

    +

    +
  • + + +
  • + + + +

    + +

    + +

    + + +
    +
    + +
    +

    %

    +
    + +
  • + +
diff --git a/views/html/charactergroups/group.tpl b/views/html/charactergroups/group.tpl new file mode 100644 index 00000000..0f6f88d3 --- /dev/null +++ b/views/html/charactergroups/group.tpl @@ -0,0 +1,50 @@ + +
+ +
+ + +
+ +

+

""

+
+
    +
  • .
  • +
  • XP
  • +
  • 1) ? _('Members') : _('Member')?>
  • +
+ +
+

+
    + +
  • + +

    + +

    +

    XP

    +
  • + +
+
+ +
+

+
    + +
  • +

    + format(new \DateTime($quest['created']))?> + + / XP +

    +
  • + +
+
diff --git a/views/html/charactergroups/groupsgroup.tpl b/views/html/charactergroups/groupsgroup.tpl new file mode 100644 index 00000000..3d337cf3 --- /dev/null +++ b/views/html/charactergroups/groupsgroup.tpl @@ -0,0 +1,25 @@ + +
+ +
+ + + +

+ +
    + +
  1. XP
  2. + +
+ + +

+
    + +
  • + +
diff --git a/views/html/charactergroups/index.tpl b/views/html/charactergroups/index.tpl new file mode 100644 index 00000000..d88d25eb --- /dev/null +++ b/views/html/charactergroups/index.tpl @@ -0,0 +1,14 @@ + +
+ +
+ + +

+
    + +
  • + +
diff --git a/views/html/charactergroupsquests/quest.tpl b/views/html/charactergroupsquests/quest.tpl new file mode 100644 index 00000000..6c696737 --- /dev/null +++ b/views/html/charactergroupsquests/quest.tpl @@ -0,0 +1,53 @@ + +
+ +
+ + + +

+Maximale Belohnung: XP + +
+

+

+ + + + +

+ +

+

+ +
+ + +
+

+

+
+ + +
+

+

+
+ + +
+

+
    + +
  • + format(new \DateTime($group['created']))?> + + XP +
  • + +
+
diff --git a/views/html/characters/character.tpl b/views/html/characters/character.tpl new file mode 100644 index 00000000..2fdc870f --- /dev/null +++ b/views/html/characters/character.tpl @@ -0,0 +1,110 @@ + +
+ +
+ + +

+
+
+
+
+ +
+

:  %

+
+
+

+

+
+
+

+

XP

+
+
+

.

+

+
+

+
    + +
  • + +

    + +

    + +

    + +

    +
  • + +
+
+
+ +
+
+ +
+
+

+
    + +
  • + +

     XPs

    +
  • + +
+
+
+

+
    + &$rankCharacter) : ?> +
  • + +

    .

    +

    ( XPs)

    +
  • + +
  • + +

    .

    +

    ( XPs)

    +
  • + &$rankCharacter) : ?> +
  • + +

    .

    +

    ( XPs)

    +
  • + +
+
+
+ +
+

+
    + +
  • +

    (/)

    +
    + +
    +
  • + +
+
+ + + +
+

+

+
+ + diff --git a/views/html/characters/index.tpl b/views/html/characters/index.tpl new file mode 100644 index 00000000..a0c10339 --- /dev/null +++ b/views/html/characters/index.tpl @@ -0,0 +1,23 @@ + +
+ +
+ +

+

+ + 0) : ?> + + + + diff --git a/views/html/characters/manage.tpl b/views/html/characters/manage.tpl new file mode 100644 index 00000000..17a2c82b --- /dev/null +++ b/views/html/characters/manage.tpl @@ -0,0 +1,53 @@ + +
+ +
+ +

+

+

+ +
+
+ +
    + +
  • + checked="checked" disabled="disabled"/> + +
  • + +
+
+
+ + + + + + +
+
+ + + + + + +
+
diff --git a/views/html/characters/register.tpl b/views/html/characters/register.tpl new file mode 100644 index 00000000..cc3bc005 --- /dev/null +++ b/views/html/characters/register.tpl @@ -0,0 +1,84 @@ + +
+ +
+ +

+

+ +
+ +
    + &$settings) : ?> +
  • +
      + $value) : ?> +
    • + +
    • + +
    +
  • + +
+ +
+ + +
+
    + + checked="checked" /> + + +
+
+ + +
    + &$settings) : ?> +
  • + +
+ +
+ + + + + value="" required="required"/> + + + + + +
+ +
+ +
diff --git a/views/html/error/index.tpl b/views/html/error/index.tpl new file mode 100644 index 00000000..a0b02462 --- /dev/null +++ b/views/html/error/index.tpl @@ -0,0 +1,17 @@ +

+

:

+ + +

+
+
+ +
+ +
+
+ + + +
+ diff --git a/views/html/html.tpl b/views/html/html.tpl new file mode 100644 index 00000000..69ab8f63 --- /dev/null +++ b/views/html/html.tpl @@ -0,0 +1,63 @@ + + + + + + The Legend of Z + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + diff --git a/views/html/introduction/index.tpl b/views/html/introduction/index.tpl new file mode 100644 index 00000000..42979a96 --- /dev/null +++ b/views/html/introduction/index.tpl @@ -0,0 +1,50 @@ +

+ +

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

Bereits im Sommersemester 2013 wurde das Projekt „Die Legende von Zyren“ unterstützt durch den Lehrförderungsfond der Heinrich-Heine-Universität Düsseldorf ins Leben gerufen, um die Inhalte der Vorlesung „Wissensrepräsentation“ den Studierenden des Faches Informationswissenschaft mit Hilfe von Spielelementen und -modellen zu vermitteln. Die innovative Lernumgebung besteht aus einem virtuellen Textadventure, dass über eine webbasierte Plattform zugänglich ist und realen Spielen in einer Präsenzveranstaltung, in denen die Studierenden unmittelbar in das Abenteuer eintauchen und in Teams spielerisch gegeneinander antreten.

+

Auf der Plattform spielt sich jeder der Studierenden mit seinem virtuellen Avatar durch das Reich von Zyren und erlernt und vertieft auf spielerische Weise die Inhalte der Vorlesung „Wissensrepräsentation“, die in Form von Herausforderungen oder Rätseln in das Abenteuer eingebunden wurden und über den Fortlauf und den Erfolg des Spiels entscheiden.

+

In der zusätzlichen Präsenzveranstaltung tauchen die Studierenden direkt in das Abenteuer ein und die vertiefen die Inhalte spielerisch. Hier schließen sich die Studierenden in Teams (Gilden) zusammen, müssen eigenverantwortlich Lerninhalte erarbeiten, Probleme lösen und in speziellen Gildenaufgaben gegen andere Teams antreten, um ihr kollaborativ erarbeitetes Wissen auf die Probe zu stellen.

+

Für jede erfolgreiche absolvierte Herausforderung auf der Plattform oder in der Übung erhalten die Studierenden Erfahrungspunkte und Belohnungen.

+

Um das Konzept auch anderen Fachbereichen und Lehrveranstaltungen zugänglich zu machen, wurde im Frühjahr 2014 das Projekt Questlab (Arbeitstitel „The Legend of Z“) gestartet um das Konzept zu generalisieren. Lehrende können die Plattform nun nutzen um eigene Aufgaben (Quests) zu kreieren und hochzuladen und sie optional in eine Geschichte einzubinden, die sie selbst gestalten können. Zudem wurde das Responsive Design überarbeitet und bietet nun optimalen Zugriff auf die Plattform über alle mobilen Endgeräte.

+ +

Die Legende von Zyren in der Presse

+ + +

Das Team

+

Projektleitung:

+
    +
  • Kathrin Knautz
  • +
+

Entwicklung und Evaluation des Prototypens:

+
    +
  • Lisa Orszullok
  • +
  • Simone Soubusta
  • +
  • Julia Göretz
  • +
  • Anja Wintermeyer
  • +
+

Entwicklung „The Legend of Z“:

+
    +
  • Oliver Hanraths
  • +
  • Daniel Miskovic
  • +
diff --git a/views/html/library/index.tpl b/views/html/library/index.tpl new file mode 100644 index 00000000..2dd6cd09 --- /dev/null +++ b/views/html/library/index.tpl @@ -0,0 +1,28 @@ + +
+ +
+ + +

+

+
+

0) ? $numberFormatter->format(round($totalCharacterQuestcount/$totalQuestcount*100)) : 0) ?>

+
+ +
+
+
    + +
  • + +

    +

    Fortschritt: /

    +
    + +
    +
  • + +
diff --git a/views/html/library/topic.tpl b/views/html/library/topic.tpl new file mode 100644 index 00000000..b68d2a67 --- /dev/null +++ b/views/html/library/topic.tpl @@ -0,0 +1,30 @@ + +
+ +
+ + +

+
+

Themenfortschritt: /

+
+ +
+
+ +

Quests zu diesem Thema:

+
    + +
  • +

    +
      + +
    • + +
    +
  • + +
diff --git a/views/html/menu/index.tpl b/views/html/menu/index.tpl new file mode 100644 index 00000000..0e9d4aea --- /dev/null +++ b/views/html/menu/index.tpl @@ -0,0 +1,9 @@ +
  • The Legend of Z
  • + 0) : ?>
  • +
  • + 0) : ?> + +
  • + +
  • + diff --git a/views/html/questgroups/create.tpl b/views/html/questgroups/create.tpl new file mode 100644 index 00000000..233713cc --- /dev/null +++ b/views/html/questgroups/create.tpl @@ -0,0 +1,16 @@ + +
    + +
    + +

    +

    +

    + +
    +
    + +
    +
    + +
    diff --git a/views/html/questgroups/questgroup.tpl b/views/html/questgroups/questgroup.tpl new file mode 100644 index 00000000..56e68678 --- /dev/null +++ b/views/html/questgroups/questgroup.tpl @@ -0,0 +1,72 @@ + +
    + +
    + +

    + + + + +

    :

    + +

    + + 0): ?> +
    + +

    + +
    + + + + + 0) : ?> +

    + + + + + + 0) : ?> +

    + + diff --git a/views/html/questgroupshierarchypath/index.tpl b/views/html/questgroupshierarchypath/index.tpl new file mode 100644 index 00000000..abe15abe --- /dev/null +++ b/views/html/questgroupshierarchypath/index.tpl @@ -0,0 +1,17 @@ + 0) : ?> + + diff --git a/views/html/quests/create.tpl b/views/html/quests/create.tpl new file mode 100644 index 00000000..94bc47b3 --- /dev/null +++ b/views/html/quests/create.tpl @@ -0,0 +1,41 @@ + +
    + +
    + +

    +

    +

    + +
    +
    + +
    + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    diff --git a/views/html/quests/createmedia.tpl b/views/html/quests/createmedia.tpl new file mode 100644 index 00000000..003ea042 --- /dev/null +++ b/views/html/quests/createmedia.tpl @@ -0,0 +1,19 @@ + +
    + +
    + +

    +

    Create Questsmedia

    + + +

    New mediaId:

    + + +

    Error:

    + +
    +
    +
    + +
    diff --git a/views/html/quests/index.tpl b/views/html/quests/index.tpl new file mode 100644 index 00000000..d603a056 --- /dev/null +++ b/views/html/quests/index.tpl @@ -0,0 +1,39 @@ + +
    + +
    + +

    +

    + +
    +
    + Filter + +
    + + +
    + + +
    + + diff --git a/views/html/quests/quest.tpl b/views/html/quests/quest.tpl new file mode 100644 index 00000000..d748dc6a --- /dev/null +++ b/views/html/quests/quest.tpl @@ -0,0 +1,116 @@ + +
    + +
    + +

    + +

    + + 0) : ?> +
    +

    +
    + + +

    + + + + + + + +

    + 0 || !empty($questtext['abort_text'])) : ?> +
      + +
    • + + +
    • + +
    + + +
    +
    + + + + +
    +

    +

    + +
    +

    +

    + +
    + + + +
    + +

    +

    t($quest['task'])?>

    + + + +
    +

    : +

    +
    + + +

    + +
    + + + +
    +

    + 0) : ?> +
      + + +
    • + : + + + + + +
    • + +
    • + + + + + : + + + + + + + + + +
    • + + +
    + + : + +
    + diff --git a/views/html/quests/submission.tpl b/views/html/quests/submission.tpl new file mode 100644 index 00000000..10fde93a --- /dev/null +++ b/views/html/quests/submission.tpl @@ -0,0 +1,13 @@ + +
    + +
    + +

    + +

    + +

    +
    + +
    diff --git a/views/html/quests/submissions.tpl b/views/html/quests/submissions.tpl new file mode 100644 index 00000000..a00ce56d --- /dev/null +++ b/views/html/quests/submissions.tpl @@ -0,0 +1,37 @@ + +
    + +
    + +

    + +

    + +
    +

    +
      + +
    • + +
    • + +
    + +

    +
      + +
    • + +
    • + +
    + +

    +
      + +
    • + +
    • + +
    +
    diff --git a/views/html/seminaries/create.tpl b/views/html/seminaries/create.tpl new file mode 100644 index 00000000..22a9acaa --- /dev/null +++ b/views/html/seminaries/create.tpl @@ -0,0 +1,15 @@ + +
    + +
    + +

    +

    + +
    +
    + +
    +
    + +
    diff --git a/views/html/seminaries/delete.tpl b/views/html/seminaries/delete.tpl new file mode 100644 index 00000000..8e53fdb5 --- /dev/null +++ b/views/html/seminaries/delete.tpl @@ -0,0 +1,13 @@ + +
    + +
    + +

    +

    + + +
    + + +
    diff --git a/views/html/seminaries/edit.tpl b/views/html/seminaries/edit.tpl new file mode 100644 index 00000000..27974c1b --- /dev/null +++ b/views/html/seminaries/edit.tpl @@ -0,0 +1,15 @@ + +
    + +
    + +

    +

    + +
    +
    + +
    +
    + +
    diff --git a/views/html/seminaries/index.tpl b/views/html/seminaries/index.tpl new file mode 100644 index 00000000..922a6e03 --- /dev/null +++ b/views/html/seminaries/index.tpl @@ -0,0 +1,31 @@ +

    + 0) : ?> + + +
      + +
    • + + + +
      +

      + 0) : ?> + + + + +

      +

      format(new \DateTime($seminary['created'])))?>

      +

      + + + +

      + +
      +
    • + +
    diff --git a/views/html/seminaries/seminary.tpl b/views/html/seminaries/seminary.tpl new file mode 100644 index 00000000..7aa0ce44 --- /dev/null +++ b/views/html/seminaries/seminary.tpl @@ -0,0 +1,40 @@ + +
    + +
    + +

    + 0) : ?> + + +

    + +

    + + diff --git a/views/html/seminarybar/index.tpl b/views/html/seminarybar/index.tpl new file mode 100644 index 00000000..c2b62e47 --- /dev/null +++ b/views/html/seminarybar/index.tpl @@ -0,0 +1,48 @@ +
    +

    + + +
    + + +
    +

    +

    +
    + + + +
    +

    +
      +
    • + + + +

      +

      format(new \DateTime($lastAchievement['created'])))?>

      +
    • +
    +
    + + +
    + +

    +
      + +
    • + +

      +

      ( XPs)

      +
    • + +
    +

    + +
    diff --git a/views/html/seminarymenu/index.tpl b/views/html/seminarymenu/index.tpl new file mode 100644 index 00000000..56b9307b --- /dev/null +++ b/views/html/seminarymenu/index.tpl @@ -0,0 +1,5 @@ +
  • + 0) : ?>
  • +
  • +
  • +
  • diff --git a/views/html/userroles/user.tpl b/views/html/userroles/user.tpl new file mode 100644 index 00000000..df8b7a66 --- /dev/null +++ b/views/html/userroles/user.tpl @@ -0,0 +1,5 @@ +
      + +
    • + +
    diff --git a/views/html/users/create.tpl b/views/html/users/create.tpl new file mode 100644 index 00000000..2d5484dc --- /dev/null +++ b/views/html/users/create.tpl @@ -0,0 +1,18 @@ +

    +

    + +
    +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    diff --git a/views/html/users/delete.tpl b/views/html/users/delete.tpl new file mode 100644 index 00000000..10f280ab --- /dev/null +++ b/views/html/users/delete.tpl @@ -0,0 +1,8 @@ +

    +

    + + +
    + + +
    diff --git a/views/html/users/edit.tpl b/views/html/users/edit.tpl new file mode 100644 index 00000000..510aaed3 --- /dev/null +++ b/views/html/users/edit.tpl @@ -0,0 +1,18 @@ +

    +

    + +
    +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    diff --git a/views/html/users/index.tpl b/views/html/users/index.tpl new file mode 100644 index 00000000..9f36049c --- /dev/null +++ b/views/html/users/index.tpl @@ -0,0 +1,9 @@ +

    + +
      + +
    1. format(new \DateTime($user['created'])))?>
    2. + +
    diff --git a/views/html/users/login.tpl b/views/html/users/login.tpl new file mode 100644 index 00000000..d51423f0 --- /dev/null +++ b/views/html/users/login.tpl @@ -0,0 +1,18 @@ +

    + +

    + +

    .

    + +
    +
    + +
    + +
    +
    + + + + +
    diff --git a/views/html/users/logout.tpl b/views/html/users/logout.tpl new file mode 100644 index 00000000..e69de29b diff --git a/views/html/users/register.tpl b/views/html/users/register.tpl new file mode 100644 index 00000000..29843569 --- /dev/null +++ b/views/html/users/register.tpl @@ -0,0 +1,90 @@ +

    + +

    + +
      + &$settings) : ?> +
    • +
        + $value) : ?> +
      • + getMessage(); + break; + } ?> +
      • + +
      +
    • + +
    + +
    +
    + + />
    + + />
    + + />
    + + />
    + + />
    +
    + +
    diff --git a/views/html/users/user.tpl b/views/html/users/user.tpl new file mode 100644 index 00000000..4be14ae5 --- /dev/null +++ b/views/html/users/user.tpl @@ -0,0 +1,35 @@ +

    + 0) : ?> + + +

    +

    + format(new \DateTime($user['created'])))?>
    + :
    + : +

    + +

    +
      + +
    • + +

      + +

      + 0) : ?> + + + + +

      +

      +
    • + +
    + +

    + diff --git a/views/inlineerror.tpl b/views/inlineerror.tpl new file mode 100644 index 00000000..87a2b222 --- /dev/null +++ b/views/inlineerror.tpl @@ -0,0 +1 @@ +

    Dieser Teil der Anwendung steht zur Zeit leider nicht zur Verfügung.

    diff --git a/www/.htaccess b/www/.htaccess new file mode 100644 index 00000000..63163a80 --- /dev/null +++ b/www/.htaccess @@ -0,0 +1,8 @@ + + RewriteEngine On + + RewriteBase / + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php?url=$1 [QSA,L,NE] + diff --git a/www/css/desktop.css b/www/css/desktop.css new file mode 100644 index 00000000..fb192b69 --- /dev/null +++ b/www/css/desktop.css @@ -0,0 +1,361 @@ +@charset "UTF-8"; + +/** CSS-Reset **/ + +html{font-family:'Open Sans',sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%} +body{margin:0;background:#f7f5f2;color:#5e5c58;font:100%/1.6 'Open Sans',sans-serif;-webkit-animation:bugfix infinite 1s} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +b,strong,.fwb{font-weight:bold} +dfn,.fsi{font-style:italic} +img{border:0} +h1,h2,h3{color:#103a3e} +h2{font-size:120%;margin-top:25px} +h3{font-size:100%} +ul,ol,nav{padding:0;list-style-type:none} +p{margin:0 0 16px;padding:0} +audio,canvas,video{display:inline-block} +audio:not([controls]){display:none;height:0} +[hidden]{display:none} +abbr[title]{border-bottom:1px dotted} +hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace,serif;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:87.5%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +svg:not(:root){overflow:hidden} +figure{margin:0} +fieldset{border:0;margin:0;padding:0} +legend{border:0;padding:0;margin-bottom:15px} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0} +input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box} +input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} + + +/** Initial **/ + +@-webkit-keyframes bugfix{from{padding:0}to{padding:0}} + +.cf:before,.cf:after{display:table;content:""} +.cf:after{clear:both} +.cf{zoom:1} +.cb{clear:both} + +.wrap{margin:0 5%;max-height:999999px;min-height:1px} + +a{color:#50a4ab;text-decoration:none} +a:hover{color:#62c5cd} +.fa{padding:0 10px 0 0} +.fwb{font-weight:700} + +header{background:#0f373c;margin-bottom:19px;position:fixed;width:100%;z-index:99} +header nav{z-index:99;margin:0 5%} + +header .fa{color:#5e9499} +header a:hover .fa{color:#7fb0b4} + +menu{display:none;opacity:0;position:absolute;margin:0;padding:0 0 15px;background:#0f373c;right:0;left:0} +menu li{display:block;margin:0 5%} + +menu a,#profile{display:block;padding:15px 0;color:#d9e5e7} +menu a:hover,#profile:hover{color:#fff} + +menu .active{color:#fff} +menu .active .fa,.crewards .unlocked .fa{color:#bcd75e} + +menu .smnry{font-size:0.875em;padding:8px 0 8px 12px;background:#0c2e32;border-bottom:2px solid #0f373c;border-radius:3px} + +#profile{float:right} +.circlenote{margin-left:15px;background:#bcd75e;border-radius:0.8em;display:inline-block;color:#0f373c;font-weight:bold;line-height:1.6em;text-align:center;width:1.6em;font-size:.8em} + +#toggle,.toggle{display:none} +.toggle{display:inline-block;padding:15px 15px 15px 0;position:relative;cursor:pointer;-webkit-touch-callout:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none} +#toggle:checked ~ menu{display:block;opacity:1} +#navicon{display:block;color:#d9e5e7} + +article{padding:70px 0 30px} +aside{display:none} + +.moodpic{margin:-15px -5.5% 0 -5.5%;overflow:hidden} +.moodpic img{width:100vw} + +.breadcrumbs li{display:block;font-size:.875em} +.breadcrumbs .fa{padding-right:5px;font-size:.75em;color:#989693} + +.questgroups li{margin:0 0 25px 0;border-radius:3px;overflow:hidden} +.questgroups img{display:block;width:100%;} +.questgroups section{padding:20px;background:#fff} + +.qg li{margin-left:10px;padding:0 0 25px 30px;border:2px solid #dbd9d5;border-width:0 0 0 2px} +.qgicon{margin:17px 0 0 -39px;background:#f7f5f2;font-size:1.25em;float:left} +.qgicon.locked{margin-top:-2px} +.qgtitle a{background:#5cb6bd;border-radius:3px 3px 0 0;display:block;padding:20px;color:#fff;font-weight:700} +.qgtitle a:hover{background:#62c5cd} +.qgprogress,.qghidden{margin-bottom:4px;padding:20px 20px 4px 20px;background:#fff;border-radius:0 0 3px 3px} + +.qglist li{margin: 0 0 5px 0} +.qglist .qgtitle a{padding:14px;border-radius:3px} +.qglist .qgtitle .solved{background:#fff;color:#50a4ab} +.qglist .qgtitle .solved:hover{color:#62c5cd} +.qglist .qgtitle .solved .fa{color:#bcd75e} +.qglist .qgtitle .bonus{background:#f5821f} +.qglist .qgtitle .bonus:hover{background:#f68e34} + +#qtextbox{font-size:.875em;border-radius:5px;background:#fff;border:15px solid #fff;height:200px;overflow:hidden} +.qtext{padding-right:15px} +.qtext img,.grpqimg{float:right;margin-left:15px;max-width:30%;border-radius:3px} + +#imagelightbox{cursor:pointer;position:fixed;z-index:10000;-ms-touch-action:none;touch-action:none;-webkit-box-shadow:0 0 3.125em rgba( 0, 0, 0, .75 ); -moz-box-shadow:0 0 3.125em rgba( 0, 0, 0, .75 );box-shadow:0 0 3.125em rgba( 0, 0, 0, .75 )} + +.xpinfo{display:none} +.xpbar{width:60%;float:left;height:10px;position:relative;background:#eee;border-radius:25px;margin:8px 0 16px} +.xpbar span{display:block;height:100%;border-radius:20px;background:#bcd75e;position:relative;overflow:hidden} +.xpnumeric{float:right;color:#869845} + +.cta,input[type="submit"]{display:inline-block;margin-top:10px;color:#fff;font-weight:700;padding:7px 20px;border-radius:4px} +.orange,input[type="submit"]{text-shadow:1px 2px #d4701a;background:#f5821f;border:solid #d4701a;border-width:0 0 3px 0} +.orange:hover,input[type="submit"]:hover{color:#fff;background:#f09140} + +input[type="submit"][disabled]{text-shadow:1px 2px #d48c4e;background:#f9ac69;border-color:#d48c4e} + +.admin li{margin:0 0 15px 0;display:inline-block} +.admin li a{padding:4px 15px;background:#5cb6bd;border-bottom:2px solid #50a0a6;color:#fff;border-radius:3px} +.admin li a:hover{background:#62c5cd} + + +/** Login & Registration **/ + +.logreg{width:auto;display:inline-block;padding:15px 20px;background:#eae8e4;border-radius:3px} +.logreg label{display:block;font-size:.875em} +.logreg input{margin:5px 0 15px} +.logreg .cta{display:block} + + +/** Character Profile **/ + +.cportrait img{width:100%} + +.cdata{display:inline-block;background:#fff;border-radius:3px;padding:12px 20px 0 20px;margin-bottom:5px} +.cdata.square{text-align:center;width:10%;padding-top:0} +.cdata.square.blue{background:#5cb6bd;color:#fff} +.cdata .xpbar,.ctopics .xpbar{width:100%} +.cdata .value{font-size:1.5em;margin:0;padding-top:8px} + +.crewards li{background:#fff;margin-bottom:5px;padding:15px 15px 10px 15px;border-radius:3px;font-size:.875em} +.crewards li:last-child{margin-bottom:0} +.crewards li .fa{font-size:1.25em} +.crewards p{margin-bottom:5px} + +.cgroups img{float:left;border-radius:3px;margin-right:5px;height:35px} +.cgroups p{float:left;0;padding:0 12px;line-height:35px;background:#fff;border-radius:0 3px 3px 0} +.cgroups a{float:left;padding:0 12px;line-height:35px;background:#50a4ab;color:#fff;background:#50a4ab;border-radius:3px 0 0 3px} + +.cranks li{clear:both;padding:8px 0 8px 8px;border-radius:3px} +.cranks li:nth-child(odd){background:#fff} +.cranks img{float:left;margin-right:15px;width:50px;height:50px;border-radius:25px} +.cranks p,.ctopics p{margin:0;padding:0} + +.ctopics .xpbar,.ltopics .xpbar{background:#e4e1dd} +.ctopics .xpbar span{background:#50a4ab} + + +/** Charactergroup List **/ + +.cglist li{list-style-type:decimal;background:#fff;margin:0 0 6px 25px;padding:5px 15px;border-radius:3px} +.cglist .xp{float:right} +.cgqlist li{list-style-type:square;margin-left:25px;padding:2px 15px} + + +/** Charactergroup Profile **/ + +.gbanner img{margin-top:10px;border-radius:3px} +.gbanner h1{margin:10px 0 5px 0} + +.gdata{margin:20px 0 40px 0} +.gdata li{float:left;background:#fff;padding:5px 15px;margin:0 5px 5px 0;border-radius:3px} + +.gchars li{background:#fff;padding:15px 0;border-radius:3px;float:left;margin-bottom:5px;width:49%} +.gchars li:nth-child(even){float:right} +.gchars img{width:50px;height:50px;border-radius:25px} +.gchars p{margin:0;padding:0;text-align:center} +.gchars .fa{position:absolute;margin:-10px 0 0} + +.gquests li{padding:12px 15px 0 15px;border-radius:3px} +.gquests li:nth-child(odd){background:#fff} +.gquests .date{color:#aca8a1;display:block} +.gquests .xp{display:block} + + +/** Charactergroup Quest **/ + +.grpqlist li{background:#fff;margin-bottom:6px;padding:5px 15px 5px 0;font-size:.875em;border-radius:3px} +.grpqlist .date{padding:0 15px;border-right:1px solid #dad8d5} +.grpqlist .group{padding:0 15px} +.grpqlist .xp{float:right} + + +/** Achievements **/ + +.rare ol{margin-bottom:40px} +.rare li{margin-bottom:15px} +.rare img{float:left;width:45px;height:45px;margin:4px 10px 0 0;border-radius:3px} +.rare p{margin:0} + +.achmnts li{background:#fff;margin-bottom:10px;padding:15px;border-radius:3px} +.achmnts img{width:55px;height:55px;float:left;margin-right:15px;border-radius:3px} +.achmnts p,.achmnts h3{margin:0 0 4px;font-size:1em} +.achmnts .unlcked{margin-top:3px;font-size:.875em;font-weight:normal;display:block} +.achmnts .desc{font-size:.875em;padding-top:10px} +.achmnts .prgrss{margin-top:15px} +.achmnts .xpbar{margin:8px 0 0 0;width:80%} +.achmnts .xpnumeric{margin:0} + + +/** Library **/ + +.libindxpr{margin-top:20px} +.libindxpr p{font-weight:bold;margin:0} +.libindxpr .xpbar{float:none;margin:9px 0 20px 0;background:#e4e1dd;width:100%} +.libindxpr .xpbar span{background:#50a4ab} + +.libindx li{text-align:center;background:#fff;border-radius:3px;margin:0 0 15px 0;padding:15px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} +.libindx i{margin-bottom:15px;font-size:1.25em;padding:0} +.libindx .ltopc{overflow:hidden;text-overflow:ellipsis} +.libindx .xpbar{width:100%;margin:0} + +.libtop li{background:#fff;padding:5px 5px 5px 10px;border-radius:3px;margin-bottom:6px} +.libtop p{margin-bottom:2px} +.libtop .addon li{display:inline;font-size:.875em;padding:0} +.libtop .addon li:after{content:", "} +.libtop .addon li:last-child:after{content: ""} + + +/** Quest Types **/ + +.success,.error{margin-top:15px;padding:5px 15px;border:1px solid #4F8A10;background:#DFF2BF;color:#4F8A10;border-radius:3px} +.error{border:1px solid #850000;background:#ebd3d3;color:#850000} +.success p,.error p{margin-bottom:5px} + +.solvdmsg{margin-top:20px} + +.mchoice{list-style-type:lower-alpha;list-style-position:inside} +.mchoice li{margin:0 0 10px 0} +.mchoice input[type=checkbox]{display:inline-block;margin:-19px 10px 0 24px;vertical-align:top} +.mchoice label{width:90%;display:inline-block;margin:-25px 0 0 0;vertical-align:top} + +.submit{padding:15px;background:#eae8e4;border-radius:3px;margin-bottom:15px} +.submit p{margin:15px 0 0 0;font-weight:bold} +.submit ul{list-style-type:square;margin-left:20px} + +.textinput input[type=text]{height:16px;font-size:.875em} + +.crossword table{width:100%;max-width:800px;border-spacing:2px;border-collapse:separate} +.crossword td{background:#d7d4cf;padding:1px} +.crossword input[type=text]{text-align:center;height:26px;width:100%;min-width:8px;max-width:40px;margin:0;padding:0;border:none;text-transform:uppercase} +.crossword ol{list-style-type:decimal;margin-left:25px} +.crossword li{margin-top:20px} +.crossword .index{position:absolute;font-size:0.625em;margin:-2px 0 0 2px} + +.opponent{width:50%;float:left} +.opponent .portrait{height:250px;overflow:hidden;margin-bottom:15px} +.opponent .hero{background:#fff;max-width:130px} +.opponent .boss{max-width:100%} +.opponent p{text-align:center} +.opponent .fa{font-size:1.25em;color:#c7135b;padding-right:0} +.bossfight .option{background:#fff;margin-bottom:10px;padding:15px 15px 5px;border-radius:3px} + +#dragZone{background:#eae8e4;box-sizing:border-box;padding:5px 5px 0 5px} +.drop{background:#eae8e4} + +/** Media Queries **/ + +@media only screen and (min-width:480px){ +.questgroups li,.ctopics li,.fll48{width:48%;display:inline-block;margin-right:2%} +.questgroups li:nth-child(even),.ctopics li:nth-child(even),.flr48{width:48%;display:inline-block;margin-right:0;vertical-align:top} +.xpinfo{display:inline-block;float:left;padding-right:20px} +.xpbar{width:50%} + +.cinfo{float:left;width:70%} +.cportrait{float:right;width:25%} + +.gbanner img{float:left;margin:20px 20px 0 0} +.gbanner h1{margin:25px 0 2px} + +.gchars li{width:32%;margin-right:5px} +.gchars li:nth-child(even){float:left} + +.achmnts .xpbar{width:89%} + +.libindx li{float:left;width:49%} +.libindx li:nth-child(2n){float:right} +.libindx .ltopc{height:80px} + +.opponent .hero{max-width:200px} +} + +@media only screen and (min-width:768px){ +.xpbar{width:70%} + +.gchars li{width:19%} + +.gquests .date{display:inline;margin-right:15px} +.gquests .xp{float:right} + +.rare,.hunter{float:left;width:49%} +.hunter{float:right} +.achmnts .desc{padding-top:0} +.achmnts .unlcked{font-size:.75em;float:right;margin:-6px -4px 0 0;color:#878787} + +.libindx li{float:left;width:32%;margin-right:2%} +.libindx li:nth-child(2n){float:left} +.libindx li:nth-child(3n){margin-right:0} + +.bossfight li{float:left;width:44%} +.bossfight li:nth-child(even){float:right} +.bossfight input,.bossfight p{text-align:center} +} + +@media only screen and (min-width:1024px){ +header{position:fixed;left:0;top:0;bottom:0;width:300px;margin:0} +header nav{margin:30px 10% 0} +menu{display:block;opacity:1;position:relative} +menu a{padding:10px 0} +#profile{float:none;margin:0 0 15px 12px} +#toggle,.toggle{display:none} +.wrap{margin:0 0 0 300px} +article{padding:20px 40px 80px 40px} +.moodpic{margin:-20px -40px 0 -40px} +.breadcrumbs li{display:inline;padding-right:5px} + +.gchars li{width:19%} +} + +@media only screen and (min-width:1366px){ +html{overflow-y:scroll;height:100%} +body{background:#eae8e4;height:100%} +.wrap{width:800px;max-width:800px;height:auto !important;min-height:100%;height:100%;position:relative;overflow:hidden} +article{background:#f7f5f2;float:left} +.moodpic{width:880px;height:230px} +.moodpic img{width:100%} +} + +@media only screen and (min-width:1600px){ +aside{display:block;float:left;width:350px;min-width:350px;margin:0 0 0 40px} +aside section{margin:20px 0 40px 0} +aside .char{width:120px;float:left;margin-right:10px} +aside .charstats{background:#f7f5f2;border-radius:3px;padding:10px 0;margin-top:50px} +aside .charstats li{font-size:.875em;padding:2px 0} +aside .cranks li:nth-child(odd){background:#f7f5f2} +} \ No newline at end of file diff --git a/www/error403.html b/www/error403.html new file mode 100644 index 00000000..c8867377 --- /dev/null +++ b/www/error403.html @@ -0,0 +1,14 @@ + + + + + + The Legend of Z + + + +

    The Legend of Z

    +

    Access denied.

    + + + diff --git a/www/error404.html b/www/error404.html new file mode 100644 index 00000000..874106f4 --- /dev/null +++ b/www/error404.html @@ -0,0 +1,14 @@ + + + + + + The Legend of Z + + + +

    The Legend of Z

    +

    Not found.

    + + + diff --git a/www/error500.html b/www/error500.html new file mode 100644 index 00000000..b7afa5c0 --- /dev/null +++ b/www/error500.html @@ -0,0 +1,14 @@ + + + + + + The Legend of Z + + + +

    The Legend of Z

    +

    Internal server error.

    + + + diff --git a/www/index.php b/www/index.php new file mode 100644 index 00000000..46301a31 --- /dev/null +++ b/www/index.php @@ -0,0 +1,45 @@ + + * @copyright 2013 coderkun (http://www.coderkun.de) + * @license http://www.gnu.org/licenses/gpl.html + * @link http://www.coderkun.de/projects/nre + */ + + /** + * Define constants + */ + // Directory separator + if(!defined('DS')) { + define('DS', DIRECTORY_SEPARATOR); + } + // Root directory + if(!defined('ROOT')) { + define('ROOT', dirname(dirname(__FILE__))); + } + + + /** + * De-/Activate error messages + */ + if($_SERVER['SERVER_ADDR'] == '127.0.0.1' || $_SERVER['SERVER_ADDR'] == '::1') { + error_reporting(E_ALL); + ini_set('display_errors', 1); + ini_set('log_errors', 0); + } + else { + error_reporting(E_ALL); + ini_set('display_errors', 0); + ini_set('log_errors', 1); + } + + + /** + * Run application + */ + require ROOT.DS.'bootstrap.inc'; + +?> diff --git a/www/js/dnd.js b/www/js/dnd.js new file mode 100644 index 00000000..ff1340c7 --- /dev/null +++ b/www/js/dnd.js @@ -0,0 +1,54 @@ +/** + * Drag&Drop-functions + */ + +function onDragStart(event) +{ + jQuery(event.currentTarget).addClass("drag"); + event.dataTransfer.setData("Text", event.target.id); +} + +function onDragEnter(event) +{ + if(event.target.nodeName == "DIV" && !(event.target.parentNode.id == "dropZone" && event.target.hasChildNodes())) { + jQuery(event.target).addClass('drop'); + } +} + +function onDragOver(event) +{ + if(event.target.nodeName == "DIV" && !(event.target.parentNode.id == "dropZone" && event.target.hasChildNodes())) { + event.preventDefault(); + } +} + +function onDragLeave(event) +{ + jQuery(event.target).removeClass('drop'); +} + +function onDragEnd(event) +{ + jQuery(event.currentTarget).removeClass("drag"); +} + +function onDrop(event, setId) +{ + setId = (typeof setId == 'undefined') ? true : setId; + jQuery(event.currentTarget).removeClass('drag'); + jQuery(event.target).removeClass('drop'); + event.preventDefault(); + + var data = event.dataTransfer.getData("Text"); + var dataElement = $('#'+data); + if(dataElement.parent() && $('#dnd_'+dataElement.parent().attr('id'))) { + $('#dnd_'+dataElement.parent().attr('id')).attr('value', "null"); + } + jQuery(event.target).append(dataElement); + + if(setId) { + console.log(data); + $('#dnd_' + jQuery(event.target).attr('id')).attr('value', data); + } + +} diff --git a/www/js/imagelightbox.min.js b/www/js/imagelightbox.min.js new file mode 100644 index 00000000..666b2b7f --- /dev/null +++ b/www/js/imagelightbox.min.js @@ -0,0 +1,6 @@ +/* + By Osvaldas Valutis, www.osvaldas.info + Available for use under the MIT License +*/ + +;(function(e,t,n,r){"use strict";var i=function(){var e=n.body||n.documentElement,e=e.style;if(e.WebkitTransition=="")return"-webkit-";if(e.MozTransition=="")return"-moz-";if(e.OTransition=="")return"-o-";if(e.transition=="")return"";return false},s=i()===false?false:true,o=function(e,t,n){var r={},s=i();r[s+"transform"]="translateX("+t+")";r[s+"transition"]=s+"transform "+n+"s linear";e.css(r)},u="ontouchstart"in t,a=t.navigator.pointerEnabled||t.navigator.msPointerEnabled,f=function(e){if(u)return true;if(!a||typeof e==="undefined"||typeof e.pointerType==="undefined")return false;if(typeof e.MSPOINTER_TYPE_MOUSE!=="undefined"){if(e.MSPOINTER_TYPE_MOUSE!=e.pointerType)return true}else if(e.pointerType!="mouse")return true;return false};e.fn.imageLightbox=function(r){var r=e.extend({selector:'id="imagelightbox"',allowedTypes:"png|jpg|jpeg|gif",animationSpeed:250,preloadNext:true,enableKeyboard:true,quitOnEnd:false,quitOnImgClick:false,quitOnDocClick:true,onStart:false,onEnd:false,onLoadStart:false,onLoadEnd:false},r),i=e([]),l=e(),c=e(),h=0,p=0,d=0,v=false,m=function(t){return e(t).prop("tagName").toLowerCase()=="a"&&(new RegExp(".("+r.allowedTypes+")$","i")).test(e(t).attr("href"))},g=function(){if(!c.length)return false;var n=e(t).width()*.8,r=e(t).height()*.9,i=new Image;i.src=c.attr("src");i.onload=function(){h=i.width;p=i.height;if(h>n||p>r){var s=h/p>n/r?h/n:p/r;h/=s;p/=s}c.css({width:h+"px",height:p+"px",top:(e(t).height()-p)/2+"px",left:(e(t).width()-h)/2+"px"})}},y=function(t){if(v)return false;t=typeof t==="undefined"?false:t=="left"?1:-1;if(c.length){if(t!==false&&(i.length<2||r.quitOnEnd===true&&(t===-1&&i.index(l)==0||t===1&&i.index(l)==i.length-1))){w();return false}var n={opacity:0};if(s)o(c,100*t-d+"px",r.animationSpeed/1e3);else n.left=parseInt(c.css("left"))+100*t+"px";c.animate(n,r.animationSpeed,function(){b()});d=0}v=true;if(r.onLoadStart!==false)r.onLoadStart();setTimeout(function(){c=e("").attr("src",l.attr("href")).load(function(){c.appendTo("body");g();var n={opacity:1};c.css("opacity",0);if(s){o(c,-100*t+"px",0);setTimeout(function(){o(c,0+"px",r.animationSpeed/1e3)},50)}else{var u=parseInt(c.css("left"));n.left=u+"px";c.css("left",u-100*t+"px")}c.animate(n,r.animationSpeed,function(){v=false;if(r.onLoadEnd!==false)r.onLoadEnd()});if(r.preloadNext){var a=i.eq(i.index(l)+1);if(!a.length)a=i.eq(0);e("").attr("src",a.attr("href")).load()}}).error(function(){if(r.onLoadEnd!==false)r.onLoadEnd()});var n=0,u=0,p=0;c.on(a?"pointerup MSPointerUp":"click",function(e){e.preventDefault();if(r.quitOnImgClick){w();return false}if(f(e.originalEvent))return true;var t=(e.pageX||e.originalEvent.pageX)-e.target.offsetLeft;l=i.eq(i.index(l)-(h/2>t?1:-1));if(!l.length)l=i.eq(h/2>t?i.length:0);y(h/2>t?"left":"right")}).on("touchstart pointerdown MSPointerDown",function(e){if(!f(e.originalEvent)||r.quitOnImgClick)return true;if(s)p=parseInt(c.css("left"));n=e.originalEvent.pageX||e.originalEvent.touches[0].pageX}).on("touchmove pointermove MSPointerMove",function(e){if(!f(e.originalEvent)||r.quitOnImgClick)return true;e.preventDefault();u=e.originalEvent.pageX||e.originalEvent.touches[0].pageX;d=n-u;if(s)o(c,-d+"px",0);else c.css("left",p-d+"px")}).on("touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel",function(e){if(!f(e.originalEvent)||r.quitOnImgClick)return true;if(Math.abs(d)>50){l=i.eq(i.index(l)-(d<0?1:-1));if(!l.length)l=i.eq(d<0?i.length:0);y(d>0?"right":"left")}else{if(s)o(c,0+"px",r.animationSpeed/1e3);else c.animate({left:p+"px"},r.animationSpeed/2)}})},r.animationSpeed+100)},b=function(){if(!c.length)return false;c.remove();c=e()},w=function(){if(!c.length)return false;c.animate({opacity:0},r.animationSpeed,function(){b();v=false;if(r.onEnd!==false)r.onEnd()})};e(t).on("resize",g);if(r.quitOnDocClick){e(n).on(u?"touchend":"click",function(t){if(c.length&&!e(t.target).is(c))w()})}if(r.enableKeyboard){e(n).on("keyup",function(e){if(!c.length)return true;e.preventDefault();if(e.keyCode==27)w();if(e.keyCode==37||e.keyCode==39){l=i.eq(i.index(l)-(e.keyCode==37?1:-1));if(!l.length)l=i.eq(e.keyCode==37?i.length:0);y(e.keyCode==37?"left":"right")}})}e(n).on("click",this.selector,function(t){if(!m(this))return true;t.preventDefault();if(v)return false;v=false;if(r.onStart!==false)r.onStart();l=e(this);y()});this.each(function(){if(!m(this))return true;i=i.add(e(this))});this.switchImageLightbox=function(e){var t=i.eq(e);if(t.length){var n=i.index(l);l=t;y(ef){if(b.getScrollTop()>=b.page.maxh)return!0}else if(0>=b.getScrollTop())return!0;b.scrollmom&&b.scrollmom.stop(); +b.lastdeltay+=f;b.debounced("mousewheely",function(){var d=b.lastdeltay;b.lastdeltay=0;b.rail.drag||b.doScrollBy(d)},120)}d.stopImmediatePropagation();return d.preventDefault()}var b=this;this.version="3.4.0";this.name="nicescroll";this.me=c;this.opt={doc:e("body"),win:!1};e.extend(this.opt,F);this.opt.snapbackspeed=80;if(k)for(var q in b.opt)"undefined"!=typeof k[q]&&(b.opt[q]=k[q]);this.iddoc=(this.doc=b.opt.doc)&&this.doc[0]?this.doc[0].id||"":"";this.ispage=/BODY|HTML/.test(b.opt.win?b.opt.win[0].nodeName: +this.doc[0].nodeName);this.haswrapper=!1!==b.opt.win;this.win=b.opt.win||(this.ispage?e(window):this.doc);this.docscroll=this.ispage&&!this.haswrapper?e(window):this.win;this.body=e("body");this.iframe=this.isfixed=this.viewport=!1;this.isiframe="IFRAME"==this.doc[0].nodeName&&"IFRAME"==this.win[0].nodeName;this.istextarea="TEXTAREA"==this.win[0].nodeName;this.forcescreen=!1;this.canshowonmouseevent="scroll"!=b.opt.autohidemode;this.page=this.view=this.onzoomout=this.onzoomin=this.onscrollcancel= +this.onscrollend=this.onscrollstart=this.onclick=this.ongesturezoom=this.onkeypress=this.onmousewheel=this.onmousemove=this.onmouseup=this.onmousedown=!1;this.scroll={x:0,y:0};this.scrollratio={x:0,y:0};this.cursorheight=20;this.scrollvaluemax=0;this.observerremover=this.observer=this.scrollmom=this.scrollrunning=this.checkrtlmode=!1;do this.id="ascrail"+K++;while(document.getElementById(this.id));this.hasmousefocus=this.hasfocus=this.zoomactive=this.zoom=this.selectiondrag=this.cursorfreezed=this.cursor= +this.rail=!1;this.visibility=!0;this.hidden=this.locked=!1;this.cursoractive=!0;this.overflowx=b.opt.overflowx;this.overflowy=b.opt.overflowy;this.nativescrollingarea=!1;this.checkarea=0;this.events=[];this.saved={};this.delaylist={};this.synclist={};this.lastdeltay=this.lastdeltax=0;this.detected=M();var f=e.extend({},this.detected);this.ishwscroll=(this.canhwscroll=f.hastransform&&b.opt.hwacceleration)&&b.haswrapper;this.istouchcapable=!1;f.cantouch&&(f.ischrome&&!f.isios&&!f.isandroid)&&(this.istouchcapable= +!0,f.cantouch=!1);f.cantouch&&(f.ismozilla&&!f.isios)&&(this.istouchcapable=!0,f.cantouch=!1);b.opt.enablemouselockapi||(f.hasmousecapture=!1,f.haspointerlock=!1);this.delayed=function(d,c,g,e){var f=b.delaylist[d],h=(new Date).getTime();if(!e&&f&&f.tt)return!1;f&&f.tt&&clearTimeout(f.tt);if(f&&f.last+g>h&&!f.tt)b.delaylist[d]={last:h+g,tt:setTimeout(function(){b.delaylist[d].tt=0;c.call()},g)};else if(!f||!f.tt)b.delaylist[d]={last:h,tt:0},setTimeout(function(){c.call()},0)};this.debounced=function(d, +c,g){var f=b.delaylist[d];(new Date).getTime();b.delaylist[d]=c;f||setTimeout(function(){var c=b.delaylist[d];b.delaylist[d]=!1;c.call()},g)};this.synched=function(d,c){b.synclist[d]=c;(function(){b.onsync||(v(function(){b.onsync=!1;for(d in b.synclist){var c=b.synclist[d];c&&c.call(b);b.synclist[d]=!1}}),b.onsync=!0)})();return d};this.unsynched=function(d){b.synclist[d]&&(b.synclist[d]=!1)};this.css=function(d,c){for(var g in c)b.saved.css.push([d,g,d.css(g)]),d.css(g,c[g])};this.scrollTop=function(d){return"undefined"== +typeof d?b.getScrollTop():b.setScrollTop(d)};this.scrollLeft=function(d){return"undefined"==typeof d?b.getScrollLeft():b.setScrollLeft(d)};BezierClass=function(b,c,g,f,e,h,l){this.st=b;this.ed=c;this.spd=g;this.p1=f||0;this.p2=e||1;this.p3=h||0;this.p4=l||1;this.ts=(new Date).getTime();this.df=this.ed-this.st};BezierClass.prototype={B2:function(b){return 3*b*b*(1-b)},B3:function(b){return 3*b*(1-b)*(1-b)},B4:function(b){return(1-b)*(1-b)*(1-b)},getNow:function(){var b=1-((new Date).getTime()-this.ts)/ +this.spd,c=this.B2(b)+this.B3(b)+this.B4(b);return 0>b?this.ed:this.st+Math.round(this.df*c)},update:function(b,c){this.st=this.getNow();this.ed=b;this.spd=c;this.ts=(new Date).getTime();this.df=this.ed-this.st;return this}};if(this.ishwscroll){this.doc.translate={x:0,y:0,tx:"0px",ty:"0px"};f.hastranslate3d&&f.isios&&this.doc.css("-webkit-backface-visibility","hidden");var r=function(){var d=b.doc.css(f.trstyle);return d&&"matrix"==d.substr(0,6)?d.replace(/^.*\((.*)\)$/g,"$1").replace(/px/g,"").split(/, +/): +!1};this.getScrollTop=function(d){if(!d){if(d=r())return 16==d.length?-d[13]:-d[5];if(b.timerscroll&&b.timerscroll.bz)return b.timerscroll.bz.getNow()}return b.doc.translate.y};this.getScrollLeft=function(d){if(!d){if(d=r())return 16==d.length?-d[12]:-d[4];if(b.timerscroll&&b.timerscroll.bh)return b.timerscroll.bh.getNow()}return b.doc.translate.x};this.notifyScrollEvent=document.createEvent?function(b){var c=document.createEvent("UIEvents");c.initUIEvent("scroll",!1,!0,window,1);b.dispatchEvent(c)}: +document.fireEvent?function(b){var c=document.createEventObject();b.fireEvent("onscroll");c.cancelBubble=!0}:function(b,c){};f.hastranslate3d&&b.opt.enabletranslate3d?(this.setScrollTop=function(d,c){b.doc.translate.y=d;b.doc.translate.ty=-1*d+"px";b.doc.css(f.trstyle,"translate3d("+b.doc.translate.tx+","+b.doc.translate.ty+",0px)");c||b.notifyScrollEvent(b.win[0])},this.setScrollLeft=function(d,c){b.doc.translate.x=d;b.doc.translate.tx=-1*d+"px";b.doc.css(f.trstyle,"translate3d("+b.doc.translate.tx+ +","+b.doc.translate.ty+",0px)");c||b.notifyScrollEvent(b.win[0])}):(this.setScrollTop=function(d,c){b.doc.translate.y=d;b.doc.translate.ty=-1*d+"px";b.doc.css(f.trstyle,"translate("+b.doc.translate.tx+","+b.doc.translate.ty+")");c||b.notifyScrollEvent(b.win[0])},this.setScrollLeft=function(d,c){b.doc.translate.x=d;b.doc.translate.tx=-1*d+"px";b.doc.css(f.trstyle,"translate("+b.doc.translate.tx+","+b.doc.translate.ty+")");c||b.notifyScrollEvent(b.win[0])})}else this.getScrollTop=function(){return b.docscroll.scrollTop()}, +this.setScrollTop=function(d){return b.docscroll.scrollTop(d)},this.getScrollLeft=function(){return b.docscroll.scrollLeft()},this.setScrollLeft=function(d){return b.docscroll.scrollLeft(d)};this.getTarget=function(b){return!b?!1:b.target?b.target:b.srcElement?b.srcElement:!1};this.hasParent=function(b,c){if(!b)return!1;for(var g=b.target||b.srcElement||b||!1;g&&g.id!=c;)g=g.parentNode||!1;return!1!==g};var u={thin:1,medium:3,thick:5};this.getOffset=function(){if(b.isfixed)return{top:parseFloat(b.win.css("top")), +left:parseFloat(b.win.css("left"))};if(!b.viewport)return b.win.offset();var d=b.win.offset(),c=b.viewport.offset();return{top:d.top-c.top+b.viewport.scrollTop(),left:d.left-c.left+b.viewport.scrollLeft()}};this.updateScrollBar=function(d){if(b.ishwscroll)b.rail.css({height:b.win.innerHeight()}),b.railh&&b.railh.css({width:b.win.innerWidth()});else{var c=b.getOffset(),g=c.top,f=c.left,g=g+l(b.win,"border-top-width",!0);b.win.outerWidth();b.win.innerWidth();var f=f+(b.rail.align?b.win.outerWidth()- +l(b.win,"border-right-width")-b.rail.width:l(b.win,"border-left-width")),e=b.opt.railoffset;e&&(e.top&&(g+=e.top),b.rail.align&&e.left&&(f+=e.left));b.locked||b.rail.css({top:g,left:f,height:d?d.h:b.win.innerHeight()});b.zoom&&b.zoom.css({top:g+1,left:1==b.rail.align?f-20:f+b.rail.width+4});b.railh&&!b.locked&&(g=c.top,f=c.left,d=b.railh.align?g+l(b.win,"border-top-width",!0)+b.win.innerHeight()-b.railh.height:g+l(b.win,"border-top-width",!0),f+=l(b.win,"border-left-width"),b.railh.css({top:d,left:f, +width:b.railh.width}))}};this.doRailClick=function(d,c,g){var f;b.locked||(b.cancelEvent(d),c?(c=g?b.doScrollLeft:b.doScrollTop,f=g?(d.pageX-b.railh.offset().left-b.cursorwidth/2)*b.scrollratio.x:(d.pageY-b.rail.offset().top-b.cursorheight/2)*b.scrollratio.y,c(f)):(c=g?b.doScrollLeftBy:b.doScrollBy,f=g?b.scroll.x:b.scroll.y,d=g?d.pageX-b.railh.offset().left:d.pageY-b.rail.offset().top,g=g?b.view.w:b.view.h,f>=d?c(g):c(-g)))};b.hasanimationframe=v;b.hascancelanimationframe=w;b.hasanimationframe?b.hascancelanimationframe|| +(w=function(){b.cancelAnimationFrame=!0}):(v=function(b){return setTimeout(b,15-Math.floor(+new Date/1E3)%16)},w=clearInterval);this.init=function(){b.saved.css=[];if(f.isie7mobile)return!0;f.hasmstouch&&b.css(b.ispage?e("html"):b.win,{"-ms-touch-action":"none"});b.zindex="auto";b.zindex=!b.ispage&&"auto"==b.opt.zindex?h()||"auto":b.opt.zindex;!b.ispage&&"auto"!=b.zindex&&b.zindex>x&&(x=b.zindex);b.isie&&(0==b.zindex&&"auto"==b.opt.zindex)&&(b.zindex="auto");if(!b.ispage||!f.cantouch&&!f.isieold&& +!f.isie9mobile){var d=b.docscroll;b.ispage&&(d=b.haswrapper?b.win:b.doc);f.isie9mobile||b.css(d,{"overflow-y":"hidden"});b.ispage&&f.isie7&&("BODY"==b.doc[0].nodeName?b.css(e("html"),{"overflow-y":"hidden"}):"HTML"==b.doc[0].nodeName&&b.css(e("body"),{"overflow-y":"hidden"}));f.isios&&(!b.ispage&&!b.haswrapper)&&b.css(e("body"),{"-webkit-overflow-scrolling":"touch"});var c=e(document.createElement("div"));c.css({position:"relative",top:0,"float":"right",width:b.opt.cursorwidth,height:"0px","background-color":b.opt.cursorcolor, +border:b.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":b.opt.cursorborderradius,"-moz-border-radius":b.opt.cursorborderradius,"border-radius":b.opt.cursorborderradius});c.hborder=parseFloat(c.outerHeight()-c.innerHeight());b.cursor=c;var g=e(document.createElement("div"));g.attr("id",b.id);g.addClass("nicescroll-rails");var l,k,n=["left","right"],G;for(G in n)k=n[G],(l=b.opt.railpadding[k])?g.css("padding-"+k,l+"px"):b.opt.railpadding[k]=0;g.append(c);g.width=Math.max(parseFloat(b.opt.cursorwidth), +c.outerWidth())+b.opt.railpadding.left+b.opt.railpadding.right;g.css({width:g.width+"px",zIndex:b.zindex,background:b.opt.background,cursor:"default"});g.visibility=!0;g.scrollable=!0;g.align="left"==b.opt.railalign?0:1;b.rail=g;c=b.rail.drag=!1;b.opt.boxzoom&&(!b.ispage&&!f.isieold)&&(c=document.createElement("div"),b.bind(c,"click",b.doZoom),b.zoom=e(c),b.zoom.css({cursor:"pointer","z-index":b.zindex,backgroundImage:"url("+L+"zoomico.png)",height:18,width:18,backgroundPosition:"0px 0px"}),b.opt.dblclickzoom&& +b.bind(b.win,"dblclick",b.doZoom),f.cantouch&&b.opt.gesturezoom&&(b.ongesturezoom=function(d){1.5d.scale&&b.doZoomOut(d);return b.cancelEvent(d)},b.bind(b.win,"gestureend",b.ongesturezoom)));b.railh=!1;if(b.opt.horizrailenabled){b.css(d,{"overflow-x":"hidden"});c=e(document.createElement("div"));c.css({position:"relative",top:0,height:b.opt.cursorwidth,width:"0px","background-color":b.opt.cursorcolor,border:b.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":b.opt.cursorborderradius, +"-moz-border-radius":b.opt.cursorborderradius,"border-radius":b.opt.cursorborderradius});c.wborder=parseFloat(c.outerWidth()-c.innerWidth());b.cursorh=c;var m=e(document.createElement("div"));m.attr("id",b.id+"-hr");m.addClass("nicescroll-rails");m.height=Math.max(parseFloat(b.opt.cursorwidth),c.outerHeight());m.css({height:m.height+"px",zIndex:b.zindex,background:b.opt.background});m.append(c);m.visibility=!0;m.scrollable=!0;m.align="top"==b.opt.railvalign?0:1;b.railh=m;b.railh.drag=!1}b.ispage? +(g.css({position:"fixed",top:"0px",height:"100%"}),g.align?g.css({right:"0px"}):g.css({left:"0px"}),b.body.append(g),b.railh&&(m.css({position:"fixed",left:"0px",width:"100%"}),m.align?m.css({bottom:"0px"}):m.css({top:"0px"}),b.body.append(m))):(b.ishwscroll?("static"==b.win.css("position")&&b.css(b.win,{position:"relative"}),d="HTML"==b.win[0].nodeName?b.body:b.win,b.zoom&&(b.zoom.css({position:"absolute",top:1,right:0,"margin-right":g.width+4}),d.append(b.zoom)),g.css({position:"absolute",top:0}), +g.align?g.css({right:0}):g.css({left:0}),d.append(g),m&&(m.css({position:"absolute",left:0,bottom:0}),m.align?m.css({bottom:0}):m.css({top:0}),d.append(m))):(b.isfixed="fixed"==b.win.css("position"),d=b.isfixed?"fixed":"absolute",b.isfixed||(b.viewport=b.getViewport(b.win[0])),b.viewport&&(b.body=b.viewport,!1==/relative|absolute/.test(b.viewport.css("position"))&&b.css(b.viewport,{position:"relative"})),g.css({position:d}),b.zoom&&b.zoom.css({position:d}),b.updateScrollBar(),b.body.append(g),b.zoom&& +b.body.append(b.zoom),b.railh&&(m.css({position:d}),b.body.append(m))),f.isios&&b.css(b.win,{"-webkit-tap-highlight-color":"rgba(0,0,0,0)","-webkit-touch-callout":"none"}),f.isie&&b.opt.disableoutline&&b.win.attr("hideFocus","true"),f.iswebkit&&b.opt.disableoutline&&b.win.css({outline:"none"}));!1===b.opt.autohidemode?(b.autohidedom=!1,b.rail.css({opacity:b.opt.cursoropacitymax}),b.railh&&b.railh.css({opacity:b.opt.cursoropacitymax})):!0===b.opt.autohidemode?(b.autohidedom=e().add(b.rail),f.isie8&& +(b.autohidedom=b.autohidedom.add(b.cursor)),b.railh&&(b.autohidedom=b.autohidedom.add(b.railh)),b.railh&&f.isie8&&(b.autohidedom=b.autohidedom.add(b.cursorh))):"scroll"==b.opt.autohidemode?(b.autohidedom=e().add(b.rail),b.railh&&(b.autohidedom=b.autohidedom.add(b.railh))):"cursor"==b.opt.autohidemode?(b.autohidedom=e().add(b.cursor),b.railh&&(b.autohidedom=b.autohidedom.add(b.cursorh))):"hidden"==b.opt.autohidemode&&(b.autohidedom=!1,b.hide(),b.locked=!1);if(f.isie9mobile)b.scrollmom=new H(b),b.onmangotouch= +function(d){d=b.getScrollTop();var c=b.getScrollLeft();if(d==b.scrollmom.lastscrolly&&c==b.scrollmom.lastscrollx)return!0;var g=d-b.mangotouch.sy,f=c-b.mangotouch.sx;if(0!=Math.round(Math.sqrt(Math.pow(f,2)+Math.pow(g,2)))){var p=0>g?-1:1,e=0>f?-1:1,h=+new Date;b.mangotouch.lazy&&clearTimeout(b.mangotouch.lazy);80s?s=Math.round(s/2):s>b.page.maxh&&(s=b.page.maxh+Math.round((s-b.page.maxh)/2)):(0>s&&(h=s=0),s>b.page.maxh&&(s=b.page.maxh,h=0));if(b.railh&&b.railh.scrollable){var m=b.rail.drag.sl-k;b.ishwscroll&&b.opt.bouncescroll?0>m?m=Math.round(m/2):m>b.page.maxw&&(m=b.page.maxw+ +Math.round((m-b.page.maxw)/2)):(0>m&&(l=m=0),m>b.page.maxw&&(m=b.page.maxw,l=0))}g=!1;if(b.rail.drag.dl)g=!0,"v"==b.rail.drag.dl?m=b.rail.drag.sl:"h"==b.rail.drag.dl&&(s=b.rail.drag.st);else{var p=Math.abs(p),k=Math.abs(k),n=b.opt.directionlockdeadzone;if("v"==b.rail.drag.ck){if(p>n&&k<=0.3*p)return b.rail.drag=!1,!0;k>n&&(b.rail.drag.dl="f",e("body").scrollTop(e("body").scrollTop()))}else if("h"==b.rail.drag.ck){if(k>n&&p<=0.3*az)return b.rail.drag=!1,!0;p>n&&(b.rail.drag.dl="f",e("body").scrollLeft(e("body").scrollLeft()))}}b.synched("touchmove", +function(){b.rail.drag&&2==b.rail.drag.pt&&(b.prepareTransition&&b.prepareTransition(0),b.rail.scrollable&&b.setScrollTop(s),b.scrollmom.update(l,h),b.railh&&b.railh.scrollable?(b.setScrollLeft(m),b.showCursor(s,m)):b.showCursor(s),f.isie10&&document.selection.clear())});f.ischrome&&b.istouchcapable&&(g=!1);if(g)return b.cancelEvent(d)}}}b.onmousedown=function(d,c){if(!(b.rail.drag&&1!=b.rail.drag.pt)){if(b.locked)return b.cancelEvent(d);b.cancelScroll();b.rail.drag={x:d.clientX,y:d.clientY,sx:b.scroll.x, +sy:b.scroll.y,pt:1,hr:!!c};var g=b.getTarget(d);!b.ispage&&f.hasmousecapture&&g.setCapture();b.isiframe&&!f.hasmousecapture&&(b.saved.csspointerevents=b.doc.css("pointer-events"),b.css(b.doc,{"pointer-events":"none"}));return b.cancelEvent(d)}};b.onmouseup=function(d){if(b.rail.drag&&(f.hasmousecapture&&document.releaseCapture(),b.isiframe&&!f.hasmousecapture&&b.doc.css("pointer-events",b.saved.csspointerevents),1==b.rail.drag.pt))return b.rail.drag=!1,b.cancelEvent(d)};b.onmousemove=function(d){if(b.rail.drag&& +1==b.rail.drag.pt){if(f.ischrome&&0==d.which)return b.onmouseup(d);b.cursorfreezed=!0;if(b.rail.drag.hr){b.scroll.x=b.rail.drag.sx+(d.clientX-b.rail.drag.x);0>b.scroll.x&&(b.scroll.x=0);var c=b.scrollvaluemaxw;b.scroll.x>c&&(b.scroll.x=c)}else b.scroll.y=b.rail.drag.sy+(d.clientY-b.rail.drag.y),0>b.scroll.y&&(b.scroll.y=0),c=b.scrollvaluemax,b.scroll.y>c&&(b.scroll.y=c);b.synched("mousemove",function(){b.rail.drag&&1==b.rail.drag.pt&&(b.showCursor(),b.rail.drag.hr?b.doScrollLeft(Math.round(b.scroll.x* +b.scrollratio.x),b.opt.cursordragspeed):b.doScrollTop(Math.round(b.scroll.y*b.scrollratio.y),b.opt.cursordragspeed))});return b.cancelEvent(d)}};if(f.cantouch||b.opt.touchbehavior)b.onpreventclick=function(d){if(b.preventclick)return b.preventclick.tg.onclick=b.preventclick.click,b.preventclick=!1,b.cancelEvent(d)},b.bind(b.win,"mousedown",b.ontouchstart),b.onclick=f.isios?!1:function(d){return b.lastmouseup?(b.lastmouseup=!1,b.cancelEvent(d)):!0},b.opt.grabcursorenabled&&f.cursorgrabvalue&&(b.css(b.ispage? +b.doc:b.win,{cursor:f.cursorgrabvalue}),b.css(b.rail,{cursor:f.cursorgrabvalue}));else{var r=function(d){if(b.selectiondrag){if(d){var c=b.win.outerHeight();d=d.pageY-b.selectiondrag.top;0=c&&(d-=c);b.selectiondrag.df=d}0!=b.selectiondrag.df&&(b.doScrollBy(2*-Math.floor(b.selectiondrag.df/6)),b.debounced("doselectionscroll",function(){r()},50))}};b.hasTextSelected="getSelection"in document?function(){return 0b.page.maxh?b.doScrollTop(b.page.maxh):(b.scroll.y=Math.round(b.getScrollTop()*(1/b.scrollratio.y)), +b.scroll.x=Math.round(b.getScrollLeft()*(1/b.scrollratio.x)),b.cursoractive&&b.noticeCursor());b.scroll.y&&0==b.getScrollTop()&&b.doScrollTo(Math.floor(b.scroll.y*b.scrollratio.y));return b};this.resize=b.onResize;this.lazyResize=function(d){d=isNaN(d)?30:d;b.delayed("resize",b.resize,d);return b};this._bind=function(d,c,g,f){b.events.push({e:d,n:c,f:g,b:f,q:!1});d.addEventListener?d.addEventListener(c,g,f||!1):d.attachEvent?d.attachEvent("on"+c,g):d["on"+c]=g};this.jqbind=function(d,c,g){b.events.push({e:d, +n:c,f:g,q:!0});e(d).bind(c,g)};this.bind=function(d,c,g,e){var h="jquery"in d?d[0]:d;"mousewheel"==c?"onwheel"in b.win?b._bind(h,"wheel",g,e||!1):(d="undefined"!=typeof document.onmousewheel?"mousewheel":"DOMMouseScroll",n(h,d,g,e||!1),"DOMMouseScroll"==d&&n(h,"MozMousePixelScroll",g,e||!1)):h.addEventListener?(f.cantouch&&/mouseup|mousedown|mousemove/.test(c)&&b._bind(h,"mousedown"==c?"touchstart":"mouseup"==c?"touchend":"touchmove",function(b){if(b.touches){if(2>b.touches.length){var d=b.touches.length? +b.touches[0]:b;d.original=b;g.call(this,d)}}else b.changedTouches&&(d=b.changedTouches[0],d.original=b,g.call(this,d))},e||!1),b._bind(h,c,g,e||!1),f.cantouch&&"mouseup"==c&&b._bind(h,"touchcancel",g,e||!1)):b._bind(h,c,function(d){if((d=d||window.event||!1)&&d.srcElement)d.target=d.srcElement;"pageY"in d||(d.pageX=d.clientX+document.documentElement.scrollLeft,d.pageY=d.clientY+document.documentElement.scrollTop);return!1===g.call(h,d)||!1===e?b.cancelEvent(d):!0})};this._unbind=function(b,c,g,f){b.removeEventListener? +b.removeEventListener(c,g,f):b.detachEvent?b.detachEvent("on"+c,g):b["on"+c]=!1};this.unbindAll=function(){for(var d=0;d +(b.newscrolly-h)*(e-h)||0>(b.newscrollx-l)*(c-l))&&b.cancelScroll();!1==b.opt.bouncescroll&&(0>e?e=0:e>b.page.maxh&&(e=b.page.maxh),0>c?c=0:c>b.page.maxw&&(c=b.page.maxw));if(b.scrollrunning&&c==b.newscrollx&&e==b.newscrolly)return!1;b.newscrolly=e;b.newscrollx=c;b.newscrollspeed=g||!1;if(b.timer)return!1;b.timer=setTimeout(function(){var g=b.getScrollTop(),h=b.getScrollLeft(),l,k;l=c-h;k=e-g;l=Math.round(Math.sqrt(Math.pow(l,2)+Math.pow(k,2)));l=b.newscrollspeed&&1=b.newscrollspeed&&(l*=b.newscrollspeed);b.prepareTransition(l,!0);b.timerscroll&&b.timerscroll.tm&&clearInterval(b.timerscroll.tm);0c?c=0:c>b.page.maxh&&(c=b.page.maxh);0>e?e=0:e>b.page.maxw&&(e=b.page.maxw);if(c!=b.newscrolly||e!=b.newscrollx)return b.doScrollPos(e,c,b.opt.snapbackspeed);b.onscrollend&&b.scrollrunning&&b.onscrollend.call(b,{type:"scrollend",current:{x:e,y:c},end:{x:b.newscrollx,y:b.newscrolly}});b.scrollrunning= +!1}):(this.doScrollLeft=function(c,f){var g=b.scrollrunning?b.newscrolly:b.getScrollTop();b.doScrollPos(c,g,f)},this.doScrollTop=function(c,f){var g=b.scrollrunning?b.newscrollx:b.getScrollLeft();b.doScrollPos(g,c,f)},this.doScrollPos=function(c,f,g){function e(){if(b.cancelAnimationFrame)return!0;b.scrollrunning=!0;if(r=1-r)return b.timer=v(e)||1;var c=0,d=sy=b.getScrollTop();if(b.dst.ay){var d=b.bzscroll?b.dst.py+b.bzscroll.getNow()*b.dst.ay:b.newscrolly,g=d-sy;if(0>g&&db.newscrolly)d= +b.newscrolly;b.setScrollTop(d);d==b.newscrolly&&(c=1)}else c=1;var f=sx=b.getScrollLeft();if(b.dst.ax){f=b.bzscroll?b.dst.px+b.bzscroll.getNow()*b.dst.ax:b.newscrollx;g=f-sx;if(0>g&&fb.newscrollx)f=b.newscrollx;b.setScrollLeft(f);f==b.newscrollx&&(c+=1)}else c+=1;2==c?(b.timer=0,b.cursorfreezed=!1,b.bzscroll=!1,b.scrollrunning=!1,0>d?d=0:d>b.page.maxh&&(d=b.page.maxh),0>f?f=0:f>b.page.maxw&&(f=b.page.maxw),f!=b.newscrollx||d!=b.newscrolly?b.doScrollPos(f,d):b.onscrollend&&b.onscrollend.call(b, +{type:"scrollend",current:{x:sx,y:sy},end:{x:b.newscrollx,y:b.newscrolly}})):b.timer=v(e)||1}f="undefined"==typeof f||!1===f?b.getScrollTop(!0):f;if(b.timer&&b.newscrolly==f&&b.newscrollx==c)return!0;b.timer&&w(b.timer);b.timer=0;var h=b.getScrollTop(),l=b.getScrollLeft();(0>(b.newscrolly-h)*(f-h)||0>(b.newscrollx-l)*(c-l))&&b.cancelScroll();b.newscrolly=f;b.newscrollx=c;if(!b.bouncescroll||!b.rail.visibility)0>b.newscrolly?b.newscrolly=0:b.newscrolly>b.page.maxh&&(b.newscrolly=b.page.maxh);if(!b.bouncescroll|| +!b.railh.visibility)0>b.newscrollx?b.newscrollx=0:b.newscrollx>b.page.maxw&&(b.newscrollx=b.page.maxw);b.dst={};b.dst.x=c-l;b.dst.y=f-h;b.dst.px=l;b.dst.py=h;var k=Math.round(Math.sqrt(Math.pow(b.dst.x,2)+Math.pow(b.dst.y,2)));b.dst.ax=b.dst.x/k;b.dst.ay=b.dst.y/k;var n=0,q=k;0==b.dst.x?(n=h,q=f,b.dst.ay=1,b.dst.py=0):0==b.dst.y&&(n=l,q=c,b.dst.ax=1,b.dst.px=0);k=b.getTransitionSpeed(k);g&&1>=g&&(k*=g);b.bzscroll=0=b.page.maxh||l==b.page.maxw&&c>=b.page.maxw)&&b.checkContentSize();var r=1;b.cancelAnimationFrame=!1;b.timer=1;b.onscrollstart&&!b.scrollrunning&&b.onscrollstart.call(b,{type:"scrollstart",current:{x:l,y:h},request:{x:c,y:f},end:{x:b.newscrollx,y:b.newscrolly},speed:k});e();(h==b.page.maxh&&f>=h||l==b.page.maxw&&c>=l)&&b.checkContentSize();b.noticeCursor()}},this.cancelScroll=function(){b.timer&&w(b.timer);b.timer=0;b.bzscroll=!1;b.scrollrunning=!1;return b}):(this.doScrollLeft=function(c, +f){var g=b.getScrollTop();b.doScrollPos(c,g,f)},this.doScrollTop=function(c,f){var g=b.getScrollLeft();b.doScrollPos(g,c,f)},this.doScrollPos=function(c,f,g){var e=c>b.page.maxw?b.page.maxw:c;0>e&&(e=0);var h=f>b.page.maxh?b.page.maxh:f;0>h&&(h=0);b.synched("scroll",function(){b.setScrollTop(h);b.setScrollLeft(e)})},this.cancelScroll=function(){});this.doScrollBy=function(c,f){var g=0,g=f?Math.floor((b.scroll.y-c)*b.scrollratio.y):(b.timer?b.newscrolly:b.getScrollTop(!0))-c;if(b.bouncescroll){var e= +Math.round(b.view.h/2);g<-e?g=-e:g>b.page.maxh+e&&(g=b.page.maxh+e)}b.cursorfreezed=!1;py=b.getScrollTop(!0);if(0>g&&0>=py)return b.noticeCursor();if(g>b.page.maxh&&py>=b.page.maxh)return b.checkContentSize(),b.noticeCursor();b.doScrollTop(g)};this.doScrollLeftBy=function(c,f){var g=0,g=f?Math.floor((b.scroll.x-c)*b.scrollratio.x):(b.timer?b.newscrollx:b.getScrollLeft(!0))-c;if(b.bouncescroll){var e=Math.round(b.view.w/2);g<-e?g=-e:g>b.page.maxw+e&&(g=b.page.maxw+e)}b.cursorfreezed=!1;px=b.getScrollLeft(!0); +if(0>g&&0>=px||g>b.page.maxw&&px>=b.page.maxw)return b.noticeCursor();b.doScrollLeft(g)};this.doScrollTo=function(c,f){f&&Math.round(c*b.scrollratio.y);b.cursorfreezed=!1;b.doScrollTop(c)};this.checkContentSize=function(){var c=b.getContentSize();(c.h!=b.page.h||c.w!=b.page.w)&&b.resize(!1,c)};b.onscroll=function(c){b.rail.drag||b.cursorfreezed||b.synched("scroll",function(){b.scroll.y=Math.round(b.getScrollTop()*(1/b.scrollratio.y));b.railh&&(b.scroll.x=Math.round(b.getScrollLeft()*(1/b.scrollratio.x))); +b.noticeCursor()})};b.bind(b.docscroll,"scroll",b.onscroll);this.doZoomIn=function(c){if(!b.zoomactive){b.zoomactive=!0;b.zoomrestore={style:{}};var h="position top left zIndex backgroundColor marginTop marginBottom marginLeft marginRight".split(" "),g=b.win[0].style,l;for(l in h){var k=h[l];b.zoomrestore.style[k]="undefined"!=typeof g[k]?g[k]:""}b.zoomrestore.style.width=b.win.css("width");b.zoomrestore.style.height=b.win.css("height");b.zoomrestore.padding={w:b.win.outerWidth()-b.win.width(),h:b.win.outerHeight()- +b.win.height()};f.isios4&&(b.zoomrestore.scrollTop=e(window).scrollTop(),e(window).scrollTop(0));b.win.css({position:f.isios4?"absolute":"fixed",top:0,left:0,"z-index":x+100,margin:"0px"});h=b.win.css("backgroundColor");(""==h||/transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(h))&&b.win.css("backgroundColor","#fff");b.rail.css({"z-index":x+101});b.zoom.css({"z-index":x+102});b.zoom.css("backgroundPosition","0px -18px");b.resizeZoom();b.onzoomin&&b.onzoomin.call(b);return b.cancelEvent(c)}};this.doZoomOut= +function(c){if(b.zoomactive)return b.zoomactive=!1,b.win.css("margin",""),b.win.css(b.zoomrestore.style),f.isios4&&e(window).scrollTop(b.zoomrestore.scrollTop),b.rail.css({"z-index":b.zindex}),b.zoom.css({"z-index":b.zindex}),b.zoomrestore=!1,b.zoom.css("backgroundPosition","0px 0px"),b.onResize(),b.onzoomout&&b.onzoomout.call(b),b.cancelEvent(c)};this.doZoom=function(c){return b.zoomactive?b.doZoomOut(c):b.doZoomIn(c)};this.resizeZoom=function(){if(b.zoomactive){var c=b.getScrollTop();b.win.css({width:e(window).width()- +b.zoomrestore.padding.w+"px",height:e(window).height()-b.zoomrestore.padding.h+"px"});b.onResize();b.setScrollTop(Math.min(b.page.maxh,c))}};this.init();e.nicescroll.push(this)},H=function(e){var c=this;this.nc=e;this.steptime=this.lasttime=this.speedy=this.speedx=this.lasty=this.lastx=0;this.snapy=this.snapx=!1;this.demuly=this.demulx=0;this.lastscrolly=this.lastscrollx=-1;this.timer=this.chky=this.chkx=0;this.time=function(){return+new Date};this.reset=function(e,l){c.stop();var k=c.time();c.steptime= +0;c.lasttime=k;c.speedx=0;c.speedy=0;c.lastx=e;c.lasty=l;c.lastscrollx=-1;c.lastscrolly=-1};this.update=function(e,l){var k=c.time();c.steptime=k-c.lasttime;c.lasttime=k;var k=l-c.lasty,t=e-c.lastx,b=c.nc.getScrollTop(),q=c.nc.getScrollLeft(),b=b+k,q=q+t;c.snapx=0>q||q>c.nc.page.maxw;c.snapy=0>b||b>c.nc.page.maxh;c.speedx=t;c.speedy=k;c.lastx=e;c.lasty=l};this.stop=function(){c.nc.unsynched("domomentum2d");c.timer&&clearTimeout(c.timer);c.timer=0;c.lastscrollx=-1;c.lastscrolly=-1};this.doSnapy=function(e, +l){var k=!1;0>l?(l=0,k=!0):l>c.nc.page.maxh&&(l=c.nc.page.maxh,k=!0);0>e?(e=0,k=!0):e>c.nc.page.maxw&&(e=c.nc.page.maxw,k=!0);k&&c.nc.doScrollPos(e,l,c.nc.opt.snapbackspeed)};this.doMomentum=function(e){var l=c.time(),k=e?l+e:c.lasttime;e=c.nc.getScrollLeft();var t=c.nc.getScrollTop(),b=c.nc.page.maxh,q=c.nc.page.maxw;c.speedx=0=l-k;if(0>t||t>b||0>e||e>q)k=!1;e=c.speedx&&k?c.speedx:!1;if(c.speedy&&k&&c.speedy||e){var f=Math.max(16, +c.steptime);50r||r>q))e=0.1;if(c.speedy&&(u=Math.floor(c.lastscrolly-c.speedy*(1-c.demulxy)),c.lastscrolly=u,0>u||u>b))e=0.1;c.demulxy=Math.min(1,c.demulxy+e);c.nc.synched("domomentum2d", +function(){c.speedx&&(c.nc.getScrollLeft()!=c.chkx&&c.stop(),c.chkx=r,c.nc.setScrollLeft(r));c.speedy&&(c.nc.getScrollTop()!=c.chky&&c.stop(),c.chky=u,c.nc.setScrollTop(u));c.timer||(c.nc.hideCursor(),c.doSnapy(r,u))});1>c.demulxy?c.timer=setTimeout(d,f):(c.stop(),c.nc.hideCursor(),c.doSnapy(r,u))};d()}else c.doSnapy(c.nc.getScrollLeft(),c.nc.getScrollTop())}},A=e.fn.scrollTop;e.cssHooks.pageYOffset={get:function(k,c,h){return(c=e.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollTop():A.call(k)}, +set:function(k,c){var h=e.data(k,"__nicescroll")||!1;h&&h.ishwscroll?h.setScrollTop(parseInt(c)):A.call(k,c);return this}};e.fn.scrollTop=function(k){if("undefined"==typeof k){var c=this[0]?e.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollTop():A.call(this)}return this.each(function(){var c=e.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollTop(parseInt(k)):A.call(e(this),k)})};var B=e.fn.scrollLeft;e.cssHooks.pageXOffset={get:function(k,c,h){return(c=e.data(k,"__nicescroll")|| +!1)&&c.ishwscroll?c.getScrollLeft():B.call(k)},set:function(k,c){var h=e.data(k,"__nicescroll")||!1;h&&h.ishwscroll?h.setScrollLeft(parseInt(c)):B.call(k,c);return this}};e.fn.scrollLeft=function(k){if("undefined"==typeof k){var c=this[0]?e.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollLeft():B.call(this)}return this.each(function(){var c=e.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollLeft(parseInt(k)):B.call(e(this),k)})};var C=function(k){var c=this;this.length= +0;this.name="nicescrollarray";this.each=function(e){for(var h=0;h