From 025fb8905515aea9e83ac193f1c5e4d2f703fad3 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 28 Nov 2014 12:40:27 +0000 Subject: [PATCH] 1.1 release --- .gitignore | 4 +- .htaccess | 2 +- Gruntfile.js | 158 + README.md | 51 +- bower.json | 28 + config/routes.cfm | 13 +- config/settings.cfm | 34 +- controllers/Api.cfc | 133 +- controllers/Bookings.cfc | 462 +- controllers/Controller.cfc | 179 +- controllers/Eventdata.cfc | 117 +- controllers/Locations.cfc | 97 +- controllers/Logfiles.cfc | 63 +- controllers/PasswordResets.cfc | 55 +- controllers/Permissions.cfc | 58 +- controllers/Resources.cfc | 122 +- controllers/Sessions.cfc | 80 +- controllers/Settings.cfc | 61 +- controllers/Users.cfc | 298 +- events/functions.cfm | 149 +- events/global/auth.cfm | 198 + events/global/cookies.cfm | 18 + events/global/utils.cfm | 50 + events/onrequeststart.cfm | 5 +- files/.gitignore | 1 - images/.gitignore | 1 - images/alpha.png | Bin 3271 -> 0 bytes images/hue.png | Bin 2972 -> 0 bytes images/saturation.png | Bin 8817 -> 0 bytes install/index.cfm | 7 +- install/roombooking.sql | 116 +- javascripts/bootstrap-colorpicker.js | 561 - javascripts/bootstrap.min.js | 8 - javascripts/fullcalender.js | 7 - javascripts/jquery-1.10.2.min.js | 6 - javascripts/jquery-ui-1.10.3.custom.min.js | 7 - javascripts/main.js | 91 + javascripts/modernizr-2.6.2.min.js | 4 - javascripts/moment.js | 6 - javascripts/rbs.js | 33810 ++++++++++++++++ javascripts/rbs.min.js | 12 + javascripts/timepicker.js | 2134 - lib/.gitignore | 1 - miscellaneous/Application.cfc | 6 - models/Event.cfc | 53 +- models/Eventresource.cfc | 21 +- models/Location.cfc | 19 +- models/Resource.cfc | 19 +- models/Settings.cfc | 17 +- models/User.cfc | 120 +- package.json | 30 + stylesheets/.gitignore | 1 - stylesheets/bootstrap-colorpicker.css | 117 - stylesheets/custom.css | 57 - .../fonts/glyphicons-halflings-regular.eot | Bin 14079 -> 20335 bytes .../fonts/glyphicons-halflings-regular.svg | 403 +- .../fonts/glyphicons-halflings-regular.ttf | Bin 29512 -> 41280 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 16448 -> 23320 bytes stylesheets/fullcalendar.css | 589 - stylesheets/less/core/rbs.less | 71 + stylesheets/less/core/variables.less | 857 + stylesheets/less/site/displayboard.less | 15 + stylesheets/less/site/layout.less | 49 + stylesheets/rbs.css | 7754 ++++ stylesheets/rbs.min.css | 11 + .../smoothness/images/animated-overlay.gif | Bin 1738 -> 0 bytes .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 212 -> 0 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 208 -> 0 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 335 -> 0 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 207 -> 0 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 262 -> 0 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 262 -> 0 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 332 -> 0 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 280 -> 0 bytes .../images/ui-icons_222222_256x240.png | Bin 6922 -> 0 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 4549 -> 0 bytes .../images/ui-icons_454545_256x240.png | Bin 6992 -> 0 bytes .../images/ui-icons_888888_256x240.png | Bin 6999 -> 0 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 4549 -> 0 bytes .../smoothness/jquery-ui-1.10.3.custom.css | 944 - .../jquery-ui-1.10.3.custom.min.css | 7 - stylesheets/theme.css | 9 - tests/.gitignore | 1 - views/api/display.cfm | 53 + views/api/displayboard.cfm | 7 + views/api/index.cfm | 50 +- views/bookings/_eventmodal.cfm | 13 +- views/bookings/_form.cfm | 85 +- views/bookings/add.cfm | 4 +- views/bookings/day.cfm | 48 +- views/bookings/day/_header.cfm | 6 +- views/bookings/edit.cfm | 4 +- views/bookings/index.cfm | 10 +- views/bookings/list.cfm | 1 - views/bookings/list/_filter.cfm | 24 +- views/bookings/tabs/_contact.cfm | 8 +- views/bookings/tabs/_details.cfm | 6 +- views/bookings/test.cfm | 17 - views/common/_footer.cfm | 23 + views/common/_head.cfm | 22 + views/common/_main.cfm | 9 + views/common/_navbar.cfm | 92 + views/eventdata/modal.cfm | 26 +- views/helpers.cfm | 209 +- views/layout.cfm | 178 +- views/locations/_form.cfm | 20 +- views/locations/add.cfm | 2 +- views/locations/edit.cfm | 4 +- views/locations/index.cfm | 12 +- views/logfiles/_filter.cfm | 16 +- views/logfiles/index.cfm | 12 +- views/passwordresets/_create.cfm | 19 +- views/passwordresets/edit.cfm | 6 +- views/passwordresets/new.cfm | 10 +- views/permissions/index.cfm | 32 +- views/resources/_form.cfm | 7 +- views/resources/add.cfm | 2 +- views/resources/edit.cfm | 2 +- views/resources/index.cfm | 8 +- views/sessions/_signin.cfm | 26 +- views/sessions/new.cfm | 14 +- views/settings/_form.cfm | 6 +- views/settings/index.cfm | 39 +- views/users/add.cfm | 26 +- views/users/edit.cfm | 18 +- views/users/formparts/_main.cfm | 10 +- views/users/formparts/_userpw.cfm | 6 +- views/users/formparts/_userrole.cfm | 6 +- views/users/myaccount.cfm | 18 +- web.config | 2 +- 130 files changed, 45037 insertions(+), 6558 deletions(-) create mode 100644 Gruntfile.js create mode 100644 bower.json create mode 100644 events/global/auth.cfm create mode 100644 events/global/cookies.cfm create mode 100644 events/global/utils.cfm delete mode 100644 files/.gitignore delete mode 100644 images/.gitignore delete mode 100644 images/alpha.png delete mode 100644 images/hue.png delete mode 100644 images/saturation.png delete mode 100644 javascripts/bootstrap-colorpicker.js delete mode 100644 javascripts/bootstrap.min.js delete mode 100644 javascripts/fullcalender.js delete mode 100644 javascripts/jquery-1.10.2.min.js delete mode 100644 javascripts/jquery-ui-1.10.3.custom.min.js create mode 100644 javascripts/main.js delete mode 100644 javascripts/modernizr-2.6.2.min.js delete mode 100644 javascripts/moment.js create mode 100644 javascripts/rbs.js create mode 100644 javascripts/rbs.min.js delete mode 100644 javascripts/timepicker.js delete mode 100644 lib/.gitignore delete mode 100644 miscellaneous/Application.cfc create mode 100644 package.json delete mode 100644 stylesheets/.gitignore delete mode 100644 stylesheets/bootstrap-colorpicker.css delete mode 100644 stylesheets/custom.css delete mode 100644 stylesheets/fullcalendar.css create mode 100644 stylesheets/less/core/rbs.less create mode 100644 stylesheets/less/core/variables.less create mode 100644 stylesheets/less/site/displayboard.less create mode 100644 stylesheets/less/site/layout.less create mode 100644 stylesheets/rbs.css create mode 100644 stylesheets/rbs.min.css delete mode 100644 stylesheets/smoothness/images/animated-overlay.gif delete mode 100644 stylesheets/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png delete mode 100644 stylesheets/smoothness/images/ui-bg_flat_75_ffffff_40x100.png delete mode 100644 stylesheets/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png delete mode 100644 stylesheets/smoothness/images/ui-bg_glass_65_ffffff_1x400.png delete mode 100644 stylesheets/smoothness/images/ui-bg_glass_75_dadada_1x400.png delete mode 100644 stylesheets/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png delete mode 100644 stylesheets/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png delete mode 100644 stylesheets/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png delete mode 100644 stylesheets/smoothness/images/ui-icons_222222_256x240.png delete mode 100644 stylesheets/smoothness/images/ui-icons_2e83ff_256x240.png delete mode 100644 stylesheets/smoothness/images/ui-icons_454545_256x240.png delete mode 100644 stylesheets/smoothness/images/ui-icons_888888_256x240.png delete mode 100644 stylesheets/smoothness/images/ui-icons_cd0a0a_256x240.png delete mode 100644 stylesheets/smoothness/jquery-ui-1.10.3.custom.css delete mode 100644 stylesheets/smoothness/jquery-ui-1.10.3.custom.min.css delete mode 100644 stylesheets/theme.css delete mode 100644 tests/.gitignore create mode 100644 views/api/display.cfm create mode 100644 views/api/displayboard.cfm delete mode 100644 views/bookings/test.cfm create mode 100644 views/common/_footer.cfm create mode 100644 views/common/_head.cfm create mode 100644 views/common/_main.cfm create mode 100644 views/common/_navbar.cfm diff --git a/.gitignore b/.gitignore index 4e210fb..5523d8a 100644 --- a/.gitignore +++ b/.gitignore @@ -217,4 +217,6 @@ pip-log.txt WEB-INF plugins/**/* db/sql -config/auth.cfm \ No newline at end of file +config/auth.cfm +bower_components +node_modules \ No newline at end of file diff --git a/.htaccess b/.htaccess index 04d375a..047efac 100644 --- a/.htaccess +++ b/.htaccess @@ -7,5 +7,5 @@ Options +FollowSymLinks RewriteEngine On - RewriteCond %{REQUEST_URI} !^.*/(flex2gateway|jrunscripts|cfide|cfformgateway|cffileservlet|railo-context|files|images|javascripts|miscellaneous|stylesheets|robots.txt|favicon.ico|sitemap.xml|rewrite.cfm)($|/.*$) [NC] + RewriteCond %{REQUEST_URI} !^.*/(flex2gateway|bower_components|jrunscripts|cfide|cfformgateway|cffileservlet|railo-context|files|images|javascripts|miscellaneous|stylesheets|robots.txt|favicon.ico|sitemap.xml|rewrite.cfm)($|/.*$) [NC] RewriteRule ^(.*)$ ./rewrite.cfm/$1 [NS,L] \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..985ff87 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,158 @@ +module.exports = function(grunt) { + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + // Task configuration + less: { + css: { + options: { + compress: false, //minifying the result + }, + files: { + "stylesheets/rbs.css":"stylesheets/less/core/rbs.less" + } + } + }, + + cssmin: { + options: { + banner: "/*! <%= pkg.name %> minified CSS file generated @ <%= grunt.template.today('dd-mm-yyyy') %> */\n" + }, + css: { + files: { + "stylesheets/rbs.min.css": [ "stylesheets/rbs.css" ] + } + } + }, + + clean : { + css : { + src: ["stylesheets/rbs.css", "stylesheets/rbs.min.css"] + }, + js: { + src: ["javascripts/rbs.js","javascripts/rbs.min.js"] + } + }, + + concat : { + options: { + separator: ';', + }, + frontend: { + src: [ + // Bootstrap & JQuery + 'bower_components/jquery/dist/jquery.js', + 'bower_components/bootstrap/js/alert.js', + 'bower_components/bootstrap/js/button.js', + 'bower_components/bootstrap/js/carousel.js', + 'bower_components/bootstrap/js/collapse.js', + 'bower_components/bootstrap/js/dropdown.js', + 'bower_components/bootstrap/js/modal.js', + 'bower_components/bootstrap/js/tooltip.js', + 'bower_components/bootstrap/js/popover.js', + 'bower_components/bootstrap/js/tab.js', + 'bower_components/bootstrap/js/transition.js', + // Calendar + 'bower_components/moment/moment.js', + 'bower_components/fullcalendar/dist/fullcalendar.js', + 'bower_components/eonasdan-bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js', + 'bower_components/bootstrapvalidator/dist/js/bootstrapValidator.js', + 'bower_components/minicolors/jquery.minicolors.js', + // Main.js is any custom JS + 'javascripts/main.js' + ], + dest: 'javascripts/rbs.js' + } + }, + + + + jshint: { + options: { + curly: true, + eqeqeq: true, + eqnull: true, + browser: true, + globals: { + jQuery: true + }, + }, + all: ['Gruntfile.js', 'javascripts/main.js'] + }, + + uglify : { + frontend: { + files: { + 'javascripts/rbs.min.js' : [ 'javascripts/rbs.js' ] + } + } + }, + + imagemin: { + png: { + options: { + optimizationLevel: 7 + }, + files: [ + { + // Set to true to enable the following options… + expand: true, + // cwd is 'current working directory' + cwd: '/images/', + src: ['**/*.png'], + // Could also match cwd line above. i.e. project-directory/img/ + dest: '/images/', + ext: '.png' + } + ] + }, + jpg: { + options: { + progressive: true + }, + files: [ + { + // Set to true to enable the following options… + expand: true, + // cwd is 'current working directory' + cwd: '/images/', + src: ['**/*.jpg'], + // Could also match cwd. i.e. project-directory/img/ + dest: '/images/', + ext: '.jpg' + } + ] + } + } , + + watch: { + js: { + files: ['javascripts/main.js'], + tasks: ['js'] + }, + css: { + files: ['stylesheets/less/core/*.less', 'stylesheets/less/site/*.less'], + tasks: ['css'] + } + } + + + }); + + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-imagemin'); + grunt.loadNpmTasks('grunt-contrib-less'); + grunt.loadNpmTasks('grunt-injector'); + grunt.loadNpmTasks('grunt-rev'); + + grunt.registerTask('css', ['clean:css', 'less:css', 'cssmin:css']); + grunt.registerTask('js', ['jshint', 'clean:js', 'concat', 'uglify']); + grunt.registerTask('img', ['imagemin']); + grunt.registerTask('default', ['watch']); +}; diff --git a/README.md b/README.md index f237a7c..fdf0f69 100644 --- a/README.md +++ b/README.md @@ -6,48 +6,39 @@ Author: Tom King - http://www.oxalto.co.uk / https://github.com/Neokoenig / @neo ## Demo -You can find a demo at [roombooking.oxalto.co.uk][12] +You can find a demo at [roombooking.oxalto.co.uk][1] + +## Version + +Current version is 1.1 [release notes] ## Requirements - 1. A server capable of running a cfWheels application - Adobe Coldfusion (8.x upwards) or Railo (4.x upwards), preferably with URL rewriting - 2. Database - one of MySQL / Microsoft SQL Server / Oracle / PostgreSQL - whilst this should work on any of those, it's been tested on mySQL 5.x. + - A server capable of running a cfWheels application - Adobe Coldfusion (10.x upwards) or Railo (4.x upwards), preferably with URL rewriting + - Database - mySQL 5.x. -## Installation +## Installation & Upgrading -Moved to [Wiki][13] +Please see the [Wiki][2] for all documentation. ## Notes This application uses the following plugins and 3rd party code: - 1. [ColdFusion on Wheels][1] - 2. [jQuery][2] - 3. [jQueryUI][3] - 4. [Bootstrap3] [4] - 5. [Bootswatch Themes][5] - 6. [FullCalendar][6] - 7. [TimePicker][7] - 8. [MomentJS][8] - 9. [Colorpicker for Twitter Bootstrap][9] - 11: [cfWheels FlashWrapper Plugin][11] - -[1]: http://cfwheels.org/ -[2]: http://jquery.com/ -[3]: http://jqueryui.com/ -[4]: http://getbootstrap.com/ -[5]: http://bootswatch.com/ -[6]: http://arshaw.com/fullcalendar/ -[7]: http://trentrichardson.com/examples/timepicker/ -[8]: http://momentjs.com/ -[9]: http://mjaalnir.github.io/bootstrap-colorpicker/ -[11]: https://github.com/gregstallings/cfwheels-flash-wrapper -[12]: http://roombooking.oxalto.co.uk -[13]: https://github.com/neokoenig/RoomBooking/wiki/_pages + - [ColdFusion on Wheels][3] + - [jQuery][4] + - [Bootstrap3] [5] + - [FullCalendar][6] + - [MomentJS][7] ## License Room Booking System is released under the Apache License Version 2.0. - - +[1]: http://roombooking.oxalto.co.uk +[2]: https://github.com/neokoenig/RoomBooking/wiki/_pages +[3]: http://cfwheels.org/ +[4]: http://jquery.com/ +[5]: http://getbootstrap.com/ +[6]: http://fullcalendar.io/ +[7]: http://momentjs.com/ \ No newline at end of file diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..febb20e --- /dev/null +++ b/bower.json @@ -0,0 +1,28 @@ +{ + "name": "Room Booking System", + "version": "1.1.0", + "homepage": "https://github.com/neokoenig/RoomBooking", + "authors": [ + "Tom King " + ], + "description": "Room Booking System", + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "source", + "test", + "tests" + ], + "devDependencies": { + "bootstrap": "~3.3.1", + "moment": "~2.8.3", + "fullcalendar": "~2.2.0", + "eonasdan-bootstrap-datetimepicker": "~3.1.3", + "bootstrapvalidator": "~0.5.3", + "colorpicker": "~1.0.16", + "minicolors": "~2.1.7" + } +} diff --git a/config/routes.cfm b/config/routes.cfm index f4e07eb..be0d34d 100644 --- a/config/routes.cfm +++ b/config/routes.cfm @@ -3,21 +3,20 @@ The default route is the one that will be called on your application's "home" page. ---> - // Customer Routes - addRoute(name="updateaccount", pattern="/my/account/u", controller="users", action="updateaccount"); - addRoute(name="myaccount", pattern="/my/account", controller="users", action="myaccount"); + addRoute(name="updateaccount", pattern="/my/account/u", controller="users", action="updateaccount"); + addRoute(name="myaccount", pattern="/my/account", controller="users", action="myaccount"); addRoute(name="updatepassword", pattern="/my/password/u", controller="users", action="updatepassword"); - addRoute(name="mypassword", pattern="/my/password", controller="users", action="mypassword"); - + addRoute(name="mypassword", pattern="/my/password", controller="users", action="mypassword"); + // Login/Out addRoute(name="logout", pattern="/logout", controller="sessions", action="logout"); addRoute(name="attemptlogin", pattern="/login/a/", controller="sessions", action="attemptlogin"); addRoute(name="login", pattern="/login", controller="sessions", action="new"); addRoute(name="forgetme", pattern="/forgetme", controller="sessions", action="forgetme"); - addRoute(name="denied", pattern="/denied", controller="sessions", action="denied"); + addRoute(name="denied", pattern="/denied", controller="sessions", action="denied"); // Default addRoute(name="home", pattern="", controller="bookings", action="index"); - + diff --git a/config/settings.cfm b/config/settings.cfm index ee93432..3064ffc 100644 --- a/config/settings.cfm +++ b/config/settings.cfm @@ -2,9 +2,9 @@ If you leave these settings commented out, Wheels will set the data source name to the same name as the folder the application resides in. - + ---> - + set(dataSourceName="roombooking"); -set(URLRewriting="on"); +set(URLRewriting="on"); set(allowedEnvironmentSwitchThroughURL=true); set(reloadPassword="roombooking"); @@ -25,26 +25,26 @@ set(excludeFromErrorEmail="password,hashedpassword,passwordsalt,ssn"); set(flashStorage="session"); // valid values are "session" or "cookie" set(obfuscateURLs=false); set(overwritePlugins=true); // set to false for plugin development - // BS3 form settings set(functionName="startFormTag"); set(functionName="submitTag", class="btn btn-primary", value="Save Changes"); -set(functionName="checkBox,checkBoxTag,radioButton,radioButtonTag", labelPlacement="around", prependToLabel="
", appendToLabel="
", uncheckedValue="0"); -set(functionName="radioButton,radioButtonTag", labelPlacement="around", prependToLabel="
", appendToLabel="
" ); -set(functionName="textField,textFieldTag,select,selectTag,passwordField,passwordFieldTag,textArea,textAreaTag,fileFieldTag,fileField", - class="form-control", - labelClass="control-label", - labelPlacement="before", - prependToLabel="
", - prepend="
", - append="
" ); +set(functionName="checkBox,checkBoxTag", labelPlacement="aroundRight", prependToLabel="
", appendToLabel="
", uncheckedValue="0"); +set(functionName="radioButton,radioButtonTag", labelPlacement="aroundRight", prependToLabel="
", appendToLabel="
"); +set(functionName="textField,textFieldTag,select,selectTag,passwordField,passwordFieldTag,textArea,textAreaTag,fileFieldTag,fileField", +class="form-control", +labelClass="control-label", +labelPlacement="before", +prependToLabel="
", +prepend="", +append="
" ); -set(functionName="dateTimeSelect,dateSelect", prepend="
", append="
", timeSeparator="", minuteStep="5", secondStep="30", dateOrder="day,month,year", dateSeparator="", separator=""); +set(functionName="dateTimeSelect,dateSelect", prepend="
", append="
", timeSeparator="", minuteStep="5", secondStep="10", dateOrder="day,month,year", dateSeparator="", separator=""); -set(functionName="errorMessagesFor", class="alert alert-danger"" style=""margin-left:0;padding-left:26px;"); +set(functionName="errorMessagesFor", class="alert alert-dismissable alert-danger"" style=""margin-left:0;padding-left:26px;"); set(functionName="errorMessageOn", wrapperElement="div", class="alert alert-danger"); set(functionName="flash", prepend="
×", append="
"); -set(functionName="flashMessages", prependToValue="
×", appendToValue="
"); +set(functionName="flashMessages", prependToValue="
×", appendToValue="
"); set(functionName="paginationLinks", prepend="
    ", append="
", prependToPage="
  • ", appendToPage="
  • ", linkToCurrentPage=true, classForCurrent="active", anchorDivider="
  • ...
  • "); -
    \ No newline at end of file + + \ No newline at end of file diff --git a/controllers/Api.cfc b/controllers/Api.cfc index 5cf7ca9..8e3946c 100644 --- a/controllers/Api.cfc +++ b/controllers/Api.cfc @@ -1,48 +1,75 @@ - - - - - provides("json,xml,html"); - filters(through="isValidAPIRequest", except="index"); - filters(through="checkPermissionAndRedirect", permission="allowAPI", only="index"); - filters(through="checkPermissionAndRedirect", permission="accessapplication", only="index"); - filters(through="_getLocations", only="index"); - usesLayout(template="false", only="ical"); - //filters(through="checkPermissionAndRedirect", permission="allowRSS", only="rss"); - //filters(through="checkPermissionAndRedirect", permission="allowiCAL", only="ical"); - - + // Permissions (no super.init()) + filters(through="f_isValidAPIRequest", except="index"); + filters(through="checkPermissionAndRedirect", permission="allowAPI", only="index"); - - + // Data + filters(through="_getLocations", only="index"); - + // Format + provides("json,xml,html"); + usesLayout(template="false", only="ical"); + usesLayout(template="displayboard", only="display"); + } - - - - - - +/******************** Public***********************/ + /** + * @hint Full Screen Display/digital signage + */ + public void function display() { + param name="params.maxrows" default="5" type="numeric"; + param name="params.today" default=0 type="numeric"; + request.bodyClass="displayBoard"; + var sd=createDateTime(year(now()), month(now()), day(now()), 00, 00, 00); + var ed=createDateTime(year(now()), month(now()), day(now()), 23, 59, 00); + if(params.today){ + isToday=true; + if(structKeyExists(params, "location") AND isnumeric(params.location)){ + isSingleLocation=true; + events=model("location").findAll(where="start > '#sd#' AND end < '#ed#' AND id = #params.location#", include="events", order="start", maxrows=params.maxrows); + } + else { + events=model("location").findAll(where="start > '#sd#' AND end < '#ed#'", include="events", order="start", maxrows=params.maxrows); + } + } else { if(structKeyExists(params, "location") AND isnumeric(params.location)){ + isSingleLocation=true; + events=model("location").findAll(where="start > '#now()#' AND id = #params.location#", include="events", order="start", maxrows=params.maxrows); + } + else { + events=model("location").findAll(where="start > '#now()#'", include="events", order="start", maxrows=params.maxrows); + } + } + + } + + /** + * @hint RSS2 Feed Defaults to 25 rows, but you can add maxrows=x to override + */ + public void function rss2() { + param name="params.maxrows" default="25" type="numeric"; + param name="params.format" default="xml" type="string"; + if(structKeyExists(params, "location") AND isnumeric(params.location)){ events=model("location").findAll(where="start > '#now()#' AND id = #params.location#", include="events", order="start", maxrows=params.maxrows); } else { events=model("location").findAll(where="start > '#now()#'", include="events", order="start", maxrows=params.maxrows); } renderWith(data=events); - - + } - - - + /** + * @hint iCal feed - Bit of an experiment! based on cflib.org USiCal() + */ + public void function ical() { + param name="params.maxrows" default="25" type="numeric"; var vCal = ""; var CRLF=chr(13)&chr(10); data = ""; @@ -58,9 +85,7 @@ Notes vCal = vCal & "METHOD:PUBLISH" & CRLF; vCal = vCal & "X-WR-TIMEZONE:UTC" & CRLF; vCal = vCal & "X-WR-CALDESC:#application.rbs.setting.sitetitle# Events" & CRLF; - - - + for(event in events){ vCal = vCal & "BEGIN:VEVENT" & CRLF; vCal = vCal & "UID:#createUUID()#_#application.rbs.setting.siteEmailAddress#" & CRLF; // creates a unique identifier vCal = vCal & "ORGANIZER;CN=#application.rbs.setting.sitetitle#:MAILTO:#application.rbs.setting.siteEmailAddress#" & CRLF; @@ -68,40 +93,38 @@ Notes DateFormat(now(),"yyyymmdd") & "T" & TimeFormat(now(), "HHmmss") & CRLF; vCal = vCal & "DTSTART;TZID=Eastern Time:" & - DateFormat(start,"yyyymmdd") & "T" & - TimeFormat(start, "HHmmss") & CRLF; + DateFormat(event.start,"yyyymmdd") & "T" & + TimeFormat(event.start, "HHmmss") & CRLF; vCal = vCal & "DTEND;TZID=Eastern Time:" & - DateFormat(end,"yyyymmdd") & "T" & - TimeFormat(end, "HHmmss") & CRLF; - vCal = vCal & "SUMMARY:#title#" & CRLF; - vCal = vCal & "LOCATION:#name# - #description#" & CRLF; - vCal = vCal & "DESCRIPTION:#striptags(eventdescription)#" & CRLF; + DateFormat(event.end,"yyyymmdd") & "T" & + TimeFormat(event.end, "HHmmss") & CRLF; + vCal = vCal & "SUMMARY:#event.title#" & CRLF; + vCal = vCal & "LOCATION:#event.name# - #event.description#" & CRLF; + vCal = vCal & "DESCRIPTION:#striptags(event.eventdescription)#" & CRLF; vCal = vCal & "PRIORITY:1" & CRLF; vCal = vCal & "TRANSP:OPAQUE" & CRLF; vCal = vCal & "CLASS:PUBLIC" & CRLF; vCal = vCal & "END:VEVENT" & CRLF; - - - + } vCal = vCal & "END:VCALENDAR"; data = vCal; renderWith(data=data); - - - - - - + } +/******************** Private *********************/ + /** + * @hint Whether the URL has a valid API token + */ + private void function f_isValidAPIRequest() { var r=false; if(structKeyExists(params, "token") AND len(params.token) GT 25){ if(model("user").exists(where="apitoken='#params.token#'")){ r=true; } } - if(!r){ redirectTo(route="denied", error="No API Authentication Token Present"); } - - - \ No newline at end of file + } + +} + diff --git a/controllers/Bookings.cfc b/controllers/Bookings.cfc index 4cb6c1d..3d32cc7 100644 --- a/controllers/Bookings.cfc +++ b/controllers/Bookings.cfc @@ -1,227 +1,263 @@ - - - - filters(through="_getLocations", only="index,add,edit,clone,create,update,list,day"); - filters(through="_getResources", only="index,add,edit,clone,create,update,list"); - filters(through="checkPermissionAndRedirect", permission="accessapplication"); - filters(through="checkPermissionAndRedirect", permission="accesscalendar"); - filters(through="checkPermissionAndRedirect", permission="allowRoomBooking", except="index,list,day"); - filters(through="checkPermissionAndRedirect", permission="viewRoomBooking", only="list,view"); - - +component extends="Controller" hint="Main Events/Bookings Controller" +{ + /** + * @hint Constructor. + */ + public void function init() { + super.init(); + // Additional Permissions + filters(through="checkPermissionAndRedirect", permission="accesscalendar"); + filters(through="checkPermissionAndRedirect", permission="allowRoomBooking", except="index,list,day"); + filters(through="checkPermissionAndRedirect", permission="viewRoomBooking", only="list,view"); - - - + // Data + filters(through="_getLocations", only="index,add,edit,clone,create,update,list,day"); + filters(through="_getResources", only="index,add,edit,clone,create,update,list,view"); + } + +/******************** Views ***********************/ + /** + * @hint Static display of a single event, mainly used in RSS permalinks etc + */ + public void function view() { if(structKeyExists(params, "key") AND isNumeric(params.key)){ - event=model("location").findAll(where="events.id = #params.key#", include="events"); + event=model("location").findAll(where="events.id = #params.key#", include="events(eventresources)"); } else { redirectTo(route="home", error="No event specified"); } - - - - - - nEventResources=model("eventresource").new(); - event=model("event").new(eventresources=nEventResources); - - // Listen out for event date & location passed in URL via JS - if(structKeyExists(params, "d")){ - //2015-09-25T00:00:00+01:00 - qDate=createDateTime(listFirst(params.d, '-'),ListGetAt(params.d, 2, '-'),ListGetAt(params.d, 3, '-'),00,00,00); - event.start=dateFormat(qDate, "YYYY-MM-DD") & ' ' & timeFormat(qDate, "HH:MM"); - } - if(structKeyExists(params, "key") AND isNumeric(params.key)){ - event.locationid=params.key; - } - - - - - - - // Event to clone from - event=model("event").findOne(where="id = #params.key#", include="eventresources"); - renderPage(action="add"); - - - - - - - - - - if(structkeyexists(params, "event")){ - event = model("event").new(params.event); - - if ( event.save() ) { - // Check for bulk create events - if(structKeyExists(params, "repeat") - AND params.repeat NEQ "none" - AND structKeyExists(params, "repeatno") - AND isnumeric(params.repeatno) - AND params.repeatno GTE 1) - { - for (i = 1; i lte params.repeatno; i = i + 1){ - //create placeholderevent - nevent = model("event").new(params.event); - //increment date as appropriate - if(params.repeat EQ "week"){ - nevent.start = dateAdd("d", (i * 7), nevent.start); - if(isDate(nevent.end)){ - nevent.end = dateAdd("d", (i * 7), nevent.end); - } + } - } - if(params.repeat EQ "month"){ - nevent.start = dateAdd("m", i, nevent.start); - if(isDate(nevent.end)){ - nevent.end = dateAdd("m", i, nevent.end); - } - } - // Save the child event - nevent.save(); - } - } - // Send Confirmation email if appropriate - if(structKeyExists(params.event, "emailContact") AND params.event.emailContact AND isValid("email", event.contactemail) AND !application.rbs.setting.isDemoMode){ - sendEmail( - to="#event.contactname# <#event.contactemail#>", - from="#application.rbs.setting.sitetitle# <#application.rbs.setting.siteEmailAddress#>", - template="/email/bookingNotify", - subject="Room Booking Confirmation", - event=event - ); - } - redirectTo(action="index", success="Event successfully created"); - } - else { - renderPage(action="add", error="There were problems creating that event"); - } + /** + * @hint Shows Agenda style view table for a given month + */ + public void function list() { + param name="params.m" default="#month(now())#"; + param name="params.y" default="#year(now())#"; + events=model("location").findAll(where="#_agendaListWC()#", include="events", order="start"); } - - + /** + * @hint Alternative Day View + */ + public void function day() { + param name="params.y" default=year(now()); + param name="params.m" default=month(now()); + param name="params.d" default=day(now()); - - - if(structkeyexists(params, "event")){ - event = model("event").findOne(where="id = #params.key#", include="eventresources"); - event.update(params.event); - event.save(); - if ( event.save() ) { - redirectTo(action="index", success="event successfully updated"); - } - else { - renderPage(action="edit", error="There were problems updating that event"); - } - } - - + var slotMin=application.rbs.setting.calendarSlotMinutes; + var calStart=application.rbs.setting.calendarMinTime; + var calEnd=application.rbs.setting.calendarMaxTime; + events=model("event").findAll(where="#_dayListWC()#", order="start", include="location"); + allDay=model("event").findAll(where="#_dayListWC(allday=1)#", order="start", include="location"); - - - if(structkeyexists(params, "key")){ - event = model("event").findOne(where="id = #params.key#", include="eventresources"); - if ( event.delete() ) { - redirectTo(action="index", success="event successfully deleted"); - } - else { - redirectTo(action="index", error="There were problems deleting that event"); - } - } - - + day={} + day.thedate=createDate(params.y, params.m, params.d); + day.yesterday=dateAdd("d", -1, day.thedate); + day.tomorrow=dateAdd("d", 1, day.thedate); + day.starttime=createDateTime(params.y, params.m, params.d, timeFormat(calStart, 'H'),timeFormat(calStart, 'M'),0); + day.endtime=createDateTime(params.y, params.m, params.d, timeFormat(calEnd, 'H'),timeFormat(calEnd, 'M'),0); + // Placeholder arrays + m=[]; + e=[]; + tempid=0; - - - - - - + for(location in locations){ + day.counter=day.starttime; + do { + t={}; + t.timeslot=createDateTime(year(day.thedate), month(day.thedate), day(day.thedate), TimeFormat(day.counter, "H"), TimeFormat(day.counter, "m"), 0); + eventsQ = new Query(); + eventsQ.setDBType('query'); + eventsQ.setAttributes(rs=events); // needed for QoQ + eventsQ.addParam(name='locationid', value=location.id, cfsqltype='cf_sql_numeric'); + eventsQ.addParam(name='start', value=t.timeslot, cfsqltype='cf_sql_timestamp'); + eventsQ.addParam(name='end', value=t.timeslot, cfsqltype='cf_sql_timestamp'); + eventsQ.setSQL('SELECT * FROM rs WHERE locationid =:locationid AND start <= :start AND end > :end AND allday = 0'); + locationEvents = eventsQ.execute().getResult(); - - - - - - events=model("event").findAll(where="#_dayListWC()#", order="start", include="location"); - allDay=model("event").findAll(where="#_dayListWC(allday=1)#", order="start", include="location"); - day={} - day.thedate=createDate(params.y, params.m, params.d); - // Used in Prev/Next paging - day.yesterday=dateAdd("d", -1, day.thedate); - day.tomorrow=dateAdd("d", 1, day.thedate); - // Use user prefs in calculating time - day.starttime=CreateTime(_timeParse(application.rbs.setting.calendarMinTime), 0, 0); - day.endtime=CreateTime(_timeParse(application.rbs.setting.calendarMaxTime), 59, 0); - // Placeholder arrays - m=[]; - e=[]; - tempid=0; - - - - - t={}; - t.timeslot=createDateTime(year(day.thedate), month(day.thedate), day(day.thedate), TimeFormat(i, "H"), TimeFormat(i, "m"), 0); - - - SELECT * FROM events WHERE locationid = - AND start <= - - AND end > - - AND allday = 0; - - - if(locationEvents.recordcount){ + if(locationEvents.recordcount){ if(tempid NEQ locationEvents.id){ - // First tablecell of event - t.duration=DateDiff("n", locationEvents.start, locationEvents.end); - t.rowspan=ceiling(t.duration / 15); + // Check for multiday event + if(day(locationEvents.start) != day(locationEvents.end)){ + t.isMultiday=true; + } else { + t.isMultiday=false; + } + // Check for multiday event with specific end time + if(t.isMultiday AND (day(day.thedate) EQ day(locationEvents.end))){ + t.duration=DateDiff("n", t.timeslot, locationEvents.end); + } else if(t.isMultiday AND (day(day.thedate) EQ day(locationEvents.start))) { + t.duration=DateDiff("n",day.thedate, locationEvents.start); + } else { + t.duration=DateDiff("n", locationEvents.start, locationEvents.end); + } + // Set rowspan dependent on duration + t.rowspan=ceiling(t.duration / timeFormat(SlotMin, 'M')); t.content="" & linkTo( class="remote-modal", controller='eventdata', action='getEvent', key=locationEvents.id, text=h(locationEvents.title)) & "
    " - & h(locationEvents.name) & "
    " - & "#timeFormat(locationEvents.start, "HH:MM")# - #timeFormat(locationEvents.end, "HH:MM")# " - & _durationString(t.duration); - t.class="booked first #class#"; + & h(locationEvents.name) & "
    "; + if(t.isMultiday){ + t.content=t.content & "Multiday event"; + } else { + t.content=t.content & "#timeFormat(locationEvents.start, "HH:MM")# - #timeFormat(locationEvents.end, "HH:MM")# " + & _durationString(t.duration); + } + t.class="booked first #location.class#"; if(locationEvents.recordcount GT 1) { t.content=t.content & "
    Overlapping Event!"; } } else { // Subsequent cells t.content=""; - t.class="booked #class#"; + t.class="booked #location.class#"; t.rowspan=0; } tempid=locationEvents.id; } else { + // Empty Cell t.class="free" t.content=""; t.rowspan=1; } - arrayAppend(e, t); -
    -
    - - -
    -
    + arrayAppend(e, t); + day.counter = dateAdd('n',15,day.counter); + // end nested loop + } while(day.counter LTE day.endtime); + + arrayAppend(m, e); + e=[]; + // end location loop + } + } + +/******************** Admin ***********************/ + /** + * @hint Add a new booking + */ + public void function add() { + nEventResources=model("eventresource").new(); + event=model("event").new(eventresources=nEventResources); + // Listen out for event date & location passed in URL via JS + if(structKeyExists(params, "d")){ + qDate=createDateTime(listFirst(params.d, '-'),ListGetAt(params.d, 2, '-'),ListGetAt(params.d, 3, '-'),hour(now()),00,00); + event.start=dateFormat(qDate, "DD MMM YYYY") & ' ' & timeFormat(qDate, "HH:mm") + } + if(structKeyExists(params, "key") AND isNumeric(params.key)){ + event.locationid=params.key; + } + } + + /** + * @hint Shortcut to duplicating a booking + */ + public void function clone() { + event=model("event").findOne(where="id = #params.key#", include="eventresources"); + renderPage(action="add"); + } + + /** + * @hint Event CRUD + */ + public void function edit() { + event=model("event").findOne(where="id = #params.key#", include="eventresources"); + } + + /** + * @hint Event CRUD + */ + public void function create() { + if(structkeyexists(params, "event")){ + event = model("event").new(params.event); + if ( event.save() ) { + // Check for bulk create events + if(structKeyExists(params, "repeat") + AND params.repeat NEQ "none" + AND structKeyExists(params, "repeatno") + AND isnumeric(params.repeatno) + AND params.repeatno GTE 1) + { + for (i = 1; i lte params.repeatno; i = i + 1){ + //create placeholderevent + nevent = model("event").new(params.event); + //increment date as appropriate + if(params.repeat EQ "week"){ + nevent.start = dateAdd("d", (i * 7), nevent.start); + if(isDate(nevent.end)){ + nevent.end = dateAdd("d", (i * 7), nevent.end); + } + + } + if(params.repeat EQ "month"){ + nevent.start = dateAdd("m", i, nevent.start); + if(isDate(nevent.end)){ + nevent.end = dateAdd("m", i, nevent.end); + } + } + // Save the child event + nevent.save(); + } + } + // Send Confirmation email if appropriate + if(structKeyExists(params.event, "emailContact") AND params.event.emailContact AND isValid("email", event.contactemail) AND !application.rbs.setting.isDemoMode){ + sendEmail( + to="#event.contactname# <#event.contactemail#>", + from="#application.rbs.setting.sitetitle# <#application.rbs.setting.siteEmailAddress#>", + template="/email/bookingNotify", + subject="Room Booking Confirmation", + event=event + ); + } + redirectTo(action="index", success="Event successfully created"); + } + else { + renderPage(action="add", error="There were problems creating that event"); + } + } + } - - - - - var sd=""; + /** + * @hint Event CRUD + */ + public void function update() { + if(structkeyexists(params, "event")){ + event = model("event").findOne(where="id = #params.key#", include="eventresources"); + event.update(params.event); + event.save(); + if ( event.save() ) { + redirectTo(action="index", success="event successfully updated"); + } + else { + renderPage(action="edit", error="There were problems updating that event"); + } + } + } + + /** + * @hint Event CRUD + */ + public void function delete() { + if(structkeyexists(params, "key")){ + event = model("event").findOne(where="id = #params.key#", include="eventresources"); + if ( event.delete() ) { + redirectTo(action="index", success="event successfully deleted"); + } + else { + redirectTo(action="index", error="There were problems deleting that event"); + } + } + } +/******************** Private *********************/ + /** + * @hint Conditional Where Clause for Day Listing + */ + private string function _dayListWC(numeric allday="0") { + var sd=""; var td=""; var wc=[]; // Date Filter @@ -256,12 +292,13 @@ } else { return ""; } - - + } - - - var sd=""; + /** + * @hint + */ + private string function _agendaListWC() { + var sd=""; var td=""; var wc=[]; // Date Filter @@ -286,42 +323,7 @@ } else { return ""; } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + } -
    \ No newline at end of file +} \ No newline at end of file diff --git a/controllers/Controller.cfc b/controllers/Controller.cfc index 398eac0..32c5fe8 100644 --- a/controllers/Controller.cfc +++ b/controllers/Controller.cfc @@ -1,137 +1,44 @@ - - - - - - - - - - - user=model("user").findOne(where="id=#session.currentUser.id# AND email = '#session.currentUser.email#'"); - if(!isObject(user)){ - redirectTo(route="home", error="Sorry, we couldn't find your account.."); - } - - - - - - - var scope=structNew(); - scope.id=user.id; - scope.firstname=user.firstname; - scope.lastname=user.lastname; - scope.email=user.email; - scope.role=user.role; - scope.apitoken=user.apitoken; - session.currentuser=scope; - redirectTo(route="home"); - - - - - - return StructKeyExists(session, "currentUser"); - - - - - - - - - - - - - - - - - - - - - - - - if(!isLoggedIn()){ - redirectTo(route="login"); - } - - - - - - - - - - if(isLoggedIn()){ - redirectTo(route="home"); - } - - - - - - - - - - - if(isLoggedIn()){ - arguments.userid=session.currentuser.id; - } - arguments.ipaddress=getIPAddress(); - l=model("logfile").create(arguments); - - - - - - if(structkeyexists(session,"flash")){ - if(structkeyexists(session.flash, "error")){ - addLogLine(message=session.flash.error, type="error"); - } - if(structkeyexists(session.flash, "success")){ - addLogLine(message=session.flash.success, type="success"); - } - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file +component extends="Wheels" hint="Global Controller" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Deny everything by default + filters(through="checkPermissionAndRedirect", permission="accessapplication"); + // Log everything by default + filters(through="logFlash", type="after"); + } + +/******************** Global Filters***********************/ + /** + * @hint Return all room locations + */ + public void function _getLocations() { + locations=model("location").findAll(order="name"); + } + + /** + * @hint Return all settings + */ + public void function _getSettings() { + settings=model("setting").findAll(order="category,id"); + } + + /** + * @hint Return All Resources + */ + public void function _getResources() { + resources=model("resource").findAll(order="type,name"); + } + + /** + * @hint Check is valid ajax request in filter + */ + public void function _isValidAjax() { + if(!isAjax()){ + abort; + } + } +} \ No newline at end of file diff --git a/controllers/Eventdata.cfc b/controllers/Eventdata.cfc index cc83c65..f96b8a6 100644 --- a/controllers/Eventdata.cfc +++ b/controllers/Eventdata.cfc @@ -1,24 +1,43 @@ - - - - - provides("html,json"); - usesLayout(template="modal", only="getevent"); - filters(through="_getResources", only="getevent"); - filters(through="checkPermissionAndRedirect", permission="accessapplication"); - filters(through="checkPermissionAndRedirect", permission="accesscalendar"); - - +component extends="Controller" hint="Misc Event Data" +{ + /** + * @hint Constructor. + */ + public void function init() { + super.init(); + + // Additional Permissions + filters(through="checkPermissionAndRedirect", permission="accesscalendar"); + filters(through="_isValidAjax"); + + // Data + filters(through="_getResources", only="getevent"); + + // Formats + provides("html,json"); + usesLayout(template="modal", only="getevent"); + } + +/******************** Public***********************/ + /** + * @hint Get Events For the provided range via ajax + */ + public void function getEvents() { + if(structkeyexists(params, "start") AND structkeyexists(params, "end")){ + + var sd=createDateTime(year(params.start), month(params.start), day(params.start), 00,00,00); + var ed=createDateTime(year(params.end), month(params.end), day(params.end), 00,00,00); - - - - if(isAjax() AND structkeyexists(params, "start") AND structkeyexists(params, "end")){ if(structKeyExists(params, "key")){ - data=model("location").findAll(where="start > '#eToLocal(params.start)#' AND id = #params.key#", include="events"); + //data=model("event").findAll(where="locations.id = #params.key#", include="location"); + data=model("event").findAll(select="id, title, locationid, class, start, end, allday", + where="start >= '#sd#' AND end <= '#ed#' AND locationid = '#params.key#'", include="location", + order="start ASC"); } else { - data=model("location").findAll(where="start > '#eToLocal(params.start)#'", include="events"); + data=model("event").findAll(select="id, title, locationid, class, start, end, allday", + where="start >= '#sd#' AND end <= '#ed#'", include="location", + order="start ASC"); } events=prepEventData(data); renderWith(events); @@ -26,39 +45,39 @@ else { abort; } - - - + } - - - if(isAjax() AND structKeyExists(params, "key")){ + /** + * @hint get single event via ajax, i.e for modals + */ + public void function getEvent() { + if(structKeyExists(params, "key")){ event=model("location").findAll(where="events.id = #params.key#", include="events(eventresources)"); } - - - - - - - - - - events[currentrow]["id"]=arguments.data.eventid[currentrow]; - events[currentrow]["title"]=arguments.data.title[currentrow]; - events[currentrow]["start"]=arguments.data.start[currentrow]; - events[currentrow]["end"]=arguments.data.end[currentrow]; - events[currentrow]["allDay"]=arguments.data.allDay[currentrow]; - events[currentrow]["location"]=arguments.data.name[currentrow]; - events[currentrow]["ldescription"]=arguments.data.description[currentrow]; - events[currentrow]["edescription"]=arguments.data.eventdescription[currentrow]; - events[currentrow]["className"]=arguments.data.class[currentrow]; - events[currentrow]["createdAt"]=arguments.data.createdAt[currentrow]; - events[currentrow]["updatedAt"]=arguments.data.updatedAt[currentrow]; - - - - - + } +/******************** Private *********************/ + /** + * @hint Sort out event data + */ + private array function prepEventData(data) { + var events=[]; + var c=1; + for(event in arguments.data){ + events[c]["id"]=event.id; + events[c]["title"]=event.title; + events[c]["start"]=_f_d(event.start); + events[c]["end"]=_f_d(event.end); + events[c]["allDay"]=event.allDay; + events[c]["className"]=event.class; + c++; + } + return events; + } + /** + * @hint Experimental date format + */ + private string function _f_d(str) { + return dateFormat(arguments.str, "YYYY-MM-DD") & "T" & timeFormat(arguments.str, "HH:MM:00") + } - \ No newline at end of file +} \ No newline at end of file diff --git a/controllers/Locations.cfc b/controllers/Locations.cfc index e9e706c..baa1d0c 100644 --- a/controllers/Locations.cfc +++ b/controllers/Locations.cfc @@ -1,22 +1,33 @@ - - - - filters(through="_getLocations", only="index"); - filters(through="_checkLocationsAdmin"); - filters(through="checkPermissionAndRedirect", permission="accessLocations"); - - +component extends="Controller" hint="Locations Controller" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Permission filters + super.init(); - - - - location=model("location").new(); - - + // Permissions + filters(through="f_checkLocationsAdmin"); + filters(through="checkPermissionAndRedirect", permission="accessLocations"); - - + // Data + filters(through="_getLocations", only="index"); + } + +/******************** Admin ***********************/ + /** + * @hint + */ + public void function add() { + location=model("location").new(); + } + + /** + * @hint + */ + public void function create() { if(structkeyexists(params, "location")){ location = model("location").new(params.location); if ( location.save() ) { @@ -26,17 +37,19 @@ renderPage(action="add", error="There were problems creating that location"); } } - - + } - - - location=model("location").findOne(where="id = #params.key#"); - - + /** + * @hint + */ + public void function edit() { + location=model("location").findOne(where="id = #params.key#"); + } - - + /** + * @hint + */ + public void function update() { if(structkeyexists(params, "location")){ location = model("location").findOne(where="id = #params.key#"); location.update(params.location); @@ -47,12 +60,12 @@ renderPage(action="edit", error="There were problems updating that location"); } } - - + } - - - checkLocation=model("location").findAll(); + /** + * @hint + */ + public void function delete() { if(checkLocation.recordcount GT 1){ if(structkeyexists(params, "key")){ location = model("location").findOne(where="id = #params.key#"); @@ -64,17 +77,17 @@ } } } else { - - redirectTo(action="index", error="At least one location is required."); + redirectTo(action="index", error="At least one location is required."); + } + } +/******************** Private *********************/ + /** + * @hint Whether to allow access + */ + public void function f_checkLocationsAdmin() { + if(!application.rbs.setting.allowLocations){ + redirectTo(route="home", error="Facility to edit locations has been disabled"); + } } - - - - - - - - - - \ No newline at end of file +} \ No newline at end of file diff --git a/controllers/Logfiles.cfc b/controllers/Logfiles.cfc index 0e21e35..5624e3e 100644 --- a/controllers/Logfiles.cfc +++ b/controllers/Logfiles.cfc @@ -1,45 +1,48 @@ - - + +component extends="Controller" hint="Manage Logfiles" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Permission filters + super.init(); + // Additional Permissions + filters(through="checkPermissionAndRedirect", permission="accesslogfiles"); + } - - - filters(through="checkPermissionAndRedirect", permission="accesslogfiles"); - - - - - - - - - LogFileTypes=getLogFileTypes(); - -/* Bug in RC1 with using CONCAT() etc; using email until that's fixed */ - //users=model("user").findAll(select="id,CONCAT(firstname,' ', lastname) AS fullname,email", order="lastname"); - - users=model("user").findAll(select="id,email", order="lastname"); +/******************** Public***********************/ + /** + * @hint Log viewer + */ + public void function index() { + param name="params.type" type="string" default=""; + param name="params.userid" default=""; + param name="params.rows" type="numeric" default=250; + LogFileTypes=_getLogFileTypes(); + users=model("user").findAll(select="id,email", order="lastname"); var wc = arrayNew(1); if(structKeyExists(params, "type") AND Len(params.type)){ arrayAppend(wc, "type = '#params.type#'"); } - if(structKeyExists(params, "userid") AND len(params.userid) AND isNumeric(params.userid)){ + if(structKeyExists(params, "userid") AND len(params.userid)){ arrayAppend(wc, "userid = #params.userid#"); } if(arrayLen(wc)){ wc = arrayToList(wc, " AND "); - //writeDump(wc); - //abort; logfiles=model("logfiles").findAll(where="#wc#", maxrows=params.rows, order="createdAt DESC", includeSoftDeletes=true); } else { logfiles=model("logfiles").findAll(maxrows=500, maxrows=params.rows, order="createdAt DESC", includeSoftDeletes=true); } + } +/******************** Admin ***********************/ - - - - - - - - +/******************** Private *********************/ + /** + * @hint + */ + public string function _getLogFileTypes() { + return "login,success,error,ajax,cookie"; + } +} diff --git a/controllers/PasswordResets.cfc b/controllers/PasswordResets.cfc index 7cf87f4..4a13bac 100644 --- a/controllers/PasswordResets.cfc +++ b/controllers/PasswordResets.cfc @@ -1,16 +1,22 @@ - - - - - filters(through="logFlash", type="after"); + +component extends="Controller" hint="" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Permission filters + super.init(); filters(through="redirectIfLoggedIn"); filters(through="denyInDemoMode"); - - + } - - - user = model("user").findOneByEmail(params.email); +/******************** Public***********************/ + /** + * @hint Create a pw reset email + */ + public void function create() { + user = model("user").findOneByEmail(params.email); if ( isObject(user) ) { user.createPasswordResetToken(); sendEmail(to=user.email, @@ -24,13 +30,13 @@ flashInsert(error="Hmm... we couldn't find an account for that address"); redirectTo(action="new"); } + } - - - - - - user = model("user").findOneByPasswordResetToken(params.key); + /** + * @hint Update pw form + */ + public void function edit() { + user = model("user").findOneByPasswordResetToken(params.key); if ( isObject(user) ) { if ( DateDiff("h", user.passwordResetAt, Now()) > 2 ) { redirectTo(action="new", error="Password reset has expired. [PR2]"); @@ -39,12 +45,13 @@ user.passwordToBlank(); } } - - + } - - - user = model("user").findOneByPasswordResetToken(params.key); + /** + * @hint Update pw + */ + public void function update() { + user = model("user").findOneByPasswordResetToken(params.key); user.salt=createSalt(); user.password=hashPassword(user.password, user.salt); if ( isObject(user) && user.update(params.user) ) { @@ -54,7 +61,5 @@ else { redirectTo(route="home", error="Sorry, that request failed [PR1]"); } - - - - + } + } \ No newline at end of file diff --git a/controllers/Permissions.cfc b/controllers/Permissions.cfc index 77f7a9c..9176d6b 100644 --- a/controllers/Permissions.cfc +++ b/controllers/Permissions.cfc @@ -1,35 +1,41 @@ - - - - +component extends="Controller" hint="Permissions Controller" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Permission filters + super.init(); filters(through="checkPermissionAndRedirect", permission="accessPermissions"); filters(through="denyInDemoMode", only="edit,update"); - - - - - - - - - - - - - + } + +/******************** Public***********************/ + /** + * @hint List Permissions + */ + public void function index() { + permissions=model("permission").findAll(order="id"); + } + + /** + * @hint Edit Form + */ + public void function edit() { permission=model("permission").findOne(where="id = '#params.key#'"); - if(!isObject(permission) OR application.rbs.setting.isDemoMode){ + if(!isObject(permission)){ redirectTo(back=true, error="Sorry, that permission can't be found, isn't editable or the board is in demo mode"); } - - + } - - + /** + * @hint Update + */ + public void function update() { if(structkeyexists(params, "permission")){ permission = model("permission").findOne(where="id = '#params.key#'"); - if(!isObject(permission) OR application.rbs.setting.isDemoMode){ + if(!isObject(permission)){ redirectTo(back=true, error="Sorry, that permission can't be found, isn't editable or the board is in demo mode"); } else { permission.update(params.permission); @@ -41,7 +47,7 @@ } } } - - - + } + +} diff --git a/controllers/Resources.cfc b/controllers/Resources.cfc index 54d52a7..6225b81 100644 --- a/controllers/Resources.cfc +++ b/controllers/Resources.cfc @@ -1,24 +1,38 @@ - - - - filters(through="_getresources", only="index"); - filters(through="checkPermissionAndRedirect", permission="accessresources"); - filters(through="_checkResourcesAdmin"); - filters(through="_getLocations"); - provides("html,json"); - - +component extends="Controller" hint="Resources Controller" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Permission filters + super.init(); - - - - resource=model("resource").new(); - - + // Permissions + filters(through="checkPermissionAndRedirect", permission="accessresources"); + filters(through="_checkResourcesAdmin"); + filters(through="_isValidAjax", only="checkavailability"); - - + // Data + filters(through="_getresources", only="index"); + filters(through="_getLocations"); + + // Format + provides("html,json"); + } + +/******************** Public***********************/ + /** + * @hint Add Resource + */ + public void function add() { + resource=model("resource").new(); + } + + /** + * @hint Create Resource + */ + public void function create() { if(structkeyexists(params, "resource")){ resource = model("resource").new(params.resource); if ( resource.save() ) { @@ -28,17 +42,19 @@ renderPage(action="add", error="There were problems creating that resource"); } } - - + } - - - resource=model("resource").findOne(where="id = #params.key#"); - - + /** + * @hint Edit Resource + */ + public void function edit() { + resource=model("resource").findOne(where="id = #params.key#"); + } - - + /** + * @hint Update Resource + */ + public void function update() { if(structkeyexists(params, "resource")){ resource = model("resource").findOne(where="id = #params.key#"); resource.update(params.resource); @@ -49,12 +65,13 @@ renderPage(action="edit", error="There were problems updating that resource"); } } - - + } - - - if(structkeyexists(params, "key")){ + /** + * @hint Delete Resource + */ + public void function delete() { + if(structkeyexists(params, "key")){ resource = model("resource").findOne(where="id = #params.key#"); if ( resource.delete() ) { redirectTo(action="index", success="resource successfully deleted"); @@ -63,15 +80,26 @@ redirectTo(action="index", error="There were problems deleting that resource"); } } - - - - - - - - - + } + +/******************** Private *********************/ + /** + * @hint + */ + public void function _checkResourcesAdmin() { + if (!application.rbs.setting.allowResources){ + redirectTo(route="home", error="Facility to add/edit resources has been disabled"); + } + } +/******************** Ajax/Remote/Misc*************/ + /** + * @hint Bit of a hack, but a quick lookup to see if a resource is already booked + */ + public void function checkavailability() { + param name="params.id" default="" type="numeric"; + param name="params.eventid" default="" type="numeric"; + param name="params.start" default="" type="string"; + param name="params.end" default="" type="string"; if(!isDate(params.end)){ params.end=dateAdd("h", 1, params.start); } @@ -91,13 +119,7 @@ } else { renderText(1); } - - - - - - - - - \ No newline at end of file + + } +} \ No newline at end of file diff --git a/controllers/Sessions.cfc b/controllers/Sessions.cfc index 849c2b9..2fc61d3 100644 --- a/controllers/Sessions.cfc +++ b/controllers/Sessions.cfc @@ -1,19 +1,19 @@ - - - - - filters(through="logFlash", type="after"); - filters(through="redirectIfLoggedIn", only="new"); - - - - - - - + +component extends="Controller" hint="Sessions Controller" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Permission filters - NB, doesn't go via super.init() + filters(through="redirectIfLoggedIn", only="new,attemptlogin"); + } - - +/******************** Public***********************/ + /** + * @hint Login procedure + */ + public void function attemptlogin() { var p={}; if(structkeyexists(params, "email") AND structkeyexists(params, "password")){ user = model("user").findOneByEmail(params.email); @@ -32,43 +32,35 @@ } else { addLogline(type="Login", message="PW doesn't match hashed"); - _badLogin(); + RedirectTo(error="We could not sign you in. Please try that again.", route="login");; } } else { addLogline(type="Login", message="Bad Login [User isn't object, searched for #h(params.email)#]"); - _badLogin(); + RedirectTo(error="We could not sign you in. Please try that again.", route="login"); } } - else { - addLogline(type="Login", message="Bad Login [Need Email and Password]"); - _badLogin(); + else { + addLogline(type="Login", message="Bad Login [Need Email and Password]"); + RedirectTo(error="We could not sign you in. Please try that again.", route="login"); + } } - - - - - - StructDelete(session, "currentUser"); - redirectTo(route="home", success="You have been successfully signed out"); - - - - - - setCookieForgetUsername(); - redirectTo(route="login"); - - - + /** + * @hint Logout a user + */ + public void function logout() { + StructDelete(session, "currentUser"); + redirectTo(route="home", success="You have been successfully signed out"); + } - + /** + * @hint Forget Users cookie + */ + public void function forgetme() { + setCookieForgetUsername(); + redirectTo(route="login"); + } - - - RedirectTo(error="We could not sign you in. Please try that again.", route="login"); - - - +} \ No newline at end of file diff --git a/controllers/Settings.cfc b/controllers/Settings.cfc index 0102554..4b805b4 100644 --- a/controllers/Settings.cfc +++ b/controllers/Settings.cfc @@ -1,27 +1,35 @@ - - - - filters(through="_getSettings"); - filters(through="_checkSettingsAdmin"); - filters(through="checkPermissionAndRedirect", permission="accessSettings"); - filters(through="denyInDemoMode", only="edit,update"); - - +component extends="Controller" hint="Settings Controller" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Permission filters + super.init(); + filters(through="checkPermissionAndRedirect", permission="accessSettings"); + filters(through="_checkSettingsAdmin"); + filters(through="denyInDemoMode", only="edit,update"); - + // Data + filters(through="_getSettings"); + } - - +/******************** Public***********************/ + /** + * @hint Edit a setting + */ + public void function edit() { setting=model("setting").findOne(where="id = '#params.key#'"); if(!isObject(setting) OR !setting.Editable OR application.rbs.setting.isDemoMode){ redirectTo(back=true, error="Sorry, that setting can't be found, isn't editable or the board is in demo mode"); } - - + } - - + /** + * @hint Update a setting + */ + public void function update() { if(structkeyexists(params, "setting")){ setting = model("setting").findOne(where="id = '#params.key#'"); if(!isObject(setting) OR !setting.Editable OR application.rbs.setting.isDemoMode){ @@ -36,13 +44,16 @@ } } } - - - - - - - - + } - \ No newline at end of file +/******************** Private *********************/ + /** + * @hint + */ + private void function _checkSettingsAdmin() { + if(!application.rbs.setting.allowSettings){ + redirectTo(route="home", error="Facility to edit settings has been disabled"); + } + } + +} \ No newline at end of file diff --git a/controllers/Users.cfc b/controllers/Users.cfc index 344c641..c2f7c9e 100644 --- a/controllers/Users.cfc +++ b/controllers/Users.cfc @@ -1,177 +1,181 @@ - - - - + +component extends="Controller" hint="Main User Controller" +{ + /** + * @hint Constructor. + */ + public void function init() { + // Permission filters + super.init(); filters(through="_checkLoggedIn"); - //filters(through="_checkAdmin", except="mycustomers,myaccount,updateaccount,mypassword,updatepassword,myprofile,myprofileupdate"); - filters(through="getCurrentUser", only="myaccount,updateaccount,updatepassword"); - filters(through="logFlash", type="after"); - filters(through="_getRoles", only="index,add,edit,delete,update,create"); - filters(through="checkPermissionAndRedirect", permission="accessUsers", except="myaccount,updateaccount,updatepassword"); filters(through="checkPermissionAndRedirect", permission="updateOwnAccount", only="myaccount,updateaccount,updatepassword"); - filters(through="denyInDemoMode", only="create,update,updateaccount,updatepassword,assumeuser"); - - - - + filters(through="denyInDemoMode", only="create,update,updateaccount,updatepassword,assumeuser,generateAPIKey"); + // Data + filters(through="getCurrentUser", only="myaccount,updateaccount,updatepassword"); + filters(through="_getRoles", only="index,add,edit,delete,update,create"); + } - - - if(structKeyExists(params, "password")){ - structDelete(params, "password"); - structDelete(params, "passwordConfirmation"); +/******************** Public***********************/ + /** + * @hint Main Account Update + */ + public void function updateaccount() { + if(structKeyExists(params, "password")){ + structDelete(params, "password"); + structDelete(params, "passwordConfirmation"); + } + if(structKeyExists(params, "user")){ + structDelete(params.user, "role"); + user.update(params.user); + if(user.save()){ + redirectTo(route="myaccount", success="Personal account details successfully updated"); + } + else { + renderPage(action="myaccount"); + } + } } - if(structKeyExists(params, "user")){ - structDelete(params.user, "role"); - user.update(params.user); - if(user.save()){ - redirectTo(route="myaccount", success="Personal account details successfully updated"); + + /** + * @hint Seperate PW change update + */ + public void function updatepassword() { + if(structKeyExists(params, "password") AND structKeyExists(params, "passwordConfirmation") + AND (params.password EQ params.passwordConfirmation)){ + user.update( + password=hashPassword(params.password, decryptSalt(user.salt)) + ); + if(user.save()){ + redirectTo(action="myaccount", success="Password successfully updated"); + } + else { + renderPage(action="myaccount"); + } } else { - renderPage(action="myaccount"); + redirectTo(action="myaccount", error="Password and Password Confirmation must match"); } } - - - - - - if(structKeyExists(params, "password") AND structKeyExists(params, "passwordConfirmation") - AND (params.password EQ params.passwordConfirmation)){ - user.update( - password=hashPassword(params.password, decryptSalt(user.salt)) - ); - if(user.save()){ - redirectTo(action="myaccount", success="Password successfully updated"); +/******************** Admin ***********************/ + /** + * @hint Login as targeted user + */ + public void function assumeUser() { + if(!application.rbs.setting.isdemomode){ + if(structKeyExists(params, "key")){ + user=model("user").findOne(where="id = #params.key#"); + _createUserInScope(user); + } } else { - renderPage(action="myaccount"); + redirectTo( controller="users", action="index", success="Not allowed in demo mode"); } } - else { - redirectTo(action="myaccount", error="Password and Password Confirmation must match"); + /** + * @hint Administrators only, account listings + */ + public void function index() { + param name="params.page" default=1; + users=model("user").findAll( group="id", includeSoftDeletes=false, perPage=25, page=params.page); } - - - - - - - - - if(!application.rbs.setting.isdemomode){ - if(structKeyExists(params, "key")){ - user=model("user").findOne(where="id = #params.key#"); - _createUserInScope(user); - } + /** + * @hint Add New User + */ + public void function add() { + user=model("user").new(); } - else { - redirectTo( controller="users", action="index", success="Not allowed in demo mode"); + /** + * @hint Edit User + */ + public void function edit() { + user=model("user").findOne(where="id = #params.key#"); } - - - - - - - - - - - - user=model("user").new(); - - - - - - user=model("user").findOne(where="id = #params.key#"); - - - - - - - if(structkeyexists(params, "user")){ - user = model("user").new(params.user); - if ( user.save() ) { - redirectTo( controller="users", action="index", success="User account successfully created"); - } - else { - renderPage( controller="users", action="add", error=""); + /** + * @hint Create Account + */ + public void function create() { + if(structkeyexists(params, "user")){ + user = model("user").new(params.user); + if ( user.save() ) { + redirectTo( controller="users", action="index", success="User account successfully created"); + } + else { + renderPage( controller="users", action="add", error=""); + } } } - - - - - - if(!application.rbs.setting.isdemomode){ - if(structkeyexists(params, "user")){ - user = model("user").findOne(where="id = #params.key#"); - user.update(params.user); - if ( user.save() ) { - redirectTo( controller="users", action="index", success="User account successfully updated"); - } - else { - flashInsert(error="There were problems updating that user"); - renderPage( controller="users", action="edit"); + /** + * @hint + */ + public void function update() { + if(!application.rbs.setting.isdemomode){ + if(structkeyexists(params, "user")){ + user = model("user").findOne(where="id = #params.key#"); + user.update(params.user); + if ( user.save() ) { + redirectTo( controller="users", action="index", success="User account successfully updated"); + } + else { + flashInsert(error="There were problems updating that user"); + renderPage( controller="users", action="edit"); + } + } + } else { + redirectTo( controller="users", action="index", success="Not updated in demo mode"); } } -} else {redirectTo( controller="users", action="index", success="Not updated in demo mode");} - - - - - - if(!application.rbs.setting.isdemomode){ - if(structkeyexists(params, "key")){ - user = model("user").findOne(where="id = #params.key#"); - if ( user.delete() ) { - redirectTo( controller="users", action="index", success="user successfully deleted"); + /** + * @hint Soft Delete an Account + */ + public void function delete() { + if(!application.rbs.setting.isdemomode){ + if(structkeyexists(params, "key")){ + user = model("user").findOne(where="id = #params.key#"); + if ( user.delete() ) { + redirectTo( controller="users", action="index", success="user successfully deleted"); + } + else { + redirectTo(controller="users", action="index", error="There were problems deleting that user"); + } } - else { - redirectTo(controller="users", action="index", error="There were problems deleting that user"); + } else { + redirectTo( controller="users", action="index", success="Not updated in demo mode"); } } -}else {redirectTo( controller="users", action="index", success="Not updated in demo mode");} - - - - - - if(structkeyexists(params, "key")){ - user = model("user").findOne(where="id = #params.key#", includeSoftDeletes=true); - user.deletedAt=""; - if ( user.save() ) { - redirectTo( controller="users", action="index", success="user successfully recovered"); - } - else { - redirectTo(controller="users", action="index", error="There were problems recovering that user"); + /** + * @hint Recover a deleted Account + */ + public void function recover() { + if(structkeyexists(params, "key")){ + user = model("user").findOne(where="id = #params.key#", includeSoftDeletes=true); + user.deletedAt=""; + if ( user.save() ) { + redirectTo( controller="users", action="index", success="user successfully recovered"); + } + else { + redirectTo(controller="users", action="index", error="There were problems recovering that user"); + } } } - - - - - - if(structKeyExists(params, "key") AND isnumeric(params.key)){ - user=model("user").findOneByID(params.key); - if(isObject(user)){ - user.apitoken=_generateApiKey(); - user.save(); - redirectTo(controller="users", action="index", success="Key generation successful"); +/******************** Ajax/Remote/Misc*************/ + /** + * @hint Generates An API Key for a user account + */ + public void function generateAPIKey() { + if(structKeyExists(params, "key") AND isnumeric(params.key)){ + user=model("user").findOneByID(params.key); + if(isObject(user)){ + user.apitoken=_generateApiKey(); + user.save(); + redirectTo(controller="users", action="index", success="Key generation successful"); + } else { + redirectTo(controller="users", action="index", error="Key generation failed - User not found"); + } } else { - redirectTo(controller="users", action="index", error="Key generation failed - User not found"); + redirectTo(controller="users", action="index", error="Key generation failed - Key not specified"); } - } else { - redirectTo(controller="users", action="index", error="Key generation failed - Key not specified"); } - - - - - \ No newline at end of file +} \ No newline at end of file diff --git a/events/functions.cfm b/events/functions.cfm index ee71278..21180e5 100644 --- a/events/functions.cfm +++ b/events/functions.cfm @@ -1,145 +1,6 @@ - - - - - var result="127.0.0.1"; - var myHeaders = GetHttpRequestData(); - if(structKeyExists(myHeaders, "headers") AND structKeyExists(myHeaders.headers, "x-forwarded-for")){ - result=myHeaders.headers["x-forwarded-for"]; - } - return result; - - - - - - - - - - - - - - - - if ( isLoggedIn() ) { - currentUser = model("user").findByKey(session.currentUser.id); - return currentUser; - } - - - - - - if (isLoggedIn() ) { - StructDelete(session, "currentUser"); - } - - - - - - - - - var authkeyLocation=expandPath("/config/auth.cfm"); - var authkeyDefault=createUUID(); - - - - - - - - - - - - - return hash(createUUID() & getAuthKey(), 'SHA-512'); - - - - - - return encrypt(createUUID(), getAuthKey(), 'CFMX_COMPAT'); - - - - - - - return decrypt(arguments.string, getAuthKey(), 'CFMX_COMPAT'); - - - - - - - - return hash(arguments.string & arguments.salt, 'SHA-512'); - - - - - - - - var retValue=0; - if(_permissionsSetup() AND structKeyExists(application.rbs.permission, arguments.permission)){ - retValue = application.rbs.permission[arguments.permission][_returnUserRole()]; - if(retValue == 1){ - return true; - } else { - return false; - } - //return application.rbs.permission[arguments.permission][_returnUserRole()]; - } - - - - - - - if(!checkPermission(arguments.permission)){ - redirectTo(route="denied", error="Sorry, you have insufficient permission to access this. If you believe this to be an error, please contact an administrator."); - } - - - - - - if(structKeyExists(application, "rbs") AND structKeyExists(application.rbs, "permission")){ - return true; - } - else - { - return false; - } - - - - - - if(_permissionsSetup() AND isLoggedIn() AND structKeyExists(session.currentuser, "role")){ - return session.currentuser.role; - } else { - return "guest"; - } - - - - - - - - \ No newline at end of file + + include "global/auth.cfm"; + include "global/cookies.cfm"; + include "global/utils.cfm"; + \ No newline at end of file diff --git a/events/global/auth.cfm b/events/global/auth.cfm new file mode 100644 index 0000000..e203e06 --- /dev/null +++ b/events/global/auth.cfm @@ -0,0 +1,198 @@ + + + /** + * @hint Get logged in user + */ + public void function getCurrentUser() { + user=model("user").findOne(where="id=#session.currentUser.id# AND email = '#session.currentUser.email#'"); + if(!isObject(user)){ + redirectTo(route="home", error="Sorry, we couldn't find your account.."); + } + } + + /** + * @hint Basically loads all user details and permissions into session scope for easy reference + */ + private void function _createUserInScope(required user) { + var scope=structNew(); + scope.id=user.id; + scope.firstname=user.firstname; + scope.lastname=user.lastname; + scope.email=user.email; + scope.role=user.role; + scope.apitoken=user.apitoken; + session.currentuser=scope; + redirectTo(route="home"); + } + + /** + * @hint Returns true if session exists / useful for simple checks + */ + public boolean function isLoggedIn() { + return StructKeyExists(session, "currentUser"); + } + + /** + * @hint Returns true if user is a specified role + */ + public boolean function userIsInRole(required string role) { + var r=false; + if(isLoggedin()){ + if(structKeyExists(session.currentuser, "role") AND (session.currentUser.role EQ arguments.role)){ + r=true; + } + } + return r; + } + + /** + * @hint Used in filters + */ + public void function _checkLoggedIn() { + if(!isLoggedIn()){ + redirectTo(route="login"); + } + } + + /** + * @hint Returns a list of potential roles + */ + public void function _getRoles() { + roles="admin,editor,user,guest"; + } + + /** + * @hint Shoves auth'd user elsewhere + */ + public void function redirectIfLoggedIn() { + if(isLoggedIn()){ + redirectTo(route="home"); + } + } + + /** + * @hint Returns the current signed in user + */ + public struct function currentUser() { + if ( isLoggedIn() ) { + currentUser = model("user").findByKey(session.currentUser.id); + return currentUser; + } + } + + /** + * @hint Signs out the user + */ + public void function signOut() { + if (isLoggedIn() ) { + StructDelete(session, "currentUser"); + } + } + + /* + Notes on salting / hashing: + Password is hashed using a salt + Salt is encrypted with a unique key (AuthKey) + + So to compare a password hash, you need to decrypt the salt first + + The authKey is unique per installation; It's useful as you can invalidate all the site passwords in one go by just changing it + */ + + /** + * @hint Returns a semi-unique key per installation + */ + public string function getAuthKey() { + var authkeyLocation=expandPath("/config/auth.cfm"); + var authkeyDefault=createUUID(); + if(fileExists(authkeyLocation)){ + return fileRead(authkeyLocation); + } else { + fileWrite(authkeyLocation, authkeyDefault); + return authkeyDefault; + } + } + + /** + * @hint Generate an API Key + */ + public string function _generateApiKey(){ + return hash(createUUID() & getAuthKey(), 'SHA-512'); + } + + /** + * @hint Create Salt using authkey + */ + public string function createSalt() { + return encrypt(createUUID(), getAuthKey(), 'CFMX_COMPAT'); + } + + /** + * @hint Get salt using authkey + */ + public string function decryptSalt(required string salt) { + return decrypt(arguments.salt, getAuthKey(), 'CFMX_COMPAT'); + } + + /** + * @hint Hash Password using SHA512 + */ + public string function hashPassword(required string password, required string salt) { + return hash(arguments.password & arguments.salt, 'SHA-512'); + } + + /** + * @hint Checks a permission against permissions loaded into application scope for the user + */ + public boolean function checkPermission(required string permission) { + var retValue=0; + if(_permissionsSetup() AND structKeyExists(application.rbs.permission, arguments.permission)){ + retValue = application.rbs.permission[arguments.permission][_returnUserRole()]; + if(retValue == 1){ + return true; + } else { + return false; + } + } + } + + /** + * @hint Checks a permission and redirects away to access denied, useful for use in filters etc + */ + public void function checkPermissionAndRedirect(required string permission) { + if(!checkPermission(arguments.permission)){ + redirectTo(route="denied", error="Sorry, you have insufficient permission to access this. If you believe this to be an error, please contact an administrator."); + } + } + + /** + * @hint Checks for the relevant permissions structs in application scope + */ + public boolean function _permissionsSetup() { + if(structKeyExists(application, "rbs") AND structKeyExists(application.rbs, "permission")){ + return true; + } else { + return false; + } + } + + /** + * @hint Looks for user role in session, returns guest otherwise + */ + public string function _returnUserRole() { + if(_permissionsSetup() AND isLoggedIn() AND structKeyExists(session.currentuser, "role")){ + return session.currentuser.role; + } else { + return "guest"; + } + } + + /** + * @hint Used to redirect away in demo mode + */ + public void function denyInDemoMode() { + if(application.rbs.setting.isdemomode){ + redirectTo(route="home", error="Disabled in Demo Mode"); + } + } + \ No newline at end of file diff --git a/events/global/cookies.cfm b/events/global/cookies.cfm new file mode 100644 index 0000000..578599f --- /dev/null +++ b/events/global/cookies.cfm @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/events/global/utils.cfm b/events/global/utils.cfm new file mode 100644 index 0000000..4db4dc5 --- /dev/null +++ b/events/global/utils.cfm @@ -0,0 +1,50 @@ + +//================= Generic Functions =======================---> + + /** + * @hint Get IP + */ + public string function getIPAddress() { + var result="127.0.0.1"; + var myHeaders = GetHttpRequestData(); + if(structKeyExists(myHeaders, "headers") AND structKeyExists(myHeaders.headers, "x-forwarded-for")){ + result=myHeaders.headers["x-forwarded-for"]; + } + return result; + } + /** + * @hint I know this is stupid, but it's a hack with the way I'm doing the settings in the db + */ + public string function returnStringFromBoolean(required string boo) { + if(arguments.boo){ + return "true"; + } else { + return "false"; + } + } + +//================================ Logging ====================================== + /** + * @hint Quick way to add a logline + */ + public void function addlogline() { + if(isLoggedIn()){ + arguments.userid=session.currentuser.id; + } + arguments.ipaddress=getIPAddress(); + l=model("logfile").create(arguments); + } + /** + * @hint Log the flash via filter + */ + public void function logFlash() { + if(structkeyexists(session,"flash")){ + if(structkeyexists(session.flash, "error")){ + addLogLine(message=session.flash.error, type="error"); + } + if(structkeyexists(session.flash, "success")){ + addLogLine(message=session.flash.success, type="success"); + } + } + } + \ No newline at end of file diff --git a/events/onrequeststart.cfm b/events/onrequeststart.cfm index e57ce71..1d30514 100644 --- a/events/onrequeststart.cfm +++ b/events/onrequeststart.cfm @@ -25,4 +25,7 @@ - \ No newline at end of file + + + + \ No newline at end of file diff --git a/files/.gitignore b/files/.gitignore deleted file mode 100644 index 8b13789..0000000 --- a/files/.gitignore +++ /dev/null @@ -1 +0,0 @@ - diff --git a/images/.gitignore b/images/.gitignore deleted file mode 100644 index 8b13789..0000000 --- a/images/.gitignore +++ /dev/null @@ -1 +0,0 @@ - diff --git a/images/alpha.png b/images/alpha.png deleted file mode 100644 index 38043f1c85f20fe8ce37ad380bebe2d42d14fb29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3271 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 >YjDHRkjMe3FzWUPlrRR$Vj zy^gq}0Gaop4D5MhX7(ycT`jo|BH{=ag8zt#+L72=JCHiGZ0vSpMCyHzHYyQ0LglPZ zkvbn!ickuQG>Slhysz>E_ln^ig@N$PdJMkoe_qT z(HV$s6i_Gr*agm}Dq1=sA^Kw>LdLh_HALnZhuA4nPsrQY)F|Onx0r~9%71p_tZk@Vb{T&RYVEe{&0&0f;{63aHAc9Sp3x6URK5zQH~8E#Ey9J zaCgUcm?CHHD5Y?`fnNs!J1aV_6C6^JI7he6f~JeR^pHvN3DK8`E6By|=zRfF4Boq| zhG=Fl8A3St^UN$!bgoPia*Iv4BeJ?3IRGkvP9<0_y>?M}>TJ$H3G8V(<(A6mn|n|e zAfZD~lyGGW^A!D72fpZ7%SlbvjL@@wuUIFERvd0IrPf_|RnF;}5D4@rbW|RsJa%M& z=`xkYMKd+_b&5UW*>{%}1bHHC2=bWV_t=9fl82ldHY||IIWSacxo~4o-FY}LR2>bt ze-t5uxul{%BQrcw9!`e#TSuels9HHbFA6#l`aV}o+uuaDc3M)({AjYVhD5GUZ0(oJF?eRMu?q~cSQAU7X zp}Cg@bqPj`bEgDK4w@3GD9={vNoZ6JMDt=?-vBS`RO3Kopm_jcxvHNNm#7Za(wn;4 zhV?E`j6rwqWsyCXUYANXX6BEHUyQTJj&ZAUHfFk8{@?Bw<;Zs4RK^9$C**`ztg5>(+s zlega&I$jivaS$!zyo1RMWCTx8J_}nsx~LtGGXeyEO!p&a1y4{R?2zGw;IOKCh;tmG z$bJNJuIlj+n4ORnDUU$U$XOZnJVb8jWP0|V@8EGA1PKW1SaUZs$0c}e%t4OG6JC}B zQKReJsS1hQF@4y_+E0Ejq9 zG9HRR0x2jsr|k}Dl`fFuN%D~IS7G3LLuqoVW!9#wH5`#LkKXQ-#au2V9^dg^S;nfgyUP)4))9N@$9b2z%Cx z#Hk+^lKs`v$;&!)Y7p*k2c;a)MaVj}d@@;%aR!;H&R!y#Fo;FUcpe_#=rq#oMInSy zfvtPbCW?-1oUby9+?k~1d6VT{Q%Bnm4Z~*$K|xal@0mh~XB0B32q=ppaPky8jbqt| zXq@UHs$XvwUnqnmk@}&9Lw@RD>Y_J2WpAZ|@Hj5B{dhgaC2Xn+nkH=JlO?{N3D44@ zK{;?r3@GX|gxE#e#EEQ~d~v^n_m(|$D(Jf=4F#FlZZ*7FB7}2Z3PCrP1^W@lefYU> z2>FhXD=$k#>72FQ&s7LP1!9*MOwSV+WGzlALMf24zPO+J_hw{Y;2bP084aI41ew4U zoc`~Sf9o(#6FIq*QeN(OSw`kYxxpvIkl?ZR`V?7$Gn7JV((?H}mF#$jqLs}Q6FufZ z8NnqI8KB4)N^&BRcj(>ENSV65r@)Z7V;4{ah@5so1+YR{apc}Q1(}912tE_{XvBCW zgigp|gml3*MZ_iOd7TtN0pDp#4jO#vIDN-0lf`Ke611u6ASnumA}fwCfsg|SKtOI# zF~p&1hwIq%;T$A5AprO96-&FLkR+ln6HakJPDhh<2n=t=AsqInoC8s`C$WenZ5Bk(L#qt>NFXH%Q)mt`Bn(_P;Z|hg!{0C$5p}3K@YYfhteDIpZ7_mYcywp^5V=lf_6VYGhQ_!n6CJ z8!Q4y?vLfd)VbOgUKv&fPuJl{s)W=TDIF#qI5ZF?c(qO56eFd>Ggj5NH!VETwn{1@ zmpE;+q8K?A%3Hh$O=?mn?#rhEG>!HjpCI`zf*+*936yAA;=GaghKm~)a$I*$pP{> zIe?h_Yp-wML;^Sw8n#UGt}17KqlY0t+)y|qm@1qBDL{n~5kWJzIG+c+a!7KbH$@&9 zrjFb(O8h=w)lnfAb?}VnxzeZVAu_O}biMO1IJW&X1>dQKCuw_25K>8zYyvyPJ_i}n z_53-U4D#xDA2ah(O7vNCnxgRKvZEZ6I*gQZQAs=vU8CufTye&PTf+Vt5-ST(+^?{p zv1Q_MxQ-QZQc!{zOzqp41hjCCAH1th4h|~hFLMYFeP*uU+P8Crjiq8zRE8LtWyY97 zI0q+Ag5wqEJ|PcGnY;`cs&IGO57}LgL(r|SAI|owl!(Y>9Rw+IVTXwVF5(kQ6yh`v zWN-Nt@%s!R+;xZ$66hQK9srZf5yYC2rdWcGuWVI0&b&^xh6mpYYxXI3) zD`v=0shEo;f4TE^5O1eIcJBlMk+z`=OPo;>1jU4I73T|v#@sY&B$W~9hT%>9i|<`p6`-FCKW;@WLJpz zKyaxFR3A~y-|`jQa;-t~p42*@55n(|E(y0+dReOFV;ORWl#g_lFmeC898!Rc( zYRTwb!FAJ?pMByyg-BA+L9WCr^)lP5l8U0Y$*yG{8ASyur3?<}kZaN@G_p<goBnoSm`pBnND(M5xj;@$++`&YtQxXFYT^!? zO(ZwrkF180DuPl~II|BsJU&xQxW2Em9wjhF@Kgs;(`bksm8r_3aFJb@DpxfW;Xq*T z_af^4CF^5`9iS}mqyM4XNaY%okYaU zE2*i>+;2!`+KlAMkzo_WC^8sopt!@xe97SmGliToy<)0Ysg&770>mAa3til*6Vp*M z&QSZQrJl~mzPETe%v5*U&s9K$JW`AaQcuSi%KAB{)+aS~)oX=0g?pi#GQ1+q&s9K1 zhKYzf+pnVkjwVz!0GPpB#TW<0fd_gj-STy5JwzY{6MxF&L@GrVlW>Yzh5Z0fF#&w^7w&U9L4 z5jwD4Si)hhT$jO3K znY6swF@yxrr!}9{ELETDVZd7lPP-U zPEVgn#XiCmG7?V5S@D#9Py6@M`s-mvxp+99OOca^r1e?AD5G~ADF1<;lE(F!#I1Io z-ds2d(i`Ic(^Ko!p3i_SO%@9zQT`p$cbY>uyRY47WJ(7!Q>J>m zJL^#V11biD1rYL*PBlJx@RHPoB|>;4FP(oW_?v$M;FG2Om8DRGG(EywE1BtHl#r8B z&8$on!c&RQl85|1hgZu?5&rl5Q^SXp>GMfI)jJq@P>8TX2tcdQVQC_lH8okO9cEIU z{pDV^%mhHLW5#WN*V1O*&`0h?iuJ_o7ZEtx!DJ;0K^J)Hd0h#a{vEU=UKd)ilX;u8 zT;{uS2Wf(1itvhTf^gGZZk}+(j7Mz7Asl_Po9h!OUm_v<*SCq%iwFoU@%T>biTi0+ z`&1x!138wMv-CQ0bYw1TYKE?2l?)jPS~6R0iJ43lMdw@AQ0_Ji(Ljq3W)WivQ$#2P z(fW&22o$3V7h0g^;new|-2D4IpC~MgaZd5f3>2azUbSXvsxI!6UM~==CZk4o*FYiX z)esIVIcU-k9CVx{VMyzM& zDPgFWxyya25@*kN7aioj8dVNwsIt^#sTLWnpNV+VGL#A1!IVYz7o20t9bEtz*S^Byw+XNtJopFIDu zVmN!LJMJ#;Ps~x;B0ng=RIoRHd$!*a36{J2r&7;<=NPG92wx$<#EW@G$^_Y>TG>U* zEO!v0vH>?+ewiSkOn%cZp2u%It4lBIhElRtXcM8O0=*;H2|{i?4>=_teP+9XfUEbK zz8s;fGO15tt6JFX0Y2~&m#uCl zz&p>7DBr+i%LH5g7sCCB2ClAq4G#IY>_rl;`0yta*8j!l!hZkDemnX6`)lw0pQEJM nM8@}D^;DV@iXPSegP;#Q00000NkvXXu0mjfOtYOK diff --git a/install/index.cfm b/install/index.cfm index 7324c10..454f846 100644 --- a/install/index.cfm +++ b/install/index.cfm @@ -71,7 +71,7 @@ - + @@ -121,9 +121,9 @@

    Other recommended changes:

    • /config/settings.cfm - change the application reload password
    • +
    • /config/production/settings.cfm - Add an error handling email address
    • Main Application Settings
        -
      • Change the error handling email address
      • Change the main site email address
      • Add Google Analytics tracking code
      • Add URL path to site logo
      • @@ -187,8 +187,7 @@ - - + diff --git a/install/roombooking.sql b/install/roombooking.sql index cc30038..1b73236 100644 --- a/install/roombooking.sql +++ b/install/roombooking.sql @@ -10,34 +10,47 @@ Target Server Type : MYSQL Target Server Version : 50525 File Encoding : 65001 -Date: 2014-10-25 10:01:52 +Date: 2014-11-28 12:08:54 */ SET FOREIGN_KEY_CHECKS=0; +-- ---------------------------- +-- Table structure for eventresources +-- ---------------------------- +DROP TABLE IF EXISTS `eventresources`; +CREATE TABLE `eventresources` ( + `eventid` int(11) NOT NULL, + `resourceid` int(11) NOT NULL, + PRIMARY KEY (`eventid`,`resourceid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Records of eventresources +-- ---------------------------- + -- ---------------------------- -- Table structure for events -- ---------------------------- DROP TABLE IF EXISTS `events`; CREATE TABLE `events` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `title` varchar(500) COLLATE utf8_unicode_ci NOT NULL, + `title` varchar(500) CHARACTER SET latin1 NOT NULL, `start` datetime DEFAULT NULL, `end` datetime DEFAULT NULL, `allday` int(11) NOT NULL DEFAULT '0', - `url` varchar(500) COLLATE utf8_unicode_ci DEFAULT NULL, - `className` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `description` text COLLATE utf8_unicode_ci, + `className` varchar(255) CHARACTER SET latin1 DEFAULT NULL, + `description` text CHARACTER SET latin1, `locationid` int(11) NOT NULL DEFAULT '0', - `contactname` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `contactno` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `contactemail` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, - `layoutstyle` varchar(255) COLLATE utf8_unicode_ci DEFAULT 'Standard', + `contactname` varchar(255) CHARACTER SET latin1 DEFAULT NULL, + `contactno` varchar(255) CHARACTER SET latin1 DEFAULT NULL, + `contactemail` varchar(255) CHARACTER SET latin1 DEFAULT NULL, + `layoutstyle` varchar(255) CHARACTER SET latin1 DEFAULT 'Standard', `createdat` datetime DEFAULT NULL, `updatedat` datetime DEFAULT NULL, `deletedat` datetime DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of events @@ -49,12 +62,12 @@ CREATE TABLE `events` ( DROP TABLE IF EXISTS `locations`; CREATE TABLE `locations` ( `id` int(11) NOT NULL AUTO_INCREMENT, - `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, - `description` varchar(500) COLLATE utf8_unicode_ci DEFAULT NULL, - `class` varchar(55) COLLATE utf8_unicode_ci NOT NULL, - `colour` varchar(7) COLLATE utf8_unicode_ci DEFAULT NULL, + `name` varchar(255) CHARACTER SET latin1 NOT NULL, + `description` varchar(500) CHARACTER SET latin1 DEFAULT NULL, + `class` varchar(55) CHARACTER SET latin1 NOT NULL, + `colour` varchar(7) CHARACTER SET latin1 DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of locations @@ -63,8 +76,6 @@ INSERT INTO `locations` VALUES ('1', 'Lecture Theatre', 'First Floor', 'lt', '#c INSERT INTO `locations` VALUES ('2', 'Seminar Room 1', 'Ground Floor', 'sm1', '#2c86ff'); INSERT INTO `locations` VALUES ('3', 'Seminar Room 2', 'Ground Floor', 'sm2', '#a1a100'); INSERT INTO `locations` VALUES ('4', 'Conference Room', 'Ground Floor', 'gfcr', '#FF6600'); -INSERT INTO `locations` VALUES ('5', 'Café', 'First Floor', 'cafe', '#800080'); -INSERT INTO `locations` VALUES ('6', 'AV Suite', 'Basement', 'avsuite', '#167362'); -- ---------------------------- -- Table structure for logfiles @@ -81,7 +92,11 @@ CREATE TABLE `logfiles` ( `updatedat` datetime DEFAULT NULL, `deletedat` datetime DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- ---------------------------- +-- Records of logfiles +-- ---------------------------- -- ---------------------------- -- Table structure for permissions @@ -95,24 +110,43 @@ CREATE TABLE `permissions` ( `guest` int(1) NOT NULL DEFAULT '0', `notes` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- ---------------------------- -- Records of permissions -- ---------------------------- INSERT INTO `permissions` VALUES ('accessApplication', '1', '1', '1', '1', 'Allow access to the application'); INSERT INTO `permissions` VALUES ('accessCalendar', '1', '1', '1', '1', 'Allow access to the calendar'); -INSERT INTO `permissions` VALUES ('viewRoomBooking', '1', '1', '1', '1', 'View Room Booking Details'); -INSERT INTO `permissions` VALUES ('allowAPI', '1', '1', '1', '1', 'Reserved for future use'); -INSERT INTO `permissions` VALUES ('allowiCal', '1', '1', '1', '1', 'Reserved for future use'); -INSERT INTO `permissions` VALUES ('allowRSS', '1', '1', '1', '1', 'Reserved for future use'); -INSERT INTO `permissions` VALUES ('allowRoomBooking', '1', '1', '1', '0', 'Allow Facility to create events'); -INSERT INTO `permissions` VALUES ('updateOwnAccount', '1', '1', '1', '0', 'Allows a user to update their own details'); INSERT INTO `permissions` VALUES ('accessLocations', '1', '1', '0', '0', 'Allow access to Locations Editing'); -INSERT INTO `permissions` VALUES ('accessSettings', '1', '1', '0', '0', 'Allow access to Settings'); INSERT INTO `permissions` VALUES ('accessLogfiles', '1', '0', '0', '0', 'Allow access to Logfiles'); INSERT INTO `permissions` VALUES ('accessPermissions', '1', '0', '0', '0', 'Allow access to Permissions'); +INSERT INTO `permissions` VALUES ('accessResources', '1', '0', '0', '0', 'Allow access to Resources'); +INSERT INTO `permissions` VALUES ('accessSettings', '1', '1', '0', '0', 'Allow access to Settings'); INSERT INTO `permissions` VALUES ('accessUsers', '1', '0', '0', '0', 'Allow access to User Accounts'); +INSERT INTO `permissions` VALUES ('allowAPI', '1', '1', '1', '1', 'Reserved for future use'); +INSERT INTO `permissions` VALUES ('allowiCal', '1', '1', '1', '1', 'Reserved for future use'); +INSERT INTO `permissions` VALUES ('allowRoomBooking', '1', '1', '1', '0', 'Allow Facility to create events'); +INSERT INTO `permissions` VALUES ('allowRSS', '1', '1', '1', '1', 'Reserved for future use'); +INSERT INTO `permissions` VALUES ('updateOwnAccount', '1', '1', '1', '0', 'Allows a user to update their own details'); +INSERT INTO `permissions` VALUES ('viewRoomBooking', '1', '1', '1', '1', 'View Room Booking Details'); + +-- ---------------------------- +-- Table structure for resources +-- ---------------------------- +DROP TABLE IF EXISTS `resources`; +CREATE TABLE `resources` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL, + `type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + `description` varchar(500) COLLATE utf8_unicode_ci DEFAULT NULL, + `isunique` tinyint(1) NOT NULL DEFAULT '0', + `restrictlocations` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- ---------------------------- +-- Records of resources +-- ---------------------------- -- ---------------------------- -- Table structure for settings @@ -132,32 +166,32 @@ CREATE TABLE `settings` ( -- Records of settings -- ---------------------------- INSERT INTO `settings` VALUES ('allowLocations', '1', 'setting to zero will disable the facility to edit all locations via the web interface', 'boolean', '1', 'Locations'); +INSERT INTO `settings` VALUES ('allowResources', '1', 'Allow Resource Booking alongside events', 'boolean', '1', 'General'); INSERT INTO `settings` VALUES ('allowSettings', '1', 'Warning - setting to zero will disable the facility to edit all settings via the web interface, and will only be editable via the database directly', 'boolean', '1', 'General'); -INSERT INTO `settings` VALUES ('allowThemes', '1', 'setting to zero will disable the theme preview dropdown', 'boolean', '1', 'Styling'); -INSERT INTO `settings` VALUES ('bootstraptheme', 'cosmo', 'Current bootstrap bootswatch theme to use - requires using CDN option', 'string', '1', 'Styling'); -INSERT INTO `settings` VALUES ('bootstrapthemeoptions', 'amelia,cerulean,cosmo,cyborg,flatly,journal,readable,simplex,slate,spacelab,united', 'List of possible Bootstrap 3 theme names', 'string', '0', 'Styling'); INSERT INTO `settings` VALUES ('calendarAllDaySlot', '1', 'Determines if the \"all-day\" slot is displayed at the top of the calendar.', 'boolean', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarAllDayText', 'all-day', 'The text titling the \"all-day\" slot at the top of the calendar.', 'string', '1', 'Calendar'); -INSERT INTO `settings` VALUES ('calendarAxisFormat', 'h(:mm)tt', 'Determines the time-text that will be displayed on the vertical axis of the agenda views', 'string', '1', 'Calendar'); +INSERT INTO `settings` VALUES ('calendarAxisFormat', 'h(:mm)a', 'Determines the time-text that will be displayed on the vertical axis of the agenda views', 'string', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarColumnFormatDay', 'dddd d/M', 'Column format for day', 'string', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarColumnFormatMonth', 'ddd', 'Column format for month', 'string', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarColumnFormatWeek', 'ddd d/M', 'Column format for week', 'string', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarDefaultView', 'month', 'Calendar Default View - Can be month, basicWeek, basicDay etc', 'string', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarFirstday', '1', 'Start Calendar on this day of the week: 1 = Monday, 0 = Sunday', 'boolean', '1', 'Calendar'); -INSERT INTO `settings` VALUES ('calendarHeadercenter', 'title', 'Calendar Header Display, center - see http://arshaw.com/fullcalendar/docs/display/header/', 'string', '1', 'Calendar'); -INSERT INTO `settings` VALUES ('calendarHeaderleft', 'prev,next today', 'Calendar Header Display, left - see http://arshaw.com/fullcalendar/docs/display/header/', 'string', '1', 'Calendar'); -INSERT INTO `settings` VALUES ('calendarHeaderright', 'month,agendaWeek,agendaDay', 'Calendar Header Display, right - see http://arshaw.com/fullcalendar/docs/display/header/', 'string', '1', 'Calendar'); +INSERT INTO `settings` VALUES ('calendarHeadercenter', 'title', 'Calendar Header Display, center - see http://fullcalendar.io/docs/', 'string', '1', 'Calendar'); +INSERT INTO `settings` VALUES ('calendarHeaderleft', 'prev,next today', 'Calendar Header Display, left - see http://fullcalendar.io/docs/', 'string', '1', 'Calendar'); +INSERT INTO `settings` VALUES ('calendarHeaderright', 'month,agendaWeek,agendaDay', 'Calendar Header Display, right - see http://fullcalendar.io/docs/', 'string', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarHiddenDays', '[]', 'Hide certain days (Comma Delim List in array, i.e [1,3,5] )', 'string', '1', 'Calendar'); -INSERT INTO `settings` VALUES ('calendarMintime', '7am', 'Calendar Minimum Time to display', 'string', '1', 'Calendar'); -INSERT INTO `settings` VALUES ('calendarSlotEventOverlap', '1', 'Determines if timed events in agenda view should visually overlap', 'boolean', '1', 'Calendar'); -INSERT INTO `settings` VALUES ('calendarSlotMinutes', '15', 'Calendar No of minutes to increment minutes in (integer)', 'string', '1', 'Calendar'); +INSERT INTO `settings` VALUES ('calendarMaxtime', '23:00:00', 'Calendar Maximum Time to display (format HH:MM:SS)', 'string', '1', 'Calendar'); +INSERT INTO `settings` VALUES ('calendarMintime', '07:00:00', 'Calendar Minimum Time to display (format HH:MM:SS)', 'string', '1', 'Calendar'); +INSERT INTO `settings` VALUES ('calendarSlotEventOverlap', '0', 'Determines if timed events in agenda view should visually overlap', 'boolean', '1', 'Calendar'); +INSERT INTO `settings` VALUES ('calendarSlotMinutes', '00:15:00', 'Calendar No of minutes per slot (format HH:MM:SS)', 'string', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarTimeformat', 'H:mm', 'Calendar Time Format', 'string', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarWeekends', '1', 'Calendar Whether to show weekends', 'boolean', '1', 'Calendar'); INSERT INTO `settings` VALUES ('calendarWeekNumbers', '0', 'Determines if week numbers should be displayed on the calendar.', 'boolean', '1', 'Calendar'); -INSERT INTO `settings` VALUES ('defaultDateFormat', 'DD MMM YYYY', 'Default Date Format (agenda view etc)', 'string', '1', 'general'); -INSERT INTO `settings` VALUES ('defaultTimeFormat', 'HH:MM', 'Default Time Format (agenda view etc)', 'string', '1', 'general'); +INSERT INTO `settings` VALUES ('defaultDateFormat', 'DD MMM YYYY', 'Default Date Format (agenda view etc)', 'string', '1', 'General'); +INSERT INTO `settings` VALUES ('defaultTimeFormat', 'HH:MM', 'Default Time Format (agenda view etc)', 'string', '1', 'General'); INSERT INTO `settings` VALUES ('googleanalytics', 'UA-', 'Google Anayltics Tracking Code (format: UA-XXXXX-X), set to UA- to omit', 'string', '1', 'General'); INSERT INTO `settings` VALUES ('isDemoMode', '0', 'Put the board into demo mode', 'boolean', '0', 'General'); +INSERT INTO `settings` VALUES ('resourceTypes', 'Computers,Audio Visual,Furniture', 'List of possible resource types (Comma Deliminated List of Values)', 'string', '1', 'General'); INSERT INTO `settings` VALUES ('roomlayouttypes', 'Standard,Boardroom,Lecture', 'Room Layout Types (Comma Deliminated List of Values)', 'string', '1', 'Locations'); INSERT INTO `settings` VALUES ('showlocationcolours', '1', 'Whether to display calendar entries with their location colour', 'boolean', '1', 'Locations'); INSERT INTO `settings` VALUES ('showlocationfilter', '1', 'Whether to show the Location Filter Bar on the Main Calendar Page', 'boolean', '1', 'Locations'); @@ -165,11 +199,7 @@ INSERT INTO `settings` VALUES ('sitedescription', 'Room Booking System', 'Site M INSERT INTO `settings` VALUES ('siteEmailAddress', 'bookings@domain.com', 'Main Site email address - used in booking notifications', 'string', '1', 'General'); INSERT INTO `settings` VALUES ('sitelogo', '/', 'Path or Full URL to your Site Logo, 20px x 20px recommended', 'string', '1', 'General'); INSERT INTO `settings` VALUES ('sitetitle', 'Room Booking System', 'The Main Site Title', 'string', '1', 'General'); -INSERT INTO `settings` VALUES ('usejavascriptfromCDN', '1', 'Use Javascript Libraries and CSS from CDN whereever possible', 'boolean', '1', 'General'); INSERT INTO `settings` VALUES ('version', '1.1', 'Version Number', 'string', '1', 'General'); -INSERT INTO `settings` VALUES ('wheelsErrorEmailFromAddress', 'error@domain.com', 'Production mode error email address (from)', 'string', '1', 'General'); -INSERT INTO `settings` VALUES ('wheelsErrorEmailSubject', '[Error - Room Booking]', 'Production mode error email subject', 'string', '1', 'General'); -INSERT INTO `settings` VALUES ('wheelsErrorEmailToAddress', 'error@domain.com', 'Production mode error email address (to)', 'string', '1', 'General'); -- ---------------------------- -- Table structure for users @@ -196,7 +226,7 @@ CREATE TABLE `users` ( `deletedat` datetime DEFAULT NULL, `apitoken` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- ---------------------------- -- Records of users diff --git a/javascripts/bootstrap-colorpicker.js b/javascripts/bootstrap-colorpicker.js deleted file mode 100644 index d024720..0000000 --- a/javascripts/bootstrap-colorpicker.js +++ /dev/null @@ -1,561 +0,0 @@ -/* ========================================================= - * bootstrap-colorpicker.js - * http://www.eyecon.ro/bootstrap-colorpicker - * ========================================================= - * Copyright 2012 Stefan Petre - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================= */ - -!function($) { - - // Color object - - var Color = function(val) { - this.value = { - h: 1, - s: 1, - b: 1, - a: 1 - }; - this.setColor(val); - }; - - Color.prototype = { - constructor: Color, - //parse a string to HSB - setColor: function(val) { - val = val.toLowerCase(); - var that = this; - $.each(CPGlobal.stringParsers, function(i, parser) { - var match = parser.re.exec(val), - values = match && parser.parse(match), - space = parser.space || 'rgba'; - if (values) { - if (space === 'hsla') { - that.value = CPGlobal.RGBtoHSB.apply(null, CPGlobal.HSLtoRGB.apply(null, values)); - } else { - that.value = CPGlobal.RGBtoHSB.apply(null, values); - } - return false; - } - return true; - }); - }, - setHue: function(h) { - this.value.h = 1 - h; - }, - setSaturation: function(s) { - this.value.s = s; - }, - setLightness: function(b) { - this.value.b = 1 - b; - }, - setAlpha: function(a) { - this.value.a = parseInt((1 - a) * 100, 10) / 100; - }, - // HSBtoRGB from RaphaelJS - // https://github.com/DmitryBaranovskiy/raphael/ - toRGB: function(h, s, b, a) { - if (!h) { - h = this.value.h; - s = this.value.s; - b = this.value.b; - } - h *= 360; - var R, G, B, X, C; - h = (h % 360) / 60; - C = b * s; - X = C * (1 - Math.abs(h % 2 - 1)); - R = G = B = b - C; - - h = ~~h; - R += [C, X, 0, 0, X, C][h]; - G += [X, C, C, X, 0, 0][h]; - B += [0, 0, X, C, C, X][h]; - return { - r: Math.round(R * 255), - g: Math.round(G * 255), - b: Math.round(B * 255), - a: a || this.value.a - }; - }, - toHex: function(h, s, b, a) { - var rgb = this.toRGB(h, s, b, a); - return '#' + ((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1); - }, - toHSL: function(h, s, b, a) { - if (!h) { - h = this.value.h; - s = this.value.s; - b = this.value.b; - } - var H = h, - L = (2 - s) * b, - S = s * b; - if (L > 0 && L <= 1) { - S /= L; - } else { - S /= 2 - L; - } - L /= 2; - if (S > 1) { - S = 1; - } - return { - h: H, - s: S, - l: L, - a: a || this.value.a - }; - } - }; - - var _guid = 0; - - // Picker object - - var Colorpicker = function(element, options) { - _guid++; - this.element = $(element).attr('data-colorpicker-guid', _guid); - var format = options.format || this.element.data('color-format') || 'hex'; - this.format = CPGlobal.translateFormats[format]; - this.isInput = this.element.is('input'); - this.component = this.element.is('.colorpicker-component') ? this.element.find('.add-on, .input-group-addon') : false; - - this.picker = $(CPGlobal.template).attr('data-colorpicker-guid', _guid) - .appendTo('body') - .on('mousedown.colorpicker', $.proxy(this.mousedown, this)); - - if (this.isInput) { - this.element.on({ - 'focus.colorpicker': $.proxy(this.show, this), - 'keyup.colorpicker': $.proxy(this.update, this) - }); - } else if (this.component) { - this.component.on({ - 'click.colorpicker': $.proxy(this.show, this) - }); - } else { - this.element.on({ - 'click.colorpicker': $.proxy(this.show, this) - }); - } - if (format === 'rgba' || format === 'hsla') { - this.picker.addClass('alpha'); - this.alpha = this.picker.find('.colorpicker-alpha')[0].style; - } - - if (this.component) { - this.picker.find('.colorpicker-color').hide(); - this.preview = this.element.find('i')[0].style; - } else { - this.preview = this.picker.find('div:last')[0].style; - } - - this.base = this.picker.find('div:first')[0].style; - this.update(); - - $($.proxy(function() { - this.element.trigger('create', [this]); - }, this)); - }; - - Colorpicker.prototype = { - constructor: Colorpicker, - show: function(e) { - this.picker.show(); - this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); - this.place(); - $(window).on('resize.colorpicker', $.proxy(this.place, this)); - if (!this.isInput) { - if (e) { - e.stopPropagation(); - e.preventDefault(); - } - } - $(document).on({ - 'mousedown.colorpicker': $.proxy(this.hide, this) - }); - this.element.trigger({ - type: 'showPicker', - color: this.color - }); - }, - update: function() { - var color = this.isInput ? this.element.prop('value') : this.element.data('color'); - if (typeof color === "undefined" || color === null) { - color = '#ffffff'; - } - this.color = new Color(color); - this.picker.find('i') - .eq(0).css({left: this.color.value.s * 100, top: 100 - this.color.value.b * 100}).end() - .eq(1).css('top', 100 * (1 - this.color.value.h)).end() - .eq(2).css('top', 100 * (1 - this.color.value.a)); - this.previewColor(); - }, - hide: function() { - this.picker.hide(); - $(window).off('resize', this.place); - $(document).off({ - 'mousedown': this.hide - }); - if (!this.isInput) { - if (this.component) { - //if the input value is empty, do not set any color - if (this.element.find('input').val() !== '') { - this.element.find('input').prop('value', this.format.call(this)).trigger('change'); - } - } - this.element.data('color', this.format.call(this)); - } else { - //if the input value is empty, do not set any color - if (this.element.val() !== '') { - this.element.prop('value', this.format.call(this)).trigger('change'); - } - } - this.element.trigger({ - type: 'hidePicker', - color: this.color - }); - }, - place: function() { - var offset = this.component ? this.component.offset() : this.element.offset(); - this.picker.css({ - top: offset.top + this.height, - left: offset.left - }); - }, - destroy: function() { - $('.colorpicker[data-colorpicker-guid=' + this.element.attr('data-colorpicker-guid') + ']').remove(); - this.element.removeData('colorpicker').removeAttr('data-colorpicker-guid').off('.colorpicker'); - if (this.component !== false) { - this.component.off('.colorpicker'); - } - this.element.trigger("destroy", [this]); - }, - setValue: function(value) { - // set the input or component value - if(this.isInput){ - this.element.prop('value', value); - }else{ - this.element.find('input').val(value); - this.element.data('color', value); - } - this.update(); - this.element.trigger({ - type: 'changeColor', - color: this.color - }); - }, - //preview color change - previewColor: function() { - try { - this.preview.backgroundColor = this.format.call(this); - } catch (e) { - this.preview.backgroundColor = this.color.toHex(); - } - //set the color for brightness/saturation slider - this.base.backgroundColor = this.color.toHex(this.color.value.h, 1, 1, 1); - //set te color for alpha slider - if (this.alpha) { - this.alpha.backgroundColor = this.color.toHex(); - } - }, - pointer: null, - slider: null, - mousedown: function(e) { - e.stopPropagation(); - e.preventDefault(); - - var target = $(e.target); - - //detect the slider and set the limits and callbacks - var zone = target.closest('div'); - if (!zone.is('.colorpicker')) { - if (zone.is('.colorpicker-saturation')) { - this.slider = $.extend({}, CPGlobal.sliders.saturation); - } - else if (zone.is('.colorpicker-hue')) { - this.slider = $.extend({}, CPGlobal.sliders.hue); - } - else if (zone.is('.colorpicker-alpha')) { - this.slider = $.extend({}, CPGlobal.sliders.alpha); - } else { - return false; - } - var offset = zone.offset(); - //reference to knob's style - this.slider.knob = zone.find('i')[0].style; - this.slider.left = e.pageX - offset.left; - this.slider.top = e.pageY - offset.top; - this.pointer = { - left: e.pageX, - top: e.pageY - }; - //trigger mousemove to move the knob to the current position - $(document).on({ - 'mousemove.colorpicker': $.proxy(this.mousemove, this), - 'mouseup.colorpicker': $.proxy(this.mouseup, this) - }).trigger('mousemove'); - } - return false; - }, - mousemove: function(e) { - e.stopPropagation(); - e.preventDefault(); - var left = Math.max( - 0, - Math.min( - this.slider.maxLeft, - this.slider.left + ((e.pageX || this.pointer.left) - this.pointer.left) - ) - ); - var top = Math.max( - 0, - Math.min( - this.slider.maxTop, - this.slider.top + ((e.pageY || this.pointer.top) - this.pointer.top) - ) - ); - this.slider.knob.left = left + 'px'; - this.slider.knob.top = top + 'px'; - if (this.slider.callLeft) { - this.color[this.slider.callLeft].call(this.color, left / 100); - } - if (this.slider.callTop) { - this.color[this.slider.callTop].call(this.color, top / 100); - } - this.previewColor(); - - // Set input value on mousemove - if (!this.isInput) { - try { - this.element.find('input').val(this.format.call(this)).trigger('change'); - } catch (e) { - this.element.find('input').val(this.color.toHex()).trigger('change'); - } - } else { - try { - this.element.val(this.format.call(this)).trigger('change'); - } catch (e) { - this.element.val(this.color.toHex()).trigger('change'); - } - } - - this.element.trigger({ - type: 'changeColor', - color: this.color - }); - return false; - }, - mouseup: function(e) { - e.stopPropagation(); - e.preventDefault(); - $(document).off({ - mousemove: this.mousemove, - mouseup: this.mouseup - }); - return false; - } - }; - - $.fn.colorpicker = function(option, value) { - return this.each(function() { - var $this = $(this), - data = $this.data('colorpicker'), - options = typeof option === 'object' && option; - if (!data) { - if (option !== "destroy") { - $this.data('colorpicker', (data = new Colorpicker(this, $.extend({}, $.fn.colorpicker.defaults, options)))); - } - } else { - if (typeof option === 'string') { - data[option](value); - } - } - }); - }; - - $.fn.colorpicker.defaults = { - }; - - $.fn.colorpicker.Constructor = Colorpicker; - - var CPGlobal = { - // translate a format from Color object to a string - translateFormats: { - 'rgb': function() { - var rgb = this.color.toRGB(); - return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; - }, - 'rgba': function() { - var rgb = this.color.toRGB(); - return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + rgb.a + ')'; - }, - 'hsl': function() { - var hsl = this.color.toHSL(); - return 'hsl(' + Math.round(hsl.h * 360) + ',' + Math.round(hsl.s * 100) + '%,' + Math.round(hsl.l * 100) + '%)'; - }, - 'hsla': function() { - var hsl = this.color.toHSL(); - return 'hsla(' + Math.round(hsl.h * 360) + ',' + Math.round(hsl.s * 100) + '%,' + Math.round(hsl.l * 100) + '%,' + hsl.a + ')'; - }, - 'hex': function() { - return this.color.toHex(); - } - }, - sliders: { - saturation: { - maxLeft: 100, - maxTop: 100, - callLeft: 'setSaturation', - callTop: 'setLightness' - }, - hue: { - maxLeft: 0, - maxTop: 100, - callLeft: false, - callTop: 'setHue' - }, - alpha: { - maxLeft: 0, - maxTop: 100, - callLeft: false, - callTop: 'setAlpha' - } - }, - // HSBtoRGB from RaphaelJS - // https://github.com/DmitryBaranovskiy/raphael/ - RGBtoHSB: function(r, g, b, a) { - r /= 255; - g /= 255; - b /= 255; - - var H, S, V, C; - V = Math.max(r, g, b); - C = V - Math.min(r, g, b); - H = (C === 0 ? null : - V === r ? (g - b) / C : - V === g ? (b - r) / C + 2 : - (r - g) / C + 4 - ); - H = ((H + 360) % 6) * 60 / 360; - S = C === 0 ? 0 : C / V; - return {h: H || 1, s: S, b: V, a: a || 1}; - }, - HueToRGB: function(p, q, h) { - if (h < 0) - h += 1; - else if (h > 1) - h -= 1; - - if ((h * 6) < 1) - return p + (q - p) * h * 6; - else if ((h * 2) < 1) - return q; - else if ((h * 3) < 2) - return p + (q - p) * ((2 / 3) - h) * 6; - else - return p; - }, - HSLtoRGB: function(h, s, l, a) { - if (s < 0) { - s = 0; - } - var q; - if (l <= 0.5) { - q = l * (1 + s); - } else { - q = l + s - (l * s); - } - - var p = 2 * l - q; - - var tr = h + (1 / 3); - var tg = h; - var tb = h - (1 / 3); - - var r = Math.round(CPGlobal.HueToRGB(p, q, tr) * 255); - var g = Math.round(CPGlobal.HueToRGB(p, q, tg) * 255); - var b = Math.round(CPGlobal.HueToRGB(p, q, tb) * 255); - return [r, g, b, a || 1]; - }, - // a set of RE's that can match strings and generate color tuples. - // from John Resig color plugin - // https://github.com/jquery/jquery-color/ - stringParsers: [ - { - re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, - parse: function(execResult) { - return [ - execResult[1], - execResult[2], - execResult[3], - execResult[4] - ]; - } - }, - { - re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, - parse: function(execResult) { - return [ - 2.55 * execResult[1], - 2.55 * execResult[2], - 2.55 * execResult[3], - execResult[4] - ]; - } - }, - { - re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, - parse: function(execResult) { - return [ - parseInt(execResult[1], 16), - parseInt(execResult[2], 16), - parseInt(execResult[3], 16) - ]; - } - }, - { - re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, - parse: function(execResult) { - return [ - parseInt(execResult[1] + execResult[1], 16), - parseInt(execResult[2] + execResult[2], 16), - parseInt(execResult[3] + execResult[3], 16) - ]; - } - }, - { - re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, - space: 'hsla', - parse: function(execResult) { - return [ - execResult[1] / 360, - execResult[2] / 100, - execResult[3] / 100, - execResult[4] - ]; - } - } - ], - template: '